Repository: hmatuschek/qdmr Branch: master Commit: 7b4c9c19e17a Files: 1104 Total size: 8.7 MB Directory structure: gitextract_3ebrfg9_/ ├── .clang-format ├── .github/ │ ├── FUNDING.yml │ ├── ISSUE_TEMPLATE/ │ │ ├── communication-issue.md │ │ └── decoding-issue.md │ └── workflows/ │ ├── clang.yml │ ├── flatpak.yml │ └── unittests.yml ├── .gitignore ├── .gitmodules ├── BUILD.md ├── CMakeLists.txt ├── LICENSE ├── README.md ├── cli/ │ ├── CMakeLists.txt │ ├── autodetect.cc │ ├── autodetect.hh │ ├── decodecodeplug.cc │ ├── decodecodeplug.hh │ ├── detect.cc │ ├── detect.hh │ ├── encodecallsigndb.cc │ ├── encodecallsigndb.hh │ ├── encodecodeplug.cc │ ├── encodecodeplug.hh │ ├── infofile.cc │ ├── infofile.hh │ ├── main.cc │ ├── printprogress.cc │ ├── printprogress.hh │ ├── progressbar.cc │ ├── progressbar.hh │ ├── readcodeplug.cc │ ├── readcodeplug.hh │ ├── verify.cc │ ├── verify.hh │ ├── writecallsigndb.cc │ ├── writecallsigndb.hh │ ├── writecodeplug.cc │ └── writecodeplug.hh ├── cmake/ │ ├── FindLIBUSB_1.cmake │ ├── FindYAMLCPP.cmake │ └── GenerateIcons.cmake ├── de.darc.dm3mat.qdmr.yaml ├── dist/ │ ├── 99-qdmr.rules │ ├── CMakeLists.txt │ ├── de.darc.dm3mat.qdmr.metainfo.xml │ ├── fedora/ │ │ ├── Dockerfile │ │ ├── docker-build.sh │ │ └── qdmr.spec │ ├── macosx/ │ │ ├── Info.plist │ │ └── qdmr.icns │ └── qdmr.desktop.in ├── doc/ │ ├── CMakeLists.txt │ ├── code/ │ │ ├── anytone_2tonefunction.txt │ │ ├── anytone_2toneid.txt │ │ ├── anytone_2tonesettings.txt │ │ ├── anytone_5tonefunction.txt │ │ ├── anytone_5tonefunctionlist.txt │ │ ├── anytone_5toneid.txt │ │ ├── anytone_5toneidlist.txt │ │ ├── anytone_5tonesettings.txt │ │ ├── anytone_alarmsetting.txt │ │ ├── anytone_analogalarm.txt │ │ ├── anytone_analogquickcall.txt │ │ ├── anytone_analogquickcalls.txt │ │ ├── anytone_bootsettings.txt │ │ ├── anytone_channel.txt │ │ ├── anytone_contact.txt │ │ ├── anytone_contactmapentry.txt │ │ ├── anytone_digitalalarm.txt │ │ ├── anytone_digitalalarmextension.txt │ │ ├── anytone_dmraprssettings.txt │ │ ├── anytone_dtmfcontact.txt │ │ ├── anytone_dtmfidlist.txt │ │ ├── anytone_dtmfsettings.txt │ │ ├── anytone_generalsettings.txt │ │ ├── anytone_grouplist.txt │ │ ├── anytone_hotkey.txt │ │ ├── anytone_hotkeysettings.txt │ │ ├── anytone_message.txt │ │ ├── anytone_messagelist.txt │ │ ├── anytone_radioid.txt │ │ ├── anytone_repeateroffsetfrequencies.txt │ │ ├── anytone_scanlist.txt │ │ ├── anytone_statusmessages.txt │ │ ├── anytone_wfmchannellist.txt │ │ ├── anytone_zonechannellist.txt │ │ ├── d578uv_airbandchannel.txt │ │ ├── d578uv_airbandchannellist.txt │ │ ├── d578uv_aprssettingext.txt │ │ ├── d578uv_channel.txt │ │ ├── d578uv_generalsettings.txt │ │ ├── d578uv_generalsettingsextension.txt │ │ ├── d578uv_hotkeysettings.txt │ │ ├── d868uv_callsigndbentry.txt │ │ ├── d868uv_callsigndblimit.txt │ │ ├── d868uv_channel.txt │ │ ├── d868uv_generalsettings.txt │ │ ├── d868uvanalogcontact.txt │ │ ├── d868uvchannel.txt │ │ ├── d868uvcontact.txt │ │ ├── d868uvgrouplist.txt │ │ ├── d868uvmessage.txt │ │ ├── d868uvmessagelist.txt │ │ ├── d868uvradioid.txt │ │ ├── d868uvscanlist.txt │ │ ├── d868uvzonechannels.txt │ │ ├── d878uv_aeskey.txt │ │ ├── d878uv_aprsrxentry.txt │ │ ├── d878uv_aprssetting.txt │ │ ├── d878uv_aprssettingext.txt │ │ ├── d878uv_channel.txt │ │ ├── d878uv_dmraprssettings.txt │ │ ├── d878uv_dmraprssystems.txt │ │ ├── d878uv_generalsettings.txt │ │ ├── d878uv_generalsettingsextension.txt │ │ ├── d878uv_gpsmessage.txt │ │ ├── d878uv_radioinfo.txt │ │ ├── d878uv_roamingchannel.txt │ │ ├── d878uv_roamingzone.txt │ │ ├── d878uvgpssetting.txt │ │ ├── detect_example.cc │ │ ├── dm1701_buttonsettings.txt │ │ ├── dm1701_channel.txt │ │ ├── dm1701_settings.txt │ │ ├── dm1701_zoneext.txt │ │ ├── dmr6x2uv_aprssetting.txt │ │ ├── dmr6x2uv_channel.txt │ │ ├── dmr6x2uv_generalsettings.txt │ │ ├── dmr6x2uv_settingsextension.txt │ │ ├── dr1801uv_alarmsystembankelement.txt │ │ ├── dr1801uv_alarmsystemelement.txt │ │ ├── dr1801uv_channelbankelement.txt │ │ ├── dr1801uv_channelelement.txt │ │ ├── dr1801uv_contactbankelement.txt │ │ ├── dr1801uv_contactelement.txt │ │ ├── dr1801uv_dmrsettingselement.txt │ │ ├── dr1801uv_dtmfidbankelement.txt │ │ ├── dr1801uv_dtmfidelement.txt │ │ ├── dr1801uv_dtmfsettingselement.txt │ │ ├── dr1801uv_dtmfsystembankelement.txt │ │ ├── dr1801uv_dtmfsystemelement.txt │ │ ├── dr1801uv_encryptionkeybankelement.txt │ │ ├── dr1801uv_encryptionkeyelement.txt │ │ ├── dr1801uv_grouplistbankelement.txt │ │ ├── dr1801uv_grouplistelement.txt │ │ ├── dr1801uv_keysettingselement.txt │ │ ├── dr1801uv_messagebankelement.txt │ │ ├── dr1801uv_messageelement.txt │ │ ├── dr1801uv_onetouchsettingelement.txt │ │ ├── dr1801uv_onetouchsettingselement.txt │ │ ├── dr1801uv_pttidbankelement.txt │ │ ├── dr1801uv_pttidelement.txt │ │ ├── dr1801uv_scanlistbankelement.txt │ │ ├── dr1801uv_scanlistelement.txt │ │ ├── dr1801uv_settingselement.txt │ │ ├── dr1801uv_vfobankelement.txt │ │ ├── dr1801uv_zonebankelement.txt │ │ ├── dr1801uv_zoneelement.txt │ │ ├── gd73_channel_bank.txt │ │ ├── gd73_channel_element.txt │ │ ├── gd73_contact_bank.txt │ │ ├── gd73_contact_element.txt │ │ ├── gd73_dmr_settings_element.txt │ │ ├── gd73_dtmf_code_element.txt │ │ ├── gd73_dtmf_ptt_settings.txt │ │ ├── gd73_dtmf_system_element.txt │ │ ├── gd73_encryption_key_bank.txt │ │ ├── gd73_encryption_key_element.txt │ │ ├── gd73_group_list_bank.txt │ │ ├── gd73_group_list_element.txt │ │ ├── gd73_message_bank.txt │ │ ├── gd73_message_element.txt │ │ ├── gd73_one_touch_element.txt │ │ ├── gd73_scan_list_bank.txt │ │ ├── gd73_scan_list_element.txt │ │ ├── gd73_settings_element.txt │ │ ├── gd73_timestamp.txt │ │ ├── gd73_zone_bank.txt │ │ ├── gd73_zone_element.txt │ │ ├── gd77_callsign_db_entry.txt │ │ ├── gd77_callsign_db_header.txt │ │ ├── gd77_channel.txt │ │ ├── gd77_contact.txt │ │ ├── gd77_grouplist.txt │ │ ├── gd77_grouplistbank.txt │ │ ├── gd77_scanlist.txt │ │ ├── gd77_scanlistbank.txt │ │ ├── md390_channel.txt │ │ ├── md390_menusettings.txt │ │ ├── opengd77_callsign_db_entry.txt │ │ ├── opengd77_callsign_db_header.txt │ │ ├── opengd77_channel.txt │ │ ├── opengd77_contact.txt │ │ ├── opengd77_protocol_command_clear_screen_request.txt │ │ ├── opengd77_protocol_command_control_request.txt │ │ ├── opengd77_protocol_command_display_text_request.txt │ │ ├── opengd77_protocol_command_okay_response.txt │ │ ├── opengd77_protocol_command_ping_request.txt │ │ ├── opengd77_protocol_command_render_screen_request.txt │ │ ├── opengd77_protocol_command_request.txt │ │ ├── opengd77_protocol_command_show_cps_screen_request.txt │ │ ├── opengd77_protocol_command_start_gps_request.txt │ │ ├── opengd77_protocol_read_request.txt │ │ ├── opengd77_protocol_read_response.txt │ │ ├── opengd77_radio_info.txt │ │ ├── opengd77_zone.txt │ │ ├── opengd77_zonebank.txt │ │ ├── openrtx_channel.txt │ │ ├── openrtx_contact.txt │ │ ├── openrtx_header.txt │ │ ├── openrtx_zone.txt │ │ ├── radioddity_bootsettings.txt │ │ ├── radioddity_boottext.txt │ │ ├── radioddity_buttonsettings.txt │ │ ├── radioddity_channel.txt │ │ ├── radioddity_channelbank.txt │ │ ├── radioddity_contact.txt │ │ ├── radioddity_dtmfcontact.txt │ │ ├── radioddity_generalsettings.txt │ │ ├── radioddity_grouplist.txt │ │ ├── radioddity_grouplistbank.txt │ │ ├── radioddity_menusettings.txt │ │ ├── radioddity_messagebank.txt │ │ ├── radioddity_privacy.txt │ │ ├── radioddity_scanlist.txt │ │ ├── radioddity_scanlistbank.txt │ │ ├── radioddity_vfochannel.txt │ │ ├── radioddity_zone.txt │ │ ├── radioddity_zonebank.txt │ │ ├── rd5r_channel.txt │ │ ├── rd5r_generalsettings.txt │ │ ├── rd5r_timestamp.txt │ │ ├── rd5rbootsettings.txt │ │ ├── rd5rbuttonsettings.txt │ │ ├── rd5rcontact.txt │ │ ├── rd5rdtmfcontact.txt │ │ ├── rd5rgrouplist.txt │ │ ├── rd5rgrouplisttab.txt │ │ ├── rd5rmenusettings.txt │ │ ├── rd5rmessagetab.txt │ │ ├── rd5rscanlist.txt │ │ ├── rd5rscanlisttab.txt │ │ ├── rd5rvfosettings.txt │ │ ├── rd5rzone.txt │ │ ├── rd5rzonetab.txt │ │ ├── tyt_buttonsettings.txt │ │ ├── tyt_channel.txt │ │ ├── tyt_contact.txt │ │ ├── tyt_emergencysettings.txt │ │ ├── tyt_emergencysystem.txt │ │ ├── tyt_gpssystem.txt │ │ ├── tyt_grouplist.txt │ │ ├── tyt_menusettings.txt │ │ ├── tyt_onetouchsettings.txt │ │ ├── tyt_privacy.txt │ │ ├── tyt_scanlist.txt │ │ ├── tyt_settings.txt │ │ ├── tyt_timestamp.txt │ │ ├── tyt_zone.txt │ │ ├── tytcallsigndbentry.txt │ │ ├── tytcallsigndbindex.txt │ │ ├── tytcallsigndbindexentry.txt │ │ ├── uv390_bootsettings.txt │ │ ├── uv390_channel.txt │ │ ├── uv390_menusettings.txt │ │ ├── uv390_settings.txt │ │ ├── uv390contact.txt │ │ ├── uv390gpssystem.txt │ │ ├── uv390message.txt │ │ ├── uv390rxgrouplist.txt │ │ ├── uv390scanlist.txt │ │ ├── uv390timestamp.txt │ │ ├── uv390userdb.txt │ │ ├── uv390userdbcallsign.txt │ │ ├── uv390userdbentry.txt │ │ ├── uv390zone.txt │ │ └── uv390zoneext.txt │ ├── dmr-intro/ │ │ ├── fig/ │ │ │ ├── Makefile │ │ │ ├── fm_duplex_a.tex │ │ │ ├── fm_duplex_b.tex │ │ │ ├── fm_echolink_a.tex │ │ │ ├── fm_echolink_b.tex │ │ │ ├── fm_echolink_c.tex │ │ │ ├── fm_simplex_a.tex │ │ │ ├── fm_simplex_b.tex │ │ │ ├── repeater.tex │ │ │ ├── repeater_local.tex │ │ │ ├── repeater_privatecall.tex │ │ │ ├── simplex_allcall.tex │ │ │ ├── simplex_groupcall.tex │ │ │ ├── simplex_privatecall.tex │ │ │ ├── talkgroup_ex1a.tex │ │ │ ├── talkgroup_ex1b.tex │ │ │ ├── talkgroup_ex1c.tex │ │ │ ├── timeslot.tex │ │ │ ├── trunk_net_ex1.tex │ │ │ ├── trunk_net_ex2.tex │ │ │ ├── trunk_net_ex3.tex │ │ │ ├── trunk_net_ex4a.tex │ │ │ └── trunk_net_ex4b.tex │ │ ├── script/ │ │ │ ├── Makefile │ │ │ ├── script_de.tex │ │ │ ├── script_de_01_vorwissen.tex │ │ │ ├── script_de_02_ursprung.tex │ │ │ ├── script_de_03_simplex.tex │ │ │ ├── script_de_04_lokal.tex │ │ │ ├── script_de_05_privatecall.tex │ │ │ ├── script_de_06_textmsg.tex │ │ │ ├── script_de_07_talkgroup.tex │ │ │ ├── script_de_08_aprs.tex │ │ │ ├── script_de_09_roaming.tex │ │ │ ├── script_de_10_netze.tex │ │ │ ├── script_de_11_technik.tex │ │ │ └── script_de_12_codeplug.tex │ │ ├── talk-barcamp2021/ │ │ │ └── slides.tex │ │ └── talk-ov2023/ │ │ └── slides.tex │ ├── dmrconf.in.xml │ ├── docbook_man.debian.xsl │ ├── docbook_man.fedora.xsl │ ├── docbook_man.macports.xsl │ ├── docbook_man.opensuse.xsl │ ├── fig/ │ │ └── autodetect.dot │ ├── manual/ │ │ ├── Makefile │ │ ├── cli/ │ │ │ ├── callsign.xml │ │ │ ├── codeplug.xml │ │ │ ├── commandline.xml │ │ │ ├── dangerzone.xml │ │ │ └── various.xml │ │ ├── codeplug/ │ │ │ ├── anytone/ │ │ │ │ ├── aprs.xml │ │ │ │ ├── channel.xml │ │ │ │ ├── contact.xml │ │ │ │ ├── extensions.xml │ │ │ │ ├── settings.xml │ │ │ │ └── zone.xml │ │ │ ├── aprs.xml │ │ │ ├── channels.xml │ │ │ ├── commercial/ │ │ │ │ ├── channel.xml │ │ │ │ ├── encryption.xml │ │ │ │ └── extensions.xml │ │ │ ├── contacts.xml │ │ │ ├── extensions.xml │ │ │ ├── format.xml │ │ │ ├── grouplists.xml │ │ │ ├── opengd77/ │ │ │ │ ├── channel.xml │ │ │ │ ├── contact.xml │ │ │ │ └── extensions.xml │ │ │ ├── radioddity/ │ │ │ │ ├── extensions.xml │ │ │ │ ├── generalradiosettings.xml │ │ │ │ ├── radiobootsettings.xml │ │ │ │ ├── radiobuttonsettings.xml │ │ │ │ ├── radiosettings.xml │ │ │ │ └── radiotonesettings.xml │ │ │ ├── radioids.xml │ │ │ ├── radiosettings.xml │ │ │ ├── roaming.xml │ │ │ ├── scanlists.xml │ │ │ ├── smsextension.xml │ │ │ ├── tyt/ │ │ │ │ ├── buttonsettings.xml │ │ │ │ ├── channel.xml │ │ │ │ ├── extensions.xml │ │ │ │ ├── menusettings.xml │ │ │ │ ├── radiosettings.xml │ │ │ │ └── scanlist.xml │ │ │ └── zones.xml │ │ ├── conf/ │ │ │ ├── analogchannels.xml │ │ │ ├── aprssystems.xml │ │ │ ├── contacts.xml │ │ │ ├── digitalchannels.xml │ │ │ ├── format.xml │ │ │ ├── gpssystems.xml │ │ │ ├── grouplists.xml │ │ │ ├── radiosettings.xml │ │ │ ├── roaming.xml │ │ │ ├── scanlists.xml │ │ │ └── zones.xml │ │ ├── epub/ │ │ │ └── Makefile │ │ ├── gui/ │ │ │ ├── aprs.xml │ │ │ ├── channels.xml │ │ │ ├── contacts.xml │ │ │ ├── extensions.xml │ │ │ ├── fig/ │ │ │ │ └── Makefile │ │ │ ├── grouplists.xml │ │ │ ├── gui.xml │ │ │ ├── programradio.xml │ │ │ ├── radiosettings.xml │ │ │ ├── roaming.xml │ │ │ ├── satellite.xml │ │ │ ├── scanlists.xml │ │ │ ├── settingsdialog.xml │ │ │ └── zones.xml │ │ ├── html/ │ │ │ ├── Makefile │ │ │ ├── dm3mat.darc.de_manual.xsl │ │ │ └── manual.css │ │ ├── intro/ │ │ │ ├── codeplug.xml │ │ │ ├── fig/ │ │ │ │ ├── Makefile │ │ │ │ ├── fm_duplex_a.tex │ │ │ │ ├── fm_duplex_b.tex │ │ │ │ ├── fm_echolink_a.tex │ │ │ │ ├── fm_echolink_b.tex │ │ │ │ ├── fm_echolink_c.tex │ │ │ │ ├── fm_simplex_a.tex │ │ │ │ ├── fm_simplex_b.tex │ │ │ │ ├── repeater.tex │ │ │ │ ├── repeater_local.tex │ │ │ │ ├── repeater_privatecall.tex │ │ │ │ ├── simplex_allcall.tex │ │ │ │ ├── simplex_groupcall.tex │ │ │ │ ├── simplex_privatecall.tex │ │ │ │ ├── talkgroup_ex1a.tex │ │ │ │ ├── talkgroup_ex1b.tex │ │ │ │ ├── talkgroup_ex1c.tex │ │ │ │ ├── timeslot.tex │ │ │ │ ├── trunk_net_ex1.tex │ │ │ │ ├── trunk_net_ex2.tex │ │ │ │ ├── trunk_net_ex3.tex │ │ │ │ ├── trunk_net_ex4a.tex │ │ │ │ └── trunk_net_ex4b.tex │ │ │ ├── foreknowledge.xml │ │ │ ├── introduction.xml │ │ │ ├── local.xml │ │ │ ├── networks.xml │ │ │ ├── origin.xml │ │ │ ├── private.xml │ │ │ ├── roaming.xml │ │ │ ├── simplex.xml │ │ │ ├── talkgroup.xml │ │ │ ├── technicalbackground.xml │ │ │ └── textmessages.xml │ │ ├── manual.code-workspace │ │ ├── manual.xml │ │ ├── manual_fo.debian.xsl │ │ ├── meta/ │ │ │ ├── abstract.xml │ │ │ ├── authors.xml │ │ │ ├── glossary.xml │ │ │ └── preface.xml │ │ └── reveng/ │ │ ├── codeplug.xml │ │ ├── protocol.xml │ │ └── reverseengineering.xml │ ├── qdmr.in.xml │ └── reveng/ │ ├── README.md │ ├── anytone/ │ │ ├── README.md │ │ ├── d578uv/ │ │ │ └── at_d578uv_emulator.py │ │ ├── d868uve/ │ │ │ └── at_d868uv_emulator.py │ │ ├── d878uv/ │ │ │ ├── README.md │ │ │ ├── capture_base.pcapng │ │ │ ├── capture_set_gps_on.pcapng │ │ │ ├── cpsfileformat.md │ │ │ ├── d878uv_base.hex │ │ │ ├── d878uv_set_gps_on.hex │ │ │ └── extract.py │ │ └── d878uv2/ │ │ └── at_d878uv2_emulator.py │ ├── baofeng/ │ │ ├── d6x2uv/ │ │ │ └── dmr_6x2uv_emulator.py │ │ └── dr1801/ │ │ ├── extract.py │ │ └── protocol.md │ ├── cotre/ │ │ ├── README.md │ │ └── extract.py │ ├── gd77/ │ │ ├── callsign-db.md │ │ └── dump.py │ ├── pinspect/ │ │ ├── __init__.py │ │ ├── auctus_a6.py │ │ ├── cdcacmfilter.py │ │ ├── datagram.py │ │ ├── devicefilter.py │ │ ├── packethandler.py │ │ ├── rawstreamdump.py │ │ ├── streamhandler.py │ │ └── utilities.py │ ├── radioddity/ │ │ └── gd73/ │ │ ├── README.md │ │ ├── extract.py │ │ └── protocol.md │ └── retevis/ │ └── rt84/ │ └── extract.py ├── examples/ │ ├── BER AirBand.yaml │ ├── kw.conf │ ├── kw.yaml │ ├── minimal.conf │ ├── minimal.yaml │ ├── pmr.yaml │ ├── potsdam.yaml │ └── sat.yaml ├── i18n/ │ ├── Makefile │ ├── de.ts │ ├── en_US.ts │ ├── fr.ts │ ├── it.ts │ ├── nl.ts │ ├── pl.ts │ ├── pt_BR.ts │ ├── ru.ts │ └── sv.ts ├── lib/ │ ├── CMakeLists.txt │ ├── addressmap.cc │ ├── addressmap.hh │ ├── anytone_codeplug.cc │ ├── anytone_codeplug.hh │ ├── anytone_extension.cc │ ├── anytone_extension.hh │ ├── anytone_filereader.cc │ ├── anytone_filereader.hh │ ├── anytone_interface.cc │ ├── anytone_interface.hh │ ├── anytone_limits.cc │ ├── anytone_limits.hh │ ├── anytone_radio.cc │ ├── anytone_radio.hh │ ├── anytone_satelliteconfig.cc │ ├── anytone_satelliteconfig.hh │ ├── anytone_settingsextension.cc │ ├── anytone_settingsextension.hh │ ├── auctus_a6_interface.cc │ ├── auctus_a6_interface.hh │ ├── audiosettings.cc │ ├── audiosettings.hh │ ├── bootsettings.cc │ ├── bootsettings.hh │ ├── c7000device.cc │ ├── c7000device.hh │ ├── callsigndb.cc │ ├── callsigndb.hh │ ├── channel.cc │ ├── channel.hh │ ├── channel_extension.cc │ ├── channel_extension.hh │ ├── chirpformat.cc │ ├── chirpformat.hh │ ├── codeplug.cc │ ├── codeplug.hh │ ├── commercial_extension.cc │ ├── commercial_extension.hh │ ├── config.cc │ ├── config.h.in │ ├── config.hh │ ├── configcopyvisitor.cc │ ├── configcopyvisitor.hh │ ├── configlabelingvisitor.cc │ ├── configlabelingvisitor.hh │ ├── configmergevisitor.cc │ ├── configmergevisitor.hh │ ├── configobject.cc │ ├── configobject.hh │ ├── configreference.cc │ ├── configreference.hh │ ├── contact.cc │ ├── contact.hh │ ├── crc32.cc │ ├── crc32.hh │ ├── csvreader.cc │ ├── csvreader.hh │ ├── d168uv.cc │ ├── d168uv.hh │ ├── d168uv_codeplug.cc │ ├── d168uv_codeplug.hh │ ├── d168uv_limits.cc │ ├── d168uv_limits.hh │ ├── d168uv_satelliteconfig.cc │ ├── d168uv_satelliteconfig.hh │ ├── d578uv.cc │ ├── d578uv.hh │ ├── d578uv_codeplug.cc │ ├── d578uv_codeplug.hh │ ├── d578uv_limits.cc │ ├── d578uv_limits.hh │ ├── d868uv.cc │ ├── d868uv.hh │ ├── d868uv_callsigndb.cc │ ├── d868uv_callsigndb.hh │ ├── d868uv_codeplug.cc │ ├── d868uv_codeplug.hh │ ├── d868uv_filereader.cc │ ├── d868uv_filereader.hh │ ├── d868uv_limits.cc │ ├── d868uv_limits.hh │ ├── d878uv.cc │ ├── d878uv.hh │ ├── d878uv2.cc │ ├── d878uv2.hh │ ├── d878uv2_callsigndb.cc │ ├── d878uv2_callsigndb.hh │ ├── d878uv2_codeplug.cc │ ├── d878uv2_codeplug.hh │ ├── d878uv2_limits.cc │ ├── d878uv2_limits.hh │ ├── d878uv_codeplug.cc │ ├── d878uv_codeplug.hh │ ├── d878uv_filereader.cc │ ├── d878uv_filereader.hh │ ├── d878uv_limits.cc │ ├── d878uv_limits.hh │ ├── dfu_libusb.cc │ ├── dfu_libusb.hh │ ├── dfufile.cc │ ├── dfufile.hh │ ├── dm1701.cc │ ├── dm1701.hh │ ├── dm1701_callsigndb.cc │ ├── dm1701_callsigndb.hh │ ├── dm1701_codeplug.cc │ ├── dm1701_codeplug.hh │ ├── dm1701_filereader.cc │ ├── dm1701_filereader.hh │ ├── dm1701_limits.cc │ ├── dm1701_limits.hh │ ├── dm32uv.cc │ ├── dm32uv.hh │ ├── dm32uv_callsigndb.cc │ ├── dm32uv_callsigndb.hh │ ├── dm32uv_codeplug.cc │ ├── dm32uv_codeplug.hh │ ├── dm32uv_interface.cc │ ├── dm32uv_interface.hh │ ├── dm32uv_limits.cc │ ├── dm32uv_limits.hh │ ├── dmr6x2uv.cc │ ├── dmr6x2uv.hh │ ├── dmr6x2uv2.cc │ ├── dmr6x2uv2.hh │ ├── dmr6x2uv2_codeplug.cc │ ├── dmr6x2uv2_codeplug.hh │ ├── dmr6x2uv2_limits.cc │ ├── dmr6x2uv2_limits.hh │ ├── dmr6x2uv_codeplug.cc │ ├── dmr6x2uv_codeplug.hh │ ├── dmr6x2uv_limits.cc │ ├── dmr6x2uv_limits.hh │ ├── dmrsettings.cc │ ├── dmrsettings.hh │ ├── dr1801uv.cc │ ├── dr1801uv.hh │ ├── dr1801uv_codeplug.cc │ ├── dr1801uv_codeplug.hh │ ├── dr1801uv_filereader.cc │ ├── dr1801uv_filereader.hh │ ├── dr1801uv_interface.cc │ ├── dr1801uv_interface.hh │ ├── dr1801uv_limits.cc │ ├── dr1801uv_limits.hh │ ├── dummyfilereader.cc │ ├── dummyfilereader.hh │ ├── encryptionextension.cc │ ├── encryptionextension.hh │ ├── errorstack.cc │ ├── errorstack.hh │ ├── frequency.cc │ ├── frequency.hh │ ├── gd73.cc │ ├── gd73.hh │ ├── gd73_codeplug.cc │ ├── gd73_codeplug.hh │ ├── gd73_filereader.cc │ ├── gd73_filereader.hh │ ├── gd73_interface.cc │ ├── gd73_interface.hh │ ├── gd73_limits.cc │ ├── gd73_limits.hh │ ├── gd77.cc │ ├── gd77.hh │ ├── gd77_callsigndb.cc │ ├── gd77_callsigndb.hh │ ├── gd77_codeplug.cc │ ├── gd77_codeplug.hh │ ├── gd77_filereader.cc │ ├── gd77_filereader.hh │ ├── gd77_limits.cc │ ├── gd77_limits.hh │ ├── gnsssettings.cc │ ├── gnsssettings.hh │ ├── gpssystem.cc │ ├── gpssystem.hh │ ├── hid_libusb.cc │ ├── hid_libusb.hh │ ├── hid_macos.cc │ ├── hid_macos.hh │ ├── intermediaterepresentation.cc │ ├── intermediaterepresentation.hh │ ├── interval.cc │ ├── interval.hh │ ├── level.cc │ ├── level.hh │ ├── libdmrconf.hh │ ├── logger.cc │ ├── logger.hh │ ├── md2017.cc │ ├── md2017.hh │ ├── md2017_callsigndb.cc │ ├── md2017_callsigndb.hh │ ├── md2017_codeplug.cc │ ├── md2017_codeplug.hh │ ├── md2017_filereader.cc │ ├── md2017_filereader.hh │ ├── md2017_limits.cc │ ├── md2017_limits.hh │ ├── md390.cc │ ├── md390.hh │ ├── md390_codeplug.cc │ ├── md390_codeplug.hh │ ├── md390_filereader.cc │ ├── md390_filereader.hh │ ├── md390_limits.cc │ ├── md390_limits.hh │ ├── melody.cc │ ├── melody.hh │ ├── melody_stream.cc │ ├── melody_stream.hh │ ├── opengd77.cc │ ├── opengd77.hh │ ├── opengd77_callsigndb.cc │ ├── opengd77_callsigndb.hh │ ├── opengd77_codeplug.cc │ ├── opengd77_codeplug.hh │ ├── opengd77_extension.cc │ ├── opengd77_extension.hh │ ├── opengd77_interface.cc │ ├── opengd77_interface.hh │ ├── opengd77_limits.cc │ ├── opengd77_limits.hh │ ├── opengd77_satelliteconfig.cc │ ├── opengd77_satelliteconfig.hh │ ├── opengd77base.cc │ ├── opengd77base.hh │ ├── opengd77base_callsigndb.cc │ ├── opengd77base_callsigndb.hh │ ├── opengd77base_codeplug.cc │ ├── opengd77base_codeplug.hh │ ├── opengd77base_satelliteconfig.cc │ ├── opengd77base_satelliteconfig.hh │ ├── openrtx.cc │ ├── openrtx.hh │ ├── openrtx_codeplug.cc │ ├── openrtx_codeplug.hh │ ├── openrtx_interface.cc │ ├── openrtx_interface.hh │ ├── openrtx_link.cc │ ├── openrtx_link.hh │ ├── openuv380.cc │ ├── openuv380.hh │ ├── openuv380_callsigndb.cc │ ├── openuv380_callsigndb.hh │ ├── openuv380_codeplug.cc │ ├── openuv380_codeplug.hh │ ├── openuv380_satelliteconfig.cc │ ├── openuv380_satelliteconfig.hh │ ├── orbitalelementsdatabase.cc │ ├── orbitalelementsdatabase.hh │ ├── packetstream.cc │ ├── packetstream.hh │ ├── radio.cc │ ├── radio.hh │ ├── radioddity_codeplug.cc │ ├── radioddity_codeplug.hh │ ├── radioddity_extensions.cc │ ├── radioddity_extensions.hh │ ├── radioddity_interface.cc │ ├── radioddity_interface.hh │ ├── radioddity_radio.cc │ ├── radioddity_radio.hh │ ├── radioid.cc │ ├── radioid.hh │ ├── radioinfo.cc │ ├── radioinfo.hh │ ├── radiointerface.cc │ ├── radiointerface.hh │ ├── radiolimits.cc │ ├── radiolimits.hh │ ├── radiosettings.cc │ ├── radiosettings.hh │ ├── ranges.cc │ ├── ranges.hh │ ├── rd5r.cc │ ├── rd5r.hh │ ├── rd5r_codeplug.cc │ ├── rd5r_codeplug.hh │ ├── rd5r_filereader.cc │ ├── rd5r_filereader.hh │ ├── rd5r_limits.cc │ ├── rd5r_limits.hh │ ├── roamingchannel.cc │ ├── roamingchannel.hh │ ├── roamingzone.cc │ ├── roamingzone.hh │ ├── rxgrouplist.cc │ ├── rxgrouplist.hh │ ├── satelliteconfig.cc │ ├── satelliteconfig.hh │ ├── satellitedatabase.cc │ ├── satellitedatabase.hh │ ├── scanlist.cc │ ├── scanlist.hh │ ├── signaling.cc │ ├── signaling.hh │ ├── smsextension.cc │ ├── smsextension.hh │ ├── talkgroupdatabase.cc │ ├── talkgroupdatabase.hh │ ├── tonesettings.cc │ ├── tonesettings.hh │ ├── transferflags.cc │ ├── transferflags.hh │ ├── transponderdatabase.cc │ ├── transponderdatabase.hh │ ├── tyt_callsigndb.cc │ ├── tyt_callsigndb.hh │ ├── tyt_codeplug.cc │ ├── tyt_codeplug.hh │ ├── tyt_extensions.cc │ ├── tyt_extensions.hh │ ├── tyt_interface.cc │ ├── tyt_interface.hh │ ├── tyt_radio.cc │ ├── tyt_radio.hh │ ├── usbdevice.cc │ ├── usbdevice.hh │ ├── usbserial.cc │ ├── usbserial.hh │ ├── userdatabase.cc │ ├── userdatabase.hh │ ├── utils.cc │ ├── utils.hh │ ├── uv390.cc │ ├── uv390.hh │ ├── uv390_callsigndb.cc │ ├── uv390_callsigndb.hh │ ├── uv390_codeplug.cc │ ├── uv390_codeplug.hh │ ├── uv390_filereader.cc │ ├── uv390_filereader.hh │ ├── uv390_limits.cc │ ├── uv390_limits.hh │ ├── visitor.cc │ ├── visitor.hh │ ├── xmodem.cc │ ├── xmodem.hh │ ├── zone.cc │ └── zone.hh ├── shared/ │ ├── icons/ │ │ ├── dark/ │ │ │ └── index.theme │ │ └── light/ │ │ └── index.theme │ ├── resources.qrc │ └── ui/ │ └── aboutdialog.ui ├── src/ │ ├── CMakeLists.txt │ ├── aboutdialog.ui │ ├── admitselect.cc │ ├── admitselect.hh │ ├── amchanneldialog.cc │ ├── amchanneldialog.hh │ ├── application.cc │ ├── application.hh │ ├── aprsselect.cc │ ├── aprsselect.hh │ ├── aprssystemdialog.cc │ ├── aprssystemdialog.hh │ ├── aprssystemdialog.ui │ ├── bandwidthselect.cc │ ├── bandwidthselect.hh │ ├── channel_type_edit.cc │ ├── channel_type_edit.hh │ ├── channelcombobox.cc │ ├── channelcombobox.hh │ ├── channeldialog.cc │ ├── channeldialog.hh │ ├── channeldialog.ui │ ├── channellistview.cc │ ├── channellistview.hh │ ├── channellistview.ui │ ├── channelselectiondialog.cc │ ├── channelselectiondialog.hh │ ├── channelvalidator.cc │ ├── channelvalidator.hh │ ├── collapsablewidget.cc │ ├── collapsablewidget.hh │ ├── configitemwrapper.cc │ ├── configitemwrapper.hh │ ├── configmergedialog.cc │ ├── configmergedialog.hh │ ├── configmergedialog.ui │ ├── configobjectlistview.cc │ ├── configobjectlistview.hh │ ├── configobjectlistview.ui │ ├── configobjecttableview.cc │ ├── configobjecttableview.hh │ ├── configobjecttableview.ui │ ├── configobjecttypeselectiondialog.cc │ ├── configobjecttypeselectiondialog.hh │ ├── configobjecttypeselectiondialog.ui │ ├── contactlistview.cc │ ├── contactlistview.hh │ ├── contactlistview.ui │ ├── contactselectiondialog.cc │ ├── contactselectiondialog.hh │ ├── deviceselectiondialog.cc │ ├── deviceselectiondialog.hh │ ├── deviceselectiondialog.ui │ ├── dmrchanneldialog.cc │ ├── dmrchanneldialog.hh │ ├── dmrcontactdialog.cc │ ├── dmrcontactdialog.hh │ ├── dmrcontactdialog.ui │ ├── dmriddialog.cc │ ├── dmriddialog.hh │ ├── dmriddialog.ui │ ├── dtmfcontactdialog.cc │ ├── dtmfcontactdialog.hh │ ├── dtmfcontactdialog.ui │ ├── errormessageview.cc │ ├── errormessageview.hh │ ├── errormessageview.ui │ ├── extensionview.cc │ ├── extensionview.hh │ ├── extensionview.ui │ ├── extensionwrapper.cc │ ├── extensionwrapper.hh │ ├── flageditdialog.cc │ ├── flageditdialog.hh │ ├── flageditdialog.ui │ ├── fmchanneldialog.cc │ ├── fmchanneldialog.hh │ ├── generalsettingsview.cc │ ├── generalsettingsview.hh │ ├── generalsettingsview.ui │ ├── gpssystemdialog.cc │ ├── gpssystemdialog.hh │ ├── gpssystemdialog.ui │ ├── grouplistsview.cc │ ├── grouplistsview.hh │ ├── grouplistsview.ui │ ├── hearhamrepeatersource.cc │ ├── hearhamrepeatersource.hh │ ├── idselect.cc │ ├── idselect.hh │ ├── m17channeldialog.cc │ ├── m17channeldialog.hh │ ├── m17contactdialog.cc │ ├── m17contactdialog.hh │ ├── m17contactdialog.ui │ ├── main.cc │ ├── mainwindow.cc │ ├── mainwindow.hh │ ├── mainwindow.ui │ ├── melody_edit.cc │ ├── melody_edit.hh │ ├── melody_player.cc │ ├── melody_player.hh │ ├── positioningsystemlistview.cc │ ├── positioningsystemlistview.hh │ ├── positioningsystemlistview.ui │ ├── propertydelegate.cc │ ├── propertydelegate.hh │ ├── radioidlistview.cc │ ├── radioidlistview.hh │ ├── radioidlistview.ui │ ├── radioidrepeatersource.cc │ ├── radioidrepeatersource.hh │ ├── radioselectiondialog.cc │ ├── radioselectiondialog.hh │ ├── radioselectiondialog.ui │ ├── releasenotes.cc │ ├── releasenotes.hh │ ├── repeaterbooksource.cc │ ├── repeaterbooksource.hh │ ├── repeatercompleter.cc │ ├── repeatercompleter.hh │ ├── repeaterdatabase.cc │ ├── repeaterdatabase.hh │ ├── repeatermapsource.cc │ ├── repeatermapsource.hh │ ├── roamingchanneldialog.cc │ ├── roamingchanneldialog.hh │ ├── roamingchanneldialog.ui │ ├── roamingchannellistview.cc │ ├── roamingchannellistview.hh │ ├── roamingchannellistview.ui │ ├── roamingchannelselectiondialog.cc │ ├── roamingchannelselectiondialog.hh │ ├── roamingzonedialog.cc │ ├── roamingzonedialog.hh │ ├── roamingzonedialog.ui │ ├── roamingzonelistview.cc │ ├── roamingzonelistview.hh │ ├── roamingzonelistview.ui │ ├── rxgrouplistdialog.cc │ ├── rxgrouplistdialog.hh │ ├── rxgrouplistdialog.ui │ ├── satellitedatabasedialog.cc │ ├── satellitedatabasedialog.hh │ ├── satellitedatabasedialog.ui │ ├── satelliteselectiondialog.cc │ ├── satelliteselectiondialog.hh │ ├── satelliteselectiondialog.ui │ ├── satellitetransponderdialog.cc │ ├── satellitetransponderdialog.hh │ ├── satellitetransponderdialog.ui │ ├── scanlistdialog.cc │ ├── scanlistdialog.hh │ ├── scanlistdialog.ui │ ├── scanlistsview.cc │ ├── scanlistsview.hh │ ├── scanlistsview.ui │ ├── searchpopup.cc │ ├── searchpopup.hh │ ├── selectivecallbox.cc │ ├── selectivecallbox.hh │ ├── settings.cc │ ├── settings.hh │ ├── settingsdialog.ui │ ├── squelchedit.cc │ ├── squelchedit.hh │ ├── squelchedit.ui │ ├── timeslotselect.cc │ ├── timeslotselect.hh │ ├── transponderfrequencydelegate.cc │ ├── transponderfrequencydelegate.hh │ ├── verifydialog.cc │ ├── verifydialog.hh │ ├── verifydialog.ui │ ├── zonedialog.cc │ ├── zonedialog.hh │ ├── zonedialog.ui │ ├── zonelistview.cc │ ├── zonelistview.hh │ └── zonelistview.ui └── test/ ├── CMakeLists.txt ├── chirptest.cc ├── chirptest.hh ├── configtest.cc ├── configtest.hh ├── copytest.cc ├── copytest.hh ├── crc32test.cc ├── crc32test.hh ├── d168uv_test.cc ├── d168uv_test.hh ├── d578uv_test.cc ├── d578uv_test.hh ├── d868uve_test.cc ├── d868uve_test.hh ├── d878uv2_test.cc ├── d878uv2_test.hh ├── d878uv_test.cc ├── d878uv_test.hh ├── data/ │ ├── aes_encryption.yaml │ ├── am_channel_test.yaml │ ├── anytone_auto_repeater_extension.yaml │ ├── anytone_call_hangtime.yaml │ ├── anytone_channel_data_ack.yaml │ ├── anytone_key_function.yaml │ ├── anytone_settings_display.yaml │ ├── anytone_settings_roaming.yaml │ ├── arc4_encryption.yaml │ ├── audio_settings_extension.yaml │ ├── basic_encryption.yaml │ ├── channel_frequency_test.yaml │ ├── chirp_bandwidth.csv │ ├── chirp_cross.csv │ ├── chirp_ctcss.csv │ ├── chirp_dcs.csv │ ├── chirp_simple.csv │ ├── config_test.yaml │ ├── ctcss_copy_test.yaml │ ├── ctcss_null_test.yaml │ ├── dtmf_contact.yaml │ ├── fm_aprs_test.yaml │ ├── multiple_radio_ids.yaml │ ├── opengd77_boot_melody.yaml │ ├── opengd77_simple_config.yaml │ └── roaming_channel_test.yaml ├── dm1701_test.cc ├── dm1701_test.hh ├── dm32uv_test.cc ├── dm32uv_test.hh ├── dmr6x2uv2_test.cc ├── dmr6x2uv2_test.hh ├── dmr6x2uv_test.cc ├── dmr6x2uv_test.hh ├── dr1801_test.cc ├── dr1801_test.hh ├── gd73_test.cc ├── gd73_test.hh ├── gd77_test.cc ├── gd77_test.hh ├── labeltest.cc ├── labeltest.hh ├── libdmrconfigtest.cc ├── libdmrconfigtest.hh ├── md2017_test.cc ├── md2017_test.hh ├── md390_test.cc ├── md390_test.hh ├── mergetest.cc ├── mergetest.hh ├── opengd77_test.cc ├── opengd77_test.hh ├── openuv380_test.cc ├── openuv380_test.hh ├── rd5r_test.cc ├── rd5r_test.hh ├── resources.qrc ├── smstemplatetest.cc ├── smstemplatetest.hh ├── tableformattest.cc ├── tableformattest.hh ├── trafotest.cc ├── trafotest.hh ├── utilstest.cc ├── utilstest.hh ├── uv390_test.cc └── uv390_test.hh ================================================ FILE CONTENTS ================================================ ================================================ FILE: .clang-format ================================================ # yaml-language-server: $schema=https://json.schemastore.org/clang-format.json --- Language: Cpp AccessModifierOffset: -2 AlignAfterOpenBracket: Align AlignArrayOfStructures: None AlignConsecutiveAssignments: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveBitFields: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveDeclarations: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveMacros: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveShortCaseStatements: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCaseArrows: false AlignCaseColons: false AlignConsecutiveTableGenBreakingDAGArgColons: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveTableGenCondOperatorColons: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignConsecutiveTableGenDefinitionColons: Enabled: false AcrossEmptyLines: false AcrossComments: false AlignCompound: false AlignFunctionPointers: false PadOperators: false AlignEscapedNewlines: DontAlign AlignOperands: Align AlignTrailingComments: Kind: Always OverEmptyLines: 0 AllowAllArgumentsOnNextLine: true AllowAllParametersOfDeclarationOnNextLine: true AllowBreakBeforeNoexceptSpecifier: Never AllowShortBlocksOnASingleLine: Never AllowShortCaseExpressionOnASingleLine: true AllowShortCaseLabelsOnASingleLine: false AllowShortCompoundRequirementOnASingleLine: true AllowShortEnumsOnASingleLine: true AllowShortFunctionsOnASingleLine: Inline AllowShortIfStatementsOnASingleLine: Never AllowShortLambdasOnASingleLine: All AllowShortLoopsOnASingleLine: false AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakBeforeMultilineStrings: false AttributeMacros: - __capability BinPackArguments: false BinPackParameters: false BitFieldColonSpacing: Both BraceWrapping: AfterCaseLabel: false AfterClass: true AfterControlStatement: Never AfterEnum: false AfterExternBlock: false AfterFunction: false AfterNamespace: false AfterObjCDeclaration: false AfterStruct: true AfterUnion: false BeforeCatch: false BeforeElse: false BeforeLambdaBody: false BeforeWhile: false IndentBraces: false SplitEmptyFunction: false SplitEmptyRecord: false SplitEmptyNamespace: false BreakAdjacentStringLiterals: true BreakAfterAttributes: Leave BreakAfterJavaFieldAnnotations: false BreakAfterReturnType: AllDefinitions BreakArrays: true BreakBeforeBinaryOperators: All BreakBeforeConceptDeclarations: Always BreakBeforeBraces: Custom BreakBeforeInlineASMColon: OnlyMultiline BreakBeforeTernaryOperators: true BreakConstructorInitializers: BeforeComma BreakFunctionDefinitionParameters: false BreakInheritanceList: BeforeColon BreakStringLiterals: true BreakTemplateDeclarations: Yes ColumnLimit: 100 CommentPragmas: '^ IWYU pragma:' CompactNamespaces: false ConstructorInitializerIndentWidth: 2 ContinuationIndentWidth: 2 Cpp11BracedListStyle: true DerivePointerAlignment: true DisableFormat: false EmptyLineAfterAccessModifier: Never EmptyLineBeforeAccessModifier: LogicalBlock ExperimentalAutoDetectBinPacking: false FixNamespaceComments: true ForEachMacros: - forever - foreach - Q_FOREACH - BOOST_FOREACH IfMacros: - KJ_IF_MAYBE IncludeBlocks: Preserve IncludeCategories: - Regex: '^ dmrcond read --verbose DEVICE_codeplug.dfu ``` If this succeeds, qdmr/dmrconf can talk to the device just fine. To help with identifying the decoding issue, consider adding this binary codeplug to the issue. You may need to zip it first. Also report your qdmr version, the device firmware version and the output of ``` > dmrconf decode --verbose --radio=YOUR_DEVICE DFU_FILE_NAME decoded.yaml ``` ================================================ FILE: .github/workflows/clang.yml ================================================ name: build-linux-clang-14 on: push: branches: [ "master" ] pull_request: branches: [ "master" ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release CC: /usr/bin/clang-14 CXX: /usr/bin/clang++-14 jobs: build: # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. # You can convert this to a matrix build if you need cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Update APT run: sudo apt-get update - name: Install Dependencies run: sudo apt-get install clang-14 qt6-tools-dev qt6-base-dev qt6-base-dev-tools qt6-serialport-dev qt6-positioning-dev qt6-multimedia-dev libusb-1.0-0-dev libyaml-cpp-dev librsvg2-bin - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} - name: Build # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} ================================================ FILE: .github/workflows/flatpak.yml ================================================ on: release: types: [published] name: Flatpak jobs: flatpak: name: "Flatpak" runs-on: ubuntu-latest container: image: ghcr.io/flathub-infra/flatpak-github-actions:kde-6.9 options: --privileged steps: - uses: actions/checkout@v4 - uses: flatpak/flatpak-github-actions/flatpak-builder@v6 with: bundle: qdmr.flatpak manifest-path: de.darc.dm3mat.qdmr.yaml cache-key: flatpak-builder-${{ github.sha }} - name: upload linux artifact uses: softprops/action-gh-release@v1 if: ${{startsWith(github.ref, 'refs/tags/') }} with: files: qdmr-x86_64.flatpak.zip ================================================ FILE: .github/workflows/unittests.yml ================================================ name: unittest-linux-gcc on: push: branches: [ "master", "devel" ] pull_request: branches: [ "master", "devel" ] env: # Customize the CMake build type here (Release, Debug, RelWithDebInfo, etc.) BUILD_TYPE: Release jobs: build: # The CMake configure and build commands are platform agnostic and should work equally well on Windows or Mac. # You can convert this to a matrix build if you need cross-platform coverage. # See: https://docs.github.com/en/free-pro-team@latest/actions/learn-github-actions/managing-complex-workflows#using-a-build-matrix runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - name: Update APT run: sudo apt-get update - name: Install Dependencies run: sudo apt-get install qt6-tools-dev qt6-base-dev qt6-base-dev-tools qt6-serialport-dev qt6-positioning-dev qt6-multimedia-dev libusb-1.0-0-dev libyaml-cpp-dev librsvg2-bin - name: Configure CMake # Configure CMake in a 'build' subdirectory. `CMAKE_BUILD_TYPE` is only required if you are using a single-configuration generator such as make. # See https://cmake.org/cmake/help/latest/variable/CMAKE_BUILD_TYPE.html?highlight=cmake_build_type run: cmake -B ${{github.workspace}}/build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_TESTS=On - name: Build # Build your program with the given configuration run: cmake --build ${{github.workspace}}/build --config ${{env.BUILD_TYPE}} - name: Test working-directory: ${{github.workspace}}/build # Execute tests defined by the CMake configuration. # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail run: ctest -C ${{env.BUILD_TYPE}} ================================================ FILE: .gitignore ================================================ /doc/html/ /doc/latex/ /doc/reveng/cotre/*.pcapng.gz /doc/reveng/gd77/*.pcapng.gz /doc/manual/manual.pdf /doc/manual/manual_combined.xml /doc/manual/intro/fig/*.aux /doc/manual/intro/fig/*.log /doc/manual/intro/fig/*.png /doc/manual/html/*.html /doc/manual/html/manual.xml /doc/manual/html/docbook.css /doc/manual/html/fig /parts/ /prime/ /snap/ /stage/ CMakeLists.txt.user /build/ ================================================ FILE: .gitmodules ================================================ ================================================ FILE: BUILD.md ================================================ # Building `qdmr` ## Overview `qdmr` is a Qt6-based application. This document provides instructions to build the project and details the required dependencies for Ubuntu/Debian, Fedora, Arch Linux, and macOS (with Homebrew). --- ## Dependencies The following dependencies are required: - `qt6-base-dev` - `qt6-tools-dev` - `qt6-tools-dev-tools` - `qt6-positioning-dev` - `qt6-serialport-dev` - `qt6-multimedia-dev` - `libyaml-cpp-dev` - `librsvg2-bin` - `libusb-1.0-0-dev` Equivalent packages are listed for Fedora, Arch Linux, and macOS. --- ## 1. Ubuntu / Debian ### **Install Dependencies** ```sh sudo apt update sudo apt install -y \ qt6-base-dev qt6-tools-dev qt6-tools-dev-tools \ qt6-positioning-dev qt6-serialport-dev qt6-multimedia-dev \ libyaml-cpp-dev librsvg2-bin libusb-1.0-0-dev \ build-essential cmake git ``` --- ## 2. Fedora ### **Install Dependencies** ```sh sudo dnf install -y \ qt6-qtbase-devel qt6-qttools-devel qt6-qtpositioning-devel qt6-qtserialport-devel qt6-multimedia-devel\ yaml-cpp-devel librsvg2-tools libusb1-devel \ make gcc-c++ cmake git ``` --- ## 3. Arch Linux ### **Install Dependencies** ```sh sudo pacman -S --needed \ qt6-base qt6-tools qt6-positioning qt6-serialport qt6-multimedia \ yaml-cpp librsvg libusb cmake git base-devel ``` --- ## 4. macOS (Homebrew) ### **Install Dependencies** ```sh brew install \ qt@6 yaml-cpp librsvg libusb cmake git ``` > **Note:** You may need to set environment variables for Qt6 tools if Homebrew does not symlink them into your PATH: > > ```sh > export PATH="/opt/homebrew/opt/qt@6/bin:$PATH" > export LDFLAGS="-L/opt/homebrew/opt/qt@6/lib" > export CPPFLAGS="-I/opt/homebrew/opt/qt@6/include" > ``` --- ## Build Instructions (All Platforms) 1. **Clone the repository** ```sh git clone https://github.com/hmatuschek/qdmr.git cd qdmr ``` 2. **Create a build directory** ```sh mkdir build cd build ``` 3. **Configure the project** On some systems, you may need to specify the location of your Qt6 installation: ```sh cmake .. -DQt6_ROOT=/path/to/qt6 ``` The `Qt6_ROOT` specifies the base-path for searching Qt6 related dependencies. > On macOS, you may need to help CMake find Qt6: > ```sh > cmake .. -DQt6_ROOT=$(brew --prefix qt@6) > ``` If you want to specify an install prefix , run e.g. ```sh cmake .. -DCMAKE_INSTALL_PREFIX=~/.local/ ``` The latter will prepare an user-local installation. 4. **Build the project** ```sh cmake --build . ``` 5. **(Optional) Run the application** ```sh ./src/qdmr ``` 6. **(Optional) Install** ```sh cmake --install . ``` This installs everything under the aforementioned install prefix. --- ## Troubleshooting - **Missing Packages:** Ensure all dependencies are installed as per your OS section. - **CMake Can't Find Qt6:** Double-check your `PATH`, `Qt6_ROOT`, and Qt6 installation. - **Permission Errors:** Try using `sudo` if needed for installation steps, but not for building the project. --- ## License See [LICENSE](LICENSE) for details. ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.25.0) project(qdmr VERSION 0.15.0) set(RELEASE_SUFFIX "") cmake_policy(SET CMP0100 NEW) cmake_policy(SET CMP0140 NEW) set(CMAKE_C_STANDARD 99) set(CMAKE_CXX_STANDARD 14) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(DOCBOOK2MAN_XSLT_DEFAULT "docbook_man.debian.xsl") if(UNIX AND APPLE) set(DOCBOOK2MAN_XSLT_DEFAULT "docbook_man.macports.xsl") endif(UNIX AND APPLE) option(BUILD_TESTS "Build test programs" OFF) option(BUILD_DOCS "Build API documentation" OFF) option(BUILD_MAN "Build man page for dmrconf" OFF) option(INSTALL_UDEV_RULES "Install udev rules file." ON) option(INSTALL_UDEV_PATH "Install path of udev rules file." "/etc/udev/rules.d") option(INSTALL_APPSTREAM_DATA "Install AppStream metainfo file." ON) option(INSTALL_BUNDLE "Installs QDMR as an AppBundle under MacOS X" OFF) option(BUNDLE_PATH "Where to install the MacOS X application bundle." "~/Applications") option(DOCBOOK2MAN_XSLT "Man page style sheet." ${DOCBOOK2MAN_XSLT_DEFAULT}) set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${PROJECT_SOURCE_DIR}/cmake) include(GNUInstallDirs) include(GenerateIcons) find_package(Qt6 REQUIRED COMPONENTS Core Widgets UiTools Network SerialPort Positioning Concurrent LinguistTools Multimedia) qt6_standard_project_setup() find_package(LIBUSB_1 REQUIRED) find_package(YAMLCPP REQUIRED) find_program(CONVERT_CMD rsvg-convert DOC "SVG conversion tool (librsvg)." REQUIRED) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTOUIC ON) set(CMAKE_AUTORCC ON) set(USE_FOLDERS ON) if (BUILD_MAN) find_program(XSLTPROC_EXECUTABLE NAMES xsltproc) endif() if (BUILD_DOCS) find_package(Doxygen REQUIRED dot) endif() if (${BUILD_TESTS}) find_package(Qt6 REQUIRED COMPONENTS Test) enable_testing(true) endif(${BUILD_TESTS}) IF (UNIX AND APPLE) SET(ADDITIONAL_LIBS ${ADDITIONAL_LIBS} "-framework IOKit -framework CoreFoundation") ENDIF(UNIX AND APPLE) INCLUDE_DIRECTORIES(${LIBUSB_1_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${YAMLCPP_INCLUDE_DIRS}) INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/src) INCLUDE_DIRECTORIES(${PROJECT_SOURCE_DIR}/lib) INCLUDE_DIRECTORIES(${PROJECT_BINARY_DIR}/lib) LINK_DIRECTORIES(${PROJECT_BINARY_DIR}/src) set(PROJECT_VERSION_STRING "\"${PROJECT_VERSION}\"") message(STATUS "Build version ${PROJECT_VERSION}") # Set compiler flags set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Wsign-compare") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -O0 -ggdb -fstack-protector -Wextra") set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -O3 -Wextra") # Get default install directories under Linux IF(UNIX AND NOT APPLE) INCLUDE(GNUInstallDirs) ENDIF(UNIX AND NOT APPLE) IF(UNIX AND APPLE) SET(CMAKE_INSTALL_LIBDIR "lib") SET(CMAKE_INSTALL_FULL_LIBDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_LIBDIR}") SET(CMAKE_INSTALL_INCLUDEDIR "include") SET(CMAKE_INSTALL_FULL_INCLUDEDIR "${CMAKE_INSTALL_PREFIX}/${CMAKE_INSTALL_INCLUDEDIR}") # Set RPATH under MacOS SET(CMAKE_SKIP_RPATH FALSE) SET(CMAKE_SKIP_BUILD_RPATH FALSE) SET(CMAKE_SKIP_INSTALL_RPATH FALSE) SET(CMAKE_BUILD_WITH_INSTALL_RPATH FALSE) SET(CMAKE_INSTALL_RPATH "${CMAKE_INSTALL_PREFIX}/lib") SET(CMAKE_INSTALL_RPATH_USE_LINK_PATH TRUE) message(STATUS "RPATH: ${CMAKE_INSTALL_RPATH}") ENDIF(UNIX AND APPLE) # Sources... add_subdirectory(lib) add_subdirectory(cli) add_subdirectory(src) add_subdirectory(doc) add_subdirectory(dist) if(BUILD_TESTS) enable_testing() add_subdirectory(test) endif(BUILD_TESTS) # Source distribution packages: set(CPACK_SOURCE_GENERATOR "TGZ") set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CMAKE_PROJECT_NAME}-${PROJECT_VERSION}${RELEASE_SUFFIX}") set(CPACK_SOURCE_IGNORE_FILES "/build/;/doc/html;/doc/latex;/doc/reveng;/doc/dmr-intro;/examples/*.dfu;/.git/;~$;.qm;*.user$;${CPACK_SOURCE_IGNORE_FILES}") include(CPack) ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: Copyright (C) This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: README.md ================================================ # QDMR, a GUI application and command line tool to program DMR radios Translation status ![qdmr channel editor](https://raw.githubusercontent.com/hmatuschek/qdmr/master/doc/fig/qdmr-channels.png "The qdmr CPS software.") *qdmr* is a graphical user interface (GUI) application that allows to program several types of DMR radios. To this end, it aims at being a more universal codeplug programming software (CPS) compared to the device and even revision specific CPSs provided by the manufacturers. The goal of this project is to provide a **single**, **comfortable**, [**well-documented**](https://static.dm3mat.de/qdmr/manual/) and **platform-independent** CPS for several types of (mainly Chinese) DMR radios. ## Supported Radios Currently, there are only few radios that are supported * OpenGD77, OpenRD5R, etc. firmware (since version 0.4.0) * OpenUV390, OpenDM1701, etc. firmware (since version 0.13.0) * Radioddity GD77 (since version 0.8.1) * Radioddity GD73 (since version 0.12.0) * Baofeng/Radioddity RD-5R & RD-5R+ (since version 0.2.0) * TYT MD-390 / Retevis RT8 (since version 0.9.0) * TYT MD-UV380 (since version 0.9.0) * TYT MD-UV390 / Retevis RT3S (since version 0.3.0) * TYT MD-2017 / Retevis RT82 (since version 0.9.0) * Anytone AT-D878UV (since version 0.5.0) * Anytone AT-D868UVE (since version 0.7.0) * Anytone AT-D878UVII (since version 0.8.0) * Anytone AT-D578UV (since version 0.8.0) * Anytone AT-D578UV II (since version 0.13.0) * BTECH DM-1701 / Retevis RT84 (since version 0.10.0) * BTECH BF-1801A6 (since version 0.12.0) * BTECH DMR-6x2 (since version 0.11.0) * BTECH DMR-6x2 PRO (since version 0.13.0) * Baofeng DM-32UV (since version 0.14.0) A more [detailed list](https://dm3mat.de/software/qdmr) is also available. ## Questions? * Have a look at the [discussions](https://github.com/hmatuschek/qdmr/discussions), maybe your questions has already been answered. * There is a *Matrix* chat at [#qdmr:darc.de](https://matrix.to/#/#qdmr:darc.de). * You can also follow me at [mastodon](https://mastodon.radio/@dm3mat), where I usually announce new releases. ## Want to help? * If you find any bugs or have suggestions to improve qdmr, consider [opening an issue](https://github.com/hmatuschek/qdmr/issues/new) or participate in one of the [discussions](https://github.com/hmatuschek/qdmr/discussions). * If you want to help translating qdmr in your language, checkout [qdmr's weblate project](https://translate.codeberg.org/projects/qdmr/graphical-user-interface/). ## Ecosystem As *qdmr* gets more and more popular with Linux HAMs, the ecosystem around it grows too. These are tools, that make your day-to-day usage of *qdmr* easier, by providing features, not covered in qdmr or dmrconf. * **[dmrfill](https://github.com/jancona/dmrfill)** -- Automatically extemds a qdmr YAML file with repeaters from a selected region. Get them all with one single command. * **[anytone-emu](https://github.com/dmr-tools/anytone-emu)** -- A tool for emulating radios, reverse engineering and documenting codeplugs. Also generates some [codeplug documentation](https://dmr-tools.github.io/codeplugs/). ## Releases Packaging status * **[Version 0.14.1](https://github.com/hmatuschek/qdmr/releases/tag/v0.14.0)** -- Bugfix release. * **[Version 0.14.0](https://github.com/hmatuschek/qdmr/releases/tag/v0.14.0)** -- Added support for Baofeng DM-32UV, added AM channels. * **[Version 0.13.3](https://github.com/hmatuschek/qdmr/releases/tag/v0.13.3)** -- Added support BTech DMR6X2PRO and OpenUV390. * **[Version 0.12.3](https://github.com/hmatuschek/qdmr/releases/tag/v0.12.3)** -- Added support BTech DR-1801UV (A6, still unstable) and Radioddity GD-73. * **[Version 0.11.3](https://github.com/hmatuschek/qdmr/releases/tag/v0.11.3)** -- Added proper support for BTech DMR-6X2UV, device specific settings for AnyTone devices, some bugfixes. * **[Version 0.10.4](https://github.com/hmatuschek/qdmr/releases/tag/v0.10.4)** -- Added support for BTech DM1701, some bugfixes. * **[Version 0.9.3](https://github.com/hmatuschek/qdmr/releases/tag/v0.9.3)** -- Reworked core library, added support for TyT MD-2017/Retevis RT82, TyT MD-390/Retevis RT8 & TyT MD-UV380. * **[Version 0.8.1](https://github.com/hmatuschek/qdmr/releases/tag/v0.8.1)** -- Fixed Radioddity GD-77 support (callsign db still buggy). * **[Version 0.7.0](https://github.com/hmatuschek/qdmr/releases/tag/v0.7.0)** -- Added AT-D868UVE support and many bugfixes. * **[Version 0.6.0](https://github.com/hmatuschek/qdmr/releases/tag/v0.6.4)** -- Added APRS & roaming for AT-D878UV. * **[Version 0.5.0](https://github.com/hmatuschek/qdmr/releases/tag/v0.5.0)** -- Added support for Anytone AT-D878UV. * **[Version 0.4.0](https://github.com/hmatuschek/qdmr/releases/tag/v0.4.0)** -- Added Open GD77 support. * **[Version 0.2.1](https://github.com/hmatuschek/qdmr/releases/tag/v0.2.1)** -- First public release. ## Install There are several ways to install qdmr on your system ranging from simple app-package downloads to building qdmr from its sources. For a detailed list of instructions for your system, read the [install instructions](https://dm3mat.de/software/qdmr/install). Some distributions (see badge above) already added qdmr, thus easing the install using the system package manager. ## License qdmr - A GUI application and command-line-tool to program DMR radios. Copyright (C) 2019-2026 Hannes Matuschek, DM3MAT This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. ================================================ FILE: cli/CMakeLists.txt ================================================ qt_add_executable(dmrconf WIN32 main.cc printprogress.cc printprogress.hh detect.cc detect.hh verify.cc verify.hh readcodeplug.cc readcodeplug.hh writecodeplug.cc writecodeplug.hh encodecodeplug.cc encodecodeplug.hh decodecodeplug.cc decodecodeplug.hh infofile.cc infofile.hh writecallsigndb.cc writecallsigndb.hh encodecallsigndb.cc encodecallsigndb.hh progressbar.cc progressbar.hh autodetect.cc autodetect.hh) target_link_libraries( dmrconf PRIVATE Qt6::Core Qt6::SerialPort Qt6::Network Qt6::Positioning ${LIBUSB_1_LIBRARIES} ${YAMLCPP_LIBRARIES} libdmrconf) install(TARGETS dmrconf DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) ================================================ FILE: cli/autodetect.cc ================================================ #include "autodetect.hh" #include "logger.hh" #include "radioinfo.hh" #include "usbdevice.hh" QVariant parseDeviceHandle(const QString &device) { QRegularExpression pattern("([0-9]+):([0-9]+)"); auto match = pattern.match(device.simplified()); if (match.hasMatch()) { return QVariant::fromValue(USBDeviceHandle(match.captured(1).toUInt(), match.captured(2).toUInt())); } return QVariant(device.simplified()); } void printDevices(QTextStream &out, const QList &devices) { foreach (USBDeviceDescriptor device, devices) { if (USBDeviceInfo::Class::None == device.interfaceClass()) continue; out << "Device '"; if (USBDeviceInfo::Class::Serial == device.interfaceClass()) { out << device.device().toString() << "'\n"; } else if (USBDeviceInfo::Class::DFU == device.interfaceClass()) { USBDeviceHandle addr = device.device().value(); out << QString("%1:%2").arg(addr.bus).arg(addr.device) << "'\n"; } else if (USBDeviceInfo::Class::HID == device.interfaceClass()) { USBDeviceHandle addr = device.device().value(); out << QString("%1:%2").arg(addr.bus).arg(addr.device) << "'\n"; } out << " Type: " << device.description() << "\n"; out << " Description: " << device.longDescription() << "\n"; } } Radio * autoDetect(QCommandLineParser &parser, QCoreApplication &app, const ErrorStack &err) { Q_UNUSED(app) logDebug() << "Autodetect radios."; QList interfaces = USBDeviceDescriptor::detect(); if (interfaces.isEmpty()) interfaces = USBDeviceDescriptor::detect(false); if (interfaces.isEmpty()) { errMsg(err) << "No matching USB devices are found. Check connection?"; return nullptr; } logInfo() << "Found " << interfaces.count() << " device(s):"; foreach (USBDeviceDescriptor d, interfaces) { logInfo() << " " << d.description() << "."; } USBDeviceDescriptor device; if (parser.isSet("device")) { // If a device is passed by option, search for matching handle QVariant devHandle = parseDeviceHandle(parser.value("device")); foreach (USBDeviceDescriptor dev, interfaces) { if (dev.device() == devHandle) { device = dev; break; } } if (! device.isValid()) { ErrorStack::MessageStream msg(err, __FILE__, __LINE__); msg << "Device handle '" << parser.value("device") << "' not found in:\n"; printDevices(msg, interfaces); return nullptr; } } else if (1 != interfaces.size()) { // If no device is specified, there should only be one interface ErrorStack::MessageStream msg(err, __FILE__, __LINE__); msg << "Cannot auto-detect radio, more than one matching USB devices found:" << " Use --device option to specify to which device to talk to. Devices found:\n"; printDevices(msg, interfaces); return nullptr; } else if (! interfaces.first().isSave()) { ErrorStack::MessageStream msg(err, __FILE__, __LINE__); msg << "It is not save to assume that the device:\n"; printDevices(msg, interfaces); msg << "is a DMR radio. Please specify the device explicitly to verify correctness."; return nullptr; } else { // The first device is safe to use device = interfaces.first(); } logDebug() << "Using device " << device.deviceHandle() << "."; // Handle identifiability of radio if (parser.isSet("radio")) { RadioInfo radio = RadioInfo::byKey(parser.value("radio").toLower()); if (! radio.isValid()) { errMsg(err) << "Unknown radio '" << parser.value("radio").toLower() << "'."; return nullptr; } Radio *rad = Radio::detect(device, radio, err); if (nullptr == rad) { logError() << "Cannot detect radio."; return nullptr; } return rad; } else if (! device.isSave()) { // Collect all radio keys for the device QStringList radios; foreach (RadioInfo info, RadioInfo::allRadios(device)) { radios.append(info.key()); } errMsg(err) << "It is not save or possible to identify the radio connected to the device '" << device.deviceHandle() << ". You have to specify which radio to use using " << "the --radio option. Possible radios for this device are " << radios.join(", ") << "."; return nullptr; } // Try auto-detect: Radio *rad = Radio::detect(device, RadioInfo(), err); if (nullptr == rad) { errMsg(err) << "Cannot auto-detect radio."; return nullptr; } return rad; } ================================================ FILE: cli/autodetect.hh ================================================ #ifndef AUTODETECT_HH #define AUTODETECT_HH #include "radio.hh" #include #include QVariant parseDeviceHandle(const QString &device); void printDevices(QTextStream &out, const QList &devices); Radio *autoDetect(QCommandLineParser &parser, QCoreApplication &app, const ErrorStack &err=ErrorStack()); #endif // AUTODETECT_HH ================================================ FILE: cli/decodecodeplug.cc ================================================ #include "decodecodeplug.hh" #include #include #include #include #include "logger.hh" #include "config.hh" #include "radioinfo.hh" #include "dummyfilereader.hh" #include "md390_codeplug.hh" #include "md390_filereader.hh" #include "uv390_codeplug.hh" #include "uv390_filereader.hh" #include "md2017_codeplug.hh" #include "md2017_filereader.hh" #include "dm1701_codeplug.hh" #include "dm1701_filereader.hh" #include "rd5r_codeplug.hh" #include "rd5r_filereader.hh" #include "gd77_codeplug.hh" #include "gd77_filereader.hh" #include "gd73_codeplug.hh" #include "gd73_filereader.hh" #include "opengd77_codeplug.hh" #include "openuv380_codeplug.hh" #include "openrtx_codeplug.hh" #include "d868uv_codeplug.hh" #include "d878uv_codeplug.hh" #include "d878uv2_codeplug.hh" #include "d578uv_codeplug.hh" #include "dmr6x2uv_codeplug.hh" #include "dmr6x2uv2_codeplug.hh" #include "dr1801uv_codeplug.hh" #include "dr1801uv_filereader.hh" #include "dm32uv_codeplug.hh" template bool decode(Config &config, const QString &filename, QCommandLineParser &parser, const ErrorStack &err=ErrorStack()) { Cpl codeplug; if (parser.isSet("manufacturer")) { if (! Rdr::read(filename, &codeplug, err)) { errMsg(err) << "Cannot decode manufacturer codeplug file '" << filename << "'."; return false; } } else if (! codeplug.read(filename, err)) { errMsg(err) << "Cannot decode binary codeplug file '" << filename << "'."; return false; } if (! codeplug.decode(&config, err)) { errMsg(err) << "Cannot decode binary codeplug file '" << filename << "'."; return false; } if (! codeplug.postprocess(&config, err)) { logError() << "Cannot post-process binary codeplug file '" << filename << "'."; return false; } return true; } int decodeCodeplug(QCommandLineParser &parser, QCoreApplication &app) { Q_UNUSED(app); if (2 > parser.positionalArguments().size()) parser.showHelp(-1); QString filename = parser.positionalArguments().at(1); ErrorStack err; if (! parser.isSet("radio")) { logError() << "No radio type is specified! Use the --radio option."; return -1; } if (! RadioInfo::hasRadioKey(parser.value("radio").toLower())) { QStringList radios; foreach (RadioInfo info, RadioInfo::allRadios()) radios.append(info.key()); logError() << "Unknown radio '" << parser.value("radio").toLower() << "."; logError() << "Known radios " << radios.join(", ") << "."; return -1; } RadioInfo::Radio radio = RadioInfo::byKey(parser.value("radio").toLower()).id(); Config config; switch (radio) { case RadioInfo::MD390: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::UV390: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::MD2017: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::DM1701: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::RD5R: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::GD73: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::GD77: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::OpenGD77: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::OpenUV380: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::OpenRTX: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::D868UVE: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::D878UV: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::D878UVII: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::D578UV: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::DMR6X2UV: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::DMR6X2UV2: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::DR1801UV: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; case RadioInfo::DM32UV: if (! decode(config, filename, parser, err)) { logError() << "Cannot decode codeplug '" << filename << "':\n" << err.format(); return -1; } break; default: logError() << "Decoding not implemented for " << RadioInfo::byID(radio).name() << "."; return -1; } if (3 <= parser.positionalArguments().size()) { QFileInfo info(parser.positionalArguments().at(2)); if (("conf" == info.suffix()) || ("csv" == info.suffix()) || parser.isSet("csv")) { logError() << "Export of the old table based format was disabled with 0.9.0. " "Import still works."; return -1; } else if (("yaml" == info.suffix()) || parser.isSet("yaml")) { QFile outfile(info.filePath()); if (! outfile.open(QIODevice::WriteOnly)) { logError() << "Cannot write CSV codeplug file '" << outfile.fileName() << "':\n" << outfile.errorString(); return -1; } QTextStream stream(&outfile); if (! config.toYAML(stream, err)) { logError() << "Cannot serialize codeplug to YAML:\n" << err.format(" "); } outfile.close(); } else { logError() << "Cannot determine codeplug output file format. Consider using --csv or --yaml."; return -1; } } else { QTextStream stream(stdout); if (parser.isSet("csv")) { logError() << "Export of the old table based format was disabled with 0.9.0. " "Import still works."; return -1; } else if (parser.isSet("yaml")) { if (! config.toYAML(stream, err)) { logError() << "Cannot serialize codeplug into YAML:\n" << err.format(" "); return -1; } } else { logError() << "Cannot determine codeplug output file format. Consider using --csv or --yaml."; return -1; } } return 0; } ================================================ FILE: cli/decodecodeplug.hh ================================================ #ifndef DECODECODEPLUG_HH #define DECODECODEPLUG_HH class QCoreApplication; class QCommandLineParser; int decodeCodeplug(QCommandLineParser &parser, QCoreApplication &app); #endif // DECODECODEPLUG_HH ================================================ FILE: cli/detect.cc ================================================ #include "detect.hh" #include #include #include "logger.hh" #include "radio.hh" #include "radiointerface.hh" #include "autodetect.hh" int detect(QCommandLineParser &parser, QCoreApplication &app) { // Try to detect a radio ErrorStack err; Radio *radio = autoDetect(parser, app, err); if (nullptr == radio) { logError() << "Cannot detect radio: \n" << err.format(" "); return -1; } QTextStream out(stdout); out << "Found: " << radio->name() << Qt::endl; delete radio; return 0; } ================================================ FILE: cli/detect.hh ================================================ #ifndef DETECT_HH #define DETECT_HH class QCommandLineParser; class QCoreApplication; int detect(QCommandLineParser &parser, QCoreApplication &app); #endif // DETECT_HH ================================================ FILE: cli/encodecallsigndb.cc ================================================ #include "encodecodeplug.hh" #include #include #include #include "logger.hh" #include "config.hh" #include "radioinfo.hh" #include "dm1701_callsigndb.hh" #include "uv390_callsigndb.hh" #include "md2017_callsigndb.hh" #include "opengd77_callsigndb.hh" #include "openuv380_callsigndb.hh" #include "gd77_callsigndb.hh" #include "d868uv_callsigndb.hh" #include "d878uv2_callsigndb.hh" int encodeCallsignDB(QCommandLineParser &parser, QCoreApplication &app) { Q_UNUSED(app); if (2 > parser.positionalArguments().size()) parser.showHelp(-1); UserDatabase userdb(false); if (parser.isSet("database")) { if (! userdb.load(parser.value("database"))) { logError() << "Cannot load user-db from '" << parser.value("database") << "'."; return -1; } } else if (! userdb.ready()) { logInfo() << "Downloading call-sign DB..."; // Wait for download to finish QEventLoop loop; QObject::connect(&userdb, SIGNAL(loaded()), &loop, SLOT(quit())); QObject::connect(&userdb, SIGNAL(error(QString)), &loop, SLOT(quit())); loop.exec(); // Check if call-sign DB has been loaded if (0 == userdb.count()) { logError() << "Could not download/load call-sign DB."; return -1; } } if (parser.isSet("id")) { QStringList prefixes_text = parser.value("id").split(","); QSet prefixes; foreach (QString prefix_text, prefixes_text) { bool ok=true; uint32_t prefix = prefix_text.toUInt(&ok); if (ok) prefixes.insert(prefix); } if (prefixes.isEmpty()) { logError() << "Please specify a valid DMR ID or a list of DMR prefixes for --id option."; return -1; } prefixes_text.clear(); foreach (unsigned prefix, prefixes) { prefixes_text.append(QString::number(prefix)); } logDebug() << "Sort call-sign DB w.r.t. DMR ID(s) {" << prefixes_text.join(", ") << "}."; userdb.sortUsers(prefixes); } else { logWarn() << "No ID is specified, a more or less random set of call-signs will be used " << "if the radio cannot hold the entire call-sign DB of " << userdb.count() << " entries. Specify your DMR ID with --id=YOUR_DMR_ID. dmrconf will then " << "select those entries 'closest' to you. I.e., DMR IDs with the same prefix."; } CallsignDB::Flags selection; if (parser.isSet("limit")) { bool ok=true; selection.setCountLimit(parser.value("limit").toUInt(&ok)); if (! ok) { logError() << "Please specify a valid limit for the number of callsign db entries using the -n/--limit option."; return -1; } } if (! parser.isSet("radio")) { logError() << "You have to specify the radio using the --radio option."; parser.showHelp(-1); return -1; } if (! RadioInfo::hasRadioKey(parser.value("radio").toLower())) { QStringList radios; foreach (RadioInfo info, RadioInfo::allRadios()) radios.append(info.key()); logError() << "Unknown radio '" << parser.value("radio").toLower() << "."; logError() << "Known radios " << radios.join(", ") << "."; return -1; } RadioInfo::Radio radio = RadioInfo::byKey(parser.value("radio").toLower()).id(); ErrorStack err; if (RadioInfo::UV390 == radio) { UV390CallsignDB db; if (! db.encode(&userdb, selection, err)) { logError() << "Cannot encode call-sign DB: " << err.format(); return -1; } if (! db.write(parser.positionalArguments().at(1), err)) { logError() << "Cannot write output call-sign DB file '" << parser.positionalArguments().at(1) << "': " << err.format(); return -1; } } else if (RadioInfo::MD2017 == radio) { MD2017CallsignDB db; if (! db.encode(&userdb, selection, err)) { logError() << "Cannot encode call-sign DB: " << err.format(); return -1; } if (! db.write(parser.positionalArguments().at(1), err)) { logError() << "Cannot write output call-sign DB file '" << parser.positionalArguments().at(1) << "': " << err.format(); return -1; } } else if (RadioInfo::DM1701 == radio) { DM1701CallsignDB db; if (! db.encode(&userdb, selection, err)) { logError() << "Cannot encode call-sign DB: " << err.format(); return -1; } if (! db.write(parser.positionalArguments().at(1), err)) { logError() << "Cannot write output call-sign DB file '" << parser.positionalArguments().at(1) << "': " << err.format(); return -1; } } else if (RadioInfo::OpenGD77 == radio) { OpenGD77CallsignDB db; if (! db.encode(&userdb, selection, err)) { logError() << "Cannot encode call-sign DB: " << err.format(); return -1; } if (! db.write(parser.positionalArguments().at(1), err)) { logError() << "Cannot write output call-sign DB file '" << parser.positionalArguments().at(1) << "': " << err.format(); return -1; } } else if (RadioInfo::OpenUV380 == radio) { OpenUV380CallsignDB db(false); if (! db.encode(&userdb, selection, err)) { logError() << "Cannot encode call-sign DB: " << err.format(); return -1; } if (! db.write(parser.positionalArguments().at(1), err)) { logError() << "Cannot write output call-sign DB file '" << parser.positionalArguments().at(1) << "': " << err.format(); return -1; } } else if (RadioInfo::GD77 == radio) { GD77CallsignDB db; if (! db.encode(&userdb, selection, err)) { logError() << "Cannot encode call-sign DB: " << err.format(); return -1; } if (! db.write(parser.positionalArguments().at(1), err)) { logError() << "Cannot write output call-sign DB file '" << parser.positionalArguments().at(1) << "': " << err.format(); return -1; } } else if ((RadioInfo::D868UVE == radio) || (RadioInfo::D878UV == radio) || (RadioInfo::DMR6X2UV == radio)){ D868UVCallsignDB db; if (! db.encode(&userdb, selection, err)) { logError() << "Cannot encode call-sign DB: " << err.format(); return -1; } if (! db.write(parser.positionalArguments().at(1), err)) { logError() << "Cannot write output call-sign DB file '" << parser.positionalArguments().at(1) << "': " << err.format(); return -1; } } else if ((RadioInfo::D878UVII == radio) || (RadioInfo::D578UV == radio) || (RadioInfo::DMR6X2UV2 == radio)){ D878UV2CallsignDB db; if (! db.encode(&userdb, selection, err)) { logError() << "Cannot encode call-sign DB: " << err.format(); return -1; } if (! db.write(parser.positionalArguments().at(1), err)) { logError() << "Cannot write output call-sign DB file '" << parser.positionalArguments().at(1) << "': " << err.format(); return -1; } } else { logError() << "Cannot encode calls-sign DB: Not implemented for '" << parser.value("radio") << "'."; return -1; } return 0; } ================================================ FILE: cli/encodecallsigndb.hh ================================================ #ifndef ENCODECALLSIGNDB_HH #define ENCODECALLSIGNDB_HH class QCoreApplication; class QCommandLineParser; int encodeCallsignDB(QCommandLineParser &parser, QCoreApplication &app); #endif // ENCODECALLSIGNDB_HH ================================================ FILE: cli/encodecodeplug.cc ================================================ #include "encodecodeplug.hh" #include #include #include #include #include "logger.hh" #include "config.hh" #include "radioinfo.hh" #include "rd5r_codeplug.hh" #include "gd73_codeplug.hh" #include "gd77_codeplug.hh" #include "opengd77_codeplug.hh" #include "openuv380_codeplug.hh" #include "openrtx_codeplug.hh" #include "md390_codeplug.hh" #include "uv390_codeplug.hh" #include "md2017_codeplug.hh" #include "d868uv_codeplug.hh" #include "d878uv_codeplug.hh" #include "d878uv2_codeplug.hh" #include "d578uv_codeplug.hh" #include "d168uv_codeplug.hh" #include "dmr6x2uv_codeplug.hh" #include "dmr6x2uv2_codeplug.hh" #include "dr1801uv_codeplug.hh" #include "dm32uv_codeplug.hh" template bool encode(Config &config, Codeplug::Flags flags, QCommandLineParser &parser) { ErrorStack err; T codeplug; Config *intermediate = codeplug.preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return false; } if (! codeplug.encode(intermediate, flags, err)) { logError() << "Cannot encode codeplug: " << err.format(); delete intermediate; return false; } delete intermediate; codeplug.image(0).sort(); if (! codeplug.write(parser.positionalArguments().at(2), err)) { logError() << "Cannot write output codeplug file '" << parser.positionalArguments().at(1) << "': " << err.format(); return false; } return true; } int encodeCodeplug(QCommandLineParser &parser, QCoreApplication &app) { Q_UNUSED(app); if (3 > parser.positionalArguments().size()) parser.showHelp(-1); QFileInfo fileinfo(parser.positionalArguments().at(1)); if (! parser.isSet("radio")) { logError() << "You have to specify the radio using the --radio option."; parser.showHelp(-1); return -1; } if (! RadioInfo::hasRadioKey(parser.value("radio").toLower())) { QStringList radios; foreach (RadioInfo info, RadioInfo::allRadios()) radios.append(info.key()); logError() << "Unknown radio '" << parser.value("radio").toLower() << "."; logError() << "Known radios " << radios.join(", ") << "."; return -1; } RadioInfo::Radio radio = RadioInfo::byKey(parser.value("radio").toLower()).id(); Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (parser.isSet("auto-enable-gps")) flags.setAutoEnableGPS(true); if (parser.isSet("auto-enable-roaming")) flags.setAutoEnableRoaming(true); Config config; ErrorStack err; if (parser.isSet("csv") || ("conf" == fileinfo.suffix()) || ("csv" == fileinfo.suffix())) { QString errorMessage; QFile infile(fileinfo.canonicalFilePath()); if (! infile.open(QIODevice::ReadOnly)) { logError() << "Cannot encode CSV codeplug file '" << fileinfo.fileName() << "': " << infile.errorString(); return -1; } QTextStream stream(&infile); if (! config.readCSV(stream, errorMessage)) { logError() << "Cannot parse CSV codeplug '" << infile.fileName() << "': " << errorMessage; return -1; } } else if (parser.isSet("yaml") || ("yaml" == fileinfo.suffix()) || ("yml" == fileinfo.suffix())) { if (! config.readYAML(fileinfo.canonicalFilePath(), err)) { logError() << "Cannot parse YAML codeplug '" << fileinfo.fileName() << "':\n" << err.format(" "); return -1; } } else { logError() << "Cannot determine input file type, consider using --csv or --yaml."; return -1; } switch (radio) { case RadioInfo::MD390: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::UV390: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::MD2017: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::DM1701: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::RD5R: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::GD73: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::GD77: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::OpenGD77: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::OpenUV380: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::OpenRTX: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::D868UVE: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::D878UV: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::D878UVII: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::D578UV: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::D168UV: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::DMR6X2UV: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::DMR6X2UV2: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::DR1801UV: if (! encode(config, flags, parser)) return -1; break; case RadioInfo::DM32UV: if (! encode(config, flags, parser)) return -1; break; default: logError() << "Cannot encode codeplug: Unknown radio '" << parser.value("radio") << "'."; return -1; } return 0; } ================================================ FILE: cli/encodecodeplug.hh ================================================ #ifndef ENCODECODEPLUG_HH #define ENCODECODEPLUG_HH class QCoreApplication; class QCommandLineParser; int encodeCodeplug(QCommandLineParser &parser, QCoreApplication &app); #endif // ENCODECODEPLUG_HH ================================================ FILE: cli/infofile.cc ================================================ #include "infofile.hh" #include #include #include #include "logger.hh" #include "dfufile.hh" int infoFile(QCommandLineParser &parser, QCoreApplication &app) { Q_UNUSED(app) if (2 > parser.positionalArguments().size()) parser.showHelp(-1); QString filename = parser.positionalArguments().at(1); DFUFile file; ErrorStack err; if (! file.read(filename, err)) { logError() << "Cannot read codeplug file '" << filename << "': " << err.format(); return -1; } QTextStream out(stdout); file.dump(out); return 0; } ================================================ FILE: cli/infofile.hh ================================================ #ifndef INFOFILE_HH #define INFOFILE_HH class QCoreApplication; class QCommandLineParser; int infoFile(QCommandLineParser &parser, QCoreApplication &app); #endif // INFOFILE_HH ================================================ FILE: cli/main.cc ================================================ #include #include #include #include "logger.hh" #include "config.h" #include "detect.hh" #include "verify.hh" #include "radioinfo.hh" #include "readcodeplug.hh" #include "writecodeplug.hh" #include "writecallsigndb.hh" #include "encodecodeplug.hh" #include "encodecallsigndb.hh" #include "decodecodeplug.hh" #include "infofile.hh" #include "uv390_codeplug.hh" int main(int argc, char *argv[]) { // Install log handler to stderr. QTextStream out(stderr); StreamLogHandler *handler = new StreamLogHandler(out, LogMessage::WARNING, true); Logger::get().addHandler(handler); // Instantiate core application QCoreApplication app(argc, argv); app.setApplicationName("dmrconf"); app.setOrganizationName("DM3MAT"); app.setOrganizationDomain("dm3mat.darc.de"); app.setApplicationVersion(VERSION_STRING); QCommandLineParser parser; parser.setApplicationDescription( QCoreApplication::translate( "main", "Up- and download codeplugs for cheap Chinese DMR radios.")); parser.addHelpOption(); parser.addVersionOption(); parser.addOption({ {"V","verbose"}, QCoreApplication::translate("main", "Verbose output.") }); parser.addOption({ {"c", "csv"}, QCoreApplication::translate("main", "Up- and download codeplugs in CSV format.") }); parser.addOption({ {"y", "yaml"}, QCoreApplication::translate("main", "Up- and download codeplugs in extensible YAML format.") }); parser.addOption({ {"b", "bin"}, QCoreApplication::translate("main", "Up- and download codeplugs in binary format.") }); parser.addOption({ {"m", "manufacturer"}, QCoreApplication::translate("main", "Given file is manufacturer codeplug file. " " Can be used with 'decode'.") }); parser.addOption({ {"D","device"}, QCoreApplication::translate("main", "Specifies the device to use to talk to " "the radio. If not specified, the dmrconf will try to detect the radio " "automatically. Please note, that for some radios the device must be specified."), QCoreApplication::translate("main", "DEVICE") }); parser.addOption({ {"R", "radio"}, QCoreApplication::translate("main", "Specifies the radio. This option can also " "be used to override the auto-detection of radios. Be careful using this " "option when writing to the device. A incompatible code-plug might be written."), QCoreApplication::translate("main", "RADIO") }); parser.addOption({ {"i", "id"}, QCoreApplication::translate("main", "Specifies the DMR id."), QCoreApplication::translate("main", "ID") }); parser.addOption({ {"n", "limit"}, QCoreApplication::translate("main", "Limits several amounts, depending on the " "context. When encoding/writing the callsign db, this option specifies the " "maximum number of callsigns to encode."), QCoreApplication::translate("main", "N") }); parser.addOption({ {"B","database"}, QCoreApplication::translate("main", "Specifies the user DB json file when " "writing the callsign db."), "FILENAME" }); parser.addOption(QCommandLineOption( "init-codeplug", QCoreApplication::translate( "main", "Initializes the code-plug in the radio. If not present (default) " "the code-plug gets updated, maintaining all settings made earlier."))); parser.addOption(QCommandLineOption( "update-device-clock", QCoreApplication::translate( "main", "If present, the device clock gets set to the system time on any " "transfer to the radio."))); parser.addOption(QCommandLineOption( "auto-enable-gps", QCoreApplication::translate( "main", "Automatically enables GPS if there is a GPS/APRS system used by any " "channel."))); parser.addOption(QCommandLineOption( "auto-enable-roaming", QCoreApplication::translate("main", "Automatically enables roaming if there is a " "roaming zone used by any channel."))); parser.addOption(QCommandLineOption( "ignore-limits", QCoreApplication::translate("main", "Disables some limit checks."))); parser.addOption(QCommandLineOption( "list-radios", QCoreApplication::translate("main", "Lists all supported radios including the " "keys to be used with the --radio option."))); parser.addPositionalArgument( "command", QCoreApplication::translate( "main", "Specifies the command to perform. Either detect, verify, read, write, " "write-db, encode, encode-db, decode or info. Consult the man-page of dmrconf for a " "detailed description of these commands."), QCoreApplication::translate("main", "[command]")); parser.addPositionalArgument( "file", QCoreApplication::translate( "main", "The code-plug file. Either binary (extension .dfu), text/csv (extension .conf " "or .csv) or YAML format (extension .yaml). The format can be forced using the --csv, " "--yaml or --binary options."), QCoreApplication::translate("main", "[filename]")); parser.process(app); if (parser.isSet("list-radios")) { QList radios = RadioInfo::allRadios(); QTextStream out(stdout); out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(18); out << " Key"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "| "; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(18); out << "Name"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "| "; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(60); out << "Manufacturer"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "\n"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar('-'); out.setFieldWidth(18); out << "-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "+-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar('-'); out.setFieldWidth(18); out << "-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "+-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar('-'); out.setFieldWidth(60); out << "-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "\n"; foreach (RadioInfo radio, radios) { out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(18); out << (" " +radio.key()); out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "| "; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(18); out << radio.name(); out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "| "; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << radio.manufacturer() << "\n"; } out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar('-'); out.setFieldWidth(18); out << "-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "+-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar('-'); out.setFieldWidth(18); out << "-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "+-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar('-'); out.setFieldWidth(60); out << "-"; out.setFieldAlignment(QTextStream::AlignLeft); out.setPadChar(' '); out.setFieldWidth(0); out << "\n"; out.flush(); return 0; } if (1 > parser.positionalArguments().size()) parser.showHelp(-1); if (parser.isSet("verbose")) handler->setMinLevel(LogMessage::DEBUG); int res = -1; QString command = parser.positionalArguments().at(0); if ("detect" == command) res = detect(parser, app); else if ("verify" == command) res = verify(parser, app); else if ("read" == command) res = readCodeplug(parser, app); else if ("write" == command) res = writeCodeplug(parser, app); else if ("write-db" == command) res = writeCallsignDB(parser, app); else if ("encode" == command) res = encodeCodeplug(parser, app); else if ("encode-db" == command) res = encodeCallsignDB(parser, app); else if ("decode" == command) res = decodeCodeplug(parser, app); else if ("info" == command) res = infoFile(parser, app); else parser.showHelp(-1); // Allow some pending events to be processed (e.g., deleteLater()) QEventLoop loop; while(loop.processEvents()) {} return res; } ================================================ FILE: cli/printprogress.cc ================================================ #include "printprogress.hh" #include void print_progress(int prog) { QTextStream out(stdout); out << "\r"; out.setFieldWidth(2); out << prog; out.setFieldWidth(0); out << "%"; prog = (prog*20)/100; out << "["; for (int i=0; i i) std::cerr << "="; else std::cerr << " "; } std::cerr << "] " << percent <<"%" << std::endl; } void updateProgress(unsigned percent) { std::cerr << "\033[1A\033[K"; showProgress(percent); } ================================================ FILE: cli/progressbar.hh ================================================ #ifndef PROGRESSBAR_HH #define PROGRESSBAR_HH #include void showProgress(unsigned percent=0); void updateProgress(unsigned percent); #endif // PROGRESSBAR_HH ================================================ FILE: cli/readcodeplug.cc ================================================ #include "readcodeplug.hh" #include #include #include #include "logger.hh" #include "radio.hh" #include "printprogress.hh" #include "config.hh" #include "codeplug.hh" #include "progressbar.hh" #include "autodetect.hh" int readCodeplug(QCommandLineParser &parser, QCoreApplication &app) { Q_UNUSED(app); if (2 > parser.positionalArguments().size()) parser.showHelp(-1); ErrorStack err; Radio *radio = autoDetect(parser, app, err); if (nullptr == radio) { logError() << "Cannot detect radio: " << err.format(); return -1; } QString filename = parser.positionalArguments().at(1); if (! parser.isSet("verbose")) { showProgress(); QObject::connect(radio, &Radio::downloadProgress, updateProgress); } TransferFlags flags; flags.setBlocking(true); Config config; if (! radio->startDownload(flags, err)) { logError() << "Codeplug download error: " << err.format(); return -1; } if (Radio::StatusError == radio->status()) { logError() << "Codeplug download error: " << err.format(); return -1; } logDebug() << "Save codeplug at '" << filename << "'."; // If output is CSV -> decode code-plug if (parser.isSet("csv") || (filename.endsWith(".conf") || filename.endsWith(".csv"))) { logError() << "Export of the old table based format was disabled with 0.9.0. " "Import still works."; return -1; } else if (parser.isSet("yaml") || filename.endsWith(".yaml")) { // decode codeplug if (! radio->codeplug().decode(&config, err)) { logError() << "Cannot decode codeplug: " << err.format(); return -1; } // post-process decoded codeplug if (! radio->codeplug().postprocess(&config, err)) { logError() << "Cannot post-process codeplug: " << err.format(); return -1; } // try to write YAML file QFile file(filename); if (! file.open(QIODevice::WriteOnly)) { logError() << "Cannot write YAML file '" << filename << "': " << file.errorString(); return -1; } QTextStream stream(&file); if (! config.toYAML(stream)) { logError() << "Cannot serialize config to YAML file '" << filename << "'."; return -1; } stream.flush(); file.close(); } else if (parser.isSet("bin") || filename.endsWith(".bin") || filename.endsWith(".dfu")) { // otherwise write binary code-plug if (! radio->codeplug().write(filename, err)) { logError() << "Cannot dump codeplug into file '" << filename << "': " << err.format(); return -1; } } else { logError() << "Cannot determine file output type from '" << filename << "'. " << "Consider using --csv, --yaml or --bin."; return -1; } return 0; } ================================================ FILE: cli/readcodeplug.hh ================================================ #ifndef READCODEPLUG_HH #define READCODEPLUG_HH class QCommandLineParser; class QCoreApplication; int readCodeplug(QCommandLineParser &parser, QCoreApplication &app); #endif // READCODEPLUG_HH ================================================ FILE: cli/verify.cc ================================================ #include "verify.hh" #include #include #include #include #include #include #include "logger.hh" #include "config.hh" #include "csvreader.hh" #include "dfufile.hh" #include "radiolimits.hh" #include "rd5r.hh" #include "uv390.hh" #include "md2017.hh" #include "dm1701.hh" #include "gd73.hh" #include "gd77.hh" #include "opengd77.hh" #include "openuv380.hh" #include "d868uv.hh" #include "d878uv.hh" #include "d878uv2.hh" #include "d578uv.hh" int verify(QCommandLineParser &parser, QCoreApplication &app) { Q_UNUSED(app); if (2 > parser.positionalArguments().size()) parser.showHelp(-1); QString filename = parser.positionalArguments().at(1); QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { logError() << "Cannot open file" << filename; return -1; } Config config; // Determine type by ending or flag if (parser.isSet("csv") || (filename.endsWith(".conf") || filename.endsWith(".csv"))) { QTextStream stream(&file); QString errorMessage; if (! CSVReader::read(&config, stream, errorMessage)) { logError() << "Cannot read config file '" << filename << "': " << errorMessage; return -1; } logInfo() << "Verify '" << filename << "': No syntax issues found."; } else if (parser.isSet("bin") || (filename.endsWith(".bin") || filename.endsWith(".dfu"))) { logError() << "Verification of binary code-plugs makes no sense."; return -1; } else if (parser.isSet("yaml") || (filename.endsWith(".yaml") || filename.endsWith(".yml"))) { ErrorStack err; if (! config.readYAML(filename,err)) { logError() << "Cannot read codeplug file '" << filename << "': " << err.format(); return -1; } } else { logError() << "Cannot determine filetype from filename '" << filename << "': Consider using --csv."; return -1; } if (! parser.isSet("radio")) { logInfo() << "To verify the codeplug against a specific radio, conser using the --radio=RADIO option."; return 0; } RadioLimitContext ctx; RadioInfo::Radio radio = RadioInfo::byKey(parser.value("radio").toLower()).id(); switch (radio) { case RadioInfo::RD5R: { RD5R radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::UV390: { UV390 radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::MD2017: { MD2017 radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::DM1701: { DM1701 radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::GD73: { GD73 radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::GD77: { GD77 radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::OpenGD77: { OpenGD77 radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::OpenUV380: { OpenUV380 radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::D868UV: { D868UV radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::D878UV: { D878UV radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::D878UVII: { D878UV2 radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; case RadioInfo::D578UV: { D578UV radio; ErrorStack err; Config *intermediate = radio.codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } radio.limits().verifyConfig(intermediate, ctx); delete intermediate; } break; default: logError() << "Cannot verify code-plug against unknown radio '" << radio << "'."; return -1; } bool valid = true; for (int i=0; i #include #include "logger.hh" #include "radio.hh" #include "userdatabase.hh" #include "progressbar.hh" #include "callsigndb.hh" #include "autodetect.hh" int writeCallsignDB(QCommandLineParser &parser, QCoreApplication &app) { UserDatabase userdb(false); if (parser.isSet("database")) { if (! userdb.load(parser.value("database"))) { logError() << "Cannot load user-db from '" << parser.value("database") << "'."; return -1; } } else if (! userdb.ready()) { logInfo() << "Downloading call-sign DB..."; // Wait for download to finish QEventLoop loop; QObject::connect(&userdb, SIGNAL(loaded()), &loop, SLOT(quit())); QObject::connect(&userdb, SIGNAL(error(QString)), &loop, SLOT(quit())); loop.exec(); // Check if call-sign DB has been loaded if (0 == userdb.count()) { logError() << "Could not download/load call-sign DB."; return -1; } } if (parser.isSet("id")) { QStringList prefixes_text = parser.value("id").split(","); QSet prefixes; foreach (QString prefix_text, prefixes_text) { bool ok=true; uint32_t prefix = prefix_text.toUInt(&ok); if (ok) prefixes.insert(prefix); } if (prefixes.isEmpty()) { logError() << "Please specify a valid DMR ID or a list of DMR prefixes for --id option."; return -1; } prefixes_text.clear(); foreach (unsigned prefix, prefixes) { prefixes_text.append(QString::number(prefix)); } logDebug() << "Sort call-sign DB w.r.t. DMR ID(s) {" << prefixes_text.join(", ") << "}."; userdb.sortUsers(prefixes); } else { logWarn() << "No ID is specified, a more or less random set of call-signs will be used " << "if the radio cannot hold the entire call-sign DB of " << userdb.count() << " entries. Specify your DMR ID with --id=YOUR_DMR_ID. dmrconf will then " << "select those entries 'closest' to you. I.e., DMR IDs with the same prefix."; } CallsignDB::Flags selection; selection.setUpdateDeviceClock(parser.isSet("update-device-clock")); if (parser.isSet("limit")) { bool ok=true; selection.setCountLimit(parser.value("limit").toUInt(&ok)); if (! ok) { logError() << "Please specify a valid limit for the number of callsign db entries using the -n/--limit option."; return -1; } } ErrorStack err; Radio *radio = autoDetect(parser, app, err); if (nullptr == radio) { logError() << "Could not detect radio: " << err.format(); return -1; } if (! parser.isSet("verbose")) { showProgress(); QObject::connect(radio, &Radio::downloadProgress, updateProgress); } selection.setBlocking(true); if (! radio->startUploadCallsignDB(&userdb, selection, err)) { logError() << "Could not upload call-sign DB to radio: " << err.format(); return -1; } return 0; } ================================================ FILE: cli/writecallsigndb.hh ================================================ #ifndef WRITECALLSIGNDB_HH #define WRITECALLSIGNDB_HH class QCoreApplication; class QCommandLineParser; int writeCallsignDB(QCommandLineParser &parser, QCoreApplication &app); #endif // WRITECALLSIGNDB_HH ================================================ FILE: cli/writecodeplug.cc ================================================ #include "writecodeplug.hh" #include #include #include #include "logger.hh" #include "radio.hh" #include "config.hh" #include "progressbar.hh" #include "autodetect.hh" #include "radiolimits.hh" int writeCodeplug(QCommandLineParser &parser, QCoreApplication &app) { if (2 > parser.positionalArguments().size()) parser.showHelp(-1); QString filename = parser.positionalArguments().at(1); QFileInfo fileinfo(filename); QString errorMessage; Config config; if (parser.isSet("csv") || ("csv" == fileinfo.suffix()) || ("conf"==fileinfo.suffix())) { if (! config.readCSV(filename, errorMessage)) { logError() << "Cannot read CSV file '" << filename << "': " << errorMessage; return -1; } } else if (parser.isSet("yaml") || ("yaml" == fileinfo.suffix()) || ("yml" == fileinfo.suffix())) { ErrorStack err; if (! config.readYAML(fileinfo.canonicalFilePath(), err)) { logError() << "Cannot parse YAML codeplug '" << fileinfo.fileName() << "': " << err.format(); return -1; } } logDebug() << "Read codeplug from '" << filename << "'."; ErrorStack err; Radio *radio = autoDetect(parser, app, err); if (nullptr == radio) { logError() << "Cannot detect radio:" << err.format(); return -1; } RadioLimitContext ctx(parser.isSet("ignore-limits")); Config *intermediate = radio->codeplug().preprocess(&config, err); if (nullptr == intermediate) { logError() << "Cannot pre-process codeplug: " << err.format(); return -1; } bool verified = true; radio->limits().verifyConfig(intermediate, ctx); // Only print warnings for (int i=0; iname() << "."; if (! radio->startUpload(intermediate, flags, err)) { logError() << "Codeplug upload error: " << err.format(); return -1; } if (Radio::StatusError == radio->status()) { logError() << "Codeplug upload error: " << err.format(); return -1; } logDebug() << "Upload completed."; return 0; } ================================================ FILE: cli/writecodeplug.hh ================================================ #ifndef WRITECODEPLUG_HH #define WRITECODEPLUG_HH class QCoreApplication; class QCommandLineParser; int writeCodeplug(QCommandLineParser &parser, QCoreApplication &app); #endif // WRITECODEPLUG_HH ================================================ FILE: cmake/FindLIBUSB_1.cmake ================================================ # - Try to find libusb-1.0 # Once done this will define # # LIBUSB_1_FOUND - system has libusb # LIBUSB_1_INCLUDE_DIRS - the libusb include directory # LIBUSB_1_LIBRARIES - Link these to use libusb # LIBUSB_1_DEFINITIONS - Compiler switches required for using libusb # # Adapted from cmake-modules Google Code project # # Copyright (c) 2006 Andreas Schneider # # (Changes for libusb) Copyright (c) 2008 Kyle Machulis # # Redistribution and use is allowed according to the terms of the New BSD license. # # CMake-Modules Project New BSD License # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, this # list of conditions and the following disclaimer. # # * 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. # # * Neither the name of the CMake-Modules Project nor the names of its # contributors may be used to endorse or promote products derived from this # software without specific prior written permission. # # 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 OWNER 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. # if (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS) # in cache already set(LIBUSB_FOUND TRUE) else (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS) find_path(LIBUSB_1_INCLUDE_DIR NAMES libusb.h PATHS /usr/include /usr/local/include /sw/include PATH_SUFFIXES libusb-1.0 ) find_library(LIBUSB_1_LIBRARY NAMES usb-1.0 usb PATHS /usr/lib /usr/local/lib /sw/lib ) set(LIBUSB_1_INCLUDE_DIRS ${LIBUSB_1_INCLUDE_DIR} ) set(LIBUSB_1_LIBRARIES ${LIBUSB_1_LIBRARY} ) if (LIBUSB_1_INCLUDE_DIRS AND LIBUSB_1_LIBRARIES) set(LIBUSB_1_FOUND TRUE) endif (LIBUSB_1_INCLUDE_DIRS AND LIBUSB_1_LIBRARIES) if (LIBUSB_1_FOUND) if (NOT libusb_1_FIND_QUIETLY) message(STATUS "Found libusb-1.0:") message(STATUS " - Includes: ${LIBUSB_1_INCLUDE_DIRS}") message(STATUS " - Libraries: ${LIBUSB_1_LIBRARIES}") endif (NOT libusb_1_FIND_QUIETLY) else (LIBUSB_1_FOUND) if (libusb_1_FIND_REQUIRED) message(FATAL_ERROR "Could not find libusb") endif (libusb_1_FIND_REQUIRED) endif (LIBUSB_1_FOUND) # show the LIBUSB_1_INCLUDE_DIRS and LIBUSB_1_LIBRARIES variables only in the advanced view mark_as_advanced(LIBUSB_1_INCLUDE_DIRS LIBUSB_1_LIBRARIES) endif (LIBUSB_1_LIBRARIES AND LIBUSB_1_INCLUDE_DIRS) ================================================ FILE: cmake/FindYAMLCPP.cmake ================================================ # - Try to find yaml-cpp # Once done this will define # # YAMLCPP_FOUND - system has yaml-cpp # YAMLCPP_INCLUDE_DIRS - the yaml-cpp include directory # YAMLCPP_LIBRARIES - Link these to use yaml-cpp # YAMLCPP_DEFINITIONS - Compiler switches required for using yaml-cpp if (YAMLCPP_LIBRARIES AND YAMLCPP_INCLUDE_DIRS) # in cache already set(YAMLCPP_FOUND TRUE) else (YAMLCPP_LIBRARIES AND YAMLCPP_INCLUDE_DIRS) find_path(YAMLCPP_INCLUDE_DIR NAMES yaml-cpp/yaml.h PATHS /usr/include /usr/local/include /opt/local /sw/include ) find_library(YAMLCPP_LIBRARY NAMES yaml-cpp PATHS /usr/lib /usr/local/lib /opt/local /sw/lib ) set(YAMLCPP_INCLUDE_DIRS ${YAMLCPP_INCLUDE_DIR} ) set(YAMLCPP_LIBRARIES ${YAMLCPP_LIBRARY} ) if (YAMLCPP_INCLUDE_DIRS AND YAMLCPP_LIBRARIES) set(YAMLCPP_FOUND TRUE) endif (YAMLCPP_INCLUDE_DIRS AND YAMLCPP_LIBRARIES) if (YAMLCPP_FOUND) if (NOT YAMLCPP_FIND_QUIETLY) message(STATUS "Found yaml-cpp:") message(STATUS " - Includes: ${YAMLCPP_INCLUDE_DIRS}") message(STATUS " - Libraries: ${YAMLCPP_LIBRARIES}") endif (NOT YAMLCPP_FIND_QUIETLY) else (YAMLCPP_FOUND) if (YAMLCPP_FIND_REQUIRED) message(FATAL_ERROR "Could not find yaml-cpp") endif (YAMLCPP_FIND_REQUIRED) endif (YAMLCPP_FOUND) # show the YAMLCPP_INCLUDE_DIRS and YAMLCPP_LIBRARIES variables only in the advanced view mark_as_advanced(YAMLCPP_INCLUDE_DIRS YAMLCPP_LIBRARIES) endif (YAMLCPP_LIBRARIES AND YAMLCPP_INCLUDE_DIRS) ================================================ FILE: cmake/GenerateIcons.cmake ================================================ set(GENERATE_ICONS_OUTPUT_FILES "") function(_create_build_icon_command icon_name icon_source_dir icon_theme icon_context icon_size icon_output_dir) cmake_path(APPEND icon_source_dir ${icon_theme} ${icon_context} "${icon_name}.svg" OUTPUT_VARIABLE input_file) cmake_path(APPEND icon_output_dir ${icon_theme} "${icon_size}x${icon_size}" ${icon_context} "${icon_name}.png" OUTPUT_VARIABLE output_file) list(APPEND GENERATE_ICONS_OUTPUT_FILES ${output_file}) add_custom_command( OUTPUT ${output_file} #COMMAND convert ARGS -background none -resize ${icon_size}x${icon_size} ${input_file} ${output_file} COMMAND rsvg-convert ARGS -w ${icon_size} -h ${icon_size} -o ${output_file} ${input_file} DEPENDS ${input_file} COMMENT "Generate ${output_file}" VERBATIM) return(PROPAGATE GENERATE_ICONS_OUTPUT_FILES) endfunction() function(generate_icons) cmake_parse_arguments(GENERATE_ICONS "" "DIRECTORY;CONTEXT;" "THEMES;ICONS;SIZES" ${ARGN}) cmake_path(APPEND CMAKE_CURRENT_BINARY_DIR "icons" OUTPUT_VARIABLE GENERATE_ICONS_OUTPUT_DIR) foreach(ICON_THEME ${GENERATE_ICONS_THEMES}) file(MAKE_DIRECTORY "${GENERATE_ICONS_OUTPUT_DIR}/${ICON_THEME}/scalable/${GENERATE_ICONS_CONTEXT}") foreach(ICON_NAME ${GENERATE_ICONS_ICONS}) cmake_path(APPEND GENERATE_ICONS_DIRECTORY ${ICON_THEME} ${GENERATE_ICONS_CONTEXT} "${ICON_NAME}.svg" OUTPUT_VARIABLE source_file) cmake_path(APPEND GENERATE_ICONS_OUTPUT_DIR ${ICON_THEME} "scalable" ${GENERATE_ICONS_CONTEXT} OUTPUT_VARIABLE output_dir) cmake_path(APPEND output_dir "${ICON_NAME}.svg" OUTPUT_VARIABLE output_file) list(APPEND GENERATE_ICONS_OUTPUT_FILES ${output_file}) add_custom_command( OUTPUT ${output_file} COMMAND ${CMAKE_COMMAND} -E copy ${source_file} ${output_dir} DEPENDS ${source_file} COMMENT "Copy ${output_file}" VERBATIM) endforeach() foreach(ICON_SIZE ${GENERATE_ICONS_SIZES}) cmake_path(APPEND GENERATE_ICONS_OUTPUT_DIR ${ICON_THEME} "${ICON_SIZE}x${ICON_SIZE}" ${GENERATE_ICONS_CONTEXT} OUTPUT_VARIABLE SCALED_ICONS_OUTPUT_DIR) file(MAKE_DIRECTORY ${SCALED_ICONS_OUTPUT_DIR}) foreach(ICON_NAME ${GENERATE_ICONS_ICONS}) cmake_path(APPEND GENERATE_ICONS_DIRECTORY ${ICON_THEME} ${GENERATE_ICONS_CONTEXT} "${ICON_NAME}.svg" OUTPUT_VARIABLE source_file) _create_build_icon_command(${ICON_NAME} ${GENERATE_ICONS_DIRECTORY} ${ICON_THEME} ${GENERATE_ICONS_CONTEXT} ${ICON_SIZE} ${GENERATE_ICONS_OUTPUT_DIR}) endforeach() endforeach() endforeach() return(PROPAGATE GENERATE_ICONS_OUTPUT_FILES) endfunction() ================================================ FILE: de.darc.dm3mat.qdmr.yaml ================================================ app-id: de.darc.dm3mat.qdmr runtime: org.kde.Platform runtime-version: '6.9' sdk: org.kde.Sdk command: qdmr rename-desktop-file: qdmr.desktop rename-icon: qdmr finish-args: - --device=all - --share=network - --share=ipc - --socket=fallback-x11 - --socket=wayland - --env="QT_SERIALPORT_SKIP_UDEV_LOOKUP=1" cleanup: - /include - /lib/pkgconfig - /share/aclocal - /share/doc - /share/man - /man - "*.a" - "*.la" modules: - name: yaml-cpp buildsystem: cmake-ninja config-opts: - -DBUILD_SHARED_LIBS=ON sources: - type: git url: https://github.com/jbeder/yaml-cpp commit: f7320141120f720aecc4c32be25586e7da9eb978 # tag 0.8.0 - name: libusb1 buildsystem: autotools config-opts: - --disable-static sources: - type: git url: https://github.com/libusb/libusb.git tag: v1.0.29 commit: 15a7ebb4d426c5ce196684347d2b7cafad862626 - name: qdmr buildsystem: cmake-ninja sources: - type: dir path: . ================================================ FILE: dist/99-qdmr.rules ================================================ # Radioddity GD-73 SUBSYSTEM=="usb", ATTRS{idVendor}=="1206", ATTRS{idProduct}=="0227", MODE="660", GROUP="dialout" # TYT MD-UV380 SUBSYSTEM=="usb", ATTRS{idVendor}=="0483", ATTRS{idProduct}=="df11", MODE="660", GROUP="dialout" # Baofeng RD-5R, TD-5R # Radioddity GD-77 SUBSYSTEM=="usb", ATTRS{idVendor}=="15a2", ATTRS{idProduct}=="0073", MODE="660", GROUP="dialout" # Ignore this device in Modem Manager # Anytone AT-D868UV, AT-D878UV, AT-D578UV (II) (GD32) ATTRS{idVendor}=="28e9" ATTRS{idProduct}=="018a", ENV{ID_MM_DEVICE_IGNORE}="1" # Anytone AT-D578UV (II) (STM32) ATTRS{idVendor}=="2e3c" ATTRS{idProduct}=="5740", ENV{ID_MM_DEVICE_IGNORE}="1" # also for DR-1801UV ATTRS{idVendor}=="067b" ATTRS{idProduct}=="23a3", ENV{ID_MM_DEVICE_IGNORE}="1" ================================================ FILE: dist/CMakeLists.txt ================================================ if (UNIX AND NOT APPLE) configure_file("qdmr.desktop.in" "qdmr.desktop") install(FILES ${CMAKE_CURRENT_BINARY_DIR}/qdmr.desktop DESTINATION share/applications/) install(FILES ${PROJECT_SOURCE_DIR}/dist/qdmr32.png DESTINATION share/icons/hicolor/32x32/apps/ RENAME qdmr.png) install(FILES ${PROJECT_SOURCE_DIR}/dist/qdmr48.png DESTINATION share/icons/hicolor/48x48/apps/ RENAME qdmr.png) install(FILES ${PROJECT_SOURCE_DIR}/dist/qdmr64.png DESTINATION share/icons/hicolor/64x64/apps/ RENAME qdmr.png) install(FILES ${PROJECT_SOURCE_DIR}/dist/qdmr72.png DESTINATION share/icons/hicolor/72x72/apps/ RENAME qdmr.png) install(FILES ${PROJECT_SOURCE_DIR}/dist/qdmr96.png DESTINATION share/icons/hicolor/96x96/apps/ RENAME qdmr.png) endif(UNIX AND NOT APPLE) if (UNIX AND NOT APPLE AND ${INSTALL_APPSTREAM_DATA}) install(FILES ${PROJECT_SOURCE_DIR}/dist/de.darc.dm3mat.qdmr.metainfo.xml DESTINATION share/metainfo/) endif(UNIX AND NOT APPLE AND ${INSTALL_APPSTREAM_DATA}) ================================================ FILE: dist/de.darc.dm3mat.qdmr.metainfo.xml ================================================ de.darc.dm3mat.qdmr qdmr.desktop QDMR Hannes Matuschek GPL-3.0+ CC-BY-SA-3.0 Codeplug programming software for DMR radios

QDMR is a Qt application to program DMR radios. DMR is a digital modulation standard used in amateur and commercial radio. To this end, QDMR is an alternative codeplug programming software (CPS) that supports several radios of several manufacturers. Including AnyTone AT-D868UVE, AT-D878UV, AT-878UVII, AT-D578UV, TyT MD-390, MD-UV380, MD-UV390, MD-2017, Retevis RT8, RT3S, RT82, RT84, Radioddity RD5R, GD-77, GD-73, Baofeng DM-1701, DR-1801-A6, DMR-6X2(PRO), DM-32UV and radios running the OpenGD77 firmware.

https://raw.githubusercontent.com/hmatuschek/qdmr/master/doc/fig/qdmr-channels.png https://dm3mat.darc.de/qdmr/ https://github.com/hmatuschek/qdmr/issues
================================================ FILE: dist/fedora/Dockerfile ================================================ FROM fedora:rawhide RUN dnf -y install fedora-packager rpmdevtools wget \ cmake docbook5-style-xsl xsltproc gcc-c++ hicolor-icon-theme librsvg2-tools \ pkgconfig qt6-qtbase-devel qt6-qtconnectivity-devel qt6-qtpositioning-devel \ qt6-qtserialport-devel qt6-qtbase-devel libusb1-devel yaml-cpp-devel qt6-qttools-devel RUN rpmdev-setuptree CMD /bin/bash ================================================ FILE: dist/fedora/docker-build.sh ================================================ #!/bin/bash cp /mnt/qdmr.spec /root/rpmbuild/SPECS VERSION="0.13.2" TARBALL="v${VERSION}.tar.gz" # Download sources cd /root/rpmbuild/SOURCES if [ ! -f $TARBALL ]; then wget https://github.com/hmatuschek/qdmr/archive/refs/tags/$TARBALL fi # Copy specs and build RPM cd /root/rpmbuild/SPECS rpmbuild -ba qdmr.spec # Extract # cp /root/rpmbuild/RPMS/x86_64/*.rpm /mnt/ ================================================ FILE: dist/fedora/qdmr.spec ================================================ # # spec file for package qdmr # # Copyright (c) 2021-2025, Martin Hauke # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed # upon. The license for this file, and modifications and additions to the # file, is the same license as for the pristine package itself (unless the # license for the pristine package is not an Open Source License, in which # case the license is the MIT License). An "Open Source License" is a # license that conforms to the Open Source Definition (Version 1.9) # published by the Open Source Initiative. %define ext_man .gz %define sover 0 %define realver 0.14.0 Name: qdmr Version: %{realver} Release: 0fedora Summary: Graphical code-plug programming tool for DMR radios License: GPL-3.0-or-later Group: Productivity/Hamradio/Other URL: https://dm3mat.darc.de/qdmr/ #Git-Clone: https://github.com/hmatuschek/qdmr.git Source: https://github.com/hmatuschek/qdmr/archive/refs/tags/v%{realver}.tar.gz BuildRequires: cmake BuildRequires: docbook5-style-xsl BuildRequires: xsltproc BuildRequires: gcc-c++ BuildRequires: hicolor-icon-theme BuildRequires: librsvg2-tools BuildRequires: pkgconfig BuildRequires: pkgconfig(Qt6Core) BuildRequires: pkgconfig(Qt6Network) BuildRequires: pkgconfig(Qt6Positioning) BuildRequires: pkgconfig(Qt6SerialPort) BuildRequires: pkgconfig(Qt6Test) BuildRequires: pkgconfig(Qt6UiTools) BuildRequires: pkgconfig(Qt6Widgets) BuildRequires: pkgconfig(libusb-1.0) BuildRequires: pkgconfig(yaml-cpp) %description qdmr is a Qt application to program DMR radios. DMR is a digital modulation standard used in amateur and commercial radio. To this end, qdmr is an alternative codeplug programming software (CPS) that supports several radios across several manufacturers. Currently supported devices are: * Radioddity/Baofeng RD-5R, GD-73, GD-77 * TYT MD-380, MD-390, MD-UV380, MD-UV390, MD-2017 * Retevis RT8, RT3S, RT82 * Open GD77 firmware (GD77, RD-5R, DM-1801, MD-UV390, RT-3S & DM1701) * AnyTone AT-D868UVE, AT-D878UV, AT-878UVII, AT-D578UV, AT-D578UVII * BTech DMR-6X2, DMR-6X2PRO, DM-1701, DR-1801UVA6, DM-32UV %package -n libdmrconf%{sover} Summary: Graphical code-plug programming tool for DMR radios Group: System/Libraries %description -n libdmrconf%{sover} qDMR is a simple to use and feature-rich code-plug programming software (CPS) for cheap DMR radios. This subpackage contains shared library part of libdmrconf. %package devel Summary: Development files for dmrconf Group: Development/Libraries/C and C++ Requires: libdmrconf%{sover} = %{version} %description devel qDMR is a simple to use and feature-rich code-plug programming software (CPS) for cheap DMR radios. This subpackage contains libraries and header files for developing applications that want to make use of libdmrconf. %prep %setup -q -n %{name}-%{realver} %build %cmake -DBUILD_MAN=ON -DDOCBOOK2MAN_XSLT=docbook_man.fedora.xsl -DINSTALL_UDEV_PATH=%{_udevrulesdir} %cmake_build %install %cmake_install %post -n libdmrconf%{sover} -p /sbin/ldconfig %postun -n libdmrconf%{sover} -p /sbin/ldconfig %files %license LICENSE %doc README.md %{_bindir}/dmrconf %{_bindir}/qdmr %{_udevrulesdir}/99-qdmr.rules %{_datadir}/applications/qdmr.desktop %{_metainfodir}/de.darc.dm3mat.qdmr.metainfo.xml %{_datadir}/icons/hicolor/*/apps/qdmr.png %{_mandir}/man1/dmrconf.1%{?ext_man} %{_mandir}/man1/qdmr.1%{?ext_man} %files -n libdmrconf%{sover} %{_libdir}/libdmrconf.so.* %files devel %{_includedir}/libdmrconf/ %{_libdir}/libdmrconf.so %changelog * Sat Mar 21 2026 Hannes Matuschek - Updated to 0.14.0 * Sun Nov 30 2025 Hannes Matuschek - Updated to 0.13.2 * Tue Oct 21 2025 Hannes Matuschek - Updated to 0.13.1 * Sun Oct 19 2025 Hannes Matuschek - Updated to 0.13.0 * Thu Jul 25 2024 Hannes Matuschek - Updated to 0.12.0 - added translations - updated description - fixed so-version - adapted from openSUSE spec file ================================================ FILE: dist/macosx/Info.plist ================================================ CFBundleExecutable qdmr CFBundleGetInfoString QDMR 0.4.1 CFBundleIdentifier com.github.hmatuschek.qdmr CFBundleIconFile qdmr.icns CFBundleInfoDictionaryVersion 1.0 CFBundleName QDMR CFBundlePackageType APPL CFBundleShortVersionString 0.4 CFBundleVersion 0.4.1 LSMinimumSystemVersion 10.9.0 ================================================ FILE: dist/qdmr.desktop.in ================================================ [Desktop Entry] Name=QDMR GenericName=Codeplug programming software Comment=A universal codeplug programming software for DMR radios Exec=qdmr Icon=qdmr Type=Application Categories=Network;HamRadio; Terminal=false X-Desktop-File-Install-Version=0.24 ================================================ FILE: doc/CMakeLists.txt ================================================ # # man-page generation # message(STATUS "Using xsltproc: ${XSLTPROC_EXECUTABLE}") configure_file("dmrconf.in.xml" "dmrconf.xml") configure_file("qdmr.in.xml" "qdmr.xml") add_custom_command( OUTPUT dmrconf.1 COMMAND ${XSLTPROC_EXECUTABLE} -o ${CMAKE_CURRENT_BINARY_DIR}/dmrconf.1 ${CMAKE_CURRENT_SOURCE_DIR}/${DOCBOOK2MAN_XSLT} ${CMAKE_CURRENT_BINARY_DIR}/dmrconf.xml DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/dmrconf.xml WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating man-page for dmrconf." VERBATIM) add_custom_command( OUTPUT qdmr.1 COMMAND ${XSLTPROC_EXECUTABLE} -o ${CMAKE_CURRENT_BINARY_DIR}/qdmr.1 ${CMAKE_CURRENT_SOURCE_DIR}/${DOCBOOK2MAN_XSLT} ${CMAKE_CURRENT_BINARY_DIR}/qdmr.xml DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/qdmr.xml WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR} COMMENT "Generating man-page for qdmr." VERBATIM) if (${BUILD_MAN}) # Create man page from docbook add_custom_target(dmrconf_manpage ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/dmrconf.1) add_custom_target(qdmr_manpage ALL DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/qdmr.1) # install man page on unix systems if (UNIX) install(FILES ${CMAKE_CURRENT_BINARY_DIR}/dmrconf.1 ${CMAKE_CURRENT_BINARY_DIR}/qdmr.1 DESTINATION ${CMAKE_INSTALL_FULL_MANDIR}/man1/) endif (UNIX) endif(${BUILD_MAN}) # # optional API docs # if (${BUILD_DOCS}) set(DOXYGEN_PROJECT_NAME "libdrmconf") set(DOXYGEN_PROJECT_BRIEF "A library to program DMR radios.") set(DOXYGEN_BRIEF_MEMBER_DESC YES) set(DOXYGEN_JAVADOC_AUTOBRIEF YES) set(DOXYGEN_EXAMPLE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/code") set(DOXYGEN_DOTFILE_DIRS "${CMAKE_CURRENT_SOURCE_DIR}/fig") set(DOXYGEN_ENABLE_PREPROCESSING YES) set(DOXYGEN_MACRO_EXPANSION YES) set(DOXYGEN_EXPAND_ONLY_PREDEF YES) set(DOXYGEN_PREDEFINED __attribute__(x) =) set(DOXYGEN_IMAGE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/fig") doxygen_add_docs(apidocs ALL ${CMAKE_SOURCE_DIR}/lib COMMENT "Build API documentation") install(DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/html DESTINATION ${CMAKE_INSTALL_DOCDIR}/libdmrconf/) endif(${BUILD_DOCS}) ================================================ FILE: doc/code/anytone_2tonefunction.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Frequency 1 in 0.1Hz, little endian | Frequency 2 in 0.1Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Response | Name, up to 7 ASCII chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Pad byte 0x00 | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Response: 0=None, 1=Tone, 2=ToneRespond ================================================ FILE: doc/code/anytone_2toneid.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Frequency 1 in 0.1Hz, little endian | Frequency 2 in 0.1Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Name, up to 7 ASCII chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | Pad byte 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_2tonesettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 ... | Tone 1 duration in 100ms | Tone 2 duration in 100ms | Long tone duration in 100ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Gap duration in 10ms | Auto reset time in 10s | Sidetone enable | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_5tonefunction.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Function | Response | ID length | ID up to 24 BCD digits ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | Name up to 7 ASCII chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | Pad set to 0x00 | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Function: Specifies the function to perform. 0=OpenSquelch, 1=CallAll, 2=EmergencyAlarm, 3=RemoteKill, 4=RemoteStun, 5=RemoteWakeup, 6=GroupCall - Response: 0=None, 1=Tone, 2=ToneRespond ================================================ FILE: doc/code/anytone_5tonefunctionlist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Five-tone function 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | Five-tone function 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 01e0 | Five-tone function 15 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 01fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_5toneid.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused set to 0x00 | Standard | ID length | Tone duration in ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | ID up to 40 BCD digits ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Name, up to 7 ASCII chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | Pad byte 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Standard: 0=ZVEI1, 1=ZVEI2, 2=ZVEI3, 3=PZVEI, 4=DZVEI, 5=PDZVEI, 6=CCIR1, 7=CCIR2, 8=PCCIR, 9=EEA, 10=EuroSignal, 11=NATEL, 12=MODAT, 13=CCITT, 14=EIA ================================================ FILE: doc/code/anytone_5toneidlist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Five-tone ID 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | Five-tone ID 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c60 | Five-tone ID 99 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c7c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_5tonesettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unknown settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Unused set to 0x00 | Decoding response | Decoding standard | Radio ID length | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Decoding tone duration in ms | Radio ID up to 14 BCD digits ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Post encode delay in 10ms | PTT ID 0=off [5,75] | Auto reset time in 10s | First delay in 10ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Sidetone enable | Unknown | Stop code [0,15] | Stop time in 10ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Decode time in 10ms | Delay after stop in 10ms | Pre time in 10ms | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Unused set to 0x00 | BOT standard | BOT ID length | BOT tone duration in ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | BOT ID, up to 24 BCD digits ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | Unused set to 0x00 | EOT standard | EOT ID length | EOT tone duration in ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | EOT ID up to 24 BCD digits ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Decoding response: 0=None, 1=Tone, 2=ToneRespond - Decoding/BOT/EOT standard: 0=ZVEI1, 1=ZVEI2, 2=ZVEI3, 3=PZVEI, 4=DZVEI, 5=PDZVEI, 6=CCIR1, 7=CCIR2, 8=PCCIR, 9=EEA, 10=EuroSignal, 11=NATEL, 12=MODAT, 13=CCITT, 14=EIA ================================================ FILE: doc/code/anytone_alarmsetting.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Analog alarm settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 ... | Digital alarm settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_analogalarm.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused 0x00 | Standard | ID length | Tone duration in ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | ID up to 40 BCD encoded digits ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Name, max 7 ASCII chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | Pad byte 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field details: - Standard: Specifies the 5-tone standard. 0=ZVEI1, 1=ZVEI2, 2=ZVEI3, 3=PZVEI, 4=DZVEI, 5=PDZVEI, 6=CCIR1, 7=CCIR2, 8=PCCIR, 9=EEA, 10=EURO SIGNAL, 11=NATEL, 12=MODAT, 13=CCITT, 14=EIA. ================================================ FILE: doc/code/anytone_analogquickcall.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Type | Analog contact index, 0xff=none | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Some field details: - Type: 0=none, 1=DTMF, 2=2Tone, 3=5Tone ================================================ FILE: doc/code/anytone_analogquickcalls.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Quick call 0 | Quick call 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Quick call 2 | Quick call 3 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Unused, filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_bootsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Intro line 1, 16 x ASCII, 0-terminated and padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Intro line 2, 16 x ASCII, 0-terminated and padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Boot password, 8 x ASCII, only number chars, 0-terminated and padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Unused, filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | RX Frequency 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Tx Frequency Offset 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | RMode | 0 |BWd| PWR | CMode |TAr|CaC|RXO|CTR|TDC|TCT|RDC|RCT| CTCSS transmit | CTCSS receive | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | DCS transmit code, little endian | DCS receive code, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Custom CTCSS frequency in 0.1Hz, little endian | 2-tone decode index, 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Contact index 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Radio ID table index. | 0 |SquelchMode| 0 0 0 0 | 0 0 |OptSig | 0 0 | TxPer | Scan list index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Group list index | 2-tone ID | 5-tone ID | DTMF ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Color code |LWK|EEE|RGP|EAT| 0 |EST|SMC|TSL| AES Encryption key | Name 16 x ASCII 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | Pad byte set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Device specific settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - RMode: Repater mode where 0=simplex, 1=positive TX offset, 2=negative TX offset. - BWd: Band width where 0=narrow (12.5kHz), 1=wide (25kHz). - PWR: Power where 0=low, 1=mid, 2=high, 3=turbo. - CMode: Channel mode, 0=analog, 1=digital, 2=analog + digi RX, 3=digital + analog RX. - TAr: Enable talkaround. - CaC: Enable call confirm. - RXO: Enable RX only. - CTR: Enable CTCSS phase reversal. - TDC: Enable TX DCS code. - TCT: Enable TX CTCSS tone. - RDC: Enable RX DCS code. - RCT: Enable RX CTCSS tone. - SquelchMode: Squelch mode 0=Carrier, 1=CTCSS/DCS, 2=Optional Signaling, 3=CTCSS/DCS and Optional Signaling, 4 = CTCSS/DCS or Optional Signaling - OptSig: Optional signalling where 0=off, 1=DTMF, 2=2-tone, 3=5-tone - TxPer: TX permit/admit criterion, 0=always, 1=colorcode, 2=channel free. - LWK: Enable lone worker. - EEE: Enable enhanced encryption - RGP: Enable RX GPS: - EAT: Enable adative TDMA - EST: Enable simplex TDMA, - SMC: SMS confirmation, - TSL: Time slot where 0=TS1, 1=TS2 ================================================ FILE: doc/code/anytone_contact.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Type | Name 16 x ASCII chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | 18 unused bytes set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | ID 8 digits BCD encoded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... big endian | Call alert | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | 60 unused bytes, set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_contactmapentry.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DMR ID and group call flag, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Contact index 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - DMR ID and group call flag: The ID is encoded in BCD litte-endian, shifted to the left by one bit. Bit 0 is then the group-call flag. The combined id and flag is then stored as a 32bit little-endian. ================================================ FILE: doc/code/anytone_digitalalarm.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Action, 0=none | Alarm duration in seconds | TX duration in seconds | RX duration in seconds | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Channel index, 0-based, little endian | Channel select 1=current | Alarm repeat 0=continuous | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Voice switch BC dur in min+1 | Area switch BC dur in min+1 | VOX enable | RX alarm enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field details: - Action: Specifies the alarm action. 0=none, 1=TX and background, 2=TX and non-local alarm, 3=TX and local alarm - Channel select: If 0 use specified channel index else current channel. - Alarm repeat: Number of alarm repetitions, 0=continuous. - Voice switch broadcast duration: Specifies the time in minutes +1. That is 0=1min, 1=2min, ... - Area switch broadcast duration: Specifies the time in minutes +1. That is 0=1min, 1=2min, ... ================================================ FILE: doc/code/anytone_digitalalarmextension.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Call type | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | Destination ID ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... 8 digit BCD, big endian | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field details: - Call type: 0=private, 1=group, 2=all ================================================ FILE: doc/code/anytone_dmraprssettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Manual TX interval in sec. | Auto TX interval in sec. | Enable fixed location beacon | Latitude degrees | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Latitude minutes | Latitude seconds | South flag (0=north, 1=south) | Longitude degrees | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Longitude minutes | Longitude seconds | West flag (0=east, 1=west) | Transmit power | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Revert channel 0 index, 0-based, little endian. | Revert channel 1 index, 0-based, little endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Revert channel 6 index, 0-based, little endian. | Revert channel 7 index, 0-based, little endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | Target DMR ID, 8 digit BCD, big endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Call type | Timeslot override | Unused, set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Some field details: - Transmit power: 0=low, 1=mid, 2=high, 3=turbo. - Revert channel indices: Global index of channel, 0x0fa0=VFO A, 0x0fa1=VFO B, 0x0fa2=selected, 0xffff=none. - Call type: 0=private, 1=group, 2=all call. - Timeslot override: 0=same as channel, 1=TS1, 2=TS2 ================================================ FILE: doc/code/anytone_dtmfcontact.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0 | Number, 14 digits BCD encoded, big-endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... | Number of digits | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8 | Name, 15 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | 0x00 pad | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_dtmfidlist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | DTMF number 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | DTMF number 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00f0 | DTMF number 15 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_dtmfsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Interval symbol [0,15] | Group code [0, 15] | Response | Pre time in 10ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | First digit duration in 10ms | Auto reset time in 10s | Radio ID, 3 digits? ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 ... | Post enc. delay in 10ms | PTT ID pause in seconds | PTT ID enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | D-code pause in seconds | Side tone enable | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | BOT ID up to 16 digits, 0xff terminated/padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | EOT ID up to 16 digits, 0xff terminated/padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Remote kill ID, up to 16 digits, 0xff terminated/padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Remote stun ID, up to 16 digits, 0xff terminated/padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Response: 0=None, 1=Tone, 2=ToneRespond ================================================ FILE: doc/code/anytone_generalsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Enable key tone | Display mode | Enable automatic key lock | Automatic shutdown time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Unknown | Unknown | Boot display | Enable boot password | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Unknown | Squelch level VFO A | Squelch level VFO B | Power save mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | VOX sensitivity | VOX delay in 100+500*n ms | VFO scan type | MIC gain | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | PF1 short press function | PF2 short press function | PF3 short press function | P1 short press function | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | P2 short press function | Work mode A | Work mode B | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | Work mode MEM zone A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Work mode MEM zone B | Unknown | Enable recording | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | Display brightness | Backlight duration | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Enable GPS | Enable SMS alert | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Work mode main channel set | Enable sub channel | Unknown | Enable call alert | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | GPS Time zone | Talk permit tone | Digital call reset tone | VOX source | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Unknown | Idle channel tone | Menu exit time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Unknown | Startup tone | Enable call end prompt | Max volume | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Unknown | GPS RX positions | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Unknown | PF1 long press function | PF2 long press function | PF3 long press function | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | P1 long press function | P2 long press function | Long press duration | Enable Volume change prompt | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | Auto repeater A direction | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | Display later caller | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Unknown | Display clock | Max head phone volume | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | | Enable enhanced audio | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | VFO Scan UHF minimum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | VFO Scan UHF maximum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | VFO Scan VHF minimum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | VFO Scan VHF maximum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | Auto rep. offset index UHF | Auto rep. offset index VHF | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 ... | Call tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 | Call tone frequency 2 in Hz, little endian | Call tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 78 | Call tone frequency 4 in Hz, little endian | Call tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c | Call tone duration 1 in ms, little endian | Call tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | Call tone duration 3 in ms, little endian | Call tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 84 | Call tone duration 5 in ms, little endian | Idle tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 88 | Idle tone frequency 2 in Hz, little endian | Idle tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c | Idle tone frequency 4 in Hz, little endian | Idle tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Idle tone duration 1 in ms, little endian | Idle tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 94 | Idle tone duration 3 in ms, little endian | Idle tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 98 | Idle tone duration 5 in ms, little endian | Reset tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c | Reset tone frequency 2 in Hz, little endian | Reset tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | Reset tone frequency 4 in Hz, little endian | Reset tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a4 | Reset tone duration 1 in ms, little endian | Reset tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a8 | Reset tone duration 3 in ms, little endian | Reset tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac | Reset tone duration 5 in ms, little endian | Record delay in 200ms | Call display mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b0 | Device specific settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ cc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - GPS time zone: 00h=UTC-12, ..., 0Ch=UTC, ..., 19h=UTC+13 ================================================ FILE: doc/code/anytone_grouplist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | 64 indices of group calls, 32bit each, little endian, default 0xffffffff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 100 | Name, 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 110 | 16 unused bytes set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 11c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_hotkey.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Type (0=call, 1=menu) | Menu item | Call type | Digital call type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Contact index, 0-based, little endian, 0xffffffff=none | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Message index, 0-based | Unused filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field details: - Menu item: 1=Show SMS nenu, 2=Show new SMS menu, 3=Show hot text menu, 4=Show SMS inbox, 5=Show SMS outbox, 6=Show contacts, 7=Show manual dial menu. - Call type: 0=analog, 1=digital (only when Type=call). - Digital call type: 0xff = off, 0=Group call, 1=Private call, 2=All call, 3=Hot text, 4=Call tip (?), 5=Status message. - Contact index: May be analog quick call index (if Type=call, and Call type=analog) or contact index (if Type=call and Call type=digital). 0xffffffff = none. - Message index: May be SMS message index (if Digital call type=Hot text) or status message index (if Digital call type=Status message). 0xff = none. ================================================ FILE: doc/code/anytone_hotkeysettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Hotkey setting 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 | Hotkey setting 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 005c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0330 | Hotkey setting 17 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 035c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_message.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Message text, 99 ASCII chars, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 ... | 157 unused bytes, 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_messagelist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused 0x00 | Unused 0x00 | Next index, 0xff=EOL | Current index, 0xff=none | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | 12 unused bytes set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_radioid.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Radio ID encoded as 8 BCD digits, big endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Unused, set to 0x00 | Name, 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | 11 unused bytes, set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_repeateroffsetfrequencies.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Offset frequency 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Offset frequency 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 03e4 | Offset frequency 249 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 03e8 | Unused, filled with 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 03ec ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_scanlist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused, set to 0x00 | Priority Channel Select | Primary channel index 1 +1, little endian, 0=selected | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Secondary channel index 2 +1, little endian, 0=selected | Look back time A, in x10sec | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Look back time B, in x10sec | Dropout delay, in x10sec | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Dwell time, in x10sec | Revert channel type | Name, 16 ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | 50 x 16bit channel indices, little endian, 0xffff=empty ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 84 | 12 unused bytes , set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_statusmessages.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Status message 0, 32 x ASCII, 0 terminated and padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | Status message 0, 32 x ASCII, 0 terminated and padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 03e0 | Status message 31, 32 x ASCII, 0 terminated and padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 03fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_wfmchannellist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Channel frequency 0, 8 digit BCD, big endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Channel frequency 1, 8 digit BCD, big endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 018c | Channel frequency 99, 8 digit BCD, big endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0190 | Unused, filled with 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 019c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/anytone_zonechannellist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000 | Zone 0, channel index A little endian | Zone 1, channel index A little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 010 | Zone 2, channel index A little endian | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1f0 ... | Zone 249, channel index A little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1f4 | 12 pad bytes set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 200 | Zone 0, channel index B little endian | Zone 1, channel index B little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 210 | Zone 2, channel index B little endian | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3f0 ... | Zone 249, channel index B little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3f4 | 12 pad bytes set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d578uv_airbandchannel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Frequency, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Name 16 x ASCII, 0-padded and terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Unused, filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d578uv_airbandchannellist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Air band channel 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | Air band channel 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c60 | Air band channel 99 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c7c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d578uv_aprssettingext.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unknown settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | Fixed altitude in feet, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 |RST|RNF|RWX|RMS|RIT|ROB|RME|RPO| 0 0 0 0 0 0 0 |ROF| Unknown settings | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | FM APRS Frequency 0, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | FM APRS Frequency 1, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | FM APRS Frequency 2, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | FM APRS Frequency 3, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | FM APRS Frequency 4, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | FM APRS Frequency 5, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | FM APRS Frequency 6, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 26 | FM APRS Frequency 7, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - RPO: Report position flag. - RME: Report MIC-E flag. - ROB: Report object flag. - RIT: Report item flag. - RMS: Report message flag. - RWX: Report weather flag. - RNF: Report NEMA flag. - RST: Report status flag. - ROF: Report other flag. ================================================ FILE: doc/code/d578uv_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | RX Frequency 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Tx Frequency Offset 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | RMode | 0 |BWd| PWR | CMode |TAr|CaC|RXO|CTR|TDC|TCT|RDC|RCT| CTCSS transmit | CTCSS receive | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | DCS transmit code, little endian | DCS receive code, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Custom CTCSS frequency in 0.1Hz, little endian | 2-tone decode index, 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Contact index 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Radio ID table index. | 0 |SquelchMode| 0 0 |PTT-ID | 0 0 |OptSig | 0 0 | TxPer | Scan list index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Group list index | 2-tone ID | 5-tone ID | DTMF ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Color code |LWK|EEE|RGP|EAT| 0 |EST|SMC|TSL| AES Encryption key | Name 16 x ASCII 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | 0 0 0 0 |EXR|BTE|ETM|RNG| 0 0 0 0 0 0 |APRSRep| Analog APRS PTT | Digital APRS PTT | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | GPS System Index | Freq. corr. signed in 10Hz | Analog crambler enable | 0 0 0 0 0 |SMF|RnK|Muk| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Unused set to 0x00 | 0 0 0 0 |DAD| 0 0 0 | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - RMode: Repater mode where 0=simplex, 1=positive TX offset, 2=negative TX offset. - BWd: Band width where 0=narrow (12.5kHz), 1=wide (25kHz). - PWR: Power where 0=low, 1=mid, 2=high, 3=turbo. - CMode: Channel mode, 0=analog, 1=digital, 2=analog + digi RX, 3=digital + analog RX. - TAr: Enable talkaround. - CaC: Enable call confirm. - RXO: Enable RX only. - CTR: Enable CTCSS phase reversal. - TDC: Enable TX DCS code. - TCT: Enable TX CTCSS tone. - RDC: Enable RX DCS code. - RCT: Enable RX CTCSS tone. - SquelchMode: Squelch mode 0=Carrier, 1=CTCSS/DCS, 2=Optional Signaling, 3=CTCSS/DCS and Optional Signaling, 4 = CTCSS/DCS or Optional Signaling - PTT-ID: When to send the PTT-ID, where 0=off, 1=start, 2=end, 3=both. - OptSig: Optional signalling where 0=off, 1=DTMF, 2=2-tone, 3=5-tone - TxPer: TX permit/admit criterion, 0=always, 1=colorcode, 2=channel free. - LWK: Enable lone worker. - EEE: Enable enhanced encryption - RGP: Enable RX digital (DMR) APRS - EAT: Enable adative TDMA - EST: Enable simplex TDMA, - SMC: SMS confirmation, - TSL: Time slot where 0=TS1, 1=TS2 - XFR: Exclude from roaming - DAD: Data ACK disable. - ETM: Enable through mode - BTE: BlueTooth hands free enabled - EXR: Exclude from roaming - APRSRep: Enable APRS/GPS report, - MuK: Multiple keys - RnK: Random key - SMF: SMS forbid. ================================================ FILE: doc/code/d578uv_generalsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Key tones | Display mode | Auto key-lock enable | Auto shut-down delay | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Transmit timeout | Language | Boot display mode | Boot password enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | VFO Frequency step | Squelch level A | Squelch level B | VFO scan type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | DMR mic. gain | Work mode A | Work mode B | STE Type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | STE when no signal | Group call hang time | Private call hang time | Pre-wave delay | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Wake head period | WFM channel index | WFM Mode | Work mode MEM zone idx A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Work mode MEM zone idx B | Airband/WFM enable | Record enable | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | Unknown | Display brightness | Unknown | GPS enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | SMS alert tone enable | WFM monitor enable | Unknown | Main channel B | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Sub channel enable | TBST frequency | Call alert tone enable | GPS time zone | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Talk permit tones | DMR call reset tone enable | DMR idle channel tone | Menu exit time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Filter own ID in missed calls | Boot tone enable | Show call end prompt | Max. volume | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Remote stun & kill enable | Remote monitor enable | Get GPS position | Long key-press duration | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Show volume bar | Auto repeater mode A | DMR monitor mode | DMR monitor match CC | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | DMR monitor match ID | DMR monitor slot hold | Last caller display mode | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Analog call hang time | Show clock | Enable GPS template info | Enhance sound | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | UHF VFO scan minimum frequency in 10Hz, 32bit uint, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | UHF VFO scan maximum frequency in 10Hz, 32bit uint, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | VHF VFO scan minimum frequency in 10Hz, 32bit uint, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | VHF VFO scan maximum frequency in 10Hz, 32bit uint, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Auto-repeater VHF min frequency in 10Hz, 32bit uint, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Auto-repeater VHF max frequency in 10Hz, 32bit uint, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | Auto-repeater UHF min frequency in 10Hz, 32bit uint, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | Auto-repeater UHF max frequency in 10Hz, 32bit uint, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | Talk permit tone 1 frequency | Talk permit tone 2 frequency | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | Talk permit tone 3 frequency | Talk permit tone 4 frequency | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | Talk permit tone 5 frequency | Talk permit tone 1 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c | Talk permit tone 2 duration in 10ms, 16bit uint little endian | Talk permit tone 3 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Talk permit tone 4 duration in 10ms, 16bit uint little endian | Talk permit tone 5 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 | Idle channel tone 1 freuency in Hz, 16bit uint little endian | Idle channel tone 2 freuency in Hz, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 78 | Idle channel tone 3 freuency in Hz, 16bit uint little endian | Idle channel tone 4 freuency in Hz, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c | Idle channel tone 5 freuency in Hz, 16bit uint little endian | Idle channel tone 1 duration in 10ms 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | Idle channel tone 2 duration in 10ms 16bit uint little endian | Idle channel tone 3 duration in 10ms 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 84 | Idle channel tone 4 duration in 10ms 16bit uint little endian | Idle channel tone 5 duration in 10ms 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 88 | Call reset tone 1 freuency in Hz, 16bit uint little endian | Call reset tone 2 freuency in Hz, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c | Call reset tone 3 freuency in Hz, 16bit uint little endian | Call reset tone 4 freuency in Hz, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Call reset tone 5 freuency in Hz, 16bit uint little endian | Call reset tone 1 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 94 | Call reset tone 2 duration in 10ms, 16bit uint little endian | Call reset tone 3 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 98 | Call reset tone 4 duration in 10ms, 16bit uint little endian | Call reset tone 5 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c | Auto rep. offset index UHF | Auto rep. offset index VHF | Unknown | Priority zone A index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | Priority zone B index | Unknown | Caller display mode | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a4 | Bluetooth enable | BT + int microphone | BT + int. speaker | Plug-in recoding tone enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a8 | GPS Ranging interval | BT mic gain [0,4] | BT speaker gain [0,4] | Channel number mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac | Show current contact | Auto-roaming interval | Call sign color | GPS units | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b0 | |FKL|SkL| 0 |KbL|KnL| Auto-roaming wait | Standby text color | Standby background image | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b4 | Show last call on boot | SMS format | Auto repeater mode B | Send addr book with own code | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b8 | Boot channel enable | Boot zone A index | Boot zone B index | Boot channel index A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ bc | Unknown | Roaming zone index | Repeater check enable | Repeater check interval | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c0 | Repeater check recon. count | Auto-roaming start condition | Unknown | Show display separator | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c4 | Channel switch keep last call | Channel A name color | Repeater out of range notif. | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c8 | Auto roaming enable | P1 key short press | P2 key short press | P3 key short press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ cc | P4 key short press | P5 key short press | P6 key short press | PA key short press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d0 | PB key short press | PC key short press | PD key short press | P1 key long press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d4 | P2 key long press | P3 key long press | P4 key long | P5 key long press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d8 | P6 key long press | PA key long press | PB key long press | PC key long press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ dc | PD key long press | Unknown | Out of range notify count | Transmit timeout rekey | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ e0 | Unknown | BT hold time | BT RX delay | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ e4 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ e8 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ec | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: Bluetooth settings: - BT hold time in seconds, 0=off (default), [off, 1, ..., 30, infinity]. - BT RX delay stored in multiples of 0.5s, 0=0.5, 1=1.0s, 2=1.5s (default), ..., 10=5.5s. STE (squelch tail): - STE Type: 0=Off (default), 1=Silent, 2=120deg, 3=180deg, 4=240deg, 5=55Hz - STE when no signal: 0=Off (default), 1=55.2Hz, 2=259.2Hz WFM/Air band: - Air band/WFM enable: 0=Off (both, default), 1=WFM, 2=Air-band A, 3=Air-band B - WFM mode: 0=Channel (default), 1=VFO - Air band mode: 0=Channel (default), 1=VFO - Air band TX offset direction: 0=positive (default), 1=negative - Air band squelch: 0=Off (default), [1,5] Power save: - Auto shut-down delay: 0=off, 1=10m, 2=30min, 3=60min, 4=120min Key settings: - KnL: Knob lock enable. - KbL: Key-board lock enable. - SkL: Side-key lock enable. - FKL: Forced key lock enable. - Long key-press duration: 0=1s, 1=2s, ..., 4=5s Other: - Send addr book with own code: Sends ANI analog ID together with selected addr book ID. - Transmit timeout in multiples of 30s. 0=Off, 1=30s, ..., 8=240s - Transmit timeout rekey in seconds. 0=Off, 1=1s, ..., 255=255s. - VFO Frequency step: 0=2.5k, 1=5k, 2=6.25k, 3=8.33l, 4=10k, 5=12.5k, 6=20k, 7=25k, 8=30k, 9=50k. - Squelch level A/B: 0=Off, 1=1, ..., 5=5. - TBST frequency: 0=1000kHz, 1=1450kHz, 2=1750kHz, 3=2100kHz. - Analog call hang time in seconds, [0,30] - Priority zone A/B index 0xff=Off. DMR settings: - Group/private call hang time in seconds 1=1s, 2=2s, ..., 30=30s, 31=30min, 32=infinity. - Pre-wave delay in multiples of 20ms 0=0ms (default), 1=20ms, ..., 50=1000ms - Wake head period in multiples of 20ms 0=0ms (default), 1=20ms, ..., 60=1200ms - DMR monitor mode 0=Off, 1=Single slot, 2=Dual-slot. - SMS format: 0=Motorola (default), 1=Hytera, 2=DMR Standard Boot settings: - Boot display mode: 0=Default, 1=Custom text, 2=Custom image - Boot channel index A/B: Index within zone, 0xff = VFO Tone settings: - Talk permit tones: 0=None, 1=DMR, 2=FM, 3=Both - Key tones: 0=Off, 1=1, ..., 8=8, 9=With RX tone A, 10=With RX tone B - DMR idle channel tone: 0=Off, 1=Type 1, 2=Type 2, 3=Type 3. Display settings: - Display brightness 0=1, ..., 4=5 - Menu exit time in multiples of 5s: 0=5s, 1=10s, ..., 11=60s - Last caller display mode: 0=Off, 1=Show ID, 2=Show Call, 3=Show both - Caller display mode: 0=Show name, 1=Show call - Callsign/Channel A/B name/ color: 0=Orange (default), 1=Red, 2=Yellow, 3=Green, 4=Turquoise, 5=Blue, 6=White - Standby text color: 0=White, 1=Black, 2=Orange, 3=Red, 4=Yellow, 5=Green, 6=Turquoise, 7=Blue - Channel number mode: 0=Channel number, 1=Channel index within zone. - Standby background image: 0=Default, 1=Custom image 1, 2=Custom image 2 GPS settings: - GPS time zone: 00h=UTC-12:00, ..., 09h=UTC-3:30, 0Ah=UTC-3:00, ..., 0Dh=UTC, ..., 11h=UTC+3:30, 12h=UTC+4:00, 13h=UTC+4:30, 14h=UTC+5:00, 15h=UTC+5:30, 16h=UTC+5:45, ..., 1Ah=UTC+8:30, ..., 1Fh=UTC+13:00 - GPS Ranging interval in seconds [5,255] - GPS units: 0=metric, 1=imperial Scan settings: - VFO scan type: 0=TO (fixed time, default), 1=CO (carrier), 2=SE (stop) Auto-repeater settings: - Auto repeater mode A/B: 0=Off, 1=positive, 2=negative - Auto rep. offset index UHF/VHF: index, 0xff=Off. - Repeater check interval in multiples of 5s: 0=5s, 1=10s, ..., 9=50 - Repeater check recon. count 0=1, 1=2, 2=3 - Repeater out of range notif.: 0=None, 1=Bell, 2=Voice - Out of range notify count: 0=1, 1=2, ..., 9=10. - Auto-roaming start condition: 0=time, 1=repeater out of range - Auto-roaming interval in minutes. 0=1min, 1=2min, ..., 255=256min. - Auto-roaming wait interval in seconds, 0=off, 1=1s, ..., Audio settings: - Max. volume: 0=Indoors (default), 1=1, ..., 8=8. - DMR mic. gain: 0=1 (default), ..., 4=5 ================================================ FILE: doc/code/d578uv_generalsettingsextension.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c | Unknown | Alias priority | Alias encoding | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | Weather alarm enable | Cross-band repeater enable | Unknown | Speaker mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0024 | Unknown | Mic-speaker path | GPS mode | BT PTT hold enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0028 | PTT sleep time | Fan control | Weather channel index | Man dial group call hang time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c | Man dial priv call hang time | Knob short press | Knob long press | Channel B name color | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 | Encryption mode | Pro-mode enable | STE duration | HandOpMode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0034 | Zone name A color | Zone name B color | Auto shut-down mode | Air band mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0038 | Air band channel index | Air band TX offset direction | Air band squelch | |SCC|STS|SCT| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c | FM channel idle tone enable | Date format | Unknown | FM mic gain | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0040 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0044 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0048 | Unknown | Transmit timeout predict | GPS roaming enable | Cross band rep. CC match mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004c | Cross band rep. Ch A TS | Cross band rep. Ch B TS | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0054 | BT handset VOX level | BT handset VOX delay | BT handset vol A | BT handset vol B | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0058 | Call end tone frequency 1 in Hz, 16bit uint little endian | Call end tone frequency 2 in Hz, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 005c | Call end tone frequency 3 in Hz, 16bit uint little endian | Call end tone frequency 4 in Hz, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0060 | Call end tone frequency 5 in Hz, 16bit uint little endian | Call end tone 1 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0064 | Call end tone 2 duration in 10ms, 16bit uint little endian | Call end tone 3 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0068 | Call end tone 4 duration in 10ms, 16bit uint little endian | Call end tone 5 duration in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 006c | All-call tone frequency 1 in Hz, 16bit uint little endian | All-call tone frequency 2 in Hz, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0070 | All-call tone frequency 3 in Hz, 16bit uint little endian | All-call tone frequency 4 in Hz, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0074 | All-call tone frequency 5 in Hz, 16bit uint little endian | All-call tone duration 1 in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0078 | All-call tone duration 2 in 10ms, 16bit uint little endian | All-call tone duration 3 in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 007c | All-call tone duration 4 in 10ms, 16bit uint little endian | All-call tone duration 5 in 10ms, 16bit uint little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0080 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0084 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0088 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 008c | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0090 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0094 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0098 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 009c | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00a0 | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00a4 | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 01fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - PTT sleep time: in minutes [1,4], 0=infinite - BT handset VOX level [0,8], default=0 - BT handset VOX delay in 0.5s. 0=0.5s, 1=1.0s, ..., 9=5.0s - BT handset vol A/B [0,31] - STE duration in ms. 0=10ms (default), 1=20ms, ..., 99=1000ms. - Auto shut-down mode: 0=Reset timer on call, 1=no reset. - Mic-speaker path: 0=Master, 1=Slave - Fan control: 0=PTT, 1=Temperature, 2=Both - Speaker mode: 0=Microphone, 1=Radio, 2=Both - Encryption mode: 0=Common, 1=AES (default) - HandOpMode: Select hand microphone type. 0=Serial port (common AnyTone hand mic), 1=Voltage Detect (simple PTT mic) - Manual dial group/private call hang time in seconds 1=1s, 2=2s, ..., 30=30s, 31=30min, 32=infinity. - Talk permit melody frequencies: 16bit uint little endian in Hz. - Channel A/B name/Zone A/B name color: 0=Orange (default), 1=Red, 2=Yellow, 3=Green, 4=Turquoise, 5=Blue, 6=White - SCT: Show channel type enable - STS: Show time slot enable - SCC: Show color code enable - Date format: 0=yyyy/mm/dd, 1=dd/mm/yyyy - GPS mode: 0=GPS, 1=Baidu, 2=Both - Cross band rep. CC match mode: 0 = None, 1 = Match channel A CC, 2 = Match channel B CC - Cross band rep. Ch A/B TS: 0=Off, 1=TS1, 2=TS2 - FM mic gain: 0=1, ..., 4=5. - Alias priority: 0=Off, 1=contact, 2=over-the-air - Alias encoding: 0=ISO8, 1=ISO7, 2=Unicode ================================================ FILE: doc/code/d578uv_hotkeysettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Hotkey setting 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 | Hotkey setting 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 005c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0450 | Hotkey setting 23 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 047c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uv_callsigndbentry.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Call type | DMR ID, 8-digit BCD, big-endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | 0 0 0 |FRD| 0 0 | Ring | Body, up to 94 bytes, string list, 0-terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Call type: The call type of the entry, 0=Private, 1=Group and 2=All call. - FRD: Friend flag. - Ring: Ring tone settings, 0=Off, 1=Tone, 2=Online(?). - Body: String list variable size. Each string in list 0-terminated. List is [Name, City, Call, State, Country, Comment]. Maximum length is 96 including 0 bytes. ================================================ FILE: doc/code/d868uv_callsigndblimit.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Number of db entries, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | End of database pointer, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Unused set to 0x00000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Unused set to 0x00000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uv_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | RX Frequency 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Tx Frequency Offset 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | RMode | 0 |BWd| PWR | CMode |TAr|CaC|RXO|CTR|TDC|TCT|RDC|RCT| CTCSS transmit | CTCSS receive | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | DCS transmit code, little endian | DCS receive code, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Custom CTCSS frequency in 0.1Hz, little endian | 2-tone decode index, 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Contact index 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Radio ID table index. | 0 |SquelchMode| 0 0 0 0 | 0 0 |OptSig | 0 0 | TxPer | Scan list index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Group list index | 2-tone ID | 5-tone ID | DTMF ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Color code |LWK|EEE|RGP|EAT| 0 |EST|SMC|TSL| AES Encryption key | Name 16 x ASCII 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | 0 0 0 0 0 |DAD|ETM|RNG| 0 0 0 0 0 0 0 |TAP| DMR APRS IDX (0-based) | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Unused set to 0x00 | Unused set to 0x00 | DMR encryption idx +1, 0=off | 0 0 0 0 0 |SMF|RnK|Muk| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Unused set to 0x00000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - RMode: Repater mode where 0=simplex, 1=positive TX offset, 2=negative TX offset. - BWd: Band width where 0=narrow (12.5kHz), 1=wide (25kHz). - PWR: Power where 0=low, 1=mid, 2=high, 3=turbo. - CMode: Channel mode, 0=analog, 1=digital, 2=analog + digi RX, 3=digital + analog RX. - TAr: Enable talkaround. - CaC: Enable call confirm. - RXO: Enable RX only. - CTR: Enable CTCSS phase reversal. - TDC: Enable TX DCS code. - TCT: Enable TX CTCSS tone. - RDC: Enable RX DCS code. - RCT: Enable RX CTCSS tone. - SquelchMode: Squelch mode 0=Carrier, 1=CTCSS/DCS, 2=Optional Signaling, 3=CTCSS/DCS and Optional Signaling, 4 = CTCSS/DCS or Optional Signaling - OptSig: Optional signalling where 0=off, 1=DTMF, 2=2-tone, 3=5-tone - TxPer: TX permit/admit criterion, 0=always, 1=colorcode, 2=channel free. - LWK: Enable lone worker. - EEE: Enable enhanced encryption - RGP: Enable RX GPS: - EAT: Enable adative TDMA - EST: Enable simplex TDMA, - SMC: SMS confirmation, - TSL: Time slot where 0=TS1, 1=TS2 - DAD: Data ACK disable (inverted!) - ETM: Enable through mode - RNG: Ranging - APRSRep: Enable APRS/GPS report, - TAP: TX APRS enable - MuK: Multiple keys - RnK: Random key - SMF: SMS forbid. ================================================ FILE: doc/code/d868uv_generalsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Enable key tone | Display mode | Enable automatic key lock | Automatic shutdown time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Unknown | Unknown | Boot display | Enable boot password | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Unknown | Squelch level VFO A | Squelch level VFO B | Power save mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | VOX sensitivity | VOX delay in 100+500*n ms | VFO scan type | MIC gain | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | PF1 short press function | PF2 short press function | PF3 short press function | P1 short press function | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | P2 short press function | Work mode A | Work mode B | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | Work mode MEM zone A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Work mode MEM zone B | Unknown | Enable recording | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | Display brightness | Backlight duration | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Enable GPS | Enable SMS alert | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Work mode main channel set | Enable sub channel | Unknown | Enable call alert | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | GPS Time zone | Talk permit tone | Digital call reset tone | VOX source | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Unknown | Idle channel tone | Menu exit time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Unknown | Startup tone | Enable call end prompt | Max volume | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Unknown | GPS RX positions | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Unknown | PF1 long press function | PF2 long press function | PF3 long press function | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | P1 long press function | P2 long press function | Long press duration | Enable Volume change prompt | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | Auto repeater A direction | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | Display later caller | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Unknown | Display clock | Max head phone volume | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | | Enable enhanced audio | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | VFO Scan UHF minimum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | VFO Scan UHF maximum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | VFO Scan VHF minimum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | VFO Scan VHF maximum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | Auto rep. offset index UHF | Auto rep. offset index VHF | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 ... | Call tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 | Call tone frequency 2 in Hz, little endian | Call tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 78 | Call tone frequency 4 in Hz, little endian | Call tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c | Call tone duration 1 in ms, little endian | Call tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | Call tone duration 3 in ms, little endian | Call tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 84 | Call tone duration 5 in ms, little endian | Idle tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 88 | Idle tone frequency 2 in Hz, little endian | Idle tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c | Idle tone frequency 4 in Hz, little endian | Idle tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Idle tone duration 1 in ms, little endian | Idle tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 94 | Idle tone duration 3 in ms, little endian | Idle tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 98 | Idle tone duration 5 in ms, little endian | Reset tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c | Reset tone frequency 2 in Hz, little endian | Reset tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | Reset tone frequency 4 in Hz, little endian | Reset tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a4 | Reset tone duration 1 in ms, little endian | Reset tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a8 | Reset tone duration 3 in ms, little endian | Reset tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac | Reset tone duration 5 in ms, little endian | Record delay in 200ms | Call display mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b0 | Call display color | GPS update period | Show zone talkgroup | Key tone level | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b4 | GPS units | 0 | 0 | 0 |EPK|ESK| 0 |EKB|ENK| Show last heard | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b8 | Auto repeater VHF minimum frequency in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ bc | Auto repeater VHF maximum frequency in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c0 | Auto repeater UHF minimum frequency in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c4 | Auto repeater UHF maximum frequency in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c8 | Auto rep. direction | Unknown | Enable default channel | Default zone VFO A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ cc | Default zone VFO B | Default channel A | Default channel B | Keep last caller | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - EKN: Enable knob lock. - EKB: Enable keyboard lock. - ESK: Enable side key lock. - EPK: Enable "professional key" lock. ================================================ FILE: doc/code/d868uvanalogcontact.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Number, 14 digits BCD encoded, big-endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | Number of digits | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Name, 15 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | 0x00 pad | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uvchannel.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | RX Frequency 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Tx Frequency Offset 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | RMode | 0 |BWd| PWR | CMode |TAr|CaC|RXO|CTR|TDC|TCT|RDC|RCT| CTCSS transmit | CTCSS receive | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | DCS transmit | DCS receive | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Custom CTCSS | 2-tone decode | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Contact index 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Radio ID table index. | 0 0 0 |SQM| 0 0 |PTT-ID | 0 0 |OptSig | 0 0 | TxPer | Scan list index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Group list index | 2-tone ID | 5-tone ID | DTMF ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Color code |LWK|EEE|RGP|EAT| 0 |EST|SMC|TSL| AES Encryption key | Name 16 x ASCII 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | 0 0 0 0 0 |XFR|ETM|RNG| 0 0 0 0 0 0 |APRSRep| Analog APRS PTT | Digital APRS PTT | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | GPS System Index | Frequency correction signed | DMR encryption | 0 0 0 0 0 |MuK|RnK|SMF| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Unused set to 0x00000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ with RMode = Repater mode, BWd = Band width, PWR = Power, CMode = Channel mode, TAr=Talkaround, CaC = Call confirm, RXO = RX only, CTR = CTCSS phase reversal, TDC = TX DCS code, TCT = TX CTCSS tone, RDC = RX DCS code, RCT = RX CTCSS tone, SQM = Squelch mode, OptSig = Optional signalling, TxPer = TX permit, LWK = Lone worker, EEE = Enable enhanced encryption, RGP = Enable RX GPS, EAT = Enable adative TDMA, EST = Enable simplex TDMA, SMC = SMS confirmation, TSL = Time slot, XFR = Exclude from roaming, ETM = Enable through mode, RNG = Ranging, APRSRep = Enable APRS/GPS report, MuK = Multiple keys, RnK = Random key, SMF = SMS forbid. ================================================ FILE: doc/code/d868uvcontact.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Type | Name 16 x ASCII chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | 18 unused bytes set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | ID 8 digits BCD encoded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | Call alert | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | 60 unused bytes, set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uvgrouplist.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | 64 indices of group calls, 32bit each, default 0xffffffff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 100 | Name, 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 110 | 16 unused bytes set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 11c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uvmessage.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Message text, 99 ASCII chars, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 ... | 157 unused bytes, 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uvmessagelist.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused 0x00 | Unused 0x00 | Next index | Current index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | 12 unused bytes set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uvradioid.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Radio ID encoded as 8 BCD digits | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Unused, set to 0x00 | Name, 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | 11 unused bytes, set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uvscanlist.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused, set to 0x00 | Priority Channel Select | Priority channel index 1, 16bit index, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Priority channel index 2, 16bit index, little endian | Look back time A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Look back time B | Dropout delay | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Dwell time | Revert channel type | Name, 16 ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | 50 x 16bit channel indices, little endian, 0xffff=empty ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 84 | 12 unused bytes , set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d868uvzonechannels.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000 | Zone 0, channel index A little endian | Zone 1, channel index A little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 010 | Zone 2, channel index A little endian | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1f0 ... | Zone 249, channel index A little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1f4 | 12 pad bytes set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 200 | Zone 0, channel index A little endian | Zone 1, channel index A little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 210 | Zone 2, channel index A little endian | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3f0 ... | Zone 249, channel index A little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3f4 | 12 pad bytes set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uv_aeskey.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Key index, 0xff=off | Key data ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | Unused set to 0x00 | Unknown set to 0x40 | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uv_aprsrxentry.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Enabled | Call up to 6 ASCII chars, 0x00 terminated and filled ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | SSID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uv_aprssetting.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unknown, set to 0x000 | Unused ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | FM APRS TX delay in 20ms | Signaling type | CTCSS tone | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | DCS code, little-endian | Manual TX interval in seconds | Auto TX interval in 30s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | FM APRS Monitor enable | Fixed location flag | Latitude degrees | Latitude minutes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Latitude seconds | South flag | Longitude degrees | Longitude minutes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Longitude seconds | West flag | Destination call, 6 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | Destination SSID | Source call, 6 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | Source SSID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Path, 20 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Unused set to 0x00 | ASCII APRS Symbol Table | ASCII APRS Map Icon | Transmit power | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Prewave delay in 10ms | Unknown set to 0x01 | Unknown set to 0x03 | Unknown set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | DMR APRS Sys 0 channel index, uint16, litte-endian | DMR APRS Sys 1 channel index, uint16, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | DMR APRS Sys 2 channel index, uint16, litte-endian | DMR APRS Sys 3 channel index, uint16, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | DMR APRS Sys 4 channel index, uint16, litte-endian | DMR APRS Sys 5 channel index, uint16, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | DMR APRS Sys 6 channel index, uint16, litte-endian | DMR APRS Sys 7 channel index, uint16, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | DMR APRS Sys 0 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | DMR APRS Sys 1 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | DMR APRS Sys 2 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | DMR APRS Sys 3 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | DMR APRS Sys 4 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | DMR APRS Sys 5 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | DMR APRS Sys 6 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c | DMR APRS Sys 7 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | DMR APRS Sys 0 call type | DMR APRS Sys 1 call type | DMR APRS Sys 2 call type | DMR APRS Sys 3 call type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 | DMR APRS Sys 4 call type | DMR APRS Sys 5 call type | DMR APRS Sys 6 call type | DMR APRS Sys 7 call type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 78 | Enable roaming support | DMR APRS Sys 0 time slot | DMR APRS Sys 1 time slot | DMR APRS Sys 2 time slot | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c | DMR APRS Sys 3 time slot | DMR APRS Sys 4 time slot | DMR APRS Sys 5 time slot | DMR APRS Sys 6 time slot | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | DMR APRS Sys 7 time slot | Rep. activation delay | APRS display time | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a4 ... | Fixed height in feet, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a8 |RST|RNF|RWX|RMS|RIT|ROB|RME|RPO| 0 0 0 0 0 0 0 |ROF| FM APRS width | Pass all enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac | FM ARPS Frequency 0 in 10Hz, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b0 | FM ARPS Frequency 1 in 10Hz, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b4 | FM ARPS Frequency 2 in 10Hz, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b8 | FM ARPS Frequency 3 in 10Hz, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ bc | FM ARPS Frequency 4 in 10Hz, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c0 | FM ARPS Frequency 5 in 10Hz, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c4 | FM ARPS Frequency 6 in 10Hz, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c8 | FM ARPS Frequency 7 in 10Hz, 8 digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d0 | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - FM APRS TX delay: 0=0ms, 1=20ms, ..., 255=5100ms - Signaling type: 0=off, 1=CTCSS, 2=DCS, default=off - CTCSS Tone: 0= 61.2Hz, 1= 67.0Hz, 2= 69.3Hz, 3= 71.9Hz, 4= 74.4Hz, 5= 77.0Hz, 6= 79.7Hz, 7=82.5Hz, 8= 85.4Hz, 9= 88.5Hz, 10= 91.5Hz, 11= 94.8Hz, 12= 97.4Hz, 13=100.0Hz, 14=103.5Hz, 15=107.2Hz, 16=110.9Hz, 17=114.8Hz, 18=118.8Hz, 19=123.0Hz, 20=127.3Hz, 21=131.8Hz, 22=136.5Hz, 23=141.3Hz, 24=146.2Hz, 25=151.4Hz, 26=156.7Hz, 27=159.8Hz, 28=162.2Hz, 29=165.5Hz, 30=167.9Hz, 31=171.3Hz, 32=173.8Hz. 33=179.9Hz, 34=183.5Hz, 35=186.2Hz, 36=189.9Hz, 37=192.8Hz, 38=196.6Hz, 39=199.5Hz, 40=203.5Hz, 41=206.5Hz, 42=210.7Hz, 43=218.1Hz, 44=225.7Hz, 45=229.1Hz, 46=233.6Hz, 47=241.8Hz, 48=250.3Hz, 49=254.1Hz - Path: 20 x ASCII, 0-padded path string. Format is comma-separated CALL1-SSID,CALL2-SSID,... - DMR APRS Sys N channel index: uint16 channel index, 0-based, little-endian, [0,4000], 0x0fa0=VFO A, 0x0fa1=VFO B, 0x0fa2=Selected - DMR APRS Sys N time slot: 0 = Channel, 1 = Timeslot 1, 2 = Timeslot 2 - Transmit power: 0=low, 1=mid, 2=high, 3=turbo. - Pewave delay: in multiples of 10ms [0,2550ms], default=0ms. - Manual TX interval: n+1 seconds. [0,255], default=0s - Auto TX interval: 0=Off, 1=30s, ..., 255=7650s, default=off - FM APRS Monitor enable: If enabled, the radio will monitor send FM APRS transmissions. default=off - Rep. activation delay: 0=Off, 1=100ms, ..., 10=1000ms. - APRS display time: 0=3s, 1=4s, ..., 12=15s, 13=infinite, default=3s - RPO: Report position flag. - RME: Report MIC-E flag. - ROB: Report object flag. - RIT: Report item flag. - RMS: Report message flag. - RWX: Report weather flag. - RNF: Report NEMA flag. - RST: Report status flag. - ROF: Report other flag. - FM APRS width: 0=narrow, 1=wide. - Pass all: 0=Off, 1=On, no idea. ================================================ FILE: doc/code/d878uv_aprssettingext.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unknown settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | Fixed altitude in feet, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 |RST|RNF|RWX|RMS|RIT|ROB|RME|RPO| 0 0 0 0 0 0 0 |ROF| Unknown settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - RPO: Report position flag. - RME: Report MIC-E flag. - ROB: Report object flag. - RIT: Report item flag. - RMS: Report message flag. - RWX: Report weather flag. - RNF: Report NEMA flag. - RST: Report status flag. - ROF: Report other flag. ================================================ FILE: doc/code/d878uv_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | RX Frequency 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Tx Frequency Offset 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | RMode | 0 |BWd| PWR | CMode |TAr|CaC|RXO|CTR|TDC|TCT|RDC|RCT| CTCSS transmit | CTCSS receive | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | DCS transmit code, little endian | DCS receive code, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Custom CTCSS frequency in 0.1Hz, little endian | 2-tone decode index, 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Contact index 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Radio ID table index. | 0 |SquelchMode| 0 0 |PTT-ID | 0 0 |OptSig | 0 0 | TxPer | Scan list index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Group list index | 2-tone ID | 5-tone ID | DTMF ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Color code |LWK|EEE|RGP|EAT| 0 |EST|SMC|TSL| AES Encryption key | Name 16 x ASCII 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | 0 0 0 0 |DAD|XFR|ETM|RNG| 0 0 0 0 0 0 |APRSRep| Analog APRS PTT | Digital APRS PTT | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | GPS System Index | Freq. corr. signed in 10Hz | DMR encryption idx +1, 0=off | 0 0 0 0 0 |SMF|RnK|Muk| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | FM APRS Frequency index | Unused set to 0x000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - RMode: Repater mode where 0=simplex, 1=positive TX offset, 2=negative TX offset. - BWd: Band width where 0=narrow (12.5kHz), 1=wide (25kHz). - PWR: Power where 0=low, 1=mid, 2=high, 3=turbo. - CMode: Channel mode, 0=analog, 1=digital, 2=analog + digi RX, 3=digital + analog RX. - TAr: Enable talkaround. - CaC: Enable call confirm. - RXO: Enable RX only. - CTR: Enable CTCSS phase reversal. - TDC: Enable TX DCS code. - TCT: Enable TX CTCSS tone. - RDC: Enable RX DCS code. - RCT: Enable RX CTCSS tone. - SquelchMode: Squelch mode 0=Carrier, 1=CTCSS/DCS, 2=Optional Signaling, 3=CTCSS/DCS and Optional Signaling, 4 = CTCSS/DCS or Optional Signaling - PTT-ID: When to send the PTT-ID, where 0=off, 1=start, 2=end, 3=both. - OptSig: Optional signalling where 0=off, 1=DTMF, 2=2-tone, 3=5-tone - TxPer: TX permit/admit criterion, 0=always, 1=colorcode, 2=channel free. - LWK: Enable lone worker. - EEE: Enable enhanced encryption - RGP: Enable RX digital (DMR) APRS - EAT: Enable adative TDMA - EST: Enable simplex TDMA, - SMC: SMS confirmation, - TSL: Time slot where 0=TS1, 1=TS2 - XFR: Exclude from roaming - DAD: Data ACK disable. - ETM: Enable through mode - RNG: Ranging - APRSRep: Enable APRS/GPS report, - MuK: Multiple keys - RnK: Random key - SMF: SMS forbid. ================================================ FILE: doc/code/d878uv_dmraprssettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Channel index 0, uint16 little endian | Channel index 1, uint16 little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | Channel index 6, uint16 little endian | Channel index 7, uint16 little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Destination contact index 0, uint32 little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Destination contact index 1, uint32 little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c | Destination contact index 7, uint32 little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 | Call type 0 | Call type 1 | Call type 2 | Call type 3 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0034 | Call type 4 | Call type 5 | Call type 6 | Call type 7 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0038 | Romaing enable | Time slot 0 | Time slot 1 | Time slot 2 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c | Time slot 3 | Time slot 4 | Time slot 5 | Time slot 6 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0040 | Time slot 7 | Repeater delay in 100ms | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 005c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uv_dmraprssystems.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Digital channel index 0, 16bit little-endian | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | Digital channel index 7, 16bit little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Destination DMR ID 0, 32bit BCD big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Destination DMR ID 7, 32bit BCD big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Calltype channel 0 | Calltype channel 1 | Calltype channel 2 | Calltype channel 3 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Calltype channel 4 | Calltype channel 5 | Calltype channel 6 | Calltype channel 7 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Roaming support | Timeslot channel 0 | Timeslot channel 1 | Timeslot channel 2 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Timeslot channel 3 | Timeslot channel 4 | Timeslot channel 5 | Timeslot channel 6 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Timeslot channel 7 | Rep.activation delay in 100ms | 30 unused bytes set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uv_generalsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Enable key tone | Display mode | Enable automatic key lock | Automatic shutdown time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Transmit timeout in 30s | Language | Boot display | Enable boot password | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | VFO frequency step | Squelch level VFO A | Squelch level VFO B | Power save mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | VOX sensitivity | VOX delay | VFO scan type | DMR MIC gain | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | PF1 short press function | PF2 short press function | PF3 short press function | P1 short press function | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | P2 short press function | Work mode A | Work mode B | STE Type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | STE freq. no signal | Group call hang time in sec | Private call hang time in sec | Prewave time in 20ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | Wake head period in 20ms | WFM channel index | WFM VFO Mode enable | Work mode MEM zone A index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Work mode MEM zone B index | Unused set to 0x00 | Enable recording | DTMF duration | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Man down enable | Unused set to 0x00 | Display brightness | Backlight duration | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Enable GPS | Enable SMS alert | Unused set to 0x00 | WFM monitor enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Work mode main channel set | Enable sub channel | TBST frequency | Enable call alert | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | GPS Time zone | Talk permit tone | Digital call reset tone | VOX source | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Enable pro mode | Unused set to 0x00 | Idle channel tone | Menu exit time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Filter own ID enable | Startup tone enable | Enable call end prompt | Max speaker volume | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Remote stun/kill enable | Unused set to 0x00 | Remote monitor enable | GPS RX positions | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Unknown | PF1 long press function | PF2 long press function | PF3 long press function | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | P1 long press function | P2 long press function | Long press duration | Enable Volume change prompt | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | Auto repeater A direction | Digital monitor slot | Digital monitor color code | Digital monitor match ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Digital monitor hold slot | Display later caller | Unknown | Man down delay in sec | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Analog call hold in seconds | Display clock | Max head phone volume | Enable GPS message | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Unknown settings | Enable enhanced audio | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | VFO Scan UHF minimum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | VFO Scan UHF maximum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | VFO Scan VHF minimum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | VFO Scan VHF maximum frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | Auto rep. 1 offset index UHF | Auto rep. 1 offset index VHF | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c | | Maintain call channel | Priority zone A index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Priority zone B index | Unused set to 0x00 | Call tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 | Call tone frequency 2 in Hz, little endian | Call tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 78 | Call tone frequency 4 in Hz, little endian | Call tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c | Call tone duration 1 in ms, little endian | Call tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | Call tone duration 3 in ms, little endian | Call tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 84 | Call tone duration 5 in ms, little endian | Idle tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 88 | Idle tone frequency 2 in Hz, little endian | Idle tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c | Idle tone frequency 4 in Hz, little endian | Idle tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Idle tone duration 1 in ms, little endian | Idle tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 94 | Idle tone duration 3 in ms, little endian | Idle tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 98 | Idle tone duration 5 in ms, little endian | Reset tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c | Reset tone frequency 2 in Hz, little endian | Reset tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | Reset tone frequency 4 in Hz, little endian | Reset tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a4 | Reset tone duration 1 in ms, little endian | Reset tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a8 | Reset tone duration 3 in ms, little endian | Reset tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac | Reset tone duration 5 in ms, little endian | Record delay in 200ms | Call display mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b0 | Unknown settings | BT enable | BT and internal mic | BT and internal speaker | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b4 | Plug-in rec tone enable | GPS ranging interval in sec | BT mic gain | BT speaker gain | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b8 | Display channel number | Display contact | Auto roaming period in min | Key tone level | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ bc | Callsign color | GPS unit | 0 0 0 |KLF|LSK| 0 |LKB|LKN| Auto roam delay in seconds | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c0 | Standby text color | Standby background image | Show last call on boot | SMS format | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c4 | Auto repeater VHF 1 min frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c8 | Auto repeater VHF 1 max frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ cc | Auto repeater UHF 1 min frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d0 | Auto repeater UHF 1 max frequency in 10Hz, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d4 | Auto rep. VFO B direction | Addrbook is sent w own code | Unused set to 0x00 | Boot channel enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d8 | VFO A default zone index | VFO B default zone index | VFO A default channel index | VFO B default channel index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ dc | Default roaming zone index | Repeater range check enable | Repeater check interval in 5s | RepCheck reconnect count | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ e0 | Roam start condition | Backlight duration TX in sec | Separate display | Keep last caller | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ e4 | Channel name A color | RepCheck notification | Backlight duration RX in sec | Roaming enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ e8 | Unused set to 0x00 | Mute delay in minutes-1 | RepCheck num notification | Startup GPS test enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ec | Startup reset enable | BT hold time | BT RX delay | Unknown | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Work mode settings: - Display mode: 0=Channel, 1=Frequency - Work mode A/B: 0=Channel, 1=VFO - Work mode main channel set: 0=A, 1=B - VOX settings: - VOX sensitivity: 0=Off, 1=1, ..., 3=3 - VOX delay in 100+500*n ms - VOX source: 0=internal, 1=external, 2=both - Bluetooth settings: - BT mic gain: 0=1, ..., 3=4 (default), 4=5. - BT speaker gain: 0=1, 1=2 (default), ..., 4=5 - BT hold time in seconds: 0=Off, ..., 10=10s (default), ..., 120=120s, 121=infinite. - BT RX delay in 0.5s: 0=30ms, 1=1.0s, 2=1.5s, ... 10=5.5 - STE settings: - STE type: 0=Off, 1=Silent, 2=120deg, 3=180deg, 4=240deg - STE freq. no signal: 0=Off, 1=55.2Hz, 2=259.2Hz - Power save settings: - Automatic shutdown time: 0=Off (default), 1=10min, 2=30min, 3=60min, 4=120min - Power save mode: 0=Off, 1=1:1 (50%), 2=2:1 (66%) - Key settings: - Key functions: - Long press duration: 0=1s, 1=2s, ..., 4=5s - KLF: Key lock forced enable - LSK: Lock side key - LKB: Keyboard lock - LKN: Knob lock - Other settings: - Addrbook is sent w own code: Analog call send radio ID together with selected address book entry. - Transmit timeout in multiples of 30s. 0=Off, 1=30s, ..., 8=240s - Language: 0=English, 1=German - VFO frequency step: 0=2.5kHz, 1=5kHz, 2=6.25kHz, 3=8.33kHz, 4=10kHz, 5=12.5kHz, 6=20kHz, 7=25kHz - TBST frequency: 0=1000Hz, 1=1450Hz, 2=1750Hz, 3=2100Hz - Analog call hold in seconds [0,...,30s] - Maintain call channel: If enabled, allows to answer calls on sub-channel within 5s. - Priority zone A/B index: Zone index 0-based, 0xff=Off. - DMR settings: - Group/private call hang time in seconds: 0=1s, ..., 29=30s, 30=30min, 31=infinite. - Prewave time/wake head period in 20ms: 0=0ms, ..., 5=100ms (default), ..., 50=1000ms - Digital monitor slot: 0=Off, 1=Single slot, 2=Dual slot - SMS format: 0=Motorola, 1=Hytera, 2=DMR standard - Boot settings: - Boot display: 0=default, 1=custom text, 2=custom image - VFO A/B default channel index: Channel index within default zone. 0xff = VFO. - Tone settings: - Talk permit tone: 0=Off, 1=DMR, 2=FM, 3=both - Idle channel tone: 0=Off, 1=Type 1, 2=Type 2, 3=Type 3 - Key tone level: 0=adjustable, 1, ..., 15 - Display settings: - Display brightness: 0=1, ..., 4=5 - Backlight duration: 0=Always, 1=5s, 2=10s, ..., 6=30s, 7=1m - Backlight duration TX in sec: 0=Off, 1=1s, ..., 30=30s - Menu exit time in 5sec: 0=5s, ..., 11=60s - Display later caller: 0=Off, 1=ID, 2=Call, 3=Both - Call display mode: 0=Off, 1=Call, 2=Name - Callsign/channel name/ color: 0=Orange, 1=Red, 2=Yellow, 3=Green, 4=Turquoise, 5=Blue, 6=White - Display channel number: 0=Channel number, 1=Index in zone - Standby text color: 0=White, 1=Black, 2=Orange, 3=Red, 4=Yellow, 5=Green, 6=Turquoise, 7=Blue - Standby background image: 0=Default, 1=Custom 1, 2=Custom 2 - Separate display: Draws a line to separate upper and lower half of the display. - Keep last caller on channel switch - Backlight duration RX in sec: 0=Always, 1=1s, ..., 30=30s. - GPS settings: - GPS time-zone: 00h=UTC-12:00, ..., 09h=UTC-3:30, 0Ah=UTC-3:00, ..., 0Dh=UTC, ..., 11h=UTC+3:30, 12h=UTC+4:00, 13h=UTC+4:30, 14h=UTC+5:00, 15h=UTC+5:30, 16h=UTC+5:45, ..., 1Ah=UTC+8:30, ..., 1Fh=UTC+13:00 - GPS ranging interval in sec [5,255] - GPS units: 0=metric, 1=imperial - Scan settings: - VFO scan type: 0=fixed time, 1=carrier, 2=stop - Auto repeater settings: - Auto repeater A/B direction: 0=Off, 1=Positive, 2=Negative - Auto rep. 1 offset index UHF/VHF. Offset frequency index 0-based, 0xff=Off. - Repeater check interval in 5s: 0=5s, ..., 9=50s - RepCheck reconnect count: 0=3, 1=4, 2=5 - RepCheck notification: 0=Off, 1=Bell, 2=Voice - RepCheck num notification: 0=1, ..., 9=10. - Roam start condition: 0=time based, 1=out-of-range - Auto roaming period in min: 0=1min, ..., 255=256min. - Auto roam delay in seconds: 0=None, 1=1s, ..., 30=30min - Audio settings: - Max speaker/head phone volume: 0=Indoors, 1=1, ..., 5=5 (default), ..., 8=8 - DMR MIC gain: 0=1 (default), ..., 4=5 ================================================ FILE: doc/code/d878uv_generalsettingsextension.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Enable send alias | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Unknown settings | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Unknown settings | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | Unknown settings | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Unknown settings | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Unknown settings | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 | Unknown settings | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c | Unknown settings | Talker alias priority | Talker alias encoding | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | Enable BT PTT latch | Unknown | AutoRep UHF 2 offset index | AutoRep VHF 2 offset index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0024 | AutoRep VHF 2 minimum frequency in 10Hz, 32 bit uint, little-endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0028 | AutoRep VHF 2 maximum frequency in 10Hz, 32 bit uint, little-endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c | AutoRep UHF 2 minimum frequency in 10Hz, 32 bit uint, little-endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 | AutoRep UHF 2 maximum frequency in 10Hz, 32 bit uint, little-endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0034 | BT PTT sleep interval | GPS mode | STE duration | Man dial group call hangtime | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0038 | Man dial priv call hangtime | Channel name B color | Encryption type | Enable TOT prediction | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c | Enable TX power AGC | Zone name A color | Zone name B color | Auto shutdown mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0040 | |DCC|DSl|DCT| FM idle channel tone enable | Date format | FM mic gain [0,4] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0044 | GPS roaming enable | Unknown setting | Call end tone 1 in Hz, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0048 | Call end tone 2 in Hz, 16bit uint, little endian | Call end tone 3 in Hz, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004c | Call end tone 4 in Hz, 16bit uint, little endian | Call end tone 5 in Hz, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 | Call end tone 1 duration in 10ms, 16bit uint, little endian | Call end tone 2 duration in 10ms, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0054 | Call end tone 3 duration in 10ms, 16bit uint, little endian | Call end tone 4 duration in 10ms, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0058 | Call end tone 5 duration in 10ms, 16bit uint, little endian | All call tone 1 in Hz, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 005c | All call tone 2 in Hz, 16bit uint, little endian | All call tone 3 in Hz, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0060 | All call tone 4 in Hz, 16bit uint, little endian | All call tone 5 in Hz, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0064 | All call tone 1 duration in 10ms, 16bit uint, little endian | All call tone 2 duration in 10ms, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0068 | All call tone 3 duration in 10ms, 16bit uint, little endian | All call tone 4 duration in 10ms, 16bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 006c | All call tone 5 duration in 10ms, 16bit uint, little endian | Unknown settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 01fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Bluetooth settings: - BT PTT sleep interval: 0=Infinite, 1=1min, ..., 4=4min. - STE settings: - STE duration in multiples of 10ms. 0=10ms, ..., 24=250ms (default), ..., 99=1000ms. - Power save settings: - Auto shutdown mode: 0=Auto shutdown timer is reset on call, 1=timer is not reset on call. - Other settings: - Encryption type: 0=AES, 1=DMR standard - DMR settings: - Man dial group/private call hangtime: 0=1s, ..., 29=30s, 30=30min, 31=infinite. - Display settings: - Channel name B; zone name A/B color: 0=Orange, 1=Red, 2=Yellow, 3=Green, 4=Turquoise, 5=Blue, 6=White - DCT: Display channel type - DSl: Display slot - DCC: Display color code - Date format: 0=yyyy/mm/dd, 1=dd/mm/yyyy - GPS settings: - GPS mode: 0=GPS, 1=Beidou, 2=GPS+Beidou, 3=GLONASS, 4=GPS+GLONASS, 5=Beidou+GLONASS, 6=All 3 - Auto repeater settings: - AutoRep VHF/UHF 2 offset index: 0-based, 0xff = off. - Audio settings: - FM mic gain 0=1 (default), ..., 4=5 - Talker alias: - Talker alias priority 0=Off, 1=Contacts, 2=over-the-air - Talker alias encoding: 0=ISO-8, 1=ISO-7, 2=Unicode ================================================ FILE: doc/code/d878uv_gpsmessage.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | GPS Message, up to 32 ASCII chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Unknown settings block ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uv_radioinfo.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused, set to 0x0000 | Enable full test | Frequency range | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Enable international | Unknown, set to 0x00 | Enable band select | Unknown, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Unknown, set to 0x00 | Unknown, set to 0x01 | Unknown, set to 0x01 | Band select password ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... 4 x ASCII, 0-terminated | Unknown, set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Radio type, 7 x ASCII, 0-terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | Unknown, set 0x01 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Unknown, set to 0x00 | Unknown, filled with 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Program password, 4 x ASCII, 0-terminated | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Area code, 4 x ASCII, 0-terminated | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Serial number, 16 x ASCII, 0-terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Production date, 10 x ASCII, 0-terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 ... | Unused, set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Manufacture code, 8 x ASCII, 0-terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | Unused, filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | Maintained date, 16 x ASCII, 0-terminated. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Dealer code, 16 x ASCII, 0-terminated. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | Stock date, 16 x ASCII, 0-terminated. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Sell date, 16 x ASCII, 0-terminated. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | Seller, 16 x ASCII, 0-terminated. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b0 | Maintained note, 128 x ASCII, 0-terminated. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ fc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uv_roamingchannel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | RX Frequency in 10Hz, 32bit 8-digit BCD big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | RX Frequency in 10Hz, 32bit 8-digit BCD big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Color code | Time slot 0=TS1, 1=TS2 | Name 16b ASCII 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 ... | Unused filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uv_roamingzone.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Roaming Channel Index 0 | Roaming Channel Index 1 | Roaming Channel Index 2 | Roaming Channel Index 3 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... Roaming Channel Index 60 | Roaming Channel Index 61 | Roaming Channel Index 62 | Roaming Channel Index 63 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Channel name, 16b ASCII, 0x00 padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | 48 bytes unused, set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/d878uvgpssetting.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Digital channel index 0, 16bit little-endian | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | Digital channel index 7, 16bit little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Destination DMR ID 0, 32bit BCD big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Destination DMR ID 1, 32bit BCD big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Destination DMR ID 7, 32bit BCD big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Calltype channel 0 | Calltype channel 1 | Calltype channel 2 | Calltype channel 3 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Calltype channel 4 | Calltype channel 5 | Calltype channel 6 | Calltype channel 7 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Roaming support | Timeslot channel 0 | Timeslot channel 1 | Timeslot channel 2 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Timeslot channel 3 | Timeslot channel 4 | Timeslot channel 5 | Timeslot channel 6 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Timeslot channel 7 | Repeater activation delay | 30 unused bytes set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/detect_example.cc ================================================ #include "libdmrconf/usbdevice.hh" #include "libdmrconf/anytone_radio.hh" int main(void) { // Example code to detect an AnytoneDevice // First, search matching devices (only AnyTones) QList devices = AnytoneInterface::detect(); if (1 != devices.count()) { // Either none or more than one device found... return -1; } // A place to put error messages ErrorStack err; Radio *radio = AnytoneRadio::detect(devices.first(), RadioInfo(), err); if (nullptr == radio) { // There went something wrong, check err. return -1; } // Read codeplug from device blocking. if (! radio->startDownload(true, err)) { // Some download error, check err. delete radio; return -1; } // Decode codeplug into genericCodeplug Config genericCodeplug; if (! radio->codeplug().decode(&genericCodeplug, err)) { // Some decoding error, check err. delete radio; return -1; } // Do whatever you like with the codeplug. return 0; } ================================================ FILE: doc/code/dm1701_buttonsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused, set to 0 | Side button 1 short press | Side button 1 long press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Side button 2 short press | Side button 2 long press | Side button 3 short press | Siden button 3 long press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | P1 short press | P1 long press | P2 short press | P2 long press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Unused set to 0x00000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Unknown set to 0x01 | Long press dur x250ms | Unused set to 0xffff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | One-touch setting 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | One-touch setting 5 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dm1701_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 |LWK| 1 |SQT|ASC| BandW | ChMod | ColorCode |TimeSlt|RXO|ALT|DCC|PCC| PRIV | PrivIdx |DPD| 1 | 1 0 |EAA| 0 |RXFreqR| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 |AdimtCr|PWR|VOX| 0 |RVB|TXFreqR| Unknown set to 0xc3 | TX Contact name index + 1, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | 0 0 | Tx Timeout | Tx Timeout Rekey Delay | Emergency System index + 1 | Scan List index + 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | RX Group List index + 1 | GPS System index +1 | DTMF decode bitmap | Unused, set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency, 8 digits BCD, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency, 8 digits BCD, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | RX CTCSS/DCS | TX CTCSS/DCS | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Signaling System index +1 | TX Signaling System index +1 | Unused set to 0xff | 1 1 1 1 1 1 |RXG|TXG| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Name 16 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description LWK = Lone Worker, default=0; SQT = Squelch type, 0=tight, 1=normal, default=1; ASC = Auto scan, default=0; BandW = Bandwidth, 0=12.5kHz, 2=25kHz, default = 0; ChMod = Channel Mode, 1=Analog or 2=Digital, default=1; ColorCode = ColorCode [0..15], default=1; TimeSlt = Repeater/time-slot, 2=TS2, 1=TS1, default=1; RXO = RX Only, 0=off, 1=on, default=0; ALT = Allow Talkaround (inverted), default=1 (off); DCC = Data Call Confirmed, default=0; PCC = Private Call Confirmed, default=0; PRIV = Privacy type, 0=None, 1=Basic or 2=Enhanced, default=0 (off); PrivIdx = Privacy Index, [0,15], default=0. DPD = Display PTT ID (inverted), default = 1. EAA = Emergency Alarm Ack, default = 0; RXFreqR = RX reference frequency, 0=Low, 1=Medium, 2=High, default=0 (low); AdimtCr = Admit Criterion, 0=Always, 1=Channel Free or 2=Correct CTS/DCS, 3=Colorcode, default=0. PWR = Specifies the power, 0=low, 1=high. VOX = VOX Enable, 0=Disable, 1=Enable, default = 0; RVB = Reverse burst 0=disable, 1=enable, default=1; TXFreqR = TX reference frequency: 0=Low, 1=Medium, 2=High, default=0; TOffFre = Non-QT/DQT Turn-off Freq., 3=none, 0=259.2Hz, 1=55.2Hz, default=3; InCallC = In Call Criteria, 0=Always, 1=Follow Admit Criteria, 2=TX Interrupt, default=0; Tx Timeout = Tx Timeout x 15sec, 0-Infinite, 1=15s, etc, 37=555s, default=0; Power = Power, 0=low, 1=middle, 2=high, default=high; ALI = Allow interrupt (inverted), 0=allow, 1=Disabled, default=1; RXG = Receive GSP info (inverted), default=1; TXG = Send GSP info (inverted), default=1; ================================================ FILE: doc/code/dm1701_settings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Intro Line 1, 10 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Intro Line 2, 10 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 26 | Reserved 24 bytes, set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | 1 1 1 |MNT| 1 |DAL| 1 0 |TPA|TPD|PWE|CIT| 1 |DAT|SMR|SPR| 1 1 1 |INP| 1 0 1 | 0 |MSB| 1 1 1 1 |MSA| 1 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | DMR ID 24bit, little endian | Unused 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | TX Preamble Duration N x 60ms | Grp Call Hang Time N x 100ms | Prv. Call Hang Time N x 100ms | VOX sensitivity [1..10] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Unused, set 0x00 | Unused, set 0x00 | RX Low Bat. Intv N x 5s | Call Alert Tone Dur N x 5s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Lone Worker Resp. Time in min | Lone Worker Rem. Time in sec | Unused, set to 0x00 | Scan Dig. Hang Time N x 100ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Scan Anal. Hang Time N x 100ms| 0 0 0 0 0 0 |BcLTime| Keypad Lock Time N x 5s | Channel Mode 0xff=on 0x00=off | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | Power-on password 8 x BCD numbers, 0x00000000=default | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | Radio prog. password 8 x BCD numbers, 0x00000000=disabled | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | PC Programming password 8 x ASCII, 0x00 terminated, filled with 0xff=disabled ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | Unused set to 0xffffff | Timezone,0=UTC-12 | 1 |PCM|GCM| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c | Unused set to 0xffffffff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Radio name 16 x 16bit unicode chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Channel hang time N x 100ms | Unused, filled with 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: MNT = Monitor type: 1 = Open Squelch, 0 = Silent, default=1; DAL = Disable all LEDs (inverted); TPD = Talk permit tone digital; TPA = Talk permit tone analog; PWE = Password and lock enable (inverted); CIT = Ch. free indication tone (inverted); DAT = Disable all tones (inverted); SMR = Save Mode Receive; SPR = Save Preamble; INP = Intro Picture; MSB = Mode select B (0=VFO, 1=MR); MSA = Mode select A (0=VFO, 1=MR); BcLTime = Backlight time, 0=Always, t=n*5s; Keypad Lock Time = 0xff=manual otherwise PCM = Private call match; GCM = Group call match; ================================================ FILE: doc/code/dm1701_zoneext.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Member Channel index+1 16 VFO A | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... | Member Channel index+1 63 VFO A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | Member Channel index+1 00 VFO B | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ dc ... | Member Channel index+1 63 VFO B | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dmr6x2uv_aprssetting.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unknown, set to 0x000 | TX frequency as 8 digit BDC, big-endian in 10Hz (unused) ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | FM APRS TX delay in 20ms | Signaling type | CTCSS tone | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | DCS code, little-endian | Manual TX interval in seconds | Auto TX interval in 30s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | FM APRS Monitor enable | Fixed location flag | Latitude degrees | Latitude minutes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Latitude seconds | South flag | Longitude degrees | Longitude minutes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Longitude seconds | West flag | Destination call, 6 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | Destination SSID | Source call, 6 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | Source SSID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Path, 20 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Unused set to 0x00 | ASCII APRS Symbol Table | ASCII APRS Map Icon | Transmit power | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Prewave delay in 10ms | Unknown set to 0x01 | Unknown set to 0x03 | Unknown set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | DMR APRS Sys 0 channel index, uint16, litte-endian | DMR APRS Sys 1 channel index, uint16, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | DMR APRS Sys 2 channel index, uint16, litte-endian | DMR APRS Sys 3 channel index, uint16, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | DMR APRS Sys 4 channel index, uint16, litte-endian | DMR APRS Sys 5 channel index, uint16, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | DMR APRS Sys 6 channel index, uint16, litte-endian | DMR APRS Sys 7 channel index, uint16, litte-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | DMR APRS Sys 0 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | DMR APRS Sys 1 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | DMR APRS Sys 2 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | DMR APRS Sys 3 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | DMR APRS Sys 4 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | DMR APRS Sys 5 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | DMR APRS Sys 6 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c | DMR APRS Sys 7 destination ID, 8-digit BCD, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | DMR APRS Sys 0 call type | DMR APRS Sys 1 call type | DMR APRS Sys 2 call type | DMR APRS Sys 3 call type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 | DMR APRS Sys 4 call type | DMR APRS Sys 5 call type | DMR APRS Sys 6 call type | DMR APRS Sys 7 call type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 78 | Enable roaming support | DMR APRS Sys 0 time slot | DMR APRS Sys 1 time slot | DMR APRS Sys 2 time slot | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c | DMR APRS Sys 3 time slot | DMR APRS Sys 4 time slot | DMR APRS Sys 5 time slot | DMR APRS Sys 6 time slot | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | DMR APRS Sys 7 time slot | DMR APRS pre-wave delay | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - FM APRS TX Frequency 8 digit BCD big endian in 10Hz. - FM APRS TX delay: 0=0ms, 1=20ms, ..., 255=5100ms - Signaling type: 0=off, 1=CTCSS, 2=DCS, default=off - CTCSS Tone: 0= 61.2Hz, 1= 67.0Hz, 2= 69.3Hz, 3= 71.9Hz, 4= 74.4Hz, 5= 77.0Hz, 6= 79.7Hz, 7=82.5Hz, 8= 85.4Hz, 9= 88.5Hz, 10= 91.5Hz, 11= 94.8Hz, 12= 97.4Hz, 13=100.0Hz, 14=103.5Hz, 15=107.2Hz, 16=110.9Hz, 17=114.8Hz, 18=118.8Hz, 19=123.0Hz, 20=127.3Hz, 21=131.8Hz, 22=136.5Hz, 23=141.3Hz, 24=146.2Hz, 25=151.4Hz, 26=156.7Hz, 27=159.8Hz, 28=162.2Hz, 29=165.5Hz, 30=167.9Hz, 31=171.3Hz, 32=173.8Hz. 33=179.9Hz, 34=183.5Hz, 35=186.2Hz, 36=189.9Hz, 37=192.8Hz, 38=196.6Hz, 39=199.5Hz, 40=203.5Hz, 41=206.5Hz, 42=210.7Hz, 43=218.1Hz, 44=225.7Hz, 45=229.1Hz, 46=233.6Hz, 47=241.8Hz, 48=250.3Hz, 49=254.1Hz - Path: 20 x ASCII, 0-padded path string. Format is comma-separated CALL1-SSID,CALL2-SSID,... - DMR APRS Sys N channel index: uint16 channel index, 0-based, little-endian, [0,4000], 0x0fa0=VFO A, 0x0fa1=VFO B, 0x0fa2=Selected - DMR APRS Sys N time slot: 0 = Channel, 1 = Timeslot 1, 2 = Timeslot 2 - Transmit power: 0=low, 1=mid, 2=high, 3=turbo. - FM prewave delay: in multiples of 10ms [0,2550ms], default=0ms. - Manual TX interval: n+1 seconds. [0,255], default=0s - Auto TX interval: 0=Off, 1=30s, ..., 255=7650s, default=off - FM APRS Monitor enable: If enabled, the radio will monitor send FM APRS transmissions. default=off - Rep. activation delay: 0=Off, 1=100ms, ..., 10=1000ms. - APRS display time: 0=3s, 1=4s, ..., 12=15s, 13=infinite, default=3s - RPO: Report position flag. - RME: Report MIC-E flag. - ROB: Report object flag. - RIT: Report item flag. - RMS: Report message flag. - RWX: Report weather flag. - RNF: Report NEMA flag. - RST: Report status flag. - ROF: Report other flag. - FM APRS width: 0=narrow, 1=wide. - Pass all: 0=Off, 1=On, no idea. ================================================ FILE: doc/code/dmr6x2uv_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | RX Frequency 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Tx Frequency Offset 32bit BCD encoded in big-endian as MMMkkkhh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | RMode | 0 |BWd| PWR | CMode |TAr|CaC|RXO|CTR|TDC|TCT|RDC|RCT| CTCSS transmit | CTCSS receive | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | DCS transmit code, little endian | DCS receive code, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Custom CTCSS frequency in 0.1Hz, little endian | 2-tone decode index, 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Contact index 0-based, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Radio ID table index. | 0 |SquelchMode| 0 0 0 0 | 0 0 |OptSig | 0 0 | TxPer | 0 0 0 0 0 |XFR| 0 |RNG| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Group list index | 2-tone ID | 5-tone ID | DTMF ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Color code |LWK|EEE|???|EAT| 0 |EST|SMC|TSL| AES Encryption key | Name 16 x ASCII 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | 0 0 0 0 0 |DAD|ETM| 0 | Unused, set to 0x00 | Scanlist idx 0, 0xff=unset | Scanlist idx 1, 0xff=unset | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Scanlist idx 2, 0xff=unset | Scanlist idx 3, 0xff=unset | Scanlist idx 4, 0xff=unset | Scanlist idx 5, 0xff=unset | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Scanlist idx 6, 0xff=unset | Scanlist idx 7, 0xff=unset | ARPS Report Channel Index | 0 0 |RGP|DAP|APRSPTT|APRSTyp| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - RMode: Repater mode where 0=simplex, 1=positive TX offset, 2=negative TX offset. - BWd: Band width where 0=narrow (12.5kHz), 1=wide (25kHz). - PWR: Power where 0=low, 1=mid, 2=high, 3=turbo. - CMode: Channel mode, 0=analog, 1=digital, 2=analog + digi RX, 3=digital + analog RX. - TAr: Enable talkaround. - CaC: Enable call confirm. - RXO: Enable RX only. - CTR: Enable CTCSS phase reversal. - TDC: Enable TX DCS code. - TCT: Enable TX CTCSS tone. - RDC: Enable RX DCS code. - RCT: Enable RX CTCSS tone. - SquelchMode: Squelch mode 0=Carrier, 1=CTCSS/DCS, 2=Optional Signaling, 3=CTCSS/DCS and Optional Signaling, 4 = CTCSS/DCS or Optional Signaling - OptSig: Optional signalling where 0=off, 1=DTMF, 2=2-tone, 3=5-tone - TxPer: TX permit/admit criterion, 0=always, 1=colorcode, 2=channel free. - XFR: Exclude channel from roaming - LWK: Enable lone worker. - EEE: Enable enhanced encryption - EAT: Enable adaptive TDMA - EST: Enable simplex TDMA - SMC: SMS confirmation, - TSL: Time slot where 0=TS1, 1=TS2 - DAD: Data ACK disable (inverted!) - ETM: Enable through mode - RNG: Ranging - RGP: Enable DMR-APRS RX - DAP: Enable DMR-APRS PTT - APRSPTT: FM APRS PTT mode 0=Off, 1=start of transmission, 2 = end of transmission - ATy: APRS Type: 0=Off, 1=Analog APRS, 2=DMR-APRS ================================================ FILE: doc/code/dmr6x2uv_generalsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Idle channel tone | Display mode | Enable automatic key lock | Automatic shutdown time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Transmit timeout in 30s | Language | Boot display | Enable boot password | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | VFO frequency step | Squelch level VFO A | Squelch level VFO B | Power save mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | VOX sensitivity | VOX delay in 100+500*n ms | VFO scan type | MIC gain | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | PF1 short press function | PF2 short press function | PF3 short press function | P1 short press function | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | P2 short press function | Work mode B | Work mode A | STE Type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | STE freq. no signal | Group call hang time in sec | Private call hang time in sec | Prewave time in 20ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | Wake head period in 20ms | WFM channel index | WFM VFO Mode enable | Work mode MEM zone A idx | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Work mode MEM zone B | Unused set to 0x00 | Enable recording | DTMF duration | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Man down enable | Unused set to 0x00 | Display brightness | Backlight duration | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Enable GPS | Enable SMS alert | Unused set to 0x00 | WFM monitor enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Work mode main channel | Enable sub channel | TBST frequency | Enable call alert | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | GPS Time zone | Talk permit tone | Digital call reset tone | VOX source | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Enable pro mode | Unused set to 0x00 | Enable key tone | Menu exit time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | Filter own ID enable | Startup tone | Enable call end prompt | Max speaker volume | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Remote stun/kill enable | Unused set to 0x00 | Remote monitor enable | GPS RX positions | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Enable select TX contact | PF1 long press function | PF2 long press function | PF3 long press function | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | P1 long press function | P2 long press function | Long press duration | Enable Volume change prompt | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | Auto repeater A direction | Digital monitor slot | Digital monitor color code | Digital monitor match ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Digital monitor hold slot | Display last caller | Unknown | Man down delay in sec | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Analog call hold in seconds | Enable display clock | Max headphone volume | Enable GPS range message | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Unknown settings | Enable enhanced audio | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | VFO Scan UHF minimum frequency in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | VFO Scan UHF maximum frequency in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | VFO Scan VHF minimum frequency in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | VFO Scan VHF maximum frequency in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | Autorep. off. idx UHF, ffh=off| Autorep. off. idx VHF, ffh=off| Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c ... | Enable maintain call channel | Priority zone A idx, 0xff=off | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Priority zone B idx, 0xff=off | Enable SMS confirmation | Call tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 | Call tone frequency 2 in Hz, little endian | Call tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 78 | Call tone frequency 4 in Hz, little endian | Call tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c | Call tone duration 1 in ms, little endian | Call tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | Call tone duration 3 in ms, little endian | Call tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 84 | Call tone duration 5 in ms, little endian | Idle tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 88 | Idle tone frequency 2 in Hz, little endian | Idle tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c | Idle tone frequency 4 in Hz, little endian | Idle tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Idle tone duration 1 in ms, little endian | Idle tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 94 | Idle tone duration 3 in ms, little endian | Idle tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 98 | Idle tone duration 5 in ms, little endian | Reset tone frequency 1 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c | Reset tone frequency 2 in Hz, little endian | Reset tone frequency 3 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | Reset tone frequency 4 in Hz, little endian | Reset tone frequency 5 in Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a4 | Reset tone duration 1 in ms, little endian | Reset tone duration 2 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a8 | Reset tone duration 3 in ms, little endian | Reset tone duration 4 in ms, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac | Reset tone duration 5 in ms, little endian | Record delay in 200ms | Call display mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b0 | Call-sign display color | Enable simplex repeater | GPS ranging interv. in sec. | Enable speaker in simpl. rep. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b4 | Show current contact | Key sound adjustable | 0 0 0 0 |KNL|KBL|SKL|PKL| SimplRepSlot 0=TS1 1=TS2 2=CH | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b8 | Show last call on startup | SMS format | GPS units | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ bc | Auto repeater lower VHF frequency, in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c0 | Auto repeater upper VHF frequency, in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c4 | Auto repeater lower UHF frequency, in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c8 | Auto repeater upper UHF frequency, in 10Hz, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ cc | Auto repeater B direction | AddrBk is sent with own code | Enable default boot channel | Boot zone A idx | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d0 | Boot zone B idx | Boot ch A idx, 0xff=VFO | Boot ch B idx, 0xff=VFO | Enable keep last caller | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d4 | RX Backlight delay | Display mode | ManDial GrpCall hgn time sec. | ManDial PrvCall hgn time sec. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d8 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ dc | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Display mode: 0x00=Channel, 0x01=Frequency - VFO frequency step: 0x00=2.5kHz, 0x01=5kHz, 0x02=6.25kHz, 0x03=10kHz, 0x04=12.5kHz, 0x05=20kHz, 0x06=25kHz, 0x07=30kHz, 0x08=50kHz - Squelch level: 0x00=Off, 0x01=1, ..., 0x05=5 - Automatic shutdown time: 0x00=Off, 0x01=10min, 0x02=30min, 0x03=60min, 0x04=120min - Power save mode: 0x00=Off, 0x01=1:1, 0x02=2:1 - VOX sensitivity: 0x00=Off, 0x01=1, ..., 0x03=3 - Work mode A/B: 0x00=VFO, 0x01=MEM - Work mode main channel: 0x00=A, 0x01=B - TBST frequency: 0x00=1000Hz, 0x01=1450Hz, 0x02=1750Hz, 0x03=2100Hz - GPS Time zone: 0x00=GMT-12, ..., 0x0c=GMT, 0x19=GMT+13 - Boot display: 0x00=Default, 0x01=Custom Text, 0x02=Custom Image - VFO scan type: 0x00=TO, 0x01=CO, 0x02=SE - Key press function: 0x00=Off, 0x01=Voltage, 0x02=Power, 0x03=Repeater, 0x04=Reverse, 0x05=Digital Encryption, 0x06=Call, 0x07=VOX, 0x08=V/M, 0x09=Sub PTT, 0x0a=Scan, 0x0b=FM, 0x0c=Alarm, 0x0d=Record Switch, 0x0e=Record, 0x0f=SMS, 0x10=Dial, 0x11=GPS Information, 0x12=Monitor, 0x13=Main Ch. switch, 0x14=HotKey 1, ..., 0x19=HotKey 6, 0x1a=Work Alone, 0x1b=Nuisance Delete, 0x1c=Digital Monitor, 0x1d=Sub Ch Switch, 0x1e=Priority Zone, 0x1f=Programming Scan, 0x20=MIC sound quality, 0x21=Last call reply, 0x22=Channel type switch, 0x23=Simplex repeater, 0x24=Max. volume, 0x25=Ranging, 0x26=Channel ranging, 0x27=Slot switch, 0x28=Analog squelch, 0x29=Roaming, 0x2a=Zone select, 0x2b=Roaming settings, 0x2c=FixTime Mute, 0x2d=CTCSS/DCS settings, 0x2e=APRS type, 0x2f=APRS settings - Talk permit tone: 0x00=Off, 0x01=Digital, 0x02=Analog, 0x03=Both - STE Type: 0x00=Off, 0x01=Silent, 0x02=120deg, 0x03=180deg, 0x04=240deg - STE freq. no signal: 0x00=Off, 0x01=55.2Hz, 0x02=259.2Hz - VOX source: 0x00=Internal, 0x01=External, 0x02=Both - Display brightness: 0x00=1, ..., 0x04=5 - Backlight duration: 0x00=Always, 0x01=5s, ..., 0x06=30s, 0x07=1m, ..., 0x0a=5m - MIC gain: 0x00=1, ..., 0x04=5 - Max speaker/headphone volume: 0x00=Indoors, 0x01=1, ..., 0x08=8 - Menu exit time: (n+1)*5s - Auto repeater A/B direction: 0x00=Off, 0x01=positive, 0x02=negative - Digital monitor slot: 0x00=Off, 0x01=single, 0x02=both´ - Display last caller: 0x00=Off, 0x01=Show ID, 0x02=Show name, 0x03=Both - Call display mode: 0x00=Name based, 0x01=Call-sign based - Call-sign display color: 0x00=orange, 0x01=red, 0x02=yellow, 0x03=green, 0x04=turquoise, 0x05=blue, 0x06=white, 0x07=black - Enable keep last caller: A channel switch keeps laster caller. - RX Backlight delay: 0x00=Always, 0x01=1s, ..., 0x1e=30s. - Display mode: 0x00=black background, 0x01=blue background - Key sound adjustable: 0x00=Adjustable, 0x01=1, ..., 0x0f=15 - GPS units: 0x00=metric, 0x01=imperial - KNL: Knob lock enable - KBL: Keyboard lock enable - SKL: Side key lock enable - PKL: Professional key lock enable - SMS format: 0x00=M-format, 0x01=H-format, 0x02=DMR standard ================================================ FILE: doc/code/dmr6x2uv_settingsextension.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Enable send talker alias | Talker alias display priority | Talker alias encoding | Font Color | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Enable custom ch. background | Roaming zone index 0-based | Enable auto roaming | Enable repeater check | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Out of repeater range alert | Rep. out of range reminder | Repeater check interval | Repeater reconnections | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Start roaming condition | Auto roaming interval | Roaming eff. waiting time | Roaming return condition | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Mute timer (n+1) min | Encryption type | Zone A name color | Zone B name color | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Channel A name color | Channel B name color | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: - Talker alias display priority: 0x00=None, 0x01=Contact alias, 0x02=Over-the-air alias - Talker alias encoding: 0x00=ISO-8, 0x01=ISO-7, 0x02=Unicode - Font Color: 0x00=White, 0x01=Black, 0x02=Orange, 0x03=Red, 0x04=Yellow, 0x05=Green, 0x06=Turquoise, 0x07=Blue - Encryption type: 0x00=Common(Basic), 0x01=AES - Zone/Channel A/B name color: 0x00=Orange, 0x01=Red, 0x02=Yellow, 0x03=Green, 0x04=Turquoise, 0x05=Blue, 0x06=White, 0x07=Black - Out of repeater range alert: 0x00=Off, 0x01=Bell, 0x02=Voice - Rep. out of range reminder: Number of times, the reminder is shown (n-1), 0x00=1, 0x01=2, ..., 0x09=10 - Repeater check interval (n+1)*5 seconds: 0x00=5s, ..., 0x09=50s - Repeater reconnections: 0x00=3, 0x01=4, 0x03=5 - Start roaming condition: 0x00=fixed time, 0x01=out of range - Auto roaming interval: 0x00=1min, ..., 0xff=256min - Roaming eff. waiting time: 0x00=off, 0x01=1s, ..., 0x1e=30s - Roaming return condition: 0x00=fixed time, 0x01=out of range ================================================ FILE: doc/code/dr1801uv_alarmsystembankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Alarm system count [0-8] | Unused filled with 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Alarm system 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c | Alarm system 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00ac | Alarm system 7 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00c0 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_alarmsystemelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Index+1,0=invalid | Alarm enable | Unknown 0x0001 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Channel index, uint16 le, 0=None, 0xffff=selected | Unknown 0x0000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | System name, 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_channelbankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000000 | Number of channels | Unused filled with 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000004 | Channel 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000034 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000038 | Channel 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000068 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00cfd0 | Channel 1023 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00d000 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00d004 | Channel 0 name, 20 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00d014 ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00d018 | Channel 1 name, 20 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00d028 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 11ff00 | Channel 1023 name, 20 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 120000 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_channelelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Index, uint16, little endian, 0xffff invalid | Type, 0x01=FM, 0x03=DMR | Power 0x00=low, 0x01=high | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | RX frequency in Hz, uint32, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | TX frequency in Hz, uint32, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Contact index+1, uint16, little endian, 0=none | Admit criterion | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Color code | Time-slot, 1=TS1, 2=TS2 | 0x00 | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Encryption key index+1, 0=off | 0 0 0 0 0 0 |DCD|PCC| FM signaling | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Alarm sys index+1, 0=none | Bandwidth 0x01=12.5, 0x02=25 | Auto-scan enable | Scan list index+1, 0=none | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX CTCSS Freq in 0.1Hz/DCS code | RX sub-tone type | RX DCS inverted | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | TX CTCSS Freq in 0.1Hz/DCS code | TX sub-tone type | TX DCS inverted | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | 0x00 |TKA| 0 0 0 0 0 0 0 | Unused set to 0x0000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | PTT ID index+1, 0=none | | Group List index+1, 0=None | Unused filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | Lone worker 0x00=off, 0x01=on | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Unused, filled with 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Admit criterion: 0x00=Always, 0x01 = Same CC/CTCSS, 0x02 = Channel Free - DCD: DCDM enable - PCC: Private call confirm - Sub-tone type: 0=None, 1=CTCSS, 2=DCS - FM Signaling: 0=none, 1=DTMF - TKA: Enable talk around. ================================================ FILE: doc/code/dr1801uv_contactbankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Number of elements, uint16, little endian | Index+1 of first element, uint16, little endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Contact 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c | Contact 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5fec | Contact 1023 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6000 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_contactelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Next index+1, uint16, little endian | Name length | 0x02 or 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | DMR ID, uint24, little endian |GRP|PVC|ALC| 0 0 0 0 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Name, 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - GRP: Group call - PVC: Private call - ALC: All call ================================================ FILE: doc/code/dr1801uv_dmrsettingselement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Hold time in sec [1-90] | Remote listen in sec [10-120] | Active wait, 5ms [120-600] | Active resent count [1-10] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Pre-send count [4-15] | Kill enc. enabled | Active enc. enabled | Check enc. enabled | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Kill dec. enabled | Active dec. enabled | Check dec. enabled | SMS format | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Voice head count [0-20] | 0 0 0 0 |RMD|RME|CAD|CAE| Unused set 0x0000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - SMS format: 0 = Compressed IP, 1 = Defined Data, 2 = IP Data - CAD: Call alter decoding - CAE: Call alter encoding - RME: Remote monitor encoding - RME: Remote monitor decoding ================================================ FILE: doc/code/dr1801uv_dtmfidbankelement.txt ================================================ DTMF ID Bank: 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Number of IDs (fixed 16) | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | DTMF ID 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 | DTMF ID 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0130 | DTMF ID 15 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0140 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_dtmfidelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Length | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Code, 16 x one DTMF number per byte, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_dtmfsettingselement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | DTMF radio ID, 5 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 ... | Radio ID Length | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | DTMF kill code, 6 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | Kill code length | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | DTMF wake code, 6 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 ... | Wake code length | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 | Delimiter | Group code | Decode response | Auto reset time in sec. [5,60]| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c | Kill/wake enable | Kill type | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | DTMF system bank ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0054 | DTMF ID bank ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0194 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0198 | PTT ID bank ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0298 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Delimiter/Group code: 0h=None, ah=A, bh=B, ch=C, dh=D, eh=*, fh=# - Decode response: 0=None, 1=Reminder, 2=Reply, 3=Both - Kill type: 0=Disable TX, 1=Disable TX & RX ================================================ FILE: doc/code/dr1801uv_dtmfsystembankelement.txt ================================================ DTMF System Bank: 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DTMF system count (=4) | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | DTMF system 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | DTMF system 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | DTMF system 3 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_dtmfsystemelement.txt ================================================ DTMF System: 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Side tone enable | Unused, set to 0x00 | Pre time in ms, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Code duration in ms, uint16, little endian | Code Interval in ms, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Reset time in 0.1s, uint16, little endian | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_encryptionkeybankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Encryption key 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | Encryption key 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 006c | Encryption key 9 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0074 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_encryptionkeyelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Index + 1 | Key length, uint16, little endian. Fixed to 0x08 | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Key, 8 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_grouplistbankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Group list count | Unused, filled with 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Group list 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0044 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0048 | Group list 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0088 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10c0 | Group list 63 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1100 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_grouplistelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Number of members, uint16, little endian | Group list index+1, uint16, little endian, 0=invalid | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Member index0 +1, uint16, little endian | Member index1 +1, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Member index8 +1, uint16, little endian | Member index9 +1, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Unused, filled with 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_keysettingselement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Key 0 Index | Key 0, Short press function | Key 0, Long press function | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Key 1 Index | Key 1, Short press function | Key 1, Long press function | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Key 2 Index | Key 2, Short press function | Key 2, Long press function | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Unused or further key settings. Filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Key index: SideKey1 = 0, SideKey2 = 1, TopKey = 2 - Key function: None = 0, ToggleAlertTones = 1, EmergencyOn = 2, EmergencyOff = 3, TogglePower = 4, Monitor = 5, DeleteNuisance = 6, OneTouch1 = 7, OneTouch2 = 8, OneTouch3 = 9, OneTouch4 = 10, OneTouch5 = 11, ToggleTalkaround = 13, ToggleScan = 14, ToggleEncryption = 15, ToggleVOX = 16, ZoneSelect = 17, ToggleLoneWorker = 19, PhoneExit = 20 ================================================ FILE: doc/code/dr1801uv_messagebankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Number of messages, uint32, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Message 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0044 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0048 | Message 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0058 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0138 | Message 7 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0160 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_messageelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Index+1, 0=invalid | Length | 0x00 | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Message, 64 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_onetouchsettingelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Contact index+1, uint16, little endian | Action | Message index + 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Type | DTMF ID index+1 | Unused set to 0x0000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Action: 0=Call, 1=Message - Type: 0=Disabled, 1=DMR, 2=Analog ================================================ FILE: doc/code/dr1801uv_onetouchsettingselement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | One-touch setting 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | One-touch setting 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | One-touch setting 4 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_pttidbankelement.txt ================================================ PTT ID Bank: 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Number of PTT IDs (fixed 20h) | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | PTT ID 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | PTT ID 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00fc | PTT ID 31 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0100 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_pttidelement.txt ================================================ PTT ID: 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DTMF system index+1, 0=none | TX mode | ID mode | Start ID index+1, 0=none | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | End ID index+1, 0=none | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - TX mode: 0=None, 1=Pre, 2=Post, 3=Both - ID mode: 0=Forbidden, 1=Each, 2=Once ================================================ FILE: doc/code/dr1801uv_scanlistbankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Number of elements | Unused, filled with 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Scan list 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0054 | Scan list 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00a0 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 02d4 | Scan list 9 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0320 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_scanlistelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Index+1, 0=invalid | Number of entries | Priority channel 1 | Priority channel 2 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Priority channel 1 index, uint16, little endian | Priority channel 2 index, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Revert channel | 0x00 | Revert channel index, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Unused, filled with 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Name, 32 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Entry channel index 0, uint16, little endian | Entry channel index 1, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Entry channel index 14, uint16, little endian | Entry channel index 15, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Priority channel 1/2: 0=None, 1=Fixed, 2=Selected - Revert channel: 0 = Last active, 1 = Fixed, 2 = Selected ================================================ FILE: doc/code/dr1801uv_settingselement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DMR ID, uint24, little endian | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | 0x00 | 0x00 | 0x01 | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Power save enable | Power save mode | VOX level 1-3 | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | VOX delay, in 500ms | Encryption enable | Key-lock delay in sec. [1,90] | 0 0 0 0 0 |LS2|LS1|LPT| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Language | Squelch mode | 0x00 | Roger tone enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | 0x00 | 0x01 | Ring tone [1,20], 0=off | Key lock enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Radio name, 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | 0 |MST|DCE|AVE|DVE|ACO|DCO| 1 | 0x00 | 0x00 | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Reverse burst frequency 0.1 Hz, 0=off | 0x01 | Back-light time | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | VOX enable | 0x00 | Campanding (?) enable | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | 0x00 | 0x00 | Up channel mode | Down channel mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | 0x00 | 0x00 | 0x00 | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Channel display | Dual stand-by mode | Scan mode | Boot screen | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Boot line 1, 8 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | Boot line 2, 8 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | LED enable | Lone worker response time 1s | 0 0 0 0 0 0 |BPE|PPE| Prog. password length | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Programming password, 6 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 ... | 0x00 | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | Lone worker reminder time 1s | Boot password length | Boot password, 6 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Power save mode: 0x01 = 50%, 0x02 = 25%, 0x03 = 12% - LS1: Lock side-key 1 - LS2: Lock side-key 2 - LPT: Lock PTT - Language: 0 = simplified Chinese, 1 = English - DCO: Digital call out roger tone - DVE: Digital voice end roger tone - DCE: Digital call end roger tone - ACO: Analog call out roger tone - AVE: Analog voice end roger tone - MST: Message tone - Back-light time: 0 = Infinite, 1 = Off, 2 = 5s, 3 = 10s - Squelch Mode: 0x00 = Normal, 0x01 = Silent - Up/down channel mode: 0=channel, 1=VFO - Channel display: 0 = Number, 1 = Name, 2 = Frequency - Dual stand-by mode: 0=off, 1 = double double, 2 = double single - Scan mode: 0=time, 1=carrier, 2=search - Boot screen: 0 = Picture, 1 = Text - BPE: Boot password enable - PPE: Programming password enable ================================================ FILE: doc/code/dr1801uv_vfobankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000000 | VFO A ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000030 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000034 | VFO B ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000064 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000068 | VFO A name, 20 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000078 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00007c | VFO B name, 20 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00008c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_zonebankelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Zone count, uint16, little endian | Up current zone index, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Down current zone index, uint16_t, little endian | Unused, set to 0x0000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Zone 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 006c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0070 | Zone 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00d4 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c90 | Zone 149 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3cf4 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/dr1801uv_zoneelement.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Zone name, 32 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Name length, uint16, little endian | Number of entries, uint16, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Zone index, uint16, little endian | 0x00 | 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Member index 00, uint16, little endian, 0-based | Member index 01, uint16, little endian, 0-based | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | Member index 30, uint16, little endian, 0-based | Member index 31, uint16, little endian, 0-based | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_channel_bank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00000 | Channel count | Channel 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00044 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00048 | Channel 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 117bc | Channel 1023 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 11800 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_channel_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 x 16bit unicode, little endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Bandwidth | Scan list index+1 | Channel type | Enable talkaround | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Enable RX only | Unknown 0x01 | Enable scan auto-start | RX frequency ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 ... | TX frequency ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | DTMF PTT setting index+1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Power | Admit criterion | Unknown 0x00 | Unknown 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | RX tone mode | RX CTCSS frequency | RX DCS code | TX tone mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 | TX CTCSS frequency | TX DCS code | Unknown 0x00 | Unknown 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Timeslot | Color code [0-15] | Group list index+2 | Unknown 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | TX contact index+1, 16bit unsigned int, little endian | Emergency system index+1 | Unknown 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | Encryption key index+1 | Unknown 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Bandwidth: 0=12.5kHz, 1=25kHz - Channel type: 0=FM, 1=DMR - RX/TX frequency in Hz, 32bit unsigned int, little endian - Power: 0=Low, 1=High - Admit criterion: 0=Always, 1=Color code/CTCSS, 2=Free - RX/TX tone mode: 0=None, 1=CTCSS, 2=DCS, 3=DCS inverted - RX/TX CTCSS frequency: 62.5Hz, 67.0Hz, 69.3Hz, 71.9Hz, 74.4Hz, 77.0Hz, 79.7Hz, 82.5Hz, 85.4Hz, 88.5Hz, 91.5Hz, 94.8Hz, 97.4Hz, 100.0Hz, 103.5Hz, 107.2Hz, 110.9Hz, 114.8Hz, 118.8Hz, 123.0Hz, 127.3Hz, 131.8Hz, 136.5Hz, 141.3Hz, 146.2Hz, 151.4Hz, 156.7Hz, 159.8Hz, 162.2Hz, 165.5Hz, 167.9Hz, 171.3Hz, 173.8Hz, 177.3Hz, 179.9Hz, 183.5Hz, 186.2Hz, 189.9Hz, 192.8Hz, 196.6Hz, 199.5Hz, 203.5Hz, 206.5Hz, 210.7Hz, 218.1Hz, 225.7Hz, 229.1Hz, 233.6Hz, 241.8Hz, 250.3Hz, 254.1Hz - RX/TX DCS code: 23, 25, 26, 31, 32, 36, 43, 47, 51, 53, 54, 65, 71, 72, 73, 74, 114, 115, 116, 122, 125, 131, 132, 134, 143, 145, 152, 155, 156, 162, 165, 172, 174, 205, 212, 223, 225, 226, 243, 244, 245, 246, 251, 252, 255, 261, 263, 265, 266, 271, 274, 306, 311, 315, 325, 331, 332, 343, 346, 351, 356, 364, 365, 371, 411, 412, 413, 423, 431, 432, 445, 446, 452, 454, 455, 462, 464, 465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606, 612, 624, 627, 631, 632, 645, 654, 662, 703, 712, 723, 731, 732, 734, 743, 754 - Timeslot: 0=TS1, 1=TS2, 2=DCDM-TS1, 3=DCDM-TS2 - Group list index: 0=current, 1=no-match, index+2 otherwise. ================================================ FILE: doc/code/gd73_contact_bank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Contact count, 16bit unsigned int, little endian | Unused, filled 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0800 ... | Contact 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0824 ... | Contact 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9bbc ... | Contact 1023 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c00 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_contact_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 x 16bit unicode, little endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Type | DMR ID, 32bit unsigned int, little endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+ Where: - Type: 0=Group Call, 1=Private Call, 2=All Call ================================================ FILE: doc/code/gd73_dmr_settings_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Call hangtime in sec-1 [1,90] | Active wait time | Active retries | TX preambles | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Enable dec. disable-radio | Enable dec. radio-check | Enable dec. enable-radio | Unknown, filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Call hang time in seconds, stored -1s, i.e., 0=1s, 1=2s, ..., 10=11s (default), ..., 89=90s - Active wait time (WTF): 0=120ms, 1=125ms, ..., 36=300ms (default), ..., 96=600ms - Active retries (WTF) [1,10], 2=default - TX preambles (WTF) [0,63], 0=default ================================================ FILE: doc/code/gd73_dtmf_code_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Length | Digit 0 | Digit 1 | Digit 2 | Digit 3 | Digit 4 | Digit 5 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Digit 6 | Digit 7 | Digit 8 | Digit 9 | Digit 10 | Digit 11 | Digit 12 | Digit 13 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Digit 14 | Digit 15 | +---+---+---+---+---+---+---+---+ Where: - Length: Specifies the number of DTMF digits. - digits: 0=0, ..., 9=9, 10=a, ..., 13=d, 14=*, 15=# ================================================ FILE: doc/code/gd73_dtmf_ptt_settings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DTMF system index | PTT ID type | PTT ID mode | Connect ID index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Disconnect ID index | +---+---+---+---+---+---+---+---+ Where: - DTFM system index: 0-based [0,3] - PTT ID type: 0=None, 1=Pre-only (default), 2=Post-only, 3=Both - PTT ID mode: 0=Never, 1=Always, 2=Once - (Dis-)Connect ID: 0-based [0,15] ================================================ FILE: doc/code/gd73_dtmf_system_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Side-tone enable | Preamble duration | Tone duration | Pause duration | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Dead time | +---+---+---+---+---+---+---+---+ Where: - Preamble duration: multiples of 10ms: 0=0ms, ..., 40=400ms (default), ..., 100=1000ms. - Tone/pause duration: multiples of 10ms: 0=30ms (default), 1=40ms, ..., 187=1900ms. - Dead time: multiples of 200ms: 0=0.2s, 1=0.4, ..., 10=2.2s, ..., 164=33.0s. ================================================ FILE: doc/code/gd73_encryption_key_bank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Key 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 ... | Key 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0048 ... | Key 15 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_encryption_key_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Key size (in nibble) | Key data +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | +---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_group_list_bank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Group list count | Group list 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0054 | Group list 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00a4 ... | Group list 2 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50bc | Group list 249 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 510c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_group_list_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 8 x 16 bit unicode, little endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Member count | Member 0 index+1, 16bit unsigned integer, little endian | Member 1 index+1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 ... | Member 32 index+1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_message_bank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Message count | Message 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 ... | Message 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00a0 | Message 2 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04c0 | Message 15 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0510 ... | +---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_message_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Message size (chars) | Message text, 40 x 16bit unicode, little endian ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 ... | +---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_one_touch_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused/unknown 0x00 | DMR contact index+1, little endian, 0=None | One-touch action | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Message index+1, 0=None | +---+---+---+---+---+---+---+---+ Where: - One-touch action: 0=Call, 1=Message ================================================ FILE: doc/code/gd73_scan_list_bank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Scan list count | Unknown 0x01 | Unknown, filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 ... | Scan list 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 006c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 05a0 ... | Scan list 15 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0600 ... | +---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_scan_list_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 8 x 16bit unicode, little endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Channel count | Channel 0, index+1, little endian | Channel 1, index+1, ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 ... | Priority channel 1 mode | Priority channel 2 mode | Pri. ch. 1, zone index+1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Pri. ch. 2, zone index+1 | Pri. ch. 1, channel index+1, little endian | Pri. ch. 2, channel index+1... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 ... little endian | TX ch. mode | TX ch. zone index +1 | TX ch. channel index+1, ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... little endian | Hold time in 0.5s, 0..10s | TX Hold time in 0.5s, 0..5s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Priority channel 1/2 mode: 0=None, 1=Fixed, 2=Selected ================================================ FILE: doc/code/gd73_settings_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Radio name, 16 x Unicode, little-endian, filled/padded with 0x0000 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | DMR ID, 24bit unsigned int little endian | Pad byte 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Language | Unknown 0x00 | Vox level [0,4] | Squelch level [0,9] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | TOT | TX Interrupt enable | Power-save enable | Power save timeout | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Read lock enable | Write lock enable | Unknown 0x00 | Display mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | Read lock pin, 6xASCII numbers [0x30,0x39], 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 ... | Write lock pin, 6xASCII numbers [0x30,0x39], 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 38 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c | Unknown, 0x01 | DMR mic gain [0,5] | Unknown 0x01 | FM mic gain [0,5] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | Lone worker resp. timeout in minutes [1, 480], little endian | Lone worker reminder period | Boot display mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | Boot text line 1, 16 x 16bit Unicode, little endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 | Boot text line 2, 16 x 16bit Unicode, little endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 84 | Enable key tones | Key tone volume [0,13] | Low battery tone enable | Low battery tone vol. [0,13] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 88 | Long press duration | Unknown | P1 short press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c | P1 long press | P2 short press | P2 long press | Unused 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Unused 0x00 | One-touch 1 settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 94 ... | One-touch 2 settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 98 ... | One-touch 3 settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | One-touch 4 settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a4 ... | One-touch 5 settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a8 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Where: - Language: 0x00=Chinese, 0x01=English - TOT: Transmit timeout, 0=off, ..., 16=180, ..., 48=500s, N = (T-20)/10 - Display mode: 0=Channel Name, 1 = Channel Frequency - Power-save timeout: Timeout in seconds [10, 60] - Lone worker reminder period: in seconds [10, 200] - Boot display mode: 0=Off, 1=Text, 2=Image, 3=both - Long press duration: In multiple of 0.5s: 0=0s, 1=0.5s, ..., 31=15.5s - P1/2 short/long press: 0=None, 1=Radio enable, 2=Radio check, 3=Radio disable, 4=Power level, 5=Monitor, 6=Emergency on, 7=Emergency Off, 8=Zone switch, 9=Scan toggle, a=VOX toggle, b=One touch 1, ..., f=One Touch 5, 10=Talkaround toggle, 11=Lone worker, 12=TBST 1750Hz, 13=Call/Swell ================================================ FILE: doc/code/gd73_timestamp.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Frequency range | Year/100 | Year % 100 | Month | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Day | Hour | Minute | Second | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Unknown 0x05 | Unused, filled with 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | Serial number, up to 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | Model name, up to 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | Device ID, up to 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 ... | Model number, up to 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 ... | Software version, up to 16 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 ... | +---+---+---+---+---+---+---+---+ Where - Frequency range: 0 = 406.1-470.0MHz, 1=446.000-446.995MHz, 2=400.0-470.0MHz ================================================ FILE: doc/code/gd73_zone_bank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Zone count | Zone 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 ... | Zone 1 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0060 ... | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c10 | Zone 63 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c40 ... | +---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd73_zone_element.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | 8 x 16bit Unicode, little endian, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Channel count | Channel index+1 00, 16bit unsigned int, little endian | Channel index+1 01 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 ... | Channel index+1 02 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | Channel index+1 14 | Channel index+1, 15 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 ... | +---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd77_callsign_db_entry.txt ================================================ 0 8 +---+---+---+---+---+---+---+---+---+---+---+---+ | DMR ID, 8xBCD | Name, 8xASCII, 0-terminated | +---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd77_callsign_db_header.txt ================================================ 0 8 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... + |'I' 'D' '-' 'V' '0' '0' '1' 0 | # entries, LE | Entries ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... + ================================================ FILE: doc/code/gd77_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency, 8 x BCD digits, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency, 8 x BCD digits, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Mode (Analog/Digital) | Unused, set to 0x00 | Transmit timeout | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | Transmit timeout rekey | Admit criterion | Unknown, set to 0x50 | Scan list index (+1) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | CTCSS/DCS RX | CTCSS/DCS TX | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Unused, set to 0x00 | TX DTMF system index (+1) | Unused set to 0x00 | RX DTMF system index (+1) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Unused, set to 0x00 | Privacy group index | TX color-code | RX group list index (+1) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | RX color code | Emergency system index (+1) | Contact index (+1), little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 |DCC|EAA| 0 0 0 0 | ARTS | 0 |SLT| 0 |PRV| 0 0 0 |PCC| STE |NFS| 0 | PTTId | 0 |DCD|PWR|VOX|ASE|LWK|TLK|RXO|BW |SQT| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Unused set to 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ DCC = Data call confirm, EAA = Emergency Alarm ACK, SLT = Repeater slot, PRV = Privacy Enable, PCC = Private call confirm, NSF = Non-STE Frequency, DCD = Direct call dual capacity mode, PWR = Power, ASE = Autoscan enable, LWK = Lone worker enable, TLK = Talk around enable, RXO = RX only, BW = Band width, SQT = Squelch type ================================================ FILE: doc/code/gd77_contact.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Contact DMR ID 8 BCD digits, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Type 0=Grp, 1=Priv, 2 = All | RX tone 0=off, 1=on | Ring style [0..10] | Valid if 0xff, 0x00 otherwise | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd77_grouplist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Member 00 index (0=disabled), little-endian | Member 01 index, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Member 1e index, little-endian | Member 1f index, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd77_grouplistbank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | # Members GL 0 (N+1) | # Members GL 1 | # Members GL 2 | # Members GL 3 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0048 | # Members GL 48 | # Members GL 49 | # Members GL 4a | # Members GL 4e | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004c | Unused filled with 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 007c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0080 | RX Group List 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00cc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 17f0 | RX Group List 7e ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 183c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/gd77_scanlist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 15 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... |TKB| PLT |ChM| 0 0 0 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Channel 00 index +1, 0=EOL, little-endian | Channel 01 index +1, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004c | Channel 30 index +1, little-endian | Channel 31 index +1, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 | Priority Channel 1, index+2, 0=None, 1=Current, little-endian | Priority Channel 2, index+2, 0=None, 1=Current, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0054 | TX Channel, index+2, 0=last active, 1=Current, little-endian | Sig. hold time N x 25ms | Pri. sample time N x 250ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where ChM = Channel mark; PLT = PL type; TKB = Talkback ================================================ FILE: doc/code/gd77_scanlistbank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | List 00 enable (0=disabled) | List 01 enable | List 2 enable | List 3 enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c | List 3c enable | List 3d enable | List 3e enable | List 3f enable | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0040 | Scanlist 00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0094 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 15e4 | Scanlist 3f ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 163c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/md390_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 |LWK| 1 |SQT|ASC| BandW | ChMod | ColorCode |TimeSlt|RXO|ALT|DCC|PCC| PRIV | PrivIdx |DPD|CUH| 1 0 |EAA| 0 |RXFreqR| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 |AdimtCr|PWR|VOX| 0 |RVB|TXFreqR| Unknown set to 0xc3 | TX Contact name index + 1, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | 0 0 | Tx Timeout | Tx Timeout Rekey Delay | Emergency System index + 1 | Scan List index + 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | RX Group List index + 1 | GPS System index +1 | DTMF decode bitmap | Unused, set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency, 8 digits BCD, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency, 8 digits BCD, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | RX CTCSS/DCS | TX CTCSS/DCS | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Signaling System index +1 | TX Signaling System index +1 | Unused set to 0xff | 1 1 1 1 1 1 |RXG|TXG| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Name 16 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description LWK = Lone Worker, default=0; SQT = Squelch type, 0=tight, 1=normal, default=1; ASC = Auto scan, default=0; BandW = Bandwidth, 0=12.5kHz, 2=25kHz, default = 0; ChMod = Channel Mode, 1=Analog or 2=Digital, default=1; ColorCode = ColorCode [0..15], default=1; TimeSlt = Repeater/time-slot, 2=TS2, 1=TS1, default=1; RXO = RX Only, 0=off, 1=on, default=0; ALT = Allow Talkaround (inverted), default=1 (off); DCC = Data Call Confirmed, default=0; PCC = Private Call Confirmed, default=0; PRIV = Privacy type, 0=None, 1=Basic or 2=Enhanced, default=0 (off); PrivIdx = Privacy Index, [0,15], default=0. DPD = Display PTT ID (inverted), default = 1. CUH = Compressed UDP data header (inverted), default=1 (off). EAA = Emergency Alarm Ack, default = 0; RXFreqR = RX reference frequency, 0=Low, 1=Medium, 2=High, default=0 (low); AdimtCr = Admit Criterion, 0=Always, 1=Channel Free or 2=Correct CTS/DCS, 3=Colorcode, default=0. PWR = Specifies the power, 0=low, 1=high. VOX = VOX Enable, 0=Disable, 1=Enable, default = 0; RVB = Reverse burst 0=disable, 1=enable, default=1; TXFreqR = TX reference frequency: 0=Low, 1=Medium, 2=High, default=0; TOffFre = Non-QT/DQT Turn-off Freq., 3=none, 0=259.2Hz, 1=55.2Hz, default=3; InCallC = In Call Criteria, 0=Always, 1=Follow Admit Criteria, 2=TX Interrupt, default=0; Tx Timeout = Tx Timeout x 15sec, 0-Infinite, 1=15s, etc, 37=555s, default=0; Power = Power, 0=low, 1=middle, 2=high, default=high; ALI = Allow interrupt (inverted), 0=allow, 1=Disabled, default=1; RXG = Receive GSP info (inverted), default=1; TXG = Send GSP info (inverted), default=1; ================================================ FILE: doc/code/md390_menusettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Menu hang time (sec) |RDS|REN|RMO|RCK|MDL|CED|CAL|TXT|ToA|TlA|CLO|CLA|CLM|ESL|SCN| 1 |VOX| 0 |SQL|LED|KPL|INS|BKL|PWR| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | 1 | 1 | 1 |GPI| 1 |PRG|DSM|PWD| Unused, set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where: RDS = Show radio disable; REN = Show radio enable; RMO = Show remote monitor; RCK = Show radio check; MDL = Show manual dial; CED = Show contact edit; CAL = Show call alert; TXT = Show text message; ToA = Show tone or Alert; TlA = Show talkaround; CLO = Show call-log outgoing, CLA = Show call-log answered; CLM = Show call-log missed; ESL = Show edit scan list; SCN = Show scan menu; VOX = Show VOX settings; SQL = Show squelch settings; LED = Show LED indicator menu; KPL = Show keypad lock; INS = Show intro screen; BKL = Show backlight; PWR = Show power settings; RSW = Show record switch; GPI = Hide GPS information; GPS = Hide GPS settings; PRG = Hide radio program menu; DSM = Show Display mode settings; PWD = Show password settings; NWZ = Show new zone menu; ZNS = Show zone settings; TXM = Show TX mode settings; MHT = Show menu hang time; PCM = Show private-call match settings; GCM = Show group-call match settings; NSL = Show new scan list menu; EDZ = Show edit zone menu; ================================================ FILE: doc/code/opengd77_callsign_db_entry.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DMR ID stored in BCD, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | String containing Name, Callsign, etc. Up to 15 bytes ASCII, 0x00 terminated and padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_callsign_db_header.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Magic 'ID-' string | Size, 0x5d = 19bytes | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Version string, fixed to '001' | Unused, set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Number of contacts, 16bit uint, little endian | Unused set to 0x0000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency, 8 x BCD digits, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency, 8 x BCD digits, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Mode (Analog/Digital) | Power [0-10] | Unused, set to 0. | Transmit timeout in 15s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | TOT rekey delay in sec. | Admit criterion | Unknown, set to 0x50 | Scan list index (+1) 0=None. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | CTCSS/DCS RX, 4-digit BCD little endian, 0xffff=off. | CTCSS/DCS TX, 4-digit BCD little endian, 0xffff=off. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Unused, set to 0x00 | TX DTMF system index |RID| Unused set to 0 | Radio ID, 24bit big-endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 ... | TX color-code | RX group list index (+1) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | RX color code | Emergency system index (+1) | Contact index (+1), little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 |DCC|EAA| 0 0 0 0 | ARTS | 0 |SLT| 0 |PRV| 0 0 0 |PCC| STE |NFS| 0 | PTTId | 0 |DCD| 0 |VOX|SZS|SAS|TLK|RXO|BW |SQT| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Unused set to 0 | Squelch in 5%, 0=default | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ RID = Channel radio ID enable, default=0. DCC = Data call confirm, EAA = Emergency Alarm ACK, SLT = Repeater slot, PRV = Privacy Enable, PCC = Private call confirm, NSF = Non-STE Frequency, DCD = Direct call dual capacity mode, PWR = Power, SZS = Scan zone skip enable, default=off. Was "auto scan enable" in orig. GD77. SAS = Scan all skip enable, default=off. Was "lone worker enable" in orig. GD77. TLK = Talk around enable, RXO = RX only, BW = Band width, SQT = Squelch type ================================================ FILE: doc/code/opengd77_contact.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Contact DMR ID 8 BCD digits. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Type 0=Grp, 1=Priv, 2 = All | RX tone 0=off, 1=on | Ring style [0..10] | Timeslot override | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_command_clear_screen_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'C' (43h) | 01h | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_command_control_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'C' (43h) | 06h | Action | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_command_display_text_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'C' (43h) | 03h | Column address (usually 0) | Row address (00h, 10h, ..) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Size (bug? always 3) | Alignment | Inverted | Payload max. 16x ASCII ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_command_okay_response.txt ================================================ 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+ 00 | '-' (2dh) | +---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_command_ping_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'C' (43h) | feh | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_command_render_screen_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'C' (43h) | 03h | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_command_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+...+---+ 00 | Fixed prefix 'C' (43h) | Command flag | Payload | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+...+---+ ================================================ FILE: doc/code/opengd77_protocol_command_show_cps_screen_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'C' (43h) | 00h | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_command_start_gps_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'C' (43h) | 7h | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_read_request.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'R' (52h) | Memory | Address 32bit unsigned int, big endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | Length, 16bit unsigned int, big endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_protocol_read_response.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Fixed prefix 'R' (52h) | Length, 16bit unsigned int, big endian | Data, length bytes ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ nn ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_radio_info.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Struct version number, 32bit unsigned int, little-endian, default 0x0003 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Radio type, 32bit unsigned int, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Git version string, 16x ASCII | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 | Build date-time string, 16x ASCII ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0024 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0028 | Flash chip part number, 32 bit unsigned int, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c | Features, 16bit unsigned int, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_zone.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Channel 00 index +1, little endian. | Channel 01 index +1, little endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00ac | Channel 78 index +1, little endian. | Channel 79 index +1, little endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/opengd77_zonebank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Unknown ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | 68 bits encoding whether a particular zone within this bank is enabled. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 ... | 0 0 0 0 | Unused set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0030 | 68 Zones, 176bytes each ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2ecc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/openrtx_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Channel Mode |Unused set to 0 |RXO| BandW | Power 10dBm + n*0.2 | RX Frequency in Hz, little ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... endian | TX Frequency in Hz, little ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 ... endian | Scan list index +1, 0=None | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | Group list index +1, 0=None | Name, 32 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c ... | Description, 32 x ASCII, 0-padded ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c ... | Ch. lat. int. part, signed | Ch. lat. decimal part, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Ch. lon. int. part, signed | Ch. lon. decimal part, little endian | Ch. alt. meters, little ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 ... endian | Mode specific settings 5 bytes see below ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Channel Mode: 0=FM, 1=DMR, 2=M17 BandW: Bandwidth, ?? RXO: RX only FM settings 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 55 | CTCSS RX tone idx |RTE| CTCSS TX tone idx |TTE| Unused, set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 59 ... | +---+---+---+---+---+---+---+---+ RTE: RX Tone enable TTE: TX Tone enable DMR settings 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 55 | TX color code | RX color code | Time slot 1,2 | Contact index, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 59 | Unused, set to 0 | +---+---+---+---+---+---+---+---+ M17 settings 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 55 | TX CAN | RX CAN | Encr. mode | Channel mode | GPS mode | Contact index, little endian... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 59 ... | +---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/openrtx_contact.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 32 x ASCII, 0-pad ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Mode, only DMR or M17 | Mode specific settings, 6 bytes, see below ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Mode: 0=None, 1=FM, 2=DMR, 3=M17, only DMR and M17 are valid. DMR Contact settings. 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 21 | DMR ID, 32bit uint, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 25 | Unused, set to 0 |RXT| CType | Unused set to 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ RXT: RX Tone enable (ring). CType: Contact type. M17 Contact settings: 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 21 | M17 encoded address ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 25 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/openrtx_header.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Magic number 0x43585452, little endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Version number (MAJOR << 8) | MINOR, little endian | Author, 32 x ASCII, 0-pad ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 ... | Description, 32 x ASCII, 0-pad ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 ... | Timestamp, little endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 ... | Contact count, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Channel count | Zone count | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/openrtx_zone.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 32 x ASCII, 0-pad ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Number of channels, uint16_t, little endian | Channel Index 0, uint32_t, little endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | Channel Index 1, uint32_t, little endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 ... | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_bootsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Boot text enable (0x01=text) | Boot passwd enable | Unused, set to 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Boot passwd, 6 x BCD numbers, big endian | Unused set to 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Unused set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_boottext.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Boot message 1, 16 x ASCII, 0xff terminated and padded. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Boot message 2, 16 x ASCII, 0xff terminated and padded. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_buttonsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unknown, set to 0x01 | Long press duration in 250ms | Short SK1 event | Long SK1 event | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Short SK2 event | Long press SK2 event | Short press TK action (GD77) | Long press TK action (GD77) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | One-touch 0 action type | One-touch 0, contact index | One-touch 0, message index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | One-touch 5 action type | One-touch 5, contact index | One-touch 5, message index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 x 1byte ASCII encoded, 0xff terminated text ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency in 10Hz encoded as 8-digits BCD, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency in 10Hz encoded as 8-digits BCD, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Mode 0=analog, 1digital | Unused set to 0 | TX timeout (TOT) N x 15s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | TOT rekey delay in s | Admit 0=always, 1=free, 2=CC | !!! Unknown set to 0x50 !!! | Scan-list index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | RX CTCSS/DCS | TX CTCSS/DCS | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Unused set to 0x00 | TX signaling index +1 | Unused set to 0x00 | RX signaling index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | !!! Unknown set to 0x16 !!! | Privacy Grp 0=None, 1=53474c39| TX Color code [0,15] | RX group-list index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | RX Color code [0,15] | Emergency sys. index +1 | TX contact index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 |DCC|EAC| 0 0 0 0 0 0 | 0 |SLT| 0 |PRV| 0 0 0 |PCC| 0 0 |STE| 0 0 0 0 |DCM|PWR|VOX| 0 0 |TLK|RXO|BW | 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Unused set to 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where EAC = Emergency alarm ACK; DCC = Data call confirmed; SLT = Repeater slot {1,2}; PRV = Privacy enabled; PCC = Private call confirmed; STE = Non STE is frequency(?!?); DCM = Dual capacity direct mode enable; PWR = Power; VOX = Voice operated switch wnable; TLK = Allow talkaround; RXO = RX only; BW = Bandwidth; ================================================ FILE: doc/code/radioddity_channelbank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | 128 bits encoding whether a particular channel within this bank is enabled. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | 128 Channels, 56bytes each ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_contact.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Contact DMR ID 8 BCD digits. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Type 0=Grp, 1=Priv, 2 = All | RX tone 0=off, 1=on | Ring style [0..10] | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_dtmfcontact.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | DTMF number, 16 digits, table = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, *, #}, 1 byte per digit ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_generalsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Radio name, 8 x 1byte ASCII encoded, 0xff terminated text ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | 8-digit BCD DMR ID, big-endian. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | Unused set to 0x00000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Unused set to 0x00 | Tx preamble N x 60ms | Monitor type 0=open, 1=silent | VOX sensitivity [1..10] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Low battery interval N x 5s | Call alert dur N x 5s | Lone worker response in min | Lone worker reminder in sec | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 | Groupcall hang-time N x 500ms | Priv.call hang-time N x 500ms | ARTS tone |UNT|RST|UMV|DMV|SBP|SBR|DAT| 0 |FIT|STT|PAT|PDT| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c | 1 |IQO|DAL| 0 0 0 0 0 | ScanM |ANI|DBW|TXT| 0 0 0 | Rep. STE | Rep. delay | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | Prog. passwd 8 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0024 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where UNT = Unknown number tone RST = Reset tone UMV = Up-channel mode VFO DMV = Down-channel mode VFO SBP = Send wakeup preamble. SBR = Enable sleep mode during RX. DAT = Disables all tones; FIT = Channel free indication tone STT = Self-test pass tone PAT = Talk permit tone analog PDT = Talk permit tone digital IQO = Inhibit quick-key override DAL = Disables all LEDs ScanM = Scan mode ANI = Enable animation DBW = Always TX on active channel on double-wait TXT = TX exit tone ================================================ FILE: doc/code/radioddity_grouplist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Contact 00 index +1, 0 end of list, little endian | Contact 01 index +1, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c | Contact 14 index +1, little endian | Contact 15 index +1, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_grouplistbank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | N Contacts Group 00, 0=EOL | N Contacts Group 01 | N Contacts Group 02 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c ... | N Contacts Group 61 | N Contacts Group 62 | N Contacts Group 63 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0040 | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 007c ... | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0080 | 64 Group lists, 32 bytes each ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 087c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_menusettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Menu hang time in sec |RMO|RCK|MND|ECN|CAL|ESL|SCS|MSG|INS|BKL|PWR|TON|TLK|PRG|RDI|REN|ANC|MIC|PLK|VOX|PRV|SQL|LED|KPL| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | 1 1 1 0 0 |DBL|DSM|OTC| Unknown, set to 0xff |ChanDis| 0 0 |BKLTime|KeyLock|DblWait|KYT| 1 | 0 0 0 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ RMO = Remote monitor menu enable, RCK = Radio check menu enable, MND = Manual dial menu enable, ECN = Edit contact menu enable, CAL = Call alert menu enable, ESL = Edit scan list enable, SCS = Scan start enable, MSG = Message menu enable, INS = Intro screen menu enable, BKL = Backlight menu enable, PWR = Power setting enable, TON = Tone menu enable, TLK = Talkaround menu enable, PRG = Program-password menu enable, RDI = Radio-disable menu, REN = Radio-enable menu, ANC = Answered calls menu enable, MIC = Missed calls menu enable, PLK = Password and lock menu enable, VOX = VOX menu enable, PRV = Privacy menu enable, SQL = Squelch menu enable, LED = LED indicator menu enable, KPL = Keyboard lock menu enable, DBL = Double-wait menu enable, DSM = Display mode menu enable, OTC = Outgoing calls menu enable, BKLTime = Backlight time, DblWait = Double wait mode, KYT = Key tone enable, ================================================ FILE: doc/code/radioddity_messagebank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | # Messages | Unused filled with 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Length message 0 | Length message 1 | Length message 2 | Length message 3 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0044 | Length message 3c | Length message 3d | Length message 3e | Length message 3f | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0048 | Unused filled with 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0084 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0088 | Message 0, max 144 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0114 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 23f8 | Message 3f, max 144 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2484 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_privacy.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Privacy Type, 0=None, 1=basic | Unused, set to 0x00 | Key bitmap | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 | Unused set to 0x00000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Key 01, 32bit, 0xffff=off | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | Key 01 copy, 32bit, 0xffff=off | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Key 02, 32bit, 0xffff=off | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | Key 02 copy, 32bit, 0xffff=off | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0080 | Key 16, 32bit, 0xffff=off | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0084 | Key 16 copy, 32bit, 0xffff=off | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_scanlist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 15 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... |TKB| PLT |ChM| 0 0 0 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Channel 00 index +1, 0=EOL, little-endian | Channel 01 index +1, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004c | Channel 30 index +1, little-endian | Channel 31 index +1, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 | Priority Channel 1, index+2, 0=None, 1=Current, little-endian | Priority Channel 2, index+2, 0=None, 1=Current, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0054 | TX Channel, index+2, 0=last active, 1=Current, little-endian | Sig. hold time N x 25ms | Pri. sample time N x 250ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where ChM = Channel mark; PLT = PL type; TKB = Talkback ================================================ FILE: doc/code/radioddity_scanlistbank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Valid scanlist 00 1=Yes, 0=No | Valid scanlist 01 | Valid scanlist 02 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00f8 ... | Valid scanlist 249 | Unused set to 0x00 | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00fc | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0100 | 250 scan lists, 88 bytes each ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 56ec ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_vfochannel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency, 8 x BCD digits, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency, 8 x BCD digits, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Mode (Analog/Digital) | Unused, set to 0x00 | Transmit timeout | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | Transmit timeout rekey | Admit criterion | Unknown, set to 0x50 | Scan list index (+1) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | CTCSS/DCS RX | CTCSS/DCS TX | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Unused, set to 0x00 | TX DTMF system index (+1) | Unused set to 0x00 | RX DTMF system index (+1) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Unused, set to 0x00 | Privacy group index | TX color-code | RX group list index (+1) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | RX color code | Emergency system index (+1) | Contact index (+1), little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 |DCC|EAA| 0 0 0 0 | ARTS | 0 |SLT| 0 |PRV| 0 0 0 |PCC| STE |NFS| 0 | PTTId | 0 |DCD|PWR|VOX|ASE|LWK|TLK|RXO|BW |SQT| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Offset Frequency in 1kHz, 4-digit BCD, little endian | StepSize |OffsDir| 0 0 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ DCC = Data call confirm, EAA = Emergency Alarm ACK, SLT = Repeater slot, PRV = Privacy Enable, PCC = Private call confirm, NSF = Non-STE Frequency, DCD = Direct call dual capacity mode, PWR = Power, ASE = Autoscan enable, LWK = Lone worker enable, TLK = Talk around enable, RXO = RX only, BW = Band width, SQT = Squelch type OffsDir = Offset direction, 0=none, 1=+, 2=-, ================================================ FILE: doc/code/radioddity_zone.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Channel 00 index +1 | Channel 01 index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c | Channel 14 index +1 | Channel 15 index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/radioddity_zonebank.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | 250 bits encoding whether a particular zone within this bank is enabled. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c ... | 0 | 0 | 0 | 0 | 0 | 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | 250 Zones, 48bytes each ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2efc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5r_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name, 16 x 1byte ASCII encoded, 0xff terminated text ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency in 10Hz encoded as 8-digits BCD, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency in 10Hz encoded as 8-digits BCD, little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | Mode 0=analog, 1digital | Unused set to 0 | TX timeout (TOT) N x 15s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | TOT rekey delay in s | Admit 0=always, 1=free, 2=CC | !!! Unknown set to 0x50 !!! | Scan-list index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | RX CTCSS/DCS | TX CTCSS/DCS | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Unused set to 0x00 | TX signaling index +1 | Unused set to 0x00 | RX signaling index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | !!! Unknown set to 0x16 !!! | Privacy Grp 0=None, 1=53474c39| TX Color code [0,15] | RX group-list index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | RX Color code [0,15] | Emergency sys. index +1 | TX contact index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 |DCC|EAC| 0 0 0 0 0 0 | 0 |SLT| 0 |PRV| 0 0 0 |PCC| 0 0 |STE| 0 0 0 0 |DCM|PWR|VOX| 0 0 |TLK|RXO|BW | 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 34 | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | Squelch [0..9] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where EAC = Emergency alarm ACK; DCC = Data call confirmed; SLT = Repeater slot {1,2}; PRV = Privacy enabled; PCC = Private call confirmed; STE = Non STE is frequency(?!?); DCM = Dual capacity direct mode enable; PWR = Power; VOX = Voice operated switch wnable; TLK = Allow talkaround; RXO = RX only; BW = Bandwidth; ================================================ FILE: doc/code/rd5r_generalsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Radio name, 8 x 1byte ASCII encoded, 0xff terminated text ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | 8-digit BCD DMR ID | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c | Unused set to 0x00000000 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Unused set to 0x00 | Tx preamble N x 60ms | Monitor type 0=open, 1=silent | VOX sensitivity [1..10] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Low battery interval N x 5s | Call alert dur N x 5s | Lone worker response in min | Lone worker reminder in sec | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0018 | Groupcall hang-time N x 500ms | Priv.call hang-time N x 500ms | ARTS tone |UNT|RST|UMV|DMV|SBP|SBR|DAT| 0 |FIT|STT|PAT|PDT| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c | 1 |IQO|DAL| 0 0 0 0 0 | ScanM |ANI|DBW|TXT| 0 0 0 | Rep. STE | Rep. delay | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0020 | Prog. passwd 8 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0024 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where UNT = Unknown number tone RST = Reset tone UMV = Up-channel mode VFO DMV = Down-channel mode VFO SBP = Save battery by disable preamble SBR = Save battery by disable receive DAT = Disables all tones; FIT = Channel free indication tone STT = Self-test pass tone PAT = Talk permit tone analog PDT = Talk permit tone digital IQO = Inhibit quick-key override DAL = Disables all LEDs ScanM = Scan mode ANI = Enable animation DBW = Always TX on active channel on double-wait TXT = TX exit tone ================================================ FILE: doc/code/rd5r_timestamp.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Year, 4xBCD, big-endian | Month 2xBCD | Day 2xBCD | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Hour, 2xBCD | Minute, 2xBCD | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rbootsettings.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Boot text enable (0x01=text) | Boot passwd enable | Unused, set to 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Boot passwd, 6 x BCD numbers, big endian | Unused set to 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | Unused set to 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rbuttonsettings.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unknown, set to 0x01 | Long press duration in 250ms | Short SK1 event | Long SK1 event | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Short SK2 event | Long press SK2 event | Short press TK action (GD77) | Long press TK action (GD77) | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | One-touch 0 action type | One-touch 0, contact index | One-touch 0, message index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | One-touch 5 action type | One-touch 5, contact index | One-touch 5, message index | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rcontact.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Contact DMR ID 8 BCD digits. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0014 | Type 0=Grp, 1=Priv, 2 = All | RX tone 0=off, 1=on | Ring style [0..10] | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rdtmfcontact.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | DTMF number, 16 digits, table = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, *, #}, 1 byte per digit ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rgrouplist.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Contact 00 index +1, 0 end of list | Contact 01 index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c | Contact 14 index +1 | Contact 15 index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rgrouplisttab.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | N Contacts Group 00, 0=EOL | N Contacts Group 01 | N Contacts Group 02 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 003c ... | N Contacts Group 61 | N Contacts Group 62 | N Contacts Group 63 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0040 | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 007c ... | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0080 | 64 Group lists, 32 bytes each ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 087c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rmenusettings.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Menu hang time in sec |RMO|RCK|MND|ECN|CAL|ESL|SCS|MSG|INS|BKL|PWR|TON|TLK|PRG|RDI|REN|ANC|MIC|PLK|VOX|PRV|SQL|LED|KPL| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | 1 1 1 0 0 |DBL|DSM|OTC| Unknown, set to 0xff |ChanDis| 0 0 |BKLTime|KeyLock|DblWait|KYT| 1 | 0 0 0 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ RMO = Remote monitor menu enable, RCK = Radio check menu enable, MND = Manual dial menu enable, ECN = Edit contact menu enable, CAL = Call alert menu enable, ESL = Edit scan list enable, SCS = Scan start enable, MSG = Message menu enable, INS = Intro screen menu enable, BKL = Backlight menu enable, PWR = Power setting enable, TON = Tone menu enable, TLK = Talkaround menu enable, PRG = Program-password menu enable, RDI = Radio-disable menu, REN = Radio-enable menu, ANC = Answered calls menu enable, MIC = Missed calls menu enable, PLK = Password and lock menu enable, VOX = VOX menu enable, PRV = Privacy menu enable, SQL = Squelch menu enable, LED = LED indicator menu enable, KPL = Keyboard lock menu enable, DBL = Double-wait menu enable, DSM = Display mode menu enable, OTC = Outgoing calls menu enable, BKLTime = Backlight time, DblWait = Double wait mode, KYT = Key tone enable, ================================================ FILE: doc/code/rd5rmessagetab.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | # Messages | Unused filled with 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0004 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0008 | Length message 0 | Length message 1 | Length message 2 | Length message 3 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0044 | Length message 3c | Length message 3d | Length message 3e | Length message 3f | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0048 | Unused filled with 0 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0084 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0088 | Message 0, max 144 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0114 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 23f8 | Message 3f, max 144 x ASCII, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2484 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rscanlist.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 15 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | 0 0 0 0 |ChM| PLT |TKB| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Channel 00 index +1, 0 end of list | Channel 01 index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004c | Channel 30 index +1 | Channel 31 index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0050 | Priority Channel 1, index+2, 0=None, 1=Current | Priority Channel 2, index+2, 0=None, 1=Current | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0054 | TX Channel, index+1, 0=last active | Sig. hold time N x 25ms | Pri. sample time N x 250ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where ChM = Channel mark; PLT = PL type; TKB = Talkback ================================================ FILE: doc/code/rd5rscanlisttab.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Valid scanlist 00 1=Yes, 0=No | Valid scanlist 01 | Valid scanlist 02 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00f8 ... | Valid scanlist 249 | Unused set to 0x00 | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00fc | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0100 | 250 scan lists, 88 bytes each ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 56ec ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rvfosettings.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | VFO A settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0034 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0038 | VFO B settings ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 006c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rzone.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Name, 16 ASCII encoded bytes, 0xff terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | Channel 00 index +1 | Channel 01 index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 002c | Channel 14 index +1 | Channel 15 index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/rd5rzonetab.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | 250 bits encoding whether a particular zone within this bank is enabled. ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 001c ... | 0 | 0 | 0 | 0 | 0 | 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0010 | 250 Zones, 48bytes each ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2edc ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tyt_buttonsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused, set to 0 | Side button 1 short press | Side button 1 long press | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Side button 2 short press | Side button 2 long press | Unused set to 0x00 ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | Unknown set to 0x01 | Long press dur x250ms | Unused set to 0xffff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | One-touch setting 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | One-touch setting 5 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tyt_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 |LWK| 1 1 |ASC| BandW | ChMod | ColorCode |TimeSlt|RXO|ALT|DCC|PCC| PRIV | PrivIdx |DPD| 1 1 0 |EAA| 0 |RXFreqR| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 |AdimtCr| 1 |VOX| 0 1 |TXFreqR| Device specific settings | TX Contact name index + 1, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | 0 0 | Tx Timeout | Tx Timeout Rekey Delay | Emergency System index + 1 | Scan List index + 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | RX Group List index + 1 | GPS System index +1 | DTMF decode bitmap | Device specific settings | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency, 8 digits BCD, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency, 8 digits BCD, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | RX CTCSS/DCS | TX CTCSS/DCS | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Signaling System index +1 | TX Signaling System index +1 | Unused set to 0xff | 1 1 1 1 1 1 |RXG|TXG| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Name 16 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: LWK = Lone Worker, default=0. ASC = Auto scan, default=0. BandW = Bandwidth, 0=12.5kHz, 1=20kHz or 2=25kHz, default = 0. ChMod = Channel Mode, 1=Analog or 2=Digital, default=1. ColorCode = ColorCode [0..15], default=1. TimeSlt = Repeater/time-slot, 2=TS2, 1=TS1, default=1. RXO = RX Only, 0=off, 1=on, default=0. ALT = Allow Talkaround (inverted), default=1 (off). DCC = Data Call Confirmed, default=0; PCC = Private Call Confirmed, default=0. PRIV = Privacy type, 0=None, 1=Basic or 2=Enhanced, default=0 (basic). PrivIdx = Privacy Index, [0,15], default=0. DPD = Display PTT ID (inverted), default = 1. EAA = Emergency Alarm Ack, default = 0. RXFreqR = RX reference frequency, 0=Low, 1=Medium, 2=High, default=0 (low). AdimtCr = Admit Criterion, 0=Always, 1=Channel Free or 2=Correct CTS/DCS, 3=Colorcode, default=0. VOX = VOX Enable, 0=Disable, 1=Enable, default = 0; TXFreqR = TX reference frequency: 0=Low, 1=Medium, 2=High, default=0. Tx Timeout = Tx Timeout x 15sec, 0-Infinite, 1=15s, etc, 37=555s, default=0. LMS = Leader/MS: Leader or MS, default=1. DCD = DCDM switch (inverted), default=1. ALI = Allow interrupt (inverted), 0=allow, 1=Disabled, default=1. RXG = Receive GSP info (inverted), default=1. TXG = Send GSP info (inverted), default=1. ================================================ FILE: doc/code/tyt_contact.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DMR ID (binary coded), little endian | 1 1 |RXT| 0 0 0 | Type | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Contact Name 16 x 16bit Unicode ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where - Type = Call type where 0=disabled, 1=group, 2=private, 3=all - RXT = Receive tone enable; ================================================ FILE: doc/code/tyt_emergencysettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | 1 1 1 1 1 |ERM|RMD|RDD| Remote mon durarion in 10s | TX time-out in 25ms | Message limit [1-3] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Unused, set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where: ERM = Emergency remote monitor decode. RMD = Remote monitor decode. RDD = Radio disable decode. ================================================ FILE: doc/code/tyt_emergencysystem.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Name 16 x 16bit unicode, 0-terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | 0 1 | AMode | 1 1 | AType | Impolite retires | Polite retires | Hot MIC durarion in 10s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 | Revert channel index +1, 0=Disabled, 0xffff=selected | Unused, set to 0xffff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tyt_gpssystem.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Revert channel index + 1, little endian, 0 = current | Repeat intv. N x 30s, 0=off | Unused, set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Destination contact index + 1, little endian, 0 = none | 10 x unused bytes set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tyt_grouplist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Group List Name 16 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Member Contact index+1 00, little endian | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... | Member Contact index+1 31, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tyt_menusettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Menu hang time (sec) |RDS|REN|RMO|RCK|MDL|CED|CAL|TXT|ToA|TlA|CLO|CLA|CLM|ESL|SCN| 1 |VOX| 0 |SQL|LED|KPL|INS|BKL|PWR| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | 1 | 1 | 1 | 1 | 1 |PRG|DSM|PWD| Unused, set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where: RDS = Show radio disable; REN = Show radio enable; RMO = Show remote monitor; RCK = Show radio check; MDL = Show manual dial; CED = Show contact edit; CAL = Show call alert; TXT = Show text message; ToA = Show tone or Alert; TlA = Show talkaround; CLO = Show call-log outgoing, CLA = Show call-log answered; CLM = Show call-log missed; ESL = Show edit scan list; SCN = Show scan menu; VOX = Show VOX settings; SQL = Show squelch settings; LED = Show LED indicator menu; KPL = Show keypad lock; INS = Show intro screen; BKL = Show backlight; PWR = Show power settings; RSW = Show record switch; PRG = Hide radio program menu; DSM = Show Display mode settings; PWD = Show password settings; NWZ = Show new zone menu; ZNS = Show zone settings; TXM = Show TX mode settings; MHT = Show menu hang time; PCM = Show private-call match settings; GCM = Show group-call match settings; NSL = Show new scan list menu; EDZ = Show edit zone menu; ================================================ FILE: doc/code/tyt_onetouchsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | 1 1 | Type | Action | Message index +1, 0=none. | Contact index +1, little endian, 0=none. | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tyt_privacy.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Enhanced key 0, 16bytes raw ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Enhanced key 7, 16 bytes raw ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 7c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | Unused, filled with 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Basic key 0, 16bit uint little-endian | Basic Key 1, 16bit uint little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac | Basic key 14, 16bit uint little-endian | Basic key 15, 16bit uint little-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Note: All keys are stored in reversed order. For both, basic and AES keys. ================================================ FILE: doc/code/tyt_scanlist.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Scan List Name 16 x 16bit unicode characters, each unicode char is encoded in little-endian ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Priority Channel 1, index+1, 0x0000=current, 0xffff=none | Priority Channel 1, index+1, 0x0000=current, 0xffff=none | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 |Tx Designated Channel index+1, 0x0000=current, 0xffff=none | !!! Unknown set to 0xf1 !!! | Hold Time N x 25ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Prio. Sample Time N x 250ms | Unused, set to 0xff | Member Channel index+1 00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Member Channel index+1 01 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 ... | Member Channel index+1 30 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Note: - All indices are encoded little-endian. ================================================ FILE: doc/code/tyt_settings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Intro Line 1, 10 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Intro Line 2, 10 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 26 | Reserved 24 bytes, set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | 1 1 1 |MNT| 1 |DAL| 1 0 |TPA|TPD|PWE|CIT| 1 |DAT|SMR|SPR| 1 1 1 |INP| 1 0 1 | 0 | Unused set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | DMR ID 24bit, little endian | Unused 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | TX Preamble Duration N x 60ms | Grp Call Hang Time N x 100ms | Prv. Call Hang Time N x 100ms | VOX sensitivity [1..10] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Unused, set 0x00 | Unused, set 0x00 | RX Low Bat. Intv N x 5s | Call Alert Tone Dur N x 5s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Lone Worker Resp. Time in min | Lone Worker Rem. Time in sec | Unused, set to 0x00 | Scan Dig. Hang Time N x 100ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Scan Anal. Hang Time N x 100ms| 0 0 0 0 0 0 |BcLTime| Keypad Lock Time N x 5s | Unused, set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | Power-on password 8 x BCD numbers, 0x00000000=default | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | Radio prog. password 8 x BCD numbers, 0x00000000=disabled | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | PC Programming password 8 x ASCII, 0x00 terminated, filled with 0xff=disabled ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | Unused set to 0xffffffff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c | Unused set to 0xffffffff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Radio name 16 x 16bit unicode chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: MNT = Monitor type: 1 = Open Squelch, 0 = Silent, default=1; DAL = Disable all LEDs (inverted); TPD = Talk permit tone digital; TPA = Talk permit tone analog; PWE = Password and lock enable (inverted); CIT = Ch. free indication tone (inverted); DAT = Disable all tones (inverted); SMR = Save Mode Receive; SPR = Save Preamble; INP = Intro Picture; BcLTime = Backlight time, 0=Always, t=n*5s; Keypad Lock Time = 0xff=manual otherwise ================================================ FILE: doc/code/tyt_timestamp.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused, set to 0x00 | Date YYYY-MM-DD hh:mm:ss encoded as 14 BCD numbers ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | CPS Version, VV.VV encoded using table {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?} | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tyt_zone.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Zone name, 16 x 16bit unicode chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Member Channel index+1 00 VFO A | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | Member Channel index+1 15 VFO A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tytcallsigndbentry.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DMR ID in binary format | Unused set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Callsign 16 x ASCII ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Name, City, etc.; 100 x ASCII ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tytcallsigndbindex.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000000 | Number of entries in DB | Pad set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000004 | 4096 Index entries, see IndexElement::Entry ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004000 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/tytcallsigndbindexentry.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | 12 high-bits of the DMR ID | 20-bit index within the callsign DB list | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390_bootsettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused, set to 0xffffff | Boot zone index +1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Boot CH A index +1 (in zone) | Unused, set to 0xff | Boot CH B index +1 (in zone) | Unused set to 0xffff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 ... | Unknown index, set to 0x0001, little endian | Unused filled with 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390_channel.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 |LWK| 1 1 |ASC| BandW | ChMod | ColorCode |TimeSlt|RXO|ALT|DCC|PCC| PRIV | PrivIdx |DPD| 0 |EAA| 1 1 0 |RXFreqR| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 |AdimtCr| 1 |VOX| 0 1 |TXFreqR|TOffFre|InCallC| 0 |PrK| 0 0 | TX Contact name index + 1, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | 0 0 | Tx Timeout | Tx Timeout Rekey Delay | Emergency System index + 1 | Scan List index + 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c | RX Group List index + 1 | GPS System index +1 | DTMF decode bitmap | Squelch [0..9] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | RX Frequency, 8 digits BCD, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | TX Frequency, 8 digits BCD, little endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 18 | RX CTCSS/DCS | TX CTCSS/DCS | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c | RX Signaling System index +1 | TX Signaling System index +1 | 1 1 1 1 1 1 | Power | 1 1 1 |LMS|DCD|ALI|RXG|TXG| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Name 16 x 16bit unicode characters / Stepsize (byte 0x20) ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: LWK = Lone Worker, default=0. ASC = Auto scan, default=0. BandW = Bandwidth, 0=12.5kHz, 1=20kHz or 2=25kHz, default = 0. ChMod = Channel Mode, 1=Analog or 2=Digital, default=1. ColorCode = ColorCode [0..15], default=1. TimeSlt = Repeater/time-slot, 2=TS2, 1=TS1, default=1. RXO = RX Only, 0=off, 1=on, default=0. ALT = Allow Talkaround (inverted), default=1 (off). DCC = Data Call Confirmed, default=0; PCC = Private Call Confirmed, default=0. PRIV = Privacy type, 0=None, 1=Basic or 2=Enhanced, default=0 (basic). PrivIdx = Privacy Index, [0,15], default=0. DPD = Display PTT ID (inverted), default = 1. EAA = Emergency Alarm Ack, default = 0. RXFreqR = RX reference frequency, 0=Low, 1=Medium, 2=High, default=0 (low). AdimtCr = Admit Criterion, 0=Always, 1=Channel Free or 2=Correct CTS/DCS, 3=Colorcode, default=0. VOX = VOX Enable, 0=Disable, 1=Enable, default = 0; TXFreqR = TX reference frequency: 0=Low, 1=Medium, 2=High, default=0. TOffFre = Non-QT/DQT Turn-off Freq., 3=none, 0=259.2Hz, 1=55.2Hz, default=3. InCallC = In Call Criteria, 0=Always, 1=Follow Admit Criteria, 2=TX Interrupt, default=0. PrK = Privacy key. Also Enables encryption. Tx Timeout = Tx Timeout x 15sec, 0-Infinite, 1=15s, etc, 37=555s, default=0. Power = Power, 0=low, 1=middle, 2=high, default=high. LMS = Leader/MS: Leader or MS, default=1. DCD = DCDM switch (inverted), default=1. ALI = Allow interrupt (inverted), 0=allow, 1=Disabled, default=1. RXG = Receive GSP info (inverted), default=1. TXG = Send GSP info (inverted), default=1. ================================================ FILE: doc/code/uv390_menusettings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Menu hang time (sec) |RDS|REN|RMO|RCK|MDL|CED|CAL|TXT|ToA|TlA|CLO|CLA|CLM|ESL|SCN| 1 |VOX| 0 |SQL|LED|KPL|INS|BKL|PWR| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | 1 | 1 |RSW|GPI|GPS|PRG|DSM|PWD|NWZ|ZNS|TXM|MHT|PCM|GCM| 1 1 | 1 1 1 1 1 1 |NSL|EDZ| Unused, set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where: RDS = Show radio disable; REN = Show radio enable; RMO = Show remote monitor; RCK = Show radio check; MDL = Show manual dial; CED = Show contact edit; CAL = Show call alert; TXT = Show text message; ToA = Show tone or Alert; TlA = Show talkaround; CLO = Show call-log outgoing, CLA = Show call-log answered; CLM = Show call-log missed; ESL = Show edit scan list; SCN = Show scan menu; VOX = Show VOX settings; SQL = Show squelch settings; LED = Show LED indicator menu; KPL = Show keypad lock; INS = Show intro screen; BKL = Show backlight; PWR = Show power settings; RSW = Show record switch; GPI = Hide GPS information; GPS = Hide GPS settings; PRG = Hide radio program menu; DSM = Show Display mode settings; PWD = Show password settings; NWZ = Show new zone menu; ZNS = Show zone settings; TXM = Show TX mode settings; MHT = Show menu hang time; PCM = Show private-call match settings; GCM = Show group-call match settings; NSL = Show new scan list menu; EDZ = Show edit zone menu; ================================================ FILE: doc/code/uv390_settings.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Intro Line 1, 10 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Intro Line 2, 10 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 26 | Reserved 24 bytes, set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | TXM | 1 |MNT| 1 |DAL| 1 0 |TPA|TPD|PWE|CIT| 1 |DAT|SMR|SPR| 1 1 |KPT|INP| 1 0 |CVN| 0 |MSB| 1 1 1 1 |MSA| 1 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 44 | DMR ID 24bit, little endian | Unused 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 48 | TX Preamble Duration N x 60ms | Grp Call Hang Time N x 100ms | Prv. Call Hang Time N x 100ms | VOX sensitivity [1..10] | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 4c | Unused, set 0x00 | Unused, set 0x00 | RX Low Bat. Intv N x 5s | Call Alert Tone Dur N x 5s | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | Lone Worker Resp. Time in min | Lone Worker Rem. Time in sec | Unused, set to 0x00 | Scan Dig. Hang Time N x 100ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 54 | Scan Anal. Hang Time N x 100ms| 0 0 0 0 0 0 |BcLTime| Keypad Lock Time N x 5s | Channel Mode | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 58 | Power-on password 8 x BCD numbers, 0x00000000=default | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c | Radio prog. password 8 x BCD numbers, 0xffffffff=disabled | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | PC Programming password 8 x ASCII, 0x00 terminated, filled with 0xff=disabled ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 68 | Unused set to 0xffffff | Timezone,0=UTC-12 | 1 |PCM|GCM| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 6c | Unused set to 0xffffffff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | Radio name 16 x 16bit unicode chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 8c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | Channel hang time N x 100ms | Unused, set to 0xff | 1 1 1 1 1 |PbZ| 1 1 | Unused, set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 94 | Radio ID 1, binary encoded | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 98 | Radio ID 2, binary encoded | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 9c | Radio ID 3, binary encoded | Unused set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | 1 |ERI| MicLevel | 1 1 1 | 15 reserved bytes filled with 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ac ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ Field description: TXM = TX Mode, 0=LastCallCH, 1=LastCallCH+HandCH, 2 = DesignedCH, 3 = DesignedCH+HandCH, default=3; MNT = Monitor type: 1 = Open Squelch, 0 = Silent, default=1; DAL = Disable all LEDs (inverted); TPD = Talk permit tone digital; TPA = Talk permit tone analog; PWE = Password and lock enable (inverted); CIT = Ch. free indication tone (inverted); DAT = Disable all tones (inverted); SMR = Save Mode Receive; SPR = Save Preamble; KPT = Keypad tones; INP = Intro Picture; CVN = Channel voice announce; MSB = Mode select B (0=VFO, 1=MR); MSA = Mode select A (0=VFO, 1=MR); BcLTime = Backlight time, 0=Always, t=n*5s; PCM = Private call match; GCM = Group call match; PbZ = Public zone; ERI = Edit Radio ID (inverted); ================================================ FILE: doc/code/uv390contact.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DMR ID (binary coded) | Contact Type |RXT| 1 1 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Contact Name 16 x 16bit Unicode ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ where RXT = Receive tone; ================================================ FILE: doc/code/uv390gpssystem.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Revert channel index + 1, 0 = current | Repeat intv. N x 30s | Unused, set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Destination contact index + 1 | 10 x unused bytes set to 0xff ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390message.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 0000 | Message text, 144 x 16 bit unicode text, 0x00 terminated ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 011c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390rxgrouplist.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Group List Name 16 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Member Contact index+1 00 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... | Member Contact index+1 31 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390scanlist.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Scan List Name 16 x 16bit unicode characters ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Priority Channel 1, index+1, 0x0000=current, 0xffff=none | Priority Channel 1, index+1, 0x0000=current, 0xffff=none | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 24 |Tx Designated Channel index+1, 0x0000=current, 0xffff=none | !!! Unknown set to 0xf1 !!! | Hold Time N x 25ms | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 28 | Prio. Sample Time N x 250ms | Unused, set to 0xff | Member Channel index+1 00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 2c | Member Channel index+1 01 | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 64 ... | Member Channel index+1 30 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390timestamp.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Unused, set to 0x00 | Date YYYY-MM-DD hh:mm:ss encoded as 14 BCD numbers ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 08 | CPS Version, VV.VV encoded using table {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, <, =, >, ?} | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390userdb.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000000 | Number of entries in DB | Pad set to 0x00 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 000004 | 4096 Index entries ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004000 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 004004 | 122197 Callsign entries ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ dfffd8 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390userdbcallsign.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | DMR ID in binary format | Unused set to 0xff | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 04 | Callsign 16 x ASCII ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 14 | Name, City, etc.; 100 x ASCII ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 74 ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390userdbentry.txt ================================================ 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | 12 high-bits of the DMR ID | 20-bit index within the callsign DB list | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390zone.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Zone name, 16 x 16bit unicode chars ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 1c ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | Member Channel index+1 00 VFO A | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 3c ... | Member Channel index+1 15 VFO A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/code/uv390zoneext.txt ================================================ 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | Member Channel index+1 16 VFO A | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 5c ... | Member Channel index+1 63 VFO A | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | Member Channel index+1 00 VFO B | ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ dc ... | Member Channel index+1 63 VFO B | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ================================================ FILE: doc/dmr-intro/fig/Makefile ================================================ FIGS = fm_duplex_a.tex fm_echolink_b.tex fm_simplex_b.tex simplex_privatecall.tex talkgroup_ex1c.tex trunk_net_ex2.tex trunk_net_ex4b.tex \ fm_duplex_b.tex fm_echolink_c.tex repeater_local.tex simplex_allcall.tex talkgroup_ex1a.tex timeslot.tex trunk_net_ex3.tex \ fm_echolink_a.tex fm_simplex_a.tex repeater_privatecall.tex simplex_groupcall.tex talkgroup_ex1b.tex trunk_net_ex1.tex trunk_net_ex4a.tex ================================================ FILE: doc/dmr-intro/fig/fm_duplex_a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DL0LDS}; \user{U2}{6,0}{DL2XYZ}; \path[->] (U1) edge node[above,rotate=17] {$431.9625 MHz$} (R1) ; \path[->] (R1) edge node[above,rotate=-17] {$439.5625 MHz$} (U2); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/fm_duplex_b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DL0LDS}; \activeuser{U2}{6,0}{DL2XYZ}; \path[->] (U2) edge node[above,rotate=-17] {$431.9625 MHz$} (R1) ; \path[->] (R1) edge node[above,rotate=17] {$439.5625 MHz$} (U1); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/fm_echolink_a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DB0SP}; \repeater{R2}{6,1}{DB0LDS}; \user{U2}{9,0}{DL2XYZ}; \path[->] (U1) edge node[above,rotate=17] {$DTMF: 662699$} node[below,rotate=17]{$431.825 MHz$} (R1) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/fm_echolink_b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DB0SP}; \activerepeater{R2}{6,1}{DB0LDS}; \user{U2}{9,0}{DL2XYZ}; \path[->] (U1) edge node[above,rotate=17] {$431.825 MHz$} (R1) ; \path[->,dashed] (R1) edge node[above] {via Echolink} (R2) ; \path[->] (R2) edge node[above,rotate=-17] {$439.150 MHz$} (U2) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/fm_echolink_c.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DB0SP}; \activerepeater{R2}{6,1}{DB0LDS}; \activeuser{U2}{9,0}{DL2XYZ}; \path[->] (R1) edge node[above,rotate=17] {$439.425 MHz$} (U1) ; \path[->,dashed] (R2) edge node[above] {via Echolink} (R1) ; \path[->] (U2) edge node[above,rotate=-17] {$431.875 MHz$} (R2) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/fm_simplex_a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{U1}{0,0}{DM3MAT}; \user{U2}{6,0}{DL2XYZ}; \path[->] (U1) edge node[above] {$144.500 MHz$} (U2) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/fm_simplex_b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{U1}{0,0}{DM3MAT}; \activeuser{U2}{6,0}{DL2XYZ}; \path[->] (U2) edge node[above] {$144.500 MHz$} (U1) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/repeater.tex ================================================ \newcommand{\repeater}[3]{% \node ({#1}) at ({#2}) {% \begin{tikzpicture}% \draw [black,thick] (-.25,0) -- (0,0.5) -- (0.25,0) -- (-0.25,0);% \draw [black,thick,domain=-45:225] plot ({0.2*cos(\x)}, {0.5+0.2*sin(\x)});% \draw [black,thick,domain=-45:225] plot ({0.4*cos(\x)}, {0.5+0.4*sin(\x)});% \node (xxx) at (0,-.2) {{#3}};% \end{tikzpicture}% } % } \newcommand{\activerepeater}[3]{% \node ({#1}) at ({#2}) {% \begin{tikzpicture}% \draw [black,thick] (-.25,0) -- (0,0.5) -- (0.25,0) -- (-0.25,0);% \draw [red,thick,domain=-45:225] plot ({0.2*cos(\x)}, {0.5+0.2*sin(\x)});% \draw [red,thick,domain=-45:225] plot ({0.4*cos(\x)}, {0.5+0.4*sin(\x)});% \node (xxx) at (0,-.2) {{#3}};% \end{tikzpicture}% } % } \newcommand{\user}[3]{% \node ({#1}) at ({#2}) {% \begin{tikzpicture}% \draw [black,fill=black] (-.25,0) -- (0,0.5) -- (0.25,0) -- (-0.25,0);% \draw [black,fill=black] (0,.5) circle (.2); % \node (xxx) [text width=0.6cm, align=center] at (-.35cm,-.4) {{#3}};% \end{tikzpicture}% } % } \newcommand{\activeuser}[3]{% \node ({#1}) at ({#2}) {% \begin{tikzpicture}% \draw [red,fill=red] (-.25,0) -- (0,0.5) -- (0.25,0) -- (-0.25,0);% \draw [red,fill=red] (0,.5) circle (.2); % \node (xxx) [text width=0.6cm, align=center] at (-.35cm,-.4) {{#3}};% \end{tikzpicture}% } % } ================================================ FILE: doc/dmr-intro/fig/repeater_local.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 2,0}{DL1XYZ}; \user{u3}{ 4,0}{DL2XYZ}; \draw[dotted] (5,4) -- (5,-1); \activeuser{u4}{ 6,0}{DL3XYZ}; \user{u5}{ 8,0}{DL4XYZ}; \user{u6}{10,0}{DL5XYZ}; \activerepeater{R1}{1,3}{DB0ABC}; \repeater{R2}{3,3}{DB0DEF}; \activerepeater{R3}{7,3}{DB0GHI}; \activerepeater{R4}{9,3}{DB0JKL}; \draw[->] (u1) -- node[above,rotate=70]{GC: TG9} (R1); \draw[->] (R1) -- node[above,rotate=-70]{GC: TG9} (u2); \draw[->] (u4) -- node[above,rotate=70]{GC: TG8} (R3); \draw[->] (R3) -- node[above,rotate=-70]{GC: TG8} (u5); \draw[->] (R4) -- node[above,rotate=-70]{GC: TG8} (u6); \path[->] (R3) edge[dashed,bend left] node[above]{via Netzwerk} (R4); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/repeater_privatecall.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT 2621370}; \activerepeater{R1}{1,3}{DB0ABC}; \draw[dotted] (2,4) -- (2,-1); \user{u2}{ 4,0}{I/DL2XYZ\\2621234}; \activerepeater{R2}{3,3}{I0ABC}; \draw[->] (u1) -- node[above,rotate=70]{PC: 2621234} (R1); \draw[->] (R2) -- node[above,rotate=-70]{PC: 2621234} (u2); \path[->] (R1) edge[dashed,bend left] node[above]{via Netzwerk} (R2); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/simplex_allcall.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 6,1}{DL1XYZ, TG99}; \user{u3}{ 6,0}{DL2XYZ, TG99}; \user{u4}{ 6,-1}{DL3XYZ}; \path[->] (u1) edge[bend left] node[above, rotate=10]{$433.450 MHz$} node[below, rotate=10]{All Call} (u2); \path[->] (u1) edge node[above]{$433.450 MHz$} node[below]{All Call} (u3); \path[->] (u1) edge[bend right] node[above, rotate=-10]{$433.450 MHz$} node[below, rotate=-10]{All Call} (u4); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/simplex_groupcall.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 6,1}{DL1XYZ, TG99}; \user{u3}{ 6,0}{DL2XYZ, TG99}; \user{u4}{ 6,-1}{DL3XYZ}; \path[->] (u1) edge[bend left] node[above, rotate=10]{$433.450 MHz$} node[below, rotate=10]{GC: TG99} (u2); \path[->] (u1) edge node[above]{$433.450 MHz$} node[below]{GC: TG99} (u3); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/simplex_privatecall.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \activeuser{u2}{ 6,1}{DL1XYZ, TG99}; \user{u3}{ 6,0}{DL2XYZ, TG99}; \user{u4}{ 6,-1}{DL3XYZ}; \path[->] (u1) edge[bend left] node[above, rotate=10]{$433.450 MHz$} node[below, rotate=10]{PC: DL1XYZ} (u2); \path[->] (u2) edge[bend left] node[above, rotate=10]{$433.450 MHz$} node[below, rotate=10]{PC: DM3MAT} (u1); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/talkgroup_ex1a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 2,0}{DL1XYZ}; \user{u3}{ 6,0}{DL2XYZ}; \draw[dotted] (7,4) -- (7,-1); \user{u4}{10,0}{I/DL3XYZ}; \activerepeater{R1}{1,3}{DB0ABC, TG2621}; \activerepeater{R2}{5,3}{DB0DEF, TG2621}; \repeater{R3}{9,3}{I0ABC}; \path[->] (u1) edge node[above,rotate=70]{GC: TG2621} (R1); \path[->] (R1) edge node[above,rotate=-70]{GC: TG2621} (u2); \path[->] (R2) edge node[above,rotate=-70]{GC: TG2621} (u3); \path[->] (R1) edge[bend left] node[above]{GC: TG2621} (R2); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/talkgroup_ex1b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{u1}{ 0,0}{DM3MAT}; \user{u2}{ 2,0}{DL1XYZ}; \user{u3}{ 6,0}{DL2XYZ}; \draw[dotted] (7,4) -- (7,-1); \activeuser{u4}{10,0}{I/DL3XYZ}; \activerepeater{R1}{1,3}{DB0ABC, TG2621}; \activerepeater{R2}{5,3}{DB0DEF, TG2621}; \activerepeater{R3}{9,3}{I0ABC, (TG2621)}; \path[->] (u4) edge node[above,rotate=-70]{GC: TG2621} (R3); \path[->] (R1) edge node[above,rotate=70]{GC: TG2621} (u1); \path[->] (R1) edge node[above,rotate=-70]{GC: TG2621} (u2); \path[->] (R2) edge node[above,rotate=-70]{GC: TG2621} (u3); \path[->] (R3) edge[bend right] node[below]{GC: TG2621} (R2); \path[->] (R3) edge[bend right] node[above]{GC: TG2621} (R1); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/talkgroup_ex1c.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 2,0}{DL1XYZ}; \user{u3}{ 6,0}{DL2XYZ}; \draw[dotted] (7,4) -- (7,-1); \user{u4}{10,0}{I/DL3XYZ}; \activerepeater{R1}{1,3}{DB0ABC, TG2621}; \activerepeater{R2}{5,3}{DB0DEF, TG2621}; \activerepeater{R3}{9,3}{I0ABC, (TG2621)}; \path[->] (u1) edge node[above,rotate=70]{GC: TG2621} (R1); \path[->] (R1) edge node[above,rotate=-70]{GC: TG2621} (u2); \path[->] (R2) edge node[above,rotate=-70]{GC: TG2621} (u3); \path[->] (R3) edge node[above,rotate=-70]{GC: TG2621} (u4); \path[->] (R1) edge[bend left] node[below]{GC: TG2621} (R2); \path[->] (R1) edge[bend left] node[above]{GC: TG2621} (R3); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/timeslot.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usepackage{pgfplots} \usetikzlibrary{shapes.geometric} \usetikzlibrary{decorations, decorations.text} %\input{repeater} \begin{document} \begin{tikzpicture} \draw[|-,dotted, semithick] (-1,-0.2) -- (0,-0.2); \draw[|-,semithick] (0,-0.2) -- (1,-0.2); \draw[|-,semithick] (1,-0.2) -- (2,-0.2); \draw[|-,semithick] (2,-0.2) -- (3,-0.2); \draw[|-,semithick] (3,-0.2) -- (4,-0.2); \draw[|-,semithick] (4,-0.2) -- (5,-0.2); \draw[|-,semithick] (5,-0.2) -- (6,-0.2); \draw[|->,dotted,semithick] (6,-0.2) -- (7,-0.2); \node at (7, -.5) {$t$}; \draw [thick,decoration={brace,mirror},decorate] (0,-0.4) -- (1,-0.4) node [pos=0.5, anchor=north,yshift=-0.55] {$30\ ms$}; \fill[red!30] (0.1,0) -- (0.1,1) -- (0.9,1) -- (0.9,0) -- cycle; \node at (0.5,0.5) {TS 1}; \fill[blue!30] (1.1,0) -- (1.1,1) -- (1.9,1) -- (1.9,0) -- cycle; \node at (1.5,0.5) {TS 2}; \fill[red!30] (2.1,0) -- (2.1,1) -- (2.9,1) -- (2.9,0) -- cycle; \node at (2.5,0.5) {TS 1}; \fill[blue!30] (3.1,0) -- (3.1,1) -- (3.9,1) -- (3.9,0) -- cycle; \node at (3.5,0.5) {TS 2}; \fill[red!30] (4.1,0) -- (4.1,1) -- (4.9,1) -- (4.9,0) -- cycle; \node at (4.5,0.5) {TS 1}; \fill[blue!30] (5.1,0) -- (5.1,1) -- (5.9,1) -- (5.9,0) -- cycle; \node at (5.5,0.5) {TS 1}; \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/trunk_net_ex1.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{r1}{ 0,0}{Reinigung 1}; \user{r2}{ 2,0}{Reinigung 2}; \draw[dotted] (3,4) -- (3,-1); \user{s1}{ 4,0}{Sicherheit 1}; \user{z} { 6,0}{Zentrale}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Sicherheit 2}; \user{r3}{10,0}{Reinigung 3}; \repeater{R1}{1,3}{Terminal 1, TG: R,S}; \repeater{R2}{5,3}{Terminal 2, TG: R,S}; \repeater{R3}{9,3}{Vorfeld, TG: S}; \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/trunk_net_ex2.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{r1}{ 0,0}{Reinigung 1}; \user{r2}{ 2,0}{Reinigung 2}; \draw[dotted] (3,4) -- (3,-1); \activeuser{s1}{ 4,0}{Sicherheit 1}; \activeuser{z} { 6,0}{Zentrale}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Sicherheit 2}; \activeuser{r3}{10,0}{Reinigung 3}; \activerepeater{R1}{1,3.5}{Terminal 1, TG: R,S}; \activerepeater{R2}{5,3.5}{Terminal 2, TG: R,S}; \activerepeater{R3}{9,3.5}{Vorfeld, TG: S}; \draw[->] (r1) -- node[above,rotate=74] {PC: Reinigung 3} (R1); \path[->,dashed] (R1) edge [bend left] node[above] {via Netzwerk} (R3); \draw[->] (R3) -- node[above,rotate=-74] {PC: Reinigung 3} (r3); \draw[->] (z) -- node[above,rotate=-74] {PC: Sicherheit 1} (R2); \draw[->] (R2) -- node[above,rotate=74] {PC: Sicherheit 1} (s1); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/trunk_net_ex3.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{r1}{ 0,0}{Reinigung 1}; \activeuser{r2}{ 2,0}{Reinigung 2}; \draw[dotted] (3,4) -- (3,-1); \user{s1}{ 4,0}{Sicherheit 1}; \activeuser{z} { 6,0}{Zentrale}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Sicherheit 2}; \user{r3}{10,0}{Reinigung 3}; \activerepeater{R1}{1,3}{Terminal 1, TG: R,S}; \activerepeater{R2}{5,3}{Terminal 2, TG: R,S}; \repeater{R3}{9,3}{Vorfeld, TG: S}; \draw[->] (z) -- node[above,rotate=-74] {TG: R} (R2); \path[->,dashed] (R2) edge [bend right] node[above] {via Netzwerk} (R1); \draw[->] (R1) -- node[above,rotate=74] {TG: R} (r1); \draw[->] (R1) -- node[above,rotate=-74] {TG: R} (r2); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/trunk_net_ex4a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{r1}{ 0,0}{Reinigung 1}; \activeuser{r2}{ 2,0}{Reinigung 2}; \draw[dotted] (3,4) -- (3,-1); \user{s1}{ 4,0}{Sicherheit 1}; \user{z} { 6,0}{Zentrale}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Sicherheit 2}; \user{r3}{10,0}{Reinigung 3}; \activerepeater{R1}{1,3}{Terminal 1, TG: R,S}; \repeater{R2}{5,3}{Terminal 2, TG: R,S}; \activerepeater{R3}{9,3}{Vorfeld, TG: S,(R)}; \draw[->] (r3) -- node[above,rotate=-74] {TG: R} (R3); \path[->,dashed] (R3) edge [bend right] node[above] {via Netzwerk} (R1); \draw[->] (R1) -- node[above,rotate=74] {TG: R} (r1); \draw[->] (R1) -- node[above,rotate=-74] {TG: R} (r2); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/fig/trunk_net_ex4b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{r1}{ 0,0}{Reinigung 1}; \activeuser{r2}{ 2,0}{Reinigung 2}; \draw[dotted] (3,4) -- (3,-1); \user{s1}{ 4,0}{Sicherheit 1}; \activeuser{z} { 6,0}{Zentrale}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Sicherheit 2}; \activeuser{r3}{10,0}{Reinigung 3}; \activerepeater{R1}{1,3}{Terminal 1, TG: R,S}; \activerepeater{R2}{5,3}{Terminal 2, TG: R,S}; \activerepeater{R3}{9,3}{Vorfeld, TG: S,(R)}; \draw[->] (z) -- node[above,rotate=-74] {TG: R} (R2); \path[->,dashed] (R2) edge [bend right] node[above] {via Netzwerk} (R1); \path[->,dashed] (R2) edge [bend left] node[above] {via Netzwerk} (R3); \draw[->] (R1) -- node[above,rotate=74] {TG: R} (r1); \draw[->] (R1) -- node[above,rotate=-74] {TG: R} (r2); \draw[->] (R3) -- node[above,rotate=-74] {TG: R} (r3); \end{tikzpicture} \end{document} ================================================ FILE: doc/dmr-intro/script/Makefile ================================================ clean: rm *.aux *.idx *.ilg *.ind *.log *.out *.pdf *.synctex.gz *.toc ================================================ FILE: doc/dmr-intro/script/script_de.tex ================================================ \documentclass[11pt, a4paper,parskip=half]{scrartcl} \usepackage{standalone} \usepackage[utf8]{inputenc} \usepackage{geometry} \usepackage{graphics} \usepackage{subcaption} \usepackage{eurosym} \usepackage[ngerman]{babel} \usepackage{rotating} \usepackage{makeidx} \usepackage[tikz]{bclogo} \usepackage{tikz} \usetikzlibrary{shapes.geometric,patterns,snakes} \usepackage{hyperref} \hypersetup{colorlinks=true, linkcolor=blue, filecolor=blue, urlcolor=blue} \urlstyle{same} \title{DMR -- Digital Mobile Radio} \subtitle{Ein Mobilfunknetz für Funkamateure} \author{Hannes Matuschek, DM3MAT,\\\texttt{dm3mat [at] darc [dot] de}} \date{\today} \input{../fig/repeater} \newenvironment{merke}{\begin{bclogo}[couleur=blue!30,arrondi=.1,logo=\bccrayon,ombre=true]{Merke}}{\end{bclogo}} \newenvironment{hinweis}{\begin{bclogo}[couleur=blue!30,arrondi=.1,logo=\bcinfo,ombre=true]{Hinweis}}{\end{bclogo}} \newenvironment{achtung}{\begin{bclogo}[couleur=red!30,arrondi=.1,logo=\bcattention,ombre=true]{Achtung}}{\end{bclogo}} \newcommand{\adef}[1]{\emph{#1}\index{#1}} \newcommand{\aref}[1]{#1\index{#1}} \newcommand{\altdef}[2]{\emph{#1}\index{#1|seealso{#2}}} \makeindex \begin{document} \begin{titlepage} \maketitle \vfill \begin{abstract} Dieses Script soll eine Einführung in DMR (digital mobile radio) für den unbedarften Funkamateur oder jeden Interessierten sein. Ich versuche dem Leser Details solange zu verheimlichen, bis es absolut notwendig wird diese zu erklären. Die meisten Einführungen in DMR, die ich bisher gesehen habe, sind eher eine lange Liste von Begriffserklärungen, die ohne Erfahrung mit DMR schwer zu verstehen sind. Viel der empfundenen Komplexität von DMR, rührt aus dem Ursprung dieser Technik. DMR wurde für den kommerziellen Funk auf Großveranstaltungen oder großen industriellen Anlagen entwickelt, auch \href{https://de.wikipedia.org/wiki/B\%C3\%BCndelfunk}{Bündelfunk} genannt. Ich werde daher mit einem Beispiel erläutern wofür DMR entwickelt wurde und fange erst dann an zu erklären, wie DMR für den Amateurfunk eingesetzt wird. \end{abstract} \end{titlepage} \pagebreak \tableofcontents \pagebreak \input{script_de_01_vorwissen} \input{script_de_02_ursprung} \input{script_de_03_simplex} \input{script_de_05_privatecall} \input{script_de_04_lokal} \input{script_de_07_talkgroup} \input{script_de_06_textmsg} \input{script_de_11_technik} \input{script_de_09_roaming} \input{script_de_12_codeplug} \input{script_de_10_netze} \appendix \printindex \end{document} ================================================ FILE: doc/dmr-intro/script/script_de_01_vorwissen.tex ================================================ \section{Vorwissen: Relaisbetrieb} \label{sec:vorwissen} \index{Relaisbetrieb} In diesem Abschnitt werde ich kurz den \emph{klassischen} FM\footnote{FM steht für \emph{Frequenzmodulation} und beschreibt ein analoges Modulationsverfahren, bei dem eine Trägerfrequenz im \emph{Rhythmus} der zu übertragenen Sprache in der Frequenz verschoben wird.}-Relaisbetrieb auf VHF\footnote{Als VHF (\emph{very high frequency}) werden die Frequenzen zwischen $30Mhz$ und $300MHz$ bezeichnet.} und UHF\footnote{Als UHF (\emph{ultra high frequency}) werden die Frequenzen zwischen $300Mhz$ und $3000MHz$ bezeichnet.} im Amateurfunk beschreiben. Die allermeisten lizenzierten Funkamateure werden dies noch aus der Prüfung zur Betriebstechnik oder aus eigener Erfahrung wissen. Wenn Sie sich aber für Amateurfunk interessieren oder selbst noch keine Erfahrung mit dem Relaisbetrieb haben, empfehle ich Ihnen diesen Abschnitt zu lesen. \begin{figure}[!ht] \centering \input{../fig/fm_simplex_a} \input{../fig/fm_simplex_b} \caption{Einfacher Simplexbetrieb, DM3MAT sendet auf der Frequenz $144.500 MHz$ direkt zu DL2XYZ. Dieser antwortet dann auf der selben Frequenz.} \label{fig:basicsimlpex} \end{figure} Die meisten Verbindungen zwischen zwei Funkamateuren finden im so genannten \adef{Simplexbetrieb} statt. Das heißt, die zwei Funkamateure senden und empfangen abwechselnd auf der selben Frequenz und die Verbindung zwischen ihnen ist direkt (siehe Abb. \ref{fig:basicsimlpex}). Dies funktioniert auf Kurzwelle\footnote{Als Kurzwelle oder einfach HF (\emph{high frequency}) werden Frequenzen zwischen $3MHz$ und $30MHz$ bezeichnet.} sehr gut und man kann damit weltweite Verbindungen aufbauen. Auf höheren Frequenzen verhält sich die Radiowelle zunehmend wie Licht und es wird auf VHF und UHF schwierig ohne viel Aufwand\footnote{Auch auf VHF und UHF können sehr große Entfernungen überbrückt werden, nur sind dann große Richtantennen oder ein sehr hoher Standort von Nöten.} wesentlich weiter als bis zum Horizont zu gelangen. Diese Tatsache schränkt die Reichweite gerade von Handfunkgeräten stark ein. Um dennoch einen größeren Bereich überbrücken zu können, wenn man nicht gerade über einen hohen Berg mit einer großen Antenne verfügt, können sogenannte \emph{Repeater} oder \emph{Relais} verwendet werden. \adef{Repeater}\index{Relais|seealso{Repeater}} sind automatisch arbeitende Amateurfunkstationen, die meist in exponierten Lagen (hoher Berg oder hoher Turm) installiert werden, um einen möglichst großen Bereich abdecken zu können. Ihre Aufgabe ist es, Aussendungen von Funkamateuren zu empfangen und gleichzeitig wieder auszusenden. Da diese Repeater gleichzeitig empfangen und senden müssen, können sie das nicht auf der selben Frequenz tun. Daher werden diese Repeater im sogenannten \adef{Duplexbetrieb} gefahren. Das heißt, der Repeater empfängt auf einer Frequenz (der sog. \adef{Eingabefrequenz}) und sendet eben dieses empfangende Signal gleichzeitig auf einer anderen Frequenz (der sog. \adef{Ausgabefrequenz}) wieder aus. \begin{figure}[!ht] \centering \input{../fig/fm_duplex_a} \input{../fig/fm_duplex_b} \caption{Einfacher Repeaterbetrieb, DM3MAT sendet auf der Eingabefrequenz $431.9625 MHz$ zum Repeater (DB0LDS) und dieser setzt das empfangende Signal direct auf der Ausgabefrequenz $439.5625 MHz$ wieder ab. Auf dieser Frequenz kann DL2XYZ das umgesetzte Signal wieder empfangen.} \label{fig:basicrepeater} \end{figure} Für das konkrete Beispiel in Abbildung \ref{fig:basicrepeater} bedeutet das, dass DM3MAT auf der Repeatereingabefrequenz (hier $431.9625 MHz$) sendet. Dieses Signal wird vom Repeater (hier DB0LDS) empfangen und gleichzeitig wieder auf der Ausgabefrequenz (hier $439.5625 MHz$) ausgesandt. Diese Aussendung kann nun von DL2XYZ auf der Repeaterausgabefrequenz empfangen werden. Die Antwort von DL2XYZ an DM3MAT folgt den gleichen Weg, hier sendet DL2XYZ auf der Repeatereingabefrequenz und DM3MAT kann diese Aussendung auf der Repeaterausgabefrequenz empfangenen. Auf diese Wiese können zwei Funkamateure miteinander kommunizieren, auch wenn sie sich nicht direkt erreichen können. \subsection{Echolink} \label{sec:echolink} \index{Echolink} Wenn zwei Funkamateure miteinander kommunizieren wollen, die sehr weit voneinander entfernt sind und somit nicht beide einen gemeinsamen Repeater erreichen können, gibt es die Möglichkeit zwei Repeater \emph{zusammenzuschalten}. \begin{figure}[!ht] \centering \input{../fig/fm_echolink_a} \input{../fig/fm_echolink_b} \input{../fig/fm_echolink_c} \caption{Repeaterbetrieb mit Echolink. DM3MAT verbindet die Repeater DB0SP (bei Berlin) und DB0LEI (bei Leipzig) per Echolink. Daraufhin können DM3MAT und DL2XYZ wie über einen gemeinsamen Repeater kommunizieren.} \label{fig:echolink} \end{figure} Diese Möglichkeit nennt sich \href{http://www.echolink.org/}{Echolink}. Dieses Netzwerk erlaubt es FM Repeater per Internet miteinander zu verbinden oder sich per Internet als einzelner Teilnehmer direkt mit einem Repeater zu verbinden. Viele FM Repeater sind in diesem Netzwerk zusammengeschlossen. Es ist auch häufig möglich\footnote{Dies hängt von der Konfiguration des Repeaters ab.} per Funk einen Repeater zu steuern und ihn mit einem anderen Repeater via Echolink zu verbinden. Dazu wird die sogenannte Echolink Nummer des Ziel Repeaters per DTMF Tonwahl an den Quellrepeater gesandt. Dies ist in Abbildung \ref{fig:echolink} (Oben) dargestellt. Hier sendet DM3MAT die Echolink Nummer 662699 des Relais DB0LEI bei Leipzig per DTMF an den Repeater DB0SP nahe Berlin. Dieser (DB0SP) verbindet sich dann mit dem Zielrepeater bei Leipzig (DB0LEI) über das Echolink Netzwerk. Alle weiteren Aussendungen die der Quellrepeater (DB0SP) nun empfängt werden nicht nur lokal auf der Ausgabefrequenz ausgesandt, sonder werden auch am Zielrepeater bei Leipzig (DB0LEI) ausgesandt (Abb. \ref{fig:echolink} Mitte). Somit kann DL2XYZ in Leipzig DM3MAT hören. Ebenso werden alle Aussendungen die der Zielrepeater (DB0LEI) empfängt via Echolink zum Quellrepeater bei Berlin übertragen und auch dort ausgesandt (Abb. \ref{fig:echolink} Unten). Auf diese weise können zwei Funkamateure (in diesem Beispiel DM3MAT \& DL2XYZ), die sich nicht in der Nähe des selben Repeaters befinden, dennoch miteinander kommunizieren. \begin{merke} Sobald zwei Repeater per Echolink miteinander verbunden sind, verhalten sich beide wie ein einziger Repeater. \end{merke} Es gibt überall auf der Welt FM Repeater die per Echolink erreichbar sind. Dadurch ist es möglich jederzeit weltweite Kontakte mit einfachsten Mitteln (FM Handfunkgeräte mit DTMF Funktion sind ab ca. \EUR{40} erhältlich) herzustellen. ================================================ FILE: doc/dmr-intro/script/script_de_02_ursprung.tex ================================================ \section{DMR Einführung \& Ursprung} \label{sec:ursprung} DMR kurz für \emph{digital mobile radio} ist ein digitaler Funkstandard für Sprech- und Datenfunk. Das heißt, die Sprache wird nicht direkt per FM auf einem Kanal übertragen, sondern zuerst digitalisiert, mit einem verlustbehafteten Codec kodiert und erst dann als Datenpaket übertragen. Dies ermöglicht es, bei jedem Ruf\footnote{PTT Taste drücken, ins Funkgerät sprechen und dann die PTT Taste wieder loslassen.} zusätzliche Informationen wie Quelle und Ziel des Rufs mitzuübertragen. DMR wurde als Ersatz für den analogen Bündelfunk in der kommerziellen Anwendung entwickelt. Ein klassisches Beispiel für den kommerziellen Einsatz von DMR wäre ein Verkehrsflughafen. Damit ist nicht der Flugfunk auf dem Feld und in der Luft gemeint, sondern der Funkbetrieb zwischen dem ganzen Bodenpersonal. Auf so einem Flughafen arbeiten sehr viele Leute mit sehr unterschiedlichen Aufgaben. Da hätten wir (ohne Anspruch auf Vollständigkeit) \begin{itemize} \item Die Reinigungskolonne, \item die Sicherheitsleute wie Gepäckkontrolle oder Wachschutz, \item das Vorfeld, also die Betankung, die Gepäckverladung \& das Catering, \item die Betriebsfeuerwehr und \item die Zentrale. \end{itemize} All diese Mitarbeiter bekommen ein Funkgerät und sollen die folgenden Möglichkeiten haben: \begin{itemize} \item Direkte Kommunikation zur Zentrale, alle Personen sollen die Zentrale erreichen können. \item Direkte Kommunikation zwischen zwei Personen innerhalb ihrer Gruppe ohne das andere Gruppen gestört werden. Das heißt, die Reinigungskolonne sollte sich untereinander absprechen können, ohne die Betriebsfeuerwehr zu stören. \item Sogenannte Gruppenrufe einer Person an eine ganze Gruppe. Zum Beispiel ruft die Zentrale die gesamte Betriebsfeuerwehr an. Aber auch ein Anruf eines Wachschützers an alle anderen Wachschützer, um zum Beispiel Hilfe anzufordern. \end{itemize} Gleichzeitig ist so ein Flughafen ein riesiges Gelände. Das heißt, nicht alle Mitarbeiter können alle anderen Mitarbeiter direkt erreichen. Es müssen also Repeater aufgestellt werden, damit das gesamte Gelände und alle Innenräume per Funk abgedeckt sind. Daher wird häufig in jedem Gebäude mindestens ein Repeater aufgestellt. Vergleicht man nun die Ansprüche dieses Kommunikationsnetzes mit dem klassischen FM-Repeaterbetrieb (Abs. \ref{sec:vorwissen}), wird schnell deutlich, dass es sehr schwierig wird dieses Konzept per analog FM-Repeater umzusetzen. Vor allem wenn mehrere Repeater in einem Netz (ähnlich Echolink) verbunden sind. Jede Kommunikation zwischen zwei Personen würde dann das gesamte Kommunikationsnetz belegen. Besser wäre es, wenn nur jene Repeater aktiv würden, die für die Kommunikation zwischen zwei Teilnehmern nötig sind. Dann stünden alle anderen Repeater für weitere Verbindungen bereit. Dieses Routing von Verbindungen sollte aber automatisch geschehen, da die zwei Teilnehmer nicht immer wissen werden, wo sich die jeweils andere Person befindet und somit mit welchem Repeater sie sich verbinden müssen. Um solche komplexen Kommunikationsnetze realisieren zu können, ohne dass die Teilnehmer detailliertes Wissen über dessen physische Struktur\footnote{Wissen darüber wo sich welcher Repeater befindet und wo sich welche Teilnehemer aufhalten.} benötigen, wurde DMR entwickelt. \begin{merke} DMR hat mehr Ähnlichkeit mit einem Telefonnetz mit zusätzlichen Gruppenruf als mit klassischem FM-Repeaterbetrieb. \end{merke} Das heißt, jeder Teilnehmer und damit dessen Funkgerät besitzt eine eindeutige Nummer\index{DMR-ID}. Diese Nummer liegt im Bereich $1$--$16777215$. Und wie bei einem gewöhnlichen Telefonnetz, kann ein Teilnehmer einen Anderen mit seiner Nummer direkt anrufen. Dies wird \adef{Direktruf} oder auch \altdef{Private Call}{Direktruf} genannt. Außerdem werden Gruppen definiert, die wieder ihre eigene Nummer erhalten. Die sogenannte \adef{Sprechgruppe} oder auch \altdef{Talk Group}{Sprechgruppe} (\altdef{TG}{Sprechgruppe}). Diese Sprechgruppen dienen dazu, alle Mitarbeiter einer bestimmten Gruppe (z.B., den Wachschutz, die Betriebsfeuerwehr, etc.) gleichzeitig erreichen zu können. Das heißt, das Funkgerät einer Reinigungskraft muss wissen, dass es auf die Gruppenrufe der Sprachgruppe \emph{Reinigung} reagieren muss, aber alle anderen Sprechgruppen ignorieren soll. \begin{merke} Dieser Punkt ist sehr wichtig: Das DMR Netz selbst weiß nicht, welcher Teilnehmer zu welcher Gruppe gehört. Das Funkgerät des Teilnehmers wird so konfiguriert, dass es nur auf bestimmte Gruppenrufe reagiert. \end{merke} \begin{figure}[!ht] \centering \input{../fig/trunk_net_ex1} \caption{Ein Beispielnetzwerk für den hypothetischen Flughafen. Es gibt drei Reinigungskräfte, zwei Sicherheitsleute und eine Zentrale. Um das gesammte Gelände abdecken zu können, werden drei Repeater benötigt einer in Terminal 1, einer in Terminal 2 und einer im Vorfeld.} \label{fig:exnet1} \end{figure} In Abbildung \ref{fig:exnet1} sei ein Beispielnetzwerk für den Flughafen dargestellt (in Wirklichkeit viel größer und komplexer). Nun stellen wir uns die Situation vor, dass die Reinigungskräfte 1 \& 3 miteinander Sprechen wollen und gleichzeitig die \emph{Zentrale} mit \emph{Sicherheit 1}. In einem einfachen analog Netz, bei dem alle Repeater einfach zusammengeschaltet wären, würde das Gespräch zwischen \emph{Reinigung 1} \& \emph{3} das gesamte Netz blockieren und die Verbindung zwischen \emph{Zentrale} und \emph{Sicherheit 1} wäre nicht möglich. \begin{figure}[!ht] \centering \input{../fig/trunk_net_ex2} \caption{Zwei gleichzeitige Direktrufe (Private Calls, PC) in dem Beispielnetzwerk zwischen \emph{Reinigung 1 \& 3} sowie zwischen \emph{Zentrale} und \emph{Sicherheit 1}} \label{fig:exnet2} \end{figure} In einem DMR Netz hingegen, werden für einen Direktruf (Privat Call) nur jene Repeater verwendet, die dafür nötig sind. Dies ist in Abbildung \ref{fig:exnet2} zu sehen: \emph{Reinigung 1} startet einen Direktruf (Private Call) über ihren lokalen Repeater in \emph{Terminal 1}. Da das DMR Netzwerk weiß, über welchen Repeater \emph{Reinigung 3} zuletzt aktiv war, wird der Direktruf vom DMR Netz über eben diesen Repeater auf dem Vorfeld etabliert. Der Repeater im Terminal 2 hingegen wird für diesen Direktruf nicht aktiv. Daher steht dieser Repeater weiterhin zur Verfügung. Dies nutzt die Zentrale um \emph{Sicherheit 1} per Direktruf zu erreichen. \begin{merke} Das DMR-Netz weiß lediglich über welchen Repeater ein Teilnehmer zuletzt aktiv war. Das Netz wird daher versuchen, einen Direktruf für diesen Teilnehmer über eben diesen Repeater zu vermitteln. \end{merke} Solange das Gespräch zwischen \emph{Reinigung 1 \& 3} anhält sind aber die Repeater im Terminal 1 und auf dem Vorfeld belegt. Das heißt, die Zentrale kann \emph{Reinigung 2} und \emph{Sicherheit 2} nicht erreichen. Dies klingt schlimmer als es ist. Im Gegensatz zu klassischen Telefonaten gilt im DMR Netz ein Direktruf als unterbrochen sobald ein Teilnehmer die PTT Taste loslässt. Daher kann die Zentrale in den Umschaltpausen des Gespräches \emph{dazwischenrufen} und so zum Beispiel \emph{Sicherheit 2} erreichen. Im nächsten Beispiel (Abbildung \ref{fig:exnet3}) will die Zentrale alle Reinigungskräfte erreichen. Dazu macht sie einen Gruppenruf zur Sprechgruppe/Talk Group \emph{Reinigung} (R für Reinigung, S für Sicherheit). Damit erreicht sie die \emph{Reinigung 1 \& 2} problemlos, aber \emph{Reinigung 3} empfängt diesen Gruppenruf nicht. Dies liegt daran, dass das DMR Netz nicht weiß, welche Personen zu welcher Gruppe gehören. Da sich Reinigungskräfte üblicherweise nicht auf dem Vorfeld herumtreiben, hat der Repeater auf dem Vorfeld die Sprechgruppe \emph{Reinigung (R)} nicht \emph{abonniert} und leitet daher keine Gruppenrufe für diese Sprechgruppe weiter. \begin{figure}[p] \begin{subfigure}{\linewidth} \centering \input{../fig/trunk_net_ex3} \caption{Ein Gruppenruf zur Sprechgruppe \emph{Reinigung} von der Zentrale aus. Der Teilnehmer \emph{Reinigung 3} wird aber nicht erreicht, da der Vorfeldrepeater diese Sprechgruppe nicht abonniert hat.} \label{fig:exnet3} \end{subfigure}\vspace{0.5cm} \begin{subfigure}{\linewidth} \centering \input{../fig/trunk_net_ex4a} \caption{Teilnehmer \emph{Reinigung 3} abonniert die Sprechgruppe \emph{Reinigung} temporär auf dem Vorfeldrepeater, indem er einen Gruppenruf zu dieser Sprechgruppe startet.} \label{fig:exnet4a} \end{subfigure}\vspace{.5cm} \begin{subfigure}{\linewidth} \centering \input{../fig/trunk_net_ex4b} \caption{Nach der temporären Abonnierung, ist nun der Teilnehmer \emph{Reinigung 3} auch auf dem Vorfeld erreichbar.} \label{fig:exnet4b} \end{subfigure} \caption{Temporäres Abonnement einer Sprechgruppe auf einem Repeater.} \label{fig:exnet4} \end{figure} Damit die Reinigungskraft 3 jedoch für Gruppenrufe erreichbar bleibt, muss sie die Sprechgruppe \emph{Reinigung} auf dem Vorfeldrepeater temporär abonnieren. Dazu startet sie einen Gruppenruf zur Sprechgruppe \emph{Reinigung} vom Vorfeldrepeater aus (siehe Abb. \ref{fig:exnet4a}). Damit abonniert der Vorfeldrepeater diese Sprechgruppe für eine begrenzte Zeit\footnote{Diese Zeit wird auf jedem einzelnen Repeater konfiguriert. Üblich sind Zeiten zwischen $10$ und $30$ Minuten.} und wird während dieser Zeit Gruppenrufe dieser Sprechgruppe aussenden. Dieses temporäre Abonnement wird jedes mal erneuert oder wiederhergestellt, wenn ein Gruppenruf zu dieser Sprechgruppe von diesem Repeater aus initiiert wird. Das heißt, das Abonnement verlängert sich jedes mal, wenn \emph{Reinigung 3} einen Gruppenruf zur Sprechgruppe \emph{Reinigung} startet oder darauf antwortet\footnote{Das Antworten auf einen Gruppenruf ist technisch identisch zum Start eines neuen Gruppenrufs.}. Mit diesen Beispielen sind die wichtigsten Grundbegriffe von DMR (DMR-ID, Talk Groups, Private sowie Group Call \& Talk Group Abonnement) eingeführt und deren Verwendung in einem Beispiel DMR-Netz erläutert worden. In den nächsten Absätzen wird die Verwendung von DMR im Amateurfunk beschrieben. ================================================ FILE: doc/dmr-intro/script/script_de_03_simplex.tex ================================================ \section{DMR Simplex Betrieb} \label{sec:simplex} Die einfachste Form eines DMR QSOs\footnote{Für alle nicht-Funkamateure: QSO ist eine Abkürzung die eine Verbindung zwischen zwei Amateurfunkstationen beschreibt, gelesen als \emph{Verbindung} oder \emph{Gespräch}.} ist der \aref{Simplexbetrieb}. Dabei wird eine direkte Verbindung zwischen zwei DMR Funkgeräten aufgebaut. Wie beim DMR Repeaterbetrieb, kann so eine Verbindung ein Direktruf, Gruppenruf oder auch ein sogenannter \adef{Rundumruf} (auch \altdef{All Call}{Rundumruf} genannt) sein. \begin{figure}[!ht] \centering \input{../fig/simplex_privatecall} \caption{Beispiel eines DMR Simplex Direktrufs von DM3MAT an DL1XYZ.} \label{fig:splxpc} \end{figure} In Abbildung \ref{fig:splxpc} ist ein einfacher Simplex Direktruf von DM3MAT an DL1XYZ dargestellt sowie dessen Antwort. Beide senden und empfangen auf der selben Frequenz (hier der DMR Anruffrequenz von $433.450 MHz$). Auch wenn die beiden anderen Teilnehmer in der Nähe (DL2XYZ \& DL3XYZ) diesen Ruf physikalisch empfangen, bleiben deren Funkgeräte stumm. Wie dem auch sei, der Kanal ist jedoch während dieses Direktrufes belegt. An dieser Stelle ist es sinnvoll zu erwähnen, dass wenn DL1XYZ direkt auf den Direktruf von DM3MAT antwortet, indem er die PTT Taste drückt, er mit einem Direktruf an DM3MAT antwortet, ohne dafür die Nummer von DM3MAT aus seinen Kontakten heraussuchen zu müssen. Dieser direkte Rückruf funktioniert nur wenige Sekunden nach dem Ende des initialen Direktrufs durch DM3MAT. Nach dieser Zeitspanne wird beim drücken auf die PTT der \aref{Standardkontakt} für diesen Kanal angerufen, der für jeden Kanal im Funkgerät festgelegt werden kann (siehe Abs. \ref{sec:cp:channel}). Diese Zeitspanne (genannt \aref{Hangtime}) lässt sich ebenfalls im Funkgerät einstellen. \begin{figure}[!ht] \centering \input{../fig/simplex_groupcall} \caption{Beispiel eines DMR Simplex Gruppenrufs von DM3MAT an die Sprechgruppe TG99.} \label{fig:splxgc} \end{figure} Um im Simplexbetrieb nicht nur einzelne Teilnehmer anrufen zu können, sind auch Gruppenrufe im Simplexbetrieb möglich. Eine beliebte Sprechgruppe (Talk Group) für den Simplexbetrieb ist die Gruppe mit der Nummer 99 (TG99 abgekürzt, für \emph{Talk Group 99}). Solche Gruppenrufe werden dann von allen Funkgeräten empfangen, die entsprechend konfiguriert wurden. Wie beim Repeaterbetrieb muss auch beim Simplexbetrieb dem Funkgerät mitgeteilt werden, welche Sprechgruppen es auf welchen Kanälen empfangen soll (siehe Abs. \ref{sec:cp:grouplist}). In Abbildung \ref{fig:splxgc} ist solch ein Simplex Gruppenruf von DM3MAT an die Sprechgruppe TG99 dargestellt. Da DL1XYZ und DL2XYZ ihre Funkgeräte so konfiguriert haben, dass sie die TG99 empfangen, hören sie den Ruf von DM3MAT. Da DL3XYZ dies nicht gemacht hat, empfängt er diesen Ruf nicht. DL1XYZ und DL2XYZ können nun auf diesen Gruppenruf antworten, wenn sie innerhalb der sogenannten \adef{Haltezeit} (\altdef{Hangtime}{Haltezeit}) auf ihre PTT Taste drücken. Sie würden dann ebenfalls mit einem Gruppenruf zur TG99 antworten (der direkte Rückruf funktioniert auch für Gruppenrufe), auch wenn sie einen anderen Standardkontakt für diesen Simplexkanal eingestellt haben. \begin{figure}[!ht] \centering \input{../fig/simplex_allcall} \caption{Beispiel eines DMR All Calls von DM3MAT alle die ihn hören können.} \label{fig:splxac} \end{figure} Um wirklich sicher zu gehen, dass ein Ruf auf einem Simplexkanal von allen empfangen werden kann, sollte ein sogenannter \adef{All Call} verwendet werden. Dies ist ein spezieller Ruf an eine ganz bestimmte Nummer ($16777215$), die von allen Geräten empfangen werden, unabhängig von der Konfiguration dieser Geräte. In diesem Beispiel wird somit der Ruf von DM3MAT auch von DL3XYZ empfangen. Durch den direkten Rückruf ist es allen Teilnehmern wieder möglich auf den All Call von DM3MAT zu antworten, auch wenn diese Teilnehmer den All Call nicht als den Standardkontakt für diesen Kanal konfiguriert haben. \begin{merke} Zusammengefasst: Ein DMR-Kanal besitzt eine Sende- \& Empfangsfrequenz (bei Simplex identisch), einen Standardkontakt der angerufen wird, wenn die PTT Taste auf diesem Kanal gedrückt wird und eine Liste von Gruppenrufen, die auf diesem Kanal empfangen werden sollen. \end{merke} \subsection{DMR Simplex Frequenzen} \begin{table}[!ht] \centering \begin{tabular}{|l|c||l|c|} \hline Name & Frequenz & Name & Frequenz \\ \hline \hline S0 (Anruf) & $433.4500 MHz$ & S4 & $433.6500 MHz$ \\ S1 & $433.6125 MHz$ & S5 & $433.6625 MHz$ \\ S2 & $433.6250 MHz$ & S6 & $433.6750 MHz$ \\ S3 & $433.6375 MHz$ & S7 & $433.6875 MHz$ \\ \hline \end{tabular} \caption{Liste der acht üblichen DMR Simplexkanäle. Der Kanal \emph{S0} ist der Anrufkanal.} \label{tab:simplex} \end{table} Im Tabelle \ref{tab:simplex} sind die acht üblichen Simplexkanäle aufgelistet. Der Simplexkanal \emph{S0} ist dabei der Anrufkanal. Gerade in Ballungsgebieten sollte für das eigentliche QSO der Kanal vom Anrufkanal auf einen der sieben weiteren Simplexkanäle \emph{S1-7} gewechselt werden, um den Anrufkanal nicht zu blockieren. ================================================ FILE: doc/dmr-intro/script/script_de_04_lokal.tex ================================================ \section{Lokaler Repeater Betrieb} \label{sec:lokal} Eigentlich ist es das Ziel von DMR, transparent gegenüber Repeatern zu sein. Das heißt, es spielt keine Rolle für den Teilnehmer welchen Repeater er benutzt. Er wird immer die selben Teilnehmer erreichen können. Dieses Konzept wird aber durch die Sprechgruppen mit den Nummern $8$ und $9$ durchbrochen. \begin{figure}[!ht] \centering \input{../fig/repeater_local} \caption{Beispiel mit zwei Regionen (links \& rechts) mit je zwei Repeatern.} \label{fig:tg9tg8} \end{figure} Die Sprechgruppe 9 (kurz TG9) ist die sogenannte \emph{lokale} Sprechgruppe. Gruppenrufen zu dieser Sprechgruppe werden nicht über das Netzwerk weitergeleitet, sonder nur lokal vom jeweiligen Repeater ausgesandt. Dieser Fall ist in Abbildung \ref{fig:tg9tg8} links dargestellt. Hier sendet DM3MAT einen Gruppenruf zur TG9 über den Repeater DB0ABC. Dieser Ruf wird nicht an weitere Repeater übertragen und ist somit nur in der Umgebung des Repeaters zu hören. DL1XYZ befindet sich in der Nähe des Repeaters und kann den Ruf empfangen, wenn er sein Funkgerät so konfiguriert hat, dass es Gruppenrufe an die TG9 empfängt. Die Sprechgruppe 8 (TG8) ist die sogenannte \emph{regionale} Sprechgruppe. Ein Gruppenruf zu dieser Sprechgruppe wird meist durch alle Repeater innerhalb einer Region ausgesandt. Welche Repeater zu einer Region gehören und wie groß diese Region letztendlich ist, entscheiden die Administratoren der jeweiligen Repeater. Sie entscheiden ob ihre Repeater zu einer Region gehören sollen oder nicht. Im Beispiel in Abbildung \ref{fig:tg9tg8} rechts, sendet DL3XYZ einen Gruppenruf zur Sprechgruppe 8 an den Repeater DB0GHI, dieser sendet diesen Gruppenruf selbst aus und leitet ihn an alle Repeater im regionalen Verbund (auch \adef{Cluster}) weiter. In diesem Fall auch an den Repeater DB0JKL. Somit können alle Teilnehmer in der Region diesen Gruppenruf empfangen, solange sie ihre Funkgeräte entsprechend konfiguriert haben. In diesem Beispiel empfängt somit nicht nur DL4XYZ den Gruppenruf sondern auch DL5XYZ, auch wenn er sich nicht in der Nähe des Repeaters DB0GHI befindet. ================================================ FILE: doc/dmr-intro/script/script_de_05_privatecall.tex ================================================ \section{Direkte Anrufe} \label{sec:privatecall} Direktrufe (\aref{Private Call}) ermöglichen es mit einem anderen Teilnehmer direkt zu kommunizieren, ohne dabei weitere Teilnehmer zu stören (bis auf das Belegen eines Repeaters). Im Rahmen der DMR Einführung wurde der Direktruf auch über mehrere Repeater hinweg beschrieben. Eben dieser Aspekt von DMR ist meiner Meinung nach besonders interessant. Mit Ausnahme der Sprechgruppen 8 \& 9 (siehe Abs. \ref{sec:lokal}), sind Direkt- und Gruppenrufe in DMR transparent gegenüber den verwendeten Repeatern. Es spielt keine Rolle über welchen Repeater sich Teilnehmer an einem Direktruf beteiligen und somit auch nicht wo sie sich befinden. \begin{figure}[!ht] \centering \input{../fig/repeater_privatecall} \caption{Beispiel eines Direktrufs über Ländergrenzen hinweg.} \label{fig:pc} \end{figure} Das heißt, YLs \& OMs\footnote{Für nicht-Funkamateure: Zwei weitere typische Abkürzungen im Amateurfunk die \emph{young lady} und \emph{old man} bedeuten und alle weiblichen b.z.w. männlichen Funkamateure bezeichnet.}, die sich im Urlaub aufhalten, können wie gewohnt an ihren lokalen Nachmittagsrunden teilnehmen, indem sie am Urlaubsort einen DMR Repeater auswählen und von dort aus einen Gruppenruf zu ihrer Sprechgruppe in der Heimat starten. Damit abonnieren sie ihre Sprechgruppe an ihrem Urlaubsrepeater temporär und dieser verhält sich danach wie ein Repeater der in der Heimat steht. Ebenso können sie Direktrufe vom Urlaubsort an Bekannte absetzen und am Urlaubsort empfangen. Vorausgesetzt, sie haben sich durch kurzes drücken auf die PTT Taste beim Repeater am Urlaubsort angemeldet, damit das DMR Netz weiß, wo der Teilnehmer zu finden ist. Damit müssen die Teilnehmer in der Heimat aber nicht mehr wissen wie und wo sie den Urlauber erreichen können. Sie starten einfach einen Direktruf zum Urlauber und das DMR Netz kümmert sich um alles. In Abbildung \ref{fig:pc} ist eben solch ein Direktruf über Ländergrenzen hinweg dargestellt. DM3MAT ruft via seinem lokalen Repeater (DB0ABC) den Urlauber DL2XYZ per Direktruf an. Da sich dieser bei einem DMR Repeater (I0ABC) an seinem Urlaubsort in Italien angemeldet hat\footnote{Um sich an einem Repeater anzumelden, damit das Netzwerk weiß, dass man über diesen Repeater erreichbar ist, drückt man kurz die PTT Taste auf einem Kanal des Repeaters.}, kann der Direktruf an DL2XYZ vermittelt werden. Um diesen Direktruf durchzuführen, muss DM3MAT nicht wissen über welchen Repeater der Urlauber DL2XYZ erreichbar ist. Diese Eigenschaft des DMR Netzes stellt eine deutliche Vereinfachung gegenüber dem \aref{Echolink} Netzwerk dar. ================================================ FILE: doc/dmr-intro/script/script_de_06_textmsg.tex ================================================ \section{Datendienste} \label{sec:data} Da DMR von sich aus schon eine digitale Betriebsart ist, bei der meist Sprache in digitalisierter Form übertragen wird, ist es natürlich auch möglich reine Datendienste über DMR anzubieten. Zum einen gibt es einen Textnachrichtendienst, der dem SMS-Dienst der Mobiltelefone nachempfunden ist. Zum anderen gibt es auch die Möglichkeit, die eigene Position per DMR an das APRS\footnote{APRS steht für \emph{Automatic Packet Reporting System} und ermöglicht das Übertragen von kleinen Datensätzen über Packet-Radio wie zum Beispiel die Position, Wetter oder Textnachrichten. Mehr dazu erfahren sie in der \href{https://de.wikipedia.org/wiki/Automatic_Packet_Reporting_System}{Wikipedia}.} Netz zu übertragen. \subsection{Textnachrichten (SMS)} \label{sec:textmsg} Mit diesem Dienst können sie kurze Textnachrichten\footnote{Bis zu 144 Zeichen.} direkt an andere Teilnehmer verschicken\footnote{Sie können auch Textnachrichten an ganze Sprechgruppen versenden. Dies ist aber eher unüblich und nicht wünschenswert.}. Im Prinzip funktioniert eine Textnachricht wie ein Direktruf. Ist der andere Teilnehmer erreichbar, wird die Textnachricht übermittelt. Es gibt aber auch \emph{Servicenummern} (gebührenfrei). Wenn sie nun eine Nachricht an eine solche Nummer senden, können Sie bestimmte Informationen abrufen oder versenden. In Deutschland wären das: \begin{enumerate} \item 262993 -- GPS und Wetter \begin{itemize} \item Wenn Sie \texttt{help} senden, erhalten daraufhin eine Auflistung aller Kommandos. \item Wenn Sie \texttt{wx} senden, erhalten Sie das aktuelle Wetter am Standort des Repeaters, den Sie verwenden. \item Wenn Sie \texttt{wx STADTNAME} senden, erhalten Sie das aktuelle Wetter für die angegebene Stadt. \item Wenn Sie \texttt{gps} senden, erhalten Sie die letzte Positionsinformation, die Sie zuletzt an das DMR Netz gesendet hatten. \item Mit \texttt{gps CALL} können Sie auch die letzte Position des angegebenen Teilnehmers abfragen. \item Mit \texttt{rssi} erhalten Sie vom Repeater einen Signalrapport. \end{itemize} \item 262994 -- Repeater Informationen \& Pagernachrichten \begin{itemize} \item Wenn Sie \texttt{rpt} senden, erhalten Sie eine Liste der statisch und dynamisch abonnierten Sprechgruppen des Repeaters. \item Wenn Sie \texttt{CALL NACHRICHT}, wird die angegebene Nachricht an das angegebene Call per Pager (DAPNET) geschickt. \end{itemize} \end{enumerate} \subsection{Positionsübermittlung (APRS via DMR)} \label{sec:aprs} Wie im vorherigen Abschnitt schon erwähnt, ist es möglich seine Position ins DMR Netz zu senden. Diese wird dann üblicherweise direkt an das APRS-Netz weitergereicht und Ihre Position kann dann unter anderem bei \url{https://aprs.fi} abgefragt werden. Dazu ist jedoch ein DMR Funkgerät mit GPS Empfänger nötig. Aber auch diese Geräte sind in der Zwischenzeit nicht mehr teuer. Einfache DMR Handfunkgeräte mit GPS sind ab circa \EUR{120} zu haben. Neben dem SMS Service ist auch die Positionsübermitellung per DMR möglich. Dazu muss das GPS fähige Funkgerät so konfiguriert werden, dass die Positionsdaten auf den geeigneten Kanälen an die Nummer 262999 gesendet werden. Wie dies einzustellen ist, hängt sehr vom Hersteller des Funkgerätes ab. ================================================ FILE: doc/dmr-intro/script/script_de_07_talkgroup.tex ================================================ \section{Talkgroup Betrieb} \label{sec:talkgroup} Ein klassisches und auch schönes Beispiel für den Repeater-transparenten Sprechgruppenbetrieb (Talkgroup) ist das Szenario einer Nachmittagsrunde in einer Sprechgruppe. Zum Beispiel, die Sprechgruppe 2621 \emph{Berlin/Brandenburg} kurz BB. Diese Sprechgruppe ist bei fast allen Repeatern in Berlin und Brandenburg fest Abonniert. Das heißt, diese Runde kann ohne weiteres Zutun in ganz Berlin und Brandenburg empfangen werden (siehe Abb. \ref{fig:tgex1}). \begin{figure}[p] \centering \begin{subfigure}{\linewidth} \centering \input{../fig/talkgroup_ex1a} \caption{Beispiel für eine typische Nachmittagsrunde auf einer Sprechgruppe.} \label{fig:tgex1} \end{subfigure} \begin{subfigure}{\linewidth} \centering \input{../fig/talkgroup_ex1b} \caption{Der OM im Ausland abonniert diese Sprechgruppe an dem lokalen Repeater temporär durch einen Gruppenruf zu dieser Sprechgruppe.} \label{fig:tgex2} \end{subfigure} \begin{subfigure}{\linewidth} \centering \input{../fig/talkgroup_ex1c} \caption{Danach kann auch der OM im Urlaub wie gewohnt an dieser Nachmittagsrunde teilnehmen.} \label{fig:tgex3} \end{subfigure} \end{figure} Für einen OM im Urlaub, gilt das natürlich nicht. Ein italienischer Repeater wird sicher nicht standardmäßig die Sprechgruppe \emph{Berlin/Brandenburg} abonniert haben. Daher wird dieser OM die Sprechgruppe im Ausland auch nicht hören. Da er aber weiß, wann diese Runde beginnt, kann er vorher per Gruppenruf zu dieser Sprechgruppe von seinem Urlaubsrepeater (I0ABC) aus, diese Sprechgruppe temporär abonnieren (Abb. \ref{fig:tgex2}). Nachdem er diese Sprechgruppe beim Urlaubsrepeater abonniert hat, kann er wie gewohnt an der Nachmittagsrunde teilnehmen (Abb. \ref{fig:tgex3}). Für die anderen Teilnehmer dieser Runde ist dann nicht einmal ersichtlich, dass der Urlauber nicht über ein Relais in Berlin oder Brandenburg sondern aus dem Ausland an der Runde teilnimmt. \begin{table}[!ht] \centering \begin{tabular}{|l|c|} \hline Name & Sprechgruppe \\ \hline Global & 91 \\ Europa & 92 \\ Deutschland & 262 \\ Mecklenburg-Vorpommern \& Sachsen-Anhalt & 2620 \\ Berlin \& Brandenburg & 2621 \\ Hamburg \& Schleswig-Holstein & 2622 \\ Niedersachsen \& Bremen & 2623 \\ Nordrhein-Westfalen & 2624 \\ Rheinland-Pfalz \& Saarland & 2625 \\ Hessen & 2626 \\ Baden-Württemberg & 2627 \\ Bayern & 2628 \\ Sachsen \& Thüringen & 2629 \\ \hline \end{tabular} \caption{} \label{tab:talkgroups} \end{table} \subsection{Cluster} Im Gegensatz zur dediziert Regionalen Gruppe TG8, sind gewöhnliche Sprechgruppen von überall aus dem DMR Netz erreichbar. Das heißt, ein OM der sich gerade im Urlaub befindet und an einer Runde in dieser Sprechgruppe teilnehmen möchte, kann dies wie oben beschrieben tun. Würde diese Nachmittagsrunde aber auf der regionalen Sprechgruppe TG8 stattfinden, könnte der OM im Urlaub nicht daran teilnehmen. Er würde an seinem Urlaubsrepeater mit einem Gruppenruf zur Sprechgruppe TG8 nur Funkamateure in seiner Urlaubsregion erreichen aber nicht den regionalen Verbund von Repeatern zu Hause. Aus diesem Grund werden häufig regionale Verbünde von Repeatern mit sogenannten \emph{Clustern} verbunden. Diese \adef{Cluster} stellen dann eine weitere Sprechgruppennummer für den regionalen Verbund zur Verfügung, sodass die Sprechgruppe TG8 einer bestimmten Region auch von außen erreichbar ist. Eine Liste der Regionalcluster und der dazugehörigen Sprechgruppennummer kann unter \url{http://bm262.de/cluster/} abgerufen werden. ================================================ FILE: doc/dmr-intro/script/script_de_08_aprs.tex ================================================ ================================================ FILE: doc/dmr-intro/script/script_de_09_roaming.tex ================================================ \section{Roaming} \label{sec:roaming} Viele Relais in einer Region haben die selben Sprechgruppen abonniert, damit eine repeatertransparente Nutzung dieser Sprechgruppen möglich ist. Es ist also egal welchen Repeater Sie auf Ihrem Funkgerät ausgewählt haben, Sie können immer die gleichen Sprechgruppen verwenden. In der Region Berlin \& Brandenburg wäre dies die Sprechgruppe TG2621. Es wäre also sinnvoll eine Liste zu erstellen, in der alle Repeater eingetragen werden, die eine bestimmte Sprechgruppe abonniert haben. Wenn dann noch das Funkgerät automatisch einen erreichbaren Repeater aus dieser Liste auswählen könnte, dann könnte man mit dem Auto in dieser Region unterwegs sein und wäre immer automatisch mit dieser Sprechgruppe verbunden. Dieses Feature nennt sich \adef{Roaming} und wird von einigen meist etwas teureren Funkgeräten unterstützt (z.B., die AnyTone Geräte). Die günstigsten DMR Funkgeräte chinesischer Produktion unterstützen dieses Feature meist nicht. Um das Roaming nutzen zu können, werden zunächst alle Kanäle mit einer bestimmten Sprechgruppe in einer Liste zusammengefasst. Dies könnte eigentlich automatisch geschehen, aber die Konfigurationssoftware für diese Funkgeräte ist wirklich nicht sehr benutzerfreundlich. Wenn nun die Signalstärke eines bestimmten Repeaters dieser Liste unter einen Schwellwert (meist $-105dBm$) fällt, fängt das Funkgerät an, alle Kanäle der Roamingliste abzuklappern, bis es einen Repeater findet, dessen Signalstärke größer ist als der Schwellwert. Dies geschieht aber nur, wenn das Funkgerät auf \emph{stand-by} ist. Das heißt, wenn weder etwas auf dem aktuellen Kanal empfangen wird noch gesendet wird. Hat es einen stärkeren Repeater in der Liste gefunden, wechselt das Funkgerät automatisch den Kanal des neuen Repeater. Dies muss nicht unbedingt der stärkste Repeater der Liste am aktuellen Standort sein. Lediglich der Schwellwert ist entscheidend. Wird kein genügend starker Repeater gefunden, verbleibt das Funkgerät auf dem aktuellen Kanal. Dieses Roaming kann auch auf \emph{manuelles Roaming} eingestellt werden. Das heißt, das Roaming startet erst, wenn die Signalstärke des aktuellen Repeaters unter den Schwellwert sinkt und die PTT-Taste gedrückt wird oder ein manueller Suchlauf aus dem Menü heraus gestartet wird. ================================================ FILE: doc/dmr-intro/script/script_de_10_netze.tex ================================================ \section{DMR-Netze} \label{sec:netze} Sie kennen nun alle wichtigen Konzepte des DMR-Betriebs und auch einige der technischen Details dazu, wie das Erstellen von Codeplugs. Diese Konzepte gelten jedoch nur uneingeschränkt im sogenannten Brandmeisternetz. Dies ist jenes Netz im Hintergrund, dass ihre Direkt- oder Gruppenrufe vermittelt und Repeater miteinander verbindet. In Deutschland ist dies das dominierende Netz. Auch Weltweit sind die meisten DMR Repeater (c.a., 1500) im Brandmeisternetz miteinander verbunden. Es gibt aber auch andere DMR Netze. Zum Einen DMR-MARC (c.a., 500 Repeater) und zum Anderen DMR+ (c.a. 150 Repeater). Welches Netz wo häufiger verwendet wird, hängt stark vom Land ab. So sind in Frankreich, Spanien, den BeNeLux Staaten, Polen, Tschechien und der Slowakei fasst ausschließlich Brandmeister Repeater im Betrieb. Während in Dänemark DMR+ deutlich mehr Repeater vernetzt. In Großbritannien, den USA und Österreich sind DMR-MARC Repeater nicht selten. All diese Netze unterscheiden sich aber nicht technisch voneinander. Das heißt, die Ihnen zugewiesene DMR-ID ist in allen Netzen gültig und sie können jedes Tier-II DMR Funkgerät in allen Netzen verwenden. Lediglich die Konzepte der einzelnen Netze, vor allem wie Gruppenrufe realisiert werden, ist von Netz zu Netz verschieden. Das heißt, Sie müssen die Kanäle zu einem DMR+ Repeater leicht anders konfigurieren als Kanäle zu einem Brandmeister Repeater. \subsection{Reflektoren} \label{sec:reflector} \index{Reflektor} Im DMR+ Netz spielen sogenannte Reflektoren eine zentrale Rolle. Sie entsprechen in etwa den Sprechgruppen, wie sie im Brandmeisternetz verwendet werden. Der wesentliche Unterschied zu Sprechgruppen im Brandmeister Netz ist, dass diese Reflektoren nicht einfach per Gruppenruf angerufen werden können, sondern zunächst per Direktruf an einem Repeater temporär abonniert werden müssen. Danach verhalten sich alle Repeater, die diesen Reflektor abonniert haben, wie eine Gruppe zusammen geschalteter FM Repeater. Das heißt, ein Gruppenruf zur lokalen Sprechgruppe TG9 wird dann nicht nur lokal ausgesandt, sondern auch über alle Repeater die diesen Reflektor abonniert haben. Dies hat den Vorteil, dass die Konfiguration des Funkgerätes viel einfacher ist: Es müssen lediglich zwei Kanäle für jeden Repeater angelegt werden. Je einen für jeden Zeitschlitz und jeweils mit dem Standardkontakt zur TG9. Um einen Reflektor am aktuellen Repeater zu abonnieren, wird einfach ein Direktruf zu dem Reflektor aus der Kontaktliste heraus gestartet. Dieses Konzept ist auch näher an den \emph{alten} Konzepten aus dem FM Repeaterbetrieb mit Echolink. Jedoch gehen dadurch modernere Fähigkeiten des Netzes wie Roaming verloren. Dieses Konzept hat aber auch den Nachteil, dass die Repeatertransparenz verloren geht. Anstatt einfach einen Gruppenruf zu der gewünschten Sprechgruppe zu starten, muss zunächst der lokale Repeater \emph{konfiguriert} werden. Erst danach erfolgt alle Kommunikation über die lokale Sprechgruppe TG9, auch wenn diese Kommunikation alles andere als lokal ist. ================================================ FILE: doc/dmr-intro/script/script_de_11_technik.tex ================================================ \section{Technischer Hintergrund} \label{sec:technik} Nachdem ich in den vorherigen Abschnitten versucht habe Ihnen die Konzepte von DMR (Repeater-unabhängige Direkt und Gruppenrufe) näherzubringen, geht es in diesem Abschnitt an das Eingemachte. Da heißt, die technischen Details und Besonderheiten von DMR. Im speziellen um die Begriffe \emph{Time Slot} und \emph{Color Code}. \subsection{Zeitschlitze (Time Slots)} \label{sec:timeslot} Wie zu Beginn erwähnt, ist DMR eine digitale Übertragungstechnik, bei der Sprache zunächst digitalisiert, mit einem sogenannten Codec komprimiert und als Datenpakete übertragen werden. Modere Sprachcodecs sind in der Zwischenzeit so effizient geworden, dass es möglich ist auf einem $12.5 kHz$ breiten Kanal, zwei Sprachsignale in guter Qualität gleichzeitig zu übertragen. Dies wird auch bei DMR ausgenutzt. DMR verwendet dazu ein Verfahren das sich \adef{TDMA} nennt. \begin{figure}[!ht] \centering \input{../fig/timeslot} \caption{Graphische Darstellung der \emph{time-division media access} (TDMA) Technik.} \end{figure} Das steht für \emph{time-division media access} und beschreibt, wie zwei Teilnehmer (quasi) gleichzeitig einen physischen Kanal (also eine Frequenz) benutzen können. Dazu wird jedem der beiden ein Zeitschlitz zu geordnet (Zeitschlitz 1 und 2) und beide senden oder empfangen nur in ihrem eigenen Zeitschlitz. Diese Zeitschlitze sind sehr kurz, bei DMR nur $30ms$ lang. Diese kurze Zeit reicht jedoch aus um $60ms$ lange Sprachfetzen komprimiert zu übertragen. DMR erhält dadurch zwei völlig unabhängige Kanäle pro Frequenz. Das Bedeutet auch, dass zwei völlig unabhängige Gespräche über einen Repeater gleichzeitig laufen können. Was oder besser wann nun Zeitschlitz 1 oder 2 dran sind, legt der Repeater fest. Er gibt den Takt vor. Das bedeutet auch, dass Zeitschlitze für den Simplexbetrieb völlig unbedeutend sind. Wenn Sie später einen Simplexkanal für Ihr Funkgerät konfigurieren, ist die Zeitschlitzeinstellung egal. Was auf welchem Zeitschlitz passieren soll, hängt stark von der Konfiguration des einzelnen Repeaters ab. Grundsätzlich gilt aber: \begin{merke} Überregionale Kommunikation sollte auf Zeitschlitz 1 und lokale sowie regionale Kommunikation auf dem Zeitschlitz 2 stattfinden. \end{merke} \subsection{Farbcodes (Color Codes)} \label{sec:colorcode} \index{Color Code} Farbcodes sind ein technisches Hilfsmittel, um Störungen zwischen Repeatern zu vermeiden, die auf der selben Frequenz arbeiten. Dieses Problem tritt vor allem im kommerziellen Einsatz von DMR auf. Einem Unternehmen werden üblicherweise nur wenige Frequenzen zugewiesen, es werden mit unter aber viele Repeater benötigt um ein großes Firmengelände vollständig abdecken zu können (denken Sie an das Flughafenbeispiel). Da bleibt es nicht aus, dass verschiedenen Repeatern die selbe Frequenz zugewiesen werden muss. Wenn sich dann die Reichweiten dieser Repeater überlappen, kann es sein, dass die Aussendungen eines Teilnehmers von zwei Repeatern gleichzeitig aufgenommen werden. Um dies zu verhindern, werden den Repeatern verschiedene sogenannte Farbcodes zugewiesen. Diese kleine zusätzliche Information einer Aussendung erlaubt es einem Repeater oder jedem anderen Teilnehmer zu erkennen, ob eine Aussendung für sie bestimmt ist oder nicht. Nur wenn der Farbcode übereinstimmt, reagiert der Repeater oder das Funkgerät auf diese Aussendung. \begin{merke} Um einen Repeater nutzen zu können muss nicht nur dessen Ein- und Ausgabefrequenz sondern auch dessen Farbcode bekannt sein! \end{merke} ================================================ FILE: doc/dmr-intro/script/script_de_12_codeplug.tex ================================================ \section{Codeplug Programmierung} \label{sec:codeplug} Nachdem Sie sich mit den Konzepten und dem technischen Hintergrund von DMR auseinandergesetzt haben, geht es nun an die Konfiguration Ihres Funkgerätes. Dies geschieht üblicherweise nicht über das Bedienfeld des Funkgerätes, sonder mit Hilfe einer separaten Software, der sogenannten \adef{CPS} oder \emph{codeplug programming software}. Doch bevor Sie loslegen können benötigen Sie wie alle DMR Teilnehmer eine eindeutige Nummer, die DMR ID. \begin{hinweis} Ihre persönliche und eindeutige DMR ID erhalten Sie unter \url{https://register.ham-digital.org/}. Da Sie nachweisen müssen, dass Sie lizenzierter Funkamateur sind, müssen Sie bei der Anmeldung ihre eingescannte \emph{Zulassung zum Amateurfunkdienst} hochladen. \end{hinweis} Ihre DMR ID erhalten Sie in der Regel innerhalb von 24 Stunden per Mail. Sobald Sie eine DMR ID erhalten haben kann es los gehen. Da dieses Script für Einsteiger gedacht ist, ist es wahrscheinlich, dass Sie kein top-shelf Motorola Gerät sondern eher ein günstiges Gerät der einschlägig bekannten chinesischen Hersteller besitzen. \begin{achtung} Falls Sie noch kein DMR fähiges Funkgerät besitzen und mit dem Gedanken spielen eines zu kaufen, achten Sie unbedingt darauf, dass es DMR \textbf{Tier I \& II}\footnote{Wie so häufig ist DMR nicht ein Standard sondern eine ganze Familie von aufeinander aufbauenden Standards. DMR Tier I beschreibt im wesentlichen den DMR Simplexbetrieb und Tier II dann den Repeaterbetrieb mit zwei Zeitschlitzen. Sie benötigen also unbedingt Tier II für den Repeaterbetrieb.} unterstützt. Ignorieren Sie etwaiges Marketing-Bla-Bla der Hersteller und schauen Sie in den technischen Details nach, ob dort DMR \textbf{Tier I \& II} erwähnt wird. Falls nicht oder nicht eindeutig, lassen Sie die Finger von diesem Gerät! Dies gilt vor allem für das Baofeng MD-5R aber nicht für das Baofeng/Radioddity RD-5R\footnote{Manchmal sind es die kleinen Unterschiede die entscheidend sind.}. \end{achtung} Der Hersteller Ihres Gerätes wird auf seiner Webseite die Software die Sie zur Konfiguration benötigen, zum Download bereitstellen. Diese Software wird \emph{CPS} oder \emph{codeplug programming software} genannt. Gegebenenfalls finden Sie dort auch Firmwareupdates für Ihr Gerät. Viele Hersteller bieten für jedes einzelne Modell eine separate CPS an oder gar für jede Variation eines Modells. Achten Sie also genau darauf welche CPS Sie herunterladen. Die Konfiguration dieser Geräte unterscheidet sich von Gerät zu Gerät und mehr noch von Hersteller zu Hersteller. Jedoch sind die wesentlichen Einstellungen für Geräte dieser Klasse sehr ähnlich. Wenn Sie die CPS zum ersten mal starten, werden Sie wahrscheinlich zwei Dinge feststellen. Erstens, das Bedienkonzept dieser Software ist aus dem letzten Jahrtausend (Windows 3.11) und Zweitens, es gibt eine Unmenge an obskuren Optionen deren Funktion nicht ersichtlich ist und die größtenteils nicht Dokumentiert sind. Wenn Sie des Englischen nicht mächtig sind, werden Sie auch eine deutsche Übersetzung des Programms vermissen. Aber keine sorge, die englische Übersetzung ist meist auch so schlecht, dass es keinen Unterschied macht ob sie Englisch lesen können oder nicht. Die Konfiguration Ihres Funkgerätes erfolgt in 5-6 Schritten: \begin{enumerate} \item Allgemeine Einstellungen, \item Kontakte anlegen, \item Empfangsgruppen festlegen, \item alle Kanäle anlegen, \item Kanäle in Zonen einteilen und \item optional Scanlisten anlegen. \end{enumerate} In den folgenden Abschnitten möchte ich die einzelnen Konfigurationsschritte im Detail beschreiben. \subsection{Allgemeine Konfiguration} \label{sec:cp:basic} Die wichtigsten allgemeinen Einstellungen die Sie vornehmen müssen, ist das setzen der DMR ID und ihres Rufzeichens. Diese Optionen finden Sie meist unter der Rubrik (linke Seite) \emph{Radio Settings} oder \emph{General Settings}\footnote{Die exakten Namen der Rubriken und Felder kann sich von Hersteller zu Hersteller unterscheiden. Üblicherweise sind sie aber den hier erwähnten Namen sehr ähnlich.}. Ihre DMR ID tragen Sie dann in das Feld \emph{Radio ID} ein. Es ist durchaus möglich, dass Ihr Funkgerät mehrere DMR IDs unterstützt. Dieses Feature wird aber üblicherweise nicht verwendet. Im Gegenteil: Es stehen nur eine begrenzte Anzahl von DMR IDs sehr vielen Funkamateuren gegenüber. Beantragen sie deshalb niemals eine DMR ID für jedes Funkgerät oder jeden Accesspoint. Eine \textbf{einzige} persönliche DMR Nummer reicht völlig! Ihr Rufzeichen tragen Sie bitte in das Feld \emph{Radio Name} ebenfalls in der Rubrik \emph{Radio Settings} ein. \subsection{Kontakte Anlegen} \label{sec:cp:contact} Nachdem Sie die grundlegenden Einstellungen vorgenommen haben, können Sie Ihre Kontaktliste zusammenstellen. Diese sollte alle Sprechgruppen enthalten die Sie interessieren könnten, ihre persönlichen Kontakte wie OMs aus dem OV und einige Servicenummern wie Echo, die SMS Dienste und den All Call. Eine Beispiel für Deutschland ist in Tabelle \ref{tab:contacts} angegeben. \begin{table}[!ht] \centering \begin{tabular}{|l|c|c||l|c|c|}\hline Name & Typ & Nummer & Name & Typ & Nummer \\ \hline Lokal & Gruppenruf & 9 & Ham/SlHo & Gruppenruf & 2622 \\ Regional & Gruppenruf & 8 & NiSa/Bre & Gruppenruf & 2623 \\ TG99 & Gruppenruf & 99 & NRW & Gruppenruf & 2624 \\ Rundumruf & All Call & 16777215 & RhPf/Saar & Gruppenruf & 2625 \\ Weltweit & Gruppenruf & 91 & Hessen & Gruppenruf & 2626 \\ Europa & Gruppenruf & 92 & BaWü & Gruppenruf & 2627 \\ D-A-CH & Gruppenruf & 920 & Bay & Gruppenruf & 2628 \\ Deutschland & Gruppenruf & 262 & Sa/Th & Gruppenruf & 2629 \\ Österreich & Gruppenruf & 232 & Echo Test & Direktruf & 262997 \\ Schweiz & Gruppenruf & 228 & SMS Serv. & Direktruf & 262993 \\ EMCOM\footnote{Ausschließlich für Notfunk.} EU & Gruppenruf & 9112 & DAPNET & Direktruf & 262994 \\ EMCOM WW & Gruppenruf & 9911 & APRS GW & Direktruf & 262999 \\ MeVo/SaAn & Gruppenruf & 2620 & DM3MAT & Direktruf & 2621370 \\ Ber/Bra & Gruppenruf & 2621 & ... & ... & ... \\ \hline \end{tabular} \caption{Beispielkontakte für Deutschland.} \label{tab:contacts} \end{table} Natürlich gibt es noch viele weitere Sprechgruppen auch zu spezifischen Themen, die nicht unbedingt regional beschränkt sind. Eine recht vollständige Liste finden Sie unter \url{https://www.pistar.uk/dmr_bm_talkgroups.php}. \subsection{Empfangsgruppen Zusammenstellen} \label{sec:cp:grouplist} Im nächsten Schritt stellen Sie sogenannte \adef{Empfangsgruppen} zusammen. Dies sind Listen von Gruppenrufen, die Sie auf bestimmten Kanälen empfangen wollen. Wie schon bei der Einführung in Abschnitt \ref{sec:ursprung} erwähnt, weiß das DMR Netz nicht, für welche Sprechgruppen Sie sich interessieren. Dies kann nur Ihr Funkgerät wissen. Mit den Empfangsgruppen definieren Sie genau das. Sie werden mindestens drei Empfangsgruppen benötigen. Eine für den Simplexbetrieb, eine für die überregionale Kommunikation und je eine für regionale Kommunikation in all jenen Regionen, in denen Sie unterwegs sind. Die Simplex Empfangsgruppe ist eigentlich nicht notwendig, da Simplexrufe eigentlich immer den sog. \aref{All Call} (Rundumruf) verwenden sollten. Häufig wird aber auch die Sprechgruppe TG99, TG9 oder auch TG8 verwendet. Daher ist es ratsam eine Empfangsgruppe mit diesen Gruppenrufen anzulegen. Für die überregionale Kommunikation sollte eine Empfangsgruppe erstellt werden, die die Sprechgruppen für weltweite, innereuropäische und deutschlandweite Kommunikation enthalten. Dieser Gruppe können Sie dann noch die Sprechgruppe \emph{EMCOM EU} für europäischen Notfunk hinzufügen, damit Sie ggf. Notrufe hören und darauf reagieren können. Zuletzt sollte die Sprechgruppen für lokale/regionale Kommunikation angelegt werde. Diese sollte jeweils die Sprechgruppen TG8 und TG9 sowie die Sprechgruppe der jeweiligen Region enthalten. Für mich, der in der Berlin/Brandenburg Region lebt, aber häufig auch in Sachsen unterwegs ist, habe ich insgesamt 4 Empfangsgruppen zusammengestellt (siehe Tab. \ref{tab:grouplist}). \begin{table} \centering \begin{tabular}{|l|l|} \hline Name & Gruppenrufe \\ \hline Simplex & Lokal, Regional, TG99 \\ WW/EU/DL & Weltweit, Europa, D-A-CH, Deutschland, EMCOM EU \\ Ber/Bra & Lokal, Regional, Ber/Bra \\ Sa/Th & Lokal, Regional, Sa/Th \\ \hline \end{tabular} \caption{Ein paar Beispielempfangsgruppen. Die ersten beiden sind recht universell für Deutschland, die letzten Beiden sind für die Regionen Berlin/Brandenburg und Sachsen/Thüringen wichtig.} \label{tab:grouplist} \end{table} \subsection{Kanäle Anlegen} \label{sec:cp:channel} Bevor es los geht, sollte ich erwähnen, dass die meisten DMR Funkgeräte auch analoges FM unterstützen. Das heißt, Sie können mit ihrem DMR Funkgerät auch normalen analogen FM Simplex und Repeaterbetrieb durchführen. In diesem Abschnitt beschreibe ich aber nur die Konfiguration von DMR Kanälen (meist \emph{Digital Channel} genannt), die Konfiguration von sogenannten \emph{analogen} Kanälen wird hier nicht beschrieben. Um einen DMR Kanal anzulegen, müssen Sie im Feld \emph{Channel Type} den Wert \emph{digital} auswählen, für einen FM Kanal dann \emph{analog}. Wenn Sie schon Erfahrung mit dem \emph{klassischen} FM-Relaisbetrieb haben, wird Ihnen das Anlegen der Kanäle recht seltsam vorkommen. Im analogen FM-Relaisbetrieb haben Sie für jeden Repeater und Simplex-Kanal genau einen Kanal im Funkgerät konfiguriert. Für den DMR Betrieb werden Sie für jeden Repeater mindestens zwei (für Zeitschlitz 1 \& 2), meist aber deutlich mehr Kanäle programmieren. Lange Rede kurzer Sinn. Lassen Sie mich das an konkreten Beispielen erläutern. \subsubsection{Simplexkanäle Anlegen} \begin{table}[!ht] \begin{tabular}{|l|p{2.5cm}|p{2.5cm}|c|c|c|c|} \hline Name & RX Freq. (Ausgabe) & TX Freq. (Eingabe) & TS\footnote{Seteht für \emph{Time Slot} also Zeitschlitz.} & CC\footnote{Steht für \emph{Color Code} also Farbcode.} & TX Kontakt & Empf.gr. \\ \hline DMR S0 & $433.4500 MHz$ & $433.4500 MHz$ & 1 & 1 & Rundumruf & Simplex \\ DMR S1 & $433.6125 MHz$ & $433.6125 MHz$ & 1 & 1 & Rundumruf & Simplex \\ ... & ... & ... & ... & ... & ... & ... \\ \hline \end{tabular} \caption{Beispieltabelle für die DMR Simplexkanäle.} \label{tab:ch:simplex} \end{table} In Tabelle \ref{tab:ch:simplex} sind exemplarisch die Einstellungen der ersten 2 DMR Simplexkanäle aufgeführt. Sie sollten diese natürlich auf alle 8 DMR Simplexkanäle erweitern. Die erste Spalte gibt einfach den Namen des Kanals an. Die zweite und dritte Spalte geben die Sende- (TX) und Empfangsfrequenz (RX) des Kanals an. Da es sich hier um Simplexkanäle handelt werden natürlich jeweils die gleichen Frequenzen für RX und TX eingetragen. Im \aref{Simplexbetrieb} gibt es keinen Repeater, der den Takt angeben könnte. Daher ist die Wahl des Zeitschlitzes für Simplexkanäle egal. Üblicherweise wird hier einfach der Zeitschlitz 1 ausgewählt. Der Farbcode (Spalte 5) ist aber nicht egal. Repeater sowie auch Ihr Funkgerät akzeptieren nur dann eine Aussendung, wenn der Farbcode der Aussendung mit der Einstellung für den Kanal übereinstimmt. Bei Simplexkanälen hat man sich daher auf den Farbcode 1 geeinigt. Die sechste Spalte gibt den Standardkontakt für diesen Kanal an. Bei Simplexkanälen sollte hier immer der sogenannte. \aref{Rundumruf} (All Call) eingetragen werden. Das bedeutet, dieser \emph{Kontakt} wird immer angerufen, wenn sie diesen Kanal auf dem Funkgerät eingestellt haben und auf die PTT Taste drücken. Eine Ausnahme bildet das Antworten auf einen Ruf. Wenn Sie zum Beispiel einen Gruppenruf zur Sprechgruppe TG99 auf dem Simplexkanal empfangen und innerhalb der kurzen \aref{Hangtime} darauf antworten, werden sie nicht mit dem voreingestellten Rundumruf antworten, sondern mit dem Gruppenruf zur Sprechgruppe TG99. Dieses Verhalten ist sehr erwünscht, da es Ihnen ermöglicht auf auf Direktrufe an Sie mit einem Direktruf zu Antworten. Die letzte Spalte gibt die Empfangsgruppe des Kanals an. Damit wird festgelegt welche Sprechgruppen auf diesem Kanal empfangen werden sollen. Wie oben schon erwähnt, wäre hier eigentlich keine Eintragung nötig wenn alle Teilnehmer auf den Simplexkanälen den Rundumruf verwenden würden. Es werden aber durchaus sehr unterschiedliche Sprechgruppen auf den Simplexkanälen verwendet. Für diese Fälle hatten wir ja die Empfangsgruppe \emph{Simplex} zusammengestellt. In Ihrer CPS finden sie noch sehr viel mehr Optionen zu den Kanälen. Die Meisten können auf den Standardwerten belassen werden. Am Ende dieses Abschnittes beschreibe ich noch eine Reihe weiterer Optionen. Viele dieser Optionen betreffen Funktionen, die im Amateurfunk aber keine Verwendung finden. Die Option \adef{Admit Criterion} definiert unter welchen Umständen das senden auf dem Kanal vom Funkgerät erlaubt wird. Hier stellen Sie bitte \emph{Channel Free} ein. Dies bedeutet, dass sie nur senden dürfen, wenn der Simplexkanal frei ist. \subsubsection{Repeaterkanäle Anlegen} Das Anlegen von Repeater Kanälen ist etwas aufwendiger als das Anlegen von Simplexkanälen, da für jeden Repeater gleich mehrere Kanäle definiert werden. Bevor Sie anfangen können Repeaterkanäle anzulegen, müssen Sie natürlich erst herausfinden welche Repeater sich in Ihrer Nähe befinden. Eine gute Übersicht bietet Ihnen die Seite \url{https://repeatermap.de/}. Sie können dort unter Filter die Anzeige auf DMR Repeater beschränken. Dort finden Sie auch alle wichtigen Information zu den jeweiligen Repeatern. Das heißt deren Eingabe- und Ausgabefrequenzen und Farbcodes. Diese Informationen benötigen Sie unbedingt um Kanäle für diesen Repeater anlegen zu können. \begin{sidewaystable}[p] \centering \begin{tabular}{|l|c|c|c|c|c|c|} \hline Name & RX Freq. (Ausgabe) & TX Freq. (Eingabe) & TS\footnote{Seteht für \emph{Time Slot} also Zeitschlitz.} & CC\footnote{Steht für \emph{Color Code} also Farbcode.} & TX Kontakt & Empf.gr. \\ \hline DB0LDS TS1 & $439.5625 MHz$ & $431.9625 MHz$ & 1 & 1 & --- & WW/EU/DL \\ DB0LDS DL TS1 & $439.5625 MHz$ & $431.9625 MHz$ & 1 & 1 & Deutschland & WW/EU/DL \\ DB0LDS Sa/Th TS1 & $439.5625 MHz$ & $431.9625 MHz$ & 1 & 1 & Sa/Th & Sa/Th \\ DB0LDS TG9 TS2 & $439.5625 MHz$ & $431.9625 MHz$ & 2 & 1 & L9 & Ber/Bra \\ DB0LDS TG8 TS2 & $439.5625 MHz$ & $431.9625 MHz$ & 2 & 1 & L8 & Ber/Bra \\ DB0LDS BB TS2 & $439.5625 MHz$ & $431.9625 MHz$ & 2 & 1 & Ber/Bra & Ber/Bra \\ \hline \end{tabular} \caption{Beispielkonfiguration der Kanäle für den Repeater DB0LDS in Wildau bei Berlin.} \label{tab:ch:repeater} \end{sidewaystable} Ich denke, es ist am einfachsten Ihnen am Beispiel meiner eigenen Kanalliste (Tab. \ref{tab:ch:repeater}) für \textbf{einen} Repeater in meiner Nähe das Anlegen von Repeaterkanälen zu beschreiben. Der Repeater heißt DB0LDS und hat die Eingabe Frequenz $431.9625 MHz$ und die Ausgabe Frequenz $439.5625 MHz$. Des Weiteren erwartet er den Farbcode 1. Dies sind die elementaren Informationen zu diesem Repeater, die Sie von diversen Repeaterlisten und Karten erhalten. Diese Informationen müssen sie natürlich für alle Kanäle die diesen Repeater betreffen, eintragen. Am Ende des Abschnitts \ref{sec:timeslot} hatte ich erwähnt, dass überregionaler Funkverkehr auf Zeitschlitz 1 und Regionaler auf Zeitschlitz 2 stattfinden. Dies wurde in dieser Konfiguration umgesetzt. Der Erste Kanal \emph{DL0LDS TS1} ist ein generischer Kanal für den Zeitschlitz eins. Er besitzt keinen Standardkontakt aber eine Empfangsgruppe für die Sprechgruppen Welt, Europa und Deutschland. Dieser Kanal dient dazu, beliebige (überregionale) Gruppen- und Direktrufe aus der Kontaktliste heraus zu führen. Das heißt, um auf diesem Kanal ein QSO zu starten, kann nicht einfach die PTT Taste gedrückt werden. Denn dazu fehlt dem Kanal der Standardkontakt. Es muss erst ein Kontakt aus der Kontaktliste ausgewählt werden, der angerufen werden soll. Der zweite Kanal (\emph{DL0LDS DL TS1}) ist identisch zum Ersten bis auf den Standardkontakt\index{Kanal!Standardkontakt}. Hier ist die Sprechgruppe \emph{Deutschland} (TG262) eingetragen. Das bedeutet, wenn dieser Kanal im Funkgerät ausgewählt ist und die PTT Taste gedrückt wird, wird direkt ein Gruppenruf an diese Sprechgruppe gestartet. Einen extra Kanal für diese Sprechgruppe anzulegen, erlaubt es diesen Gruppenruf zu starten ohne ihn erst in der Kontaktliste auswählen zu müssen. Auch ist es so möglich, diese Sprechgruppe schnell temporär auf diesem Repeater zu abonnieren\footnote{Die Sprechgruppe TG262 (Deutschland) ist für diesen Repeater nicht permanent auf Zeitschlitz 1 abonniert.} indem kurz die PTT Taste gedrückt wird (siehe Abschnitt \ref{sec:talkgroup}). \begin{merke} Auf jedem Kanal kann auf einen eingehenden Ruf innerhalb der sogenannten \aref{Hangtime} geantwortet werden, egal welcher Standardkontakt für diesen Kanal festgelegt wurde. \end{merke} Ähnlich verhält es sich mit dem dritten Kanal (\emph{DB0LDS Sa/Th TS1}). Hier ist als Standardkontakt die Sprechgruppe \emph{Sachsen/Thüringen} (TG2629) eingestellt um diese schnell und leicht über diesen Repeater erreichen und temporär abonnieren zu können. Bitte beachten Sie, das für diesen Kanal der Zeitschlitz 1 verwendet wird. Der Repeater befindet sich in Brandenburg. Somit sollte die Kommunikation mit Sachsen oder Thüringen im Zeitschlitz für überregionale QSOs geführt werden. Des weiteren ist als Empfangsgruppe die regionale Empfangsgruppe für Sachsen \& Thüringen angegeben. Das bedeutet, dass Gruppenrufe an die überregionalen Sprechgruppen wie \emph{Deutschland} (TG262) auf diesem Kanal nicht empfangen werden, auch wenn er auf Zeitschlitz TS1 liegt. Kanäle vier, fünf und sechs sind für die lokale (TG9, nur auf diesem Repeater), regionale (TG8, im regionalen Repeaterverbund) und Berlin-Brandenburg-weite Kommunikation (TG2621). All diese Kanäle sind auf Zeitschlitz 2, da es sich um regionale Kommunikation handelt und haben als Empfangsgruppe \emph{Ber/Bra} gesetzt. Das heißt, auf diesen Kanälen werden die Sprechgruppen TG8, TG9 und TG2621 empfangen. Als Standardkontakt wurde die entsprechenden Sprechgruppen gesetzt. Wird auf dem Funkgerät nun der Kanal \emph{DB0LDS TG9 TS2} ausgewählt, so wird beim drücken der PTT Taste ein Gruppenruf an die lokale Sprechgruppe (TG9) von nur diesem Repeater ausgesandt. Wird jedoch der Kanal \emph{DB0LDS BB TS2} ausgewählt, wird beim Drücken der PTT Taste ein Gruppenruf an die Sprachgruppe \emph{Berlin/Brandenburg} (TG2621) gestartet und somit fast überall in Berlin und Brandenburg gehört. \begin{merke} Auf jedem Kanal kann ein beliebiger Ruf (Gruppen, Direkt, Rundum) gestartet werden indem entweder der entsprechende Kontakt in der Kontaktliste ausgewählt wird oder die DMR Nummer eingegeben wird. Dies ist unabhängig vom Standardkontakt des Kanals. Letztendlich dient der Standardkontakt eines Kanals der Bequemlichkeit. So können dedizierte Kanäle für häufig getätigte Rufe definiert werden. \end{merke} Das sogenannte \emph{Admit Criterion} sollte für alle Repeaterkanäle auf \emph{Color Code} gesetzt werden. Dies bedeutet, dass Ihr Funkgerät nur dann sendet, wenn der Kanal frei ist und der Farbcode des Repeaters mit dem Farbcode des Kanals übereinstimmt. \subsubsection{Weitere Kanaloptionen} Die Maske, mit der Sie Kanäle konfigurieren ist recht umfangreich. Es gibt eine Vielzahl an Optionen die das Verhalten dieses Kanals beeinflussen. Die meisten dieser Optionen werden im Amateurfunk aber nicht verwendet. Dennoch möchte ich diese hier kurz erklären. Die \aref{Admit Criterion} Option hatte ich zuvor schon erwähnt. Sie liegt fest, unter welchen Umständen das Funkgerät ihnen erlaubt auf dem Kanal zu senden. Meist stehen hier drei Möglichkeiten zu Verfügung. \emph{Always} bedeutet, dass Sie immer senden dürfen. \emph{Channel Free} bedeutet, dass der Kanal frei sein muss, damit Sie senden dürfen. Und \emph{Color Code} bedeutet, dass nicht nur der Kanal frei sein muss, sonder auch der Farbcode des Repeaters stimmen muss. Daher macht es Sinn \emph{Channel Free} für Simplexkanäle und \emph{Color Code} für Repeaterkanäle zu wählen. Die Option \adef{TOT} oder auch \adef{TX Timeout} legt die maximale Dauer einer Aussendung fest. Das heißt, wenn Sie die PTT Taste länger als diese Zeitspanne drücken, wird das Funkgerät Ihre Aussendung unterbrechen. Dies ist eine Funktion für den kommerziellen Einsatz, die verhindert, dass eine Fehlbedienung das DMR Netz oder auch einen Repeater blockiert. Im Amateurfunk macht dies wenig Sinn. Daher können Sie diese Option auf \emph{unendlich} stellen. Die Option \adef{Emergency System} legt das Alarm- oder Notrufsystem für diesen Kanal fest. Auch dies ist eine Funktion für den kommerziellen Einsatz und wird im Amateurfunk nicht verwendet. Die Option \adef{Privacy Group} legt die Verschlüsselung der Aussendungen für diesen Kanal fest. Diese Funktion darf im Amateurfunk gar nicht verwendet werden. Die Optionen \adef{Emergency Alarm Confirmed}, \adef{Private Call Confirmed} und \adef{Data Call Confirmed}, legen fest, wie das Funkgerät Alarme, Direktrufe und Datenübermittlung durchführt. Sind diese Optionen angewählt, versucht das Funkgerät erst eine Verbindung zum Ziel herzustellen, bevor Sie sprechen dürfen. Das heißt, das Funkgerät sendet zunächst eine Anfrage an das Ziel. Erst wenn diese Anfrage vom Ziel positiv beantwortet wurde, ertönt ein Ton und Sie können sprechen. Wenn diese Optionen nicht angewählt sind, fangen Sie sofort an Sprachdaten an das Ziel zu senden. Ich empfehle Ihnen diese Option nicht anzuwählen. Die Option \adef{Talkaround} erlaubt es Ihnen auf einem Repeaterkanal Simplexbetrieb zu fahren. Das heißt, Sie werden auf der Repeaterausgabefrequenz senden und empfangen. Dabei umgehen sie natürlich den Repeater selbst. Daher wird dieses Feature im Amateurfunk nicht verwendet. Wenn die Option \adef{RX Only} angewählt ist, können Sie auf diesen Kanal nicht senden. Die Option \adef{VOX} bedeutet \altdef{Voice Operated Switch}{VOX} und erlaubt es Ihnen automatisch von Empfang auf Senden umzuschalten sobald sie in das Mikrofon sprechen. Dies lässt sich bei einigen Funkgeräten pro Kanal oder aber auch in den allgemeinen Einstellungen aktivieren. Die Option \adef{Power} legt die Sendeleistung auf diesen Kanal fest. Wenn Sie sich direkter Nähe des Repeaters befinden, können Sie die Sendeleistung reduzieren um die Batterielaufzeit bei Handfunkgeräten zu erhöhen. Diese optionale Kanaleinstellung \aref{Scanlist} definiert welche Liste von Kanälen gescannt werden, wenn der Scan auf diesem Kanal gestartet wird. Es ist nicht zwingend notwendig, dass der Kanal selbst in dieser Scanliste enthalten ist. \subsection{Zonen Zusammenstellen} \label{sec:zone} \index{Zone} Wenn Sie nun alle für Sie interessanten Kanäle erstellt haben, werden Sie feststellen, dass die Liste doch schon recht lang und unübersichtlich ist. Alle DMR Funkgeräte organisieren daher die Kanäle in sogenannten Zonen. Diese Zonen sind einfach nur Listen von Kanälen. Wie Sie diese Listen zusammenstellen, ist allein Ihnen überlassen. Sie können die Kanäle nach Region zusammenfassen wie \emph{Zu Hause}, \emph{Arbeit}, \emph{Urlaub} etc.. Sie können sie auch nach Sprechgruppen sortieren. So können Sie ein quasi händisches Roaming für eine bestimmte Sprechgruppe realisieren. Wenn Sie dann im Auto unterwegs sind, können Sie im Funkgerät immer jenen Repeater aus der Zone auswählen, den Sie gerade erreichen können. Somit bleiben Sie immer mit dieser Sprechgruppe verbunden. Einige (eher teurere) Funkgeräte unterstützen dies mit einer automatischen Roamingfunktion, bei der der jeweils stärkste Repeater an Ihrem Standort ausgewählt wird. \begin{hinweis} Kanäle, die keiner Zone zugeordnet wurden, können nicht im Funkgerät ausgewählt werden. Es ist aber problemlos möglich, einen Kanal mehreren Zonen zuzuordnen. \end{hinweis} \subsection{Scanlisten Zusammenstellen} Scanlisten sind einfach Listen von Kanälen, die beim Starten der Scanfunktion sequenziell beobachtet werden. Wird ein Signal auf einem der Kanäle empfangen, wird der Scan unterbrochen und es kann dann auf diesen empfangenen Ruf geantwortet werden. Diese Funktion erlaubt es mehrere Kanäle zu beobachten. Zusätzlich können Sie bei vielen Geräten ein oder zwei Prioritätskanäle definieren werden, die während des Scans häufiger \emph{besucht} und somit \emph{intensiver} beobachtet werden. ================================================ FILE: doc/dmr-intro/talk-barcamp2021/slides.tex ================================================ \documentclass[aspectratio=169]{beamer} \usetheme{Boadilla} \usepackage{hyperref} \usepackage{graphicx} \usepackage{subcaption} \usepackage{standalone} \usepackage[ngerman]{babel} \usepackage{tikz} \usepackage{listings} \title[QDMR]{QDMR ein universeller Code-Plug Editor (CPS) für Linux \& Mac\\ (und vielleicht Windows)} \subtitle{} \author{Hannes, DM3MAT} \institute{\texttt{dm3mat [at] darc [dot] de}} \date{25. März 2021} \lstset{ % basicstyle=\tiny % the size of the fonts that are used for the line-numbers } \input{../fig/repeater} \begin{document} \begin{frame} \titlepage \end{frame} \begin{frame} \frametitle{Übersicht} \tableofcontents \end{frame} \section{Ganz kurze Einführung in DMR} \begin{frame} \frametitle{Einführung -- DMR ein Mobilfunknetz für den Amateurfunk} \begin{block}{Was ist DMR?} \begin{itemize} \item Ein digitaler Daten und Sprachübertragungsmodi für UKW. \item Gedacht als Ersatz für den analogen Bündelfunk. \item Endpunkte werde über \emph{Telefonnummern} identifiziert. \item Verwendet 12.5kHz Raster (wie NBFM). \item Erlaubt vernetzten Repeater betrieb (über sog. Sprechgruppen). \item Erlaubt durch Kompression das \emph{Bedienen} zweier Endgeräte auf einem physischen Kanal (zwei unabhängige \emph{Zeitschlitze}). \end{itemize} \end{block} \end{frame} \begin{frame} \frametitle{Repeaterbetrieb und Direktrufe} \begin{block}{Merke} Alle DMR Repeater sind untereinander vernetzt und vermitteln Gespräche. \end{block} \begin{center} \input{../fig/repeater_privatecall} \end{center} \end{frame} \begin{frame} \frametitle{Repeaterbetrieb und Sprechgruppen} \begin{block}{Sprechgruppe} Sprechgruppen sind reservierte Telefonnummern für Telefonkonferenzen: Ein \emph{Gruppenruf} zu einer Sprechgruppe wird von allen Repeatern ausgesandt, die diese Sprechgruppe abonniert haben. Es gibt Sprechgruppen für Regionen aber auch für bestimmte Themen. \end{block} \begin{center} \begin{tabular}{|l|c|} \hline Name & Sprechgruppe \\ \hline Lokal & 9 \\ Global & 91 \\ Europa & 92 \\ Deutschland & 262 \\ Berlin \& Brandenburg & 2621 \\ ... & ... \\ Whitesticker & 264022\\ \hline \end{tabular} \end{center} \end{frame} \begin{frame}\frametitle{Lokale und regionale Verbindungen} \begin{center} \input{../fig/repeater_local} \end{center} \end{frame} \begin{frame}\frametitle{Nachmittagsrundenbeispiel} \begin{center} \input{../fig/talkgroup_ex1a} \end{center} \end{frame} \begin{frame}\frametitle{Nachmittagsrundenbeispiel} \begin{center} \input{../fig/talkgroup_ex1b} \end{center} \end{frame} \begin{frame}\frametitle{Nachmittagsrundenbeispiel} \begin{center} \input{../fig/talkgroup_ex1c} \end{center} \end{frame} \section{Code Plugs und CPS} \begin{frame}\frametitle{Kanäle einrichten: analog} Kanäle für analog Repeater einzurichten ist leicht: Ein- und Ausgabe Frequenz und ggf. Subton. \begin{center} \begin{tabular}{l|l|l|l} Name & Ausgabe & Eingabe & Subton \\ \hline DB0LDS & $439.5625$ & $431.9625$ & $67.0Hz$ \\ \end{tabular} \end{center} \end{frame} \begin{frame}\frametitle{Kanäle einrichten: DMR} Aus Bequemlichkeit richtet man einfach für alle Sprechgruppen, an denen man teilnehmen möchte einen separaten Kanal ein. Und das für jeden Repeater, den man verwendet. \vspace{0.5cm} \begin{center} \begin{tabular}{l|l|l|l|l|l|l} Name & Ausgabe & Eingabe & CC & TS & Kontakt & Empfangsgruppe \\ \hline DL DB0LDS & $439.5625$ & $431.9625$ & 1 & 1 & 262 & 91, 92, 262 \\ Sa/Th DB0LDS & $439.5625$ & $431.9625$ & 1 & 1 & 2629 & 2629 \\ WS DB0LDS & $439.5625$ & $431.9625$ & 1 & 1 & 264022 & 264022 \\ L9 DB0LDS & $439.5625$ & $431.9625$ & 1 & 2 & L9 & 8, 9, 2621 \\ BB DB0LDS & $439.5625$ & $431.9625$ & 1 & 2 & 2621 & 8, 9, 2621 \\ \end{tabular} \end{center} \vspace{.5cm} Hier habe ich 5 logische Kanäle für einen einzigen physischen Kanal eingerichtet. Wirklich notwendig sind nur zwei, einen für Timeslot 1 und einen für Timeslot 2. \end{frame} \begin{frame} \frametitle{Code-plug programming software (CPS)} Weil eine Menge Kanäle, Sprechgruppen und Empfangsgruppen eingerichtet werden müssen, wird eine Software verwendet anstatt das Gerät über das Bedienfeld zu programmieren. Die sog. \emph{CPS}. Das Ergebnis wird \emph{code plug} genannt. \vspace{0.5cm} Probleme: \begin{itemize} \item Die Software ist Mist. Bedienkonzept aus den 90er Jahren. \item Sehr auf kommerziellen Einsatz ausgelegt. \item Sehr viele unnütze/verbotene Optionen (Verschlüsselung) \item Schlechte Übersetzung aus dem Chinesischen. \item Hardware- oder gar versionsspezifische, binäre code plugs \item Jeder muss quasi seinen eigenen code plug bauen. Es gibt keinen Austausch. \end{itemize} \end{frame} \section{QDMR -- Ein Versuch einer universellen CPS} \begin{frame} \frametitle{QDMR -- Ein Versuch einer universellen CPS} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-general-settings.png} \end{center} QDMR ist ein Versuch eine \emph{universelle} CPS zu bauen für verschiedene Geräte und Hersteller mit einem lesbaren code-plug und einer halbwegs modernen Oberfläche. \end{frame} \begin{frame} Unterstütze Geräte (bisher): \begin{itemize} \item Open GD-77 Firmware (RD-5R, GD-77, ...) \item Radioddity RD-5R \item Retevis RT3S \item TyT MD-UV390 \item AnyTone AT-D878UV \end{itemize} \pause Quasi fertig: \begin{itemize} \item Radioddity GD-77 \item TYT MD-UV380, MD-2017, MD-9600 \item Retevis RT84 \item Anytone AT-D868UVE \item BTECH DMR-6X2 (läuft wahrscheinlich schon, identisch zum AT-D878UV) \end{itemize} \pause Nahe Zukunft: \begin{itemize} \item Hytera Geräte (vielen Dank an Matt)! \end{itemize} \end{frame} \begin{frame} \begin{center} {\Huge Demo!} \end{center} \end{frame} \begin{frame} \frametitle{Grundeinstellungen (1/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-general-settings.png} \end{center} \end{frame} \begin{frame} \frametitle{Kontakte/Sprechgruppen (2/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-contacts-empty.png} \end{center} \end{frame} \begin{frame} \frametitle{Kontakte/Sprechgruppen: Direktruf} \begin{center} \includegraphics[width=0.25\linewidth]{../fig/qdmr-edit-contact-private-call.png} \end{center} \end{frame} \begin{frame} \frametitle{Kontakte/Sprechgruppen: Gruppenruf} \begin{center} \includegraphics[width=0.25\linewidth]{../fig/qdmr-edit-contact-group-call.png} \end{center} \end{frame} \begin{frame} \frametitle{Kontakte/Sprechgruppen (2/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-contacts-example.png} \end{center} \end{frame} \begin{frame} \frametitle{Empfangsgruppen (3/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-rxgroup-empty.png} \end{center} \end{frame} \begin{frame} \frametitle{Empfangsgruppen: Hinzufügen} \begin{center} \includegraphics[width=0.25\linewidth]{../fig/qdmr-edit-rxgroup-empty.png} \end{center} \end{frame} \begin{frame} \frametitle{Empfangsgruppen: Hinzufügen} \begin{center} \includegraphics[width=0.25\linewidth]{../fig/qdmr-edit-rxgroup-select.png} \end{center} \end{frame} \begin{frame} \frametitle{Empfangsgruppen (3/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-rxgroup-list.png} \end{center} \end{frame} \begin{frame} \frametitle{Kanäle (4/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-channels-empty.png} \end{center} \end{frame} \begin{frame} \frametitle{Kanäle: Analog} \begin{center} \includegraphics[width=0.5\linewidth]{../fig/qdmr-channels-edit-analog.png} \end{center} \end{frame} \begin{frame} \frametitle{Kanäle: Digital} \begin{center} \includegraphics[width=0.5\linewidth]{../fig/qdmr-channels-edit-digital.png} \end{center} \end{frame} \begin{frame} \frametitle{Kanäle (4/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-channels-example.png} \end{center} \end{frame} \begin{frame} \frametitle{Zonen (5/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-zone-empty.png} \end{center} \end{frame} \begin{frame} \frametitle{Zone hinzufügen} \begin{center} \includegraphics[width=0.5\linewidth]{../fig/qdmr-zone-edit.png} \end{center} \end{frame} \begin{frame} \frametitle{Kanäle zur Zone hinzufügen} \begin{center} \includegraphics[width=0.25\linewidth]{../fig/qdmr-zone-edit-select.png} \end{center} \end{frame} \begin{frame} \frametitle{Zonen (5/5)} \begin{center} \includegraphics[width=0.75\linewidth]{../fig/qdmr-zone-empty.png} \end{center} \end{frame} \begin{frame} \frametitle{Code-plug Format} \begin{center} \includegraphics[width=0.9\linewidth]{../fig/qdmr-codeplug-format.png} \end{center} \end{frame} \section{Down the rabbit hole} \begin{frame} \frametitle{Howto reverse engineer a code-plug?} Hilfsmittel: \begin{itemize} \item Eine Windows Instanz in einer virtuellen Maschine (VirtualBox, o.ä.) mit der original CPS \item Wireshark mit \emph{usbmon} Kernelmodul (bei allen Linux Distributionen dabei) \item Viel Geduld \item Viel Frustrationstoleranz \end{itemize} Vorgehen: \begin{itemize} \item Die Kommunikation zwischen CPS und Gerät belauschen und mitschneiden. \item Die Rohdaten aus den Mitschnitten extrahieren und Gemeinsamkeiten/Unterschiede darin finden. \item Daraus das Kommunikationsprotokoll sowie das Paketformat ableiten (sehr aufwendig). \item Danach differentielle Analyse des Code-plugs. D.h., \begin{itemize} \item \textbf{Eine} kleine Änderung im code-plug auf das Gerät spielen. \item Kommunikation mitschneiden \item Code-plug daraus extrahieren. \item Vergleichen vor und nach der Änderung. \end{itemize} \end{itemize} \end{frame} \begin{frame}[fragile]\frametitle{Howto reverse engineer a code-plug?} Beispiel Hytera (Rohdaten): \begin{verbatim} 7e 00 00 fe 20 10 00 00 00 0c 60 e5 7e 01 00 00 20 10 00 00 00 14 31 d3 02 03 02 01 00 00 2c 03 7e 01 00 00 20 10 00 00 00 24 02 f1 02 c5 01 11 00 [...] 00 5b 03 7e 01 00 00 20 10 00 00 00 14 41 c3 02 01 02 01 00 12 1c 03 \end{verbatim} \end{frame} \begin{frame}[fragile]\frametitle{Howto reverse engineer a code-plug?} Beispiel Hytera (Code-plug extrahieren): \begin{lstlisting} < RES: flags=00, src=10, dest=20, seq=0004 | RSP: type=81C7 seq=EA crc=170F (1887) | RD: addr=00000000 | | 00000000 16 01 52 43 44 42 01 db 07 02 18 50 44 37 38 30 | ..RCDB.....PD780 | | 00000010 30 30 47 30 30 4d 30 30 30 30 30 00 00 85 85 00 | 00G00M00000..... | | 00000020 00 00 00 00 00 00 56 6a 19 00 9a 00 00 00 00 00 | ......Vj........ ... \end{lstlisting} \end{frame} \begin{frame}[fragile]\frametitle{Howto reverse engineer a code-plug?} Beispiel Hytera (Code-plug vergleichen, einfach mit \emph{diff}): Hier wurde gar nichts geändert, dennoch gibt es einen unterschied im übertragenen Code-plug \begin{lstlisting} < 00000090 80 a1 03 1c e5 07 03 16 02 17 db 07 03 0a 0a 21 | ...............! --- > 00000090 80 a1 03 1c e5 07 03 16 02 1a db 07 03 0a 0a 21 | ...............! \end{lstlisting} \begin{itemize} \item \texttt{0x07e3} = 2021, \texttt{0x03} = 3, \texttt{0x16} = 22, \texttt{0x02} = 2, \texttt{0x17}=23 \item \texttt{0x07e3} = 2021, \texttt{0x03} = 3, \texttt{0x16} = 22, \texttt{0x02} = 2, \texttt{0x1a}=26 \item $\Rightarrow$ Unterschied ist Zeitstempel der letzten Programmierung. \end{itemize} \end{frame} \begin{frame}[fragile]\frametitle{Howto reverse engineer a code-plug?} Beispiel Hytera (Code-plug vergleichen, einfach mit \emph{diff}): Hier wurde die DMR ID von 1 auf 12345678 geändert: \begin{lstlisting} < 00065AA0 04 00 4c 00 00 00 62 00 00 00 01 00 00 00 10 06 | ..L...b......... --- > 00065AA0 04 00 4c 00 00 00 62 00 00 00 4e 61 bc 00 10 06 | ..L...b...Na.... \end{lstlisting} \begin{itemize} \item \texttt{0x01} = 1, \texttt{0xbc614e} = 12345678 \item $\Rightarrow$ Die DMR ID wird an der Adresse \texttt{0x00065AAA} als 24bit little-endian Zahl abgelegt. \end{itemize} \end{frame} \end{document} ================================================ FILE: doc/dmr-intro/talk-ov2023/slides.tex ================================================ \documentclass[aspectratio=169]{beamer} \usetheme{Boadilla} \usepackage{hyperref} \usepackage{graphicx} \usepackage{subcaption} \usepackage{standalone} \usepackage[ngerman]{babel} \usepackage{tikz} \usepackage{pgfplots} \usetikzlibrary{shapes.geometric} \usepackage{listings} \usepackage[utf8]{inputenc} \usepackage{tikzsymbols} \usepackage{standalone} \title[DMR]{Kurze Einführung in Digital Mobile Radio (DMR)} \subtitle{Ein Mobilfunknetz für den Amateurfunk} \author{Hannes, DM3MAT} \institute{\texttt{dm3mat [at] darc [dot] de}} \date{5. April 2023} \lstset{ % basicstyle=\tiny % the size of the fonts that are used for the line-numbers } \input{../fig/repeater} \begin{document} \begin{frame} \titlepage \end{frame} \begin{frame} \frametitle{Übersicht} \tableofcontents \end{frame} \section{Motivation} \begin{frame}{Motivation} Schnatterfunk: \begin{itemize} \item Ziel: Ich möchte mit bestimmten Leuten oder auch mit Irgendjemanden reden. \pause\item Auf UKW: Reichweite begrenzt. Gerade mit Handfunke. \pause\item Lösung: Repeater. \pause\item Nächstes Problem: Reichweite immer noch begrenzt. \pause\item Lösung: Repeater vernetzen! (Echolink) \pause\item Immer noch Probleme: \begin{itemize} \pause\item Wo sitzen die Leute, mit denen ich reden Will? \pause\item Welche Repeater sind dort in der Nähe? \pause\item Welche EL-Nummer haben die? \end{itemize} \pause\item Eigentliches Problem: Was interessieren mich Repeater? Mich interessieren die Leute! \end{itemize} \end{frame} \begin{frame}{Repeatertransparenz} Eigentlich sollten FM-Relais 4 verschiedene Anwendungsfälle abdecken: \begin{enumerate} \pause\item Direktes QSO mit einer bestimmten Person, egal wo diese sitzt. \pause\Sadey \pause\item Teilnahme an themenspezifischer Runde, egal wo die Teilnehmer sitzen. \pause\Neutrey \pause\item Teilnahme an regionaler Runde. \pause\Neutrey \pause\item QSO zum nächsten Dorf. \pause\Smiley \end{enumerate} \end{frame} \begin{frame}{Repeatertransparenz} Es wäre also schön, wenn der einzelne Repeater nicht mehr so im Zentrum stehen würde. \pause Wir vernetzen also die Repeater und packen wir was anderes in die Mitte: \begin{block}{Sprechgruppe/Talkgroup} Eine Sprechgruppe/Talkgroup ist ein virtueller Raum/Repeater. Er existiert nicht physisch durch einen Zusammenschluss bestimmter Repeater, sondern im Netz aller Repeater. Habe ich eine Sprechgruppe (TG) abonniert, höre ich alles, was in dieser TG gesagt wird. Sende ich dort hin, hören alle Teilnehmer meine Aussendung, egal über welchen Repeater. Sprechgruppen sind also repeatertransparent. \end{block} \end{frame} \section{Ursprung} \begin{frame}{Ursprung} Digital Mobile Radio (DMR) hat seinen Ursprung als digitalisierter Bündelfunk/Betriebsfunk. Daher sind einige Techniken und Begriffe an diesen angelehnt. Einige dieser Techniken werden im AFu nicht verwendet (Alarm, Verschlüsselung) oder zweckentfremdet (all call).\\[0.2cm] \pause Beispiel Flughafen (Gebäude): Es gibt eine Vielzahl an Gruppen: \begin{itemize} \item Die Reinigungskolonne, \item die Sicherheitsleute wie Gepäckkontrolle oder Wachschutz, \item die Techniker, \item die Betriebsfeuerwehr und \item die Zentrale. \end{itemize} \pause Gleichzeitig ist so ein Flughafen ein riesiges Gelände. Das heißt, nicht alle Mitarbeiter können alle anderen Mitarbeiter direkt erreichen. Es müssen also Repeater aufgestellt werden, damit das gesamte Gelände und alle Innenräume per Funk abgedeckt sind. Daher wird häufig in jedem Gebäude mindestens ein Repeater aufgestellt. \end{frame} \begin{frame}{Beispiel: Flughafen} \centering \includegraphics[width=\linewidth]{../fig/trunk_net_ex1.tex} \end{frame} \begin{frame} \centering \includegraphics[width=\linewidth]{../fig/trunk_net_ex2.tex} \end{frame} \begin{frame} \centering \includegraphics[width=\linewidth]{../fig/trunk_net_ex3.tex} \end{frame} \begin{frame} \centering \includegraphics[width=\linewidth]{../fig/trunk_net_ex4a.tex} \end{frame} \begin{frame} \centering \includegraphics[width=\linewidth]{../fig/trunk_net_ex4b.tex} \end{frame} \section{Sprechgruppen} \begin{frame}{Nachmittagsschnatterrunde} \begin{block}{Anwendungsbeispiel im Amateurfunk: die Nachmittagsrunde} \begin{itemize} \item Eine Nachmittagsrunde findet in der regionalen Sprechgruppe statt. Z.B., in der TG 2621 \emph{Berlin/Brandenburg} kurz BB. \item Alle Teilnehmer befinden sich in der Region BB, außer DL3XYZ, der ist im Urlaub. \end{itemize} \end{block} \end{frame} \begin{frame} \centering \includegraphics[width=\linewidth]{../fig/talkgroup_ex1a.tex} \end{frame} \begin{frame} \centering \includegraphics[width=\linewidth]{../fig/talkgroup_ex1b.tex} \end{frame} \begin{frame} \centering \includegraphics[width=\linewidth]{../fig/talkgroup_ex1c.tex} \end{frame} \section{Technischer Hintergrund} \begin{frame}{Technischer Hintergrund} DMR ist ein digitaler Funkstandard. D.h., die Sprache wird abgetastet, verlustbehaftet komprimiert (Codec) und digital übertragen (4-MFSK).\\[0.5cm] Da digitaler Mode, können Metadaten (Quelle, Ziel, weitere Daten) parallel mit der Sprache übertragen werden.\\[0.5cm] Moderne Codecs erlauben Sprachübertragung mit geringer Bandbreite (Datenübertragungsrate). DMR verwendet AMBE+2 (ebenso wie System Fusion).\\[0.5cm] \end{frame} \begin{frame}{Technischer Hintergrund} DMR verwendet lediglich 2.45kbit/s (3.6kbit/s mit Fehlerkorrektur)\footnote{Quelle: Vortrag DJ3EI, \url{https://www.delta25.de/dmr/dmr-einf\%C3\%BChrung.pdf}}, bei 12.5kHz Kanalbreite, ist reichlich Platz.\\[0.5cm] Wir könnten Kanalbreite halbieren (6.25kHz) oder aber zwei QSOs gleichzeitig auf einem Kanal per TDMA (time-division multiple access) ermöglichen.\\[0.5cm] \end{frame} \begin{frame}{TDMA - 2 QSOs auf einer Frequenz} \begin{center} \includegraphics[width=0.7\linewidth]{../fig/timeslot.tex} \end{center} Mit AMBE+2, können 60ms Audio in weniger als 30ms übertragen werden. D.h., wir können die Zeit in zwei Zeitschlitze aufteilen. Jeder 30ms lang.\\[0.5cm] Was/Wann Zeitschlitz 1/2 ist, bestimmt der Repeater. Er gibt den Takt vor. \\[0.5cm] Somit sind auf einem 12.5kHz Kanal zwei parallele unabhängige QSOs möglich. \end{frame} \begin{frame}{Qualität und Reichweite} Technisches Limit 150km, für Relaisbetrieb ausreichend.\\[0.5cm] Hörbare Kompressionsartefakte durch AMBE+2. Auch bei idealer Verbindung. Dennoch sehr gute Verständlichkeit, vglb. 12.5kHz FM. \\[0.5cm] Qualität beleibt auch bei schlechtem SNR erhalten. Dann aber schnelle Degradierung. \end{frame} \section{Konfiguration} \begin{frame}{Konfiguration (Brandmeister)} Die Konfiguration (sog. Codeplug erstellen) von DMR Funkgeräten ist recht komplex. \\[0.5cm] \pause In FM ist ein Kanal ein Relais. In DMR sind Relais nicht wichtig, sondern Sprechgruppen. Wir programmieren daher logische Kanäle: \begin{center} \begin{tabular}{|c|c|} \hline FM & DMR \\ \hline \hline RX Frequenz & RX Frequenz \\ TX Ablage & TX Ablage \\ --- & Zeitschlitz \\ (CTCSS/DCS) & Color Code \\ --- & TX Kontakt \\ --- & Empfangsgruppen \\\hline \end{tabular} \end{center} \end{frame} \begin{frame}{Konfiguration (Brandmeister)} \begin{description} \item[RX/TX Frequenz] Klar. \item[Zeitschlitz] Hatten wir schon. Konvention: \begin{itemize} \item TS1 überregionaler Funkverkehr \item TS2 regionaler/lokaler Funkverkehr \end{itemize} \item[Color Code] Meist fix 1. Vergleichbar mit CTCSS/DSC Tönen in FM. Vermeidet Probleme bei Überreichweiten. \item[TX Kontakt] Standard Sprechgruppe für den Kanal. Wird angerufen, wenn PTT gedrückt wird. Man kann aber auf einen empfangenden Ruf direkt antworten. \item[Empfangsgruppen] Sprechgruppen, die auf einem Kanal empfangen werden sollen. \end{description} \end{frame} \begin{frame}{Konfiguration -- DMR-ID} \begin{itemize} \item Jeder Teilnehmer, Sprechgruppe und Dienst benötigt eine eindeutige DMR ID. \item Eine ID pro Teilnehmer, nicht Gerät! Zu bekommen bei \url{https://radioid.net}. \item Funktioniert wie eine Telefonnummer, incl. Prefix. Z.B., 262/263 ... Deutschland. \item Datenbank ID$\leftrightarrow$Call frei verfügbar. Kann auch aufs Gerät gespielt werden um ID aufzulösen. \end{itemize} \end{frame} \begin{frame}{Konfiguration -- Sprechgruppen} \begin{description} \item[TG8] Regionaler Verbund von Repeatern. Nicht festgelegt. \item[TG9] Nur dieser Repeater. Verhält sich wie FM Repeater. \item[TG91] Welt weit, ist viel los dort. \item[TG92] Europa \item[TG2621] Berlin/Brandenburg \item[TG2629] Sachsen/Thüringen \end{description} \end{frame} \begin{frame}{Konfiguration -- Kanäle} In DMR werden pro Repeater mind. zwei Kanäle programmiert. Einen für jeden Zeitschlitz. Meist aber eher mehr. \\[0.5cm] Ziel: Kontakt zu Sprechgruppen. \begin{enumerate} \item Liste der Sprechgruppen zusammenstellen, die uns interessieren. \begin{itemize} \item TG9, TG2621, TG2629, TG26223 (Chaosrunde CCC), ... \end{itemize} \item Liste der Repeater raus suchen. \begin{itemize} \item DB0LDS, DB0PDM, DM0LEI, ... \end{itemize} \end{enumerate} \end{frame} \begin{frame}{Konfiguration -- Empfangsgruppen} Überbleibsel des kommerziellen Ursprungs. Wir gruppieren alle Sprechgruppen, die uns gleichzeitig interessieren. Es macht wenig Sinn, Sprechgruppen zusammenzufassen, die nie gleichzeitig empfangen werden können. Am besten, per Region:\\[0.5cm] \begin{description} \item[B/B] TG8, TG9, TG2621 \item[Sa/Th] TG8, TG9, TG2629 \item[CCC] TG26223 \end{description} \end{frame} \begin{frame}{Konfiguration -- Kanäle} Jetzt sind wir so weit, die Kanäle anlegen zu können: \begin{center} \begin{tabular}{|l|llllll|}\hline Name & RX Freq & Ablage & CC & TS & Kontakt & RX Gr. \\ \hline \hline DB0LDS BB & 439.5625 & -7.6 & 1 & 2 & Berlin/Brandenburg & B/B \\ DB0LDS ST & 439.5625 & -7.6 & 1 & 1 & Sachsen/Thüringen & Sa/Th \\ DB0LDS CCC & 439.5625 & -7.6 & 1 & 1 & Chaosrunde & CCC \\ DB0PDM BB & 438.4000 & -7.6 & 1 & 2 & Berlin/Brandenburg & B/B \\ DM0LEI BB & 439.1500 & -7.6 & 1 & 1 & Berlin/Brandenburg & B/B \\ DM0LEI ST & 439.1500 & -7.6 & 1 & 2 & Sachsen/Thüringen & Sa/Th \\ \hline \end{tabular} \end{center} \end{frame} \begin{frame}{Konfiguration -- Zonen} Da so sehr viele Kanäle entstehen, werden diese in Zonen zusammengefasst (meist nach Regionen):\\[0.5cm] \begin{description} \item[Berlin/Brandenburg] DB0LDS BB, DB0PDM BB, DB0LDS ST, DB0LDS CCC \item[Sachsen/Thüringen] DM0LEI BB, DM0LEI ST \end{description}\vspace{0.5cm} \pause Einige Geräte unterstützen auch sog. Roaming. Dabei wird einem Kanal eine Liste von alternativen Frequenzen, CCs, und TSs zugewiesen. Diese ermöglichen es, mit einer Sprechgruppe in Verbindung zu bleiben, wenn der aktuell eingestellte Kanal den Kontakt zum Repeater verliert. \end{frame} \section{Demo} \begin{frame} \begin{block}{} Demo... \end{block} \end{frame} \section*{QDMR} \begin{frame}{QDMR} Da Konfiguration recht kompliziert sein kann, wird eine Software vom Hersteller verwendet (CPS, Codeplug Programming Software). \begin{itemize} \item CPS vom Hersteller nur für Windows \item Bedienkonzept aus den 90ern. \item Miese Übersetzung aus dem Chinesischen. \item Sehr auf kommerzielle Anwender ausgerichtet. \item Keine Anbindung an User und Repeater Datenbanken. \item Kein Austausch von Konfigurationen zwischen Geräten oder gar Herstellern. \end{itemize} \end{frame} \begin{frame}{QDMR} Da ich kein Windows habe, brauchte ich eine Lösung um meine DMR Geräte zu programmieren: Also eigene Software schreiben. \begin{itemize} \item basiert auf Tool \texttt{dmrconfig} von Serge, KK6ABQ \item läuft auf Linux und Mac OS X \item verwendet gemeinsame Konfiguration für 20 verschiedene Geräte von verschiedenen Herstellern \item Einbindung der User DB (DMR ID $\leftrightarrow$ Calls) \item Einbindung der Sprechgruppen DB von Brandmeister \item Einbindung von Repeater Book \item nur 100k Zeilen C++ Code. \end{itemize} \end{frame} \end{document} ================================================ FILE: doc/dmrconf.in.xml ================================================ dmrconf 1 dmrconf ${PROJECT_VERSION} User Commands Hannes Matuschek dm3mat@darc.de Main author dmrconf Command-line tool for programming DMR radios. dmrconf file Description dmrconf is a command-line tool to program DMR radios. That is, generating and uploading codeplugs to these radios. To this end, dmrconf uses a common human-readable text format to describe the codeplug for all supported radios (see below). This allows one to share codeplugs between different radios. Additionally, dmrconf also allows one to download codeplugs from the radio and to store it in the human-readable text format. Commands detect Detects a connected radios. You may specify a specific device using the or option. read Reads a codeplug from the radio and stores it into the given file. This command may need the or options if the file type cannot be inferred from the filename. write Writes the specified codeplug to the radio. This command may need the , or options if the file type cannot be inferred from the filename. write-db Writes the call-sign database to the device. This command may need the option to select call-signs if the complete database does not fit into the device. If specified, all call-signs closest to the specified ID are used. verify Verifies the codeplug with the connected radio or the specified radio passed with the option. This command may also need the or options if the file type cannot be inferred from the filename. encode Encodes a YAML codeplug as a binary one for the connected or specified radio using the option. encode-db Encodes the call-sign database as a binary one for the connected or specified radio using the option. This command may need the option to select call-signs if the complete database does not fit into the device. If specified, all call-signs closest to the specified ID are used. decode Decodes a binary codeplug and stores the result in human-readable form. The radio must be specified using the option. info Prints some information about the given file. Options or Specifies the file format for the input file for verify, encode and write commands. This option is not needed if the filetype can be inferred from the filename. That is, if the file ends on .conf or .csv. or Specifies the file format for the input or output file for the verify, read and write commands. This option is not needed if the filetype can be inferred from the filename. That is, if the file ends on .yaml or .yml. or Specifies the file format for the input or output file for the verify, read and write commands. This option is not needed if the filetype can be inferred from the filename. That is, if the file ends on .bin or .dfu. or Specifies the file format for the input file for the decode command to be the manufacturer binary codeplug format. Not all manufacturer formats are implemented. or Specifies the device to use. Either a USB BUS:DEVICE number combination or the name of a serial interface. The device must be specified if the automatic radio detection fails, is unsave or if more than one radio is connected to the host. or NAME Specifies the radio for the verify, encode or decode commands. This option can also be used to override the automatic radio detection for the read and write commands. Be careful using this option when writing to the device. An incompatible code-plug might be written. or DMR_ID Specifies the DMR ID or a comma separated list of DMR ID prefixes for the write-db or encode-db commands. More than one ID may be specified using a comma-separator. or N Limits several amounts, depending on the context. When encoding or writing the call-sign db, this option specifies the maximum number of call-signs to encode. or JSON_FILE Specifies the call-sign database to use for writing a user-db to the device. Initializes the code-plug from scratch. If omitted (default) the codeplug on the device gets updated. This maintains all settings made earlier via the manufacturer CPS or on the radio itself, that are not part of the codeplug. If set, the device clock gets updated with the transfer to the radio. That is, with any codeplug or call-sign DB being written to the device. Not all radios support this feature. Automatically enables GPS/APRS if at least one GPS/APRS system is defined and used by any channel. Automatically enables roaming if at least one roaming zone is defined and used by any channel. Disables the enforcement of limits. Warnings are still shown. or Displays a short help message. Lists all supported radios. or Displays the version number. or Enables debug messages. Supported Radios The following list contains all supported radios and their names for the option. All radios running the Open GD77 firmware. Anytone AT-D868UV(E). Anytone AT-D878UV. Anytone AT-D878UVII. Anytone AT-D578UV (II). TYT MD-390 or Retevis RT8. TYT MD-UV390 or Retevis RT3S. TYT MD-2017 or Retevis RT82. Radioddity GD-73. Radioddity GD-77. Baofeng/Radioddity RD-5R. Baofeng DM1701 or Retevis RT84. Baofeng DR1801UV-A6. Baofeng DM-32UV. Bugs This program is still under development and may contain bugs that may cause harm to the radios and may even destroy them. Hence you may use this software on your own risk. If you want to have guaranties, consider using the CPS (code-plug programming software) supplied with your radio. ================================================ FILE: doc/docbook_man.debian.xsl ================================================ ================================================ FILE: doc/docbook_man.fedora.xsl ================================================ ================================================ FILE: doc/docbook_man.macports.xsl ================================================ ================================================ FILE: doc/docbook_man.opensuse.xsl ================================================ ================================================ FILE: doc/fig/autodetect.dot ================================================ digraph detect { graph [nodesep="0.5", ranksep="0.25"]; rankdir = LR; enum [ label="Detect all devices\nmatching VID:PID"; shape=rect; ]; single [ label="Only one USB\n device found?"; shape=diamond; ]; save [ label="Save to assume\n device is radio?"; shape=diamond; ]; userif [ label="User selects\n USB device."; shape=parallelogram; ]; ident [ label="All matching radios\n identifiable?"; shape=diamond; ]; userdev [ label="User selects\n radio."; shape=parallelogram; ]; id [ label="Connect to radio\n and request identifier."; shape=rect; ]; end [ label="Radio detected\nselected."; shape=oval; ]; {rank = same; enum; single; save; ident; id; end; } {rank = same; userif; userdev; } enum:s -> single:n; single:s -> save:n [label="yes"]; save:e -> userif:w [label="no"]; save:s -> ident:n [label="yes"]; single:e -> userif:n [label="no"]; userif:s -> ident:n; ident:e -> userdev:w [label="no"]; ident:s -> id:n [label="yes"]; userdev:s -> end:e; id:s -> end:n; } ================================================ FILE: doc/manual/Makefile ================================================ SRC = manual.xml $(shell find meta/ -name "*.*") $(shell find intro/ -name "*.*") $(shell find gui/ -name "*.*")\ $(shell find codeplug/ -name "*.*") $(shell find conf/ -name "*.*") $(shell find reveng/ -name "*.*") $(shell find cli/ -name "*.*") all: images manual.pdf manual.epub html images: make -C intro/fig make -C gui/fig manual.pdf: manual.fo fop -fo manual.fo -pdf manual.pdf manual.fo: manual_combined.xml manual_fo.debian.xsl saxon-xslt -o manual.fo manual_combined.xml manual_fo.debian.xsl xslthl.config="/usr/share/xslthl/highlighters/xslthl-config.xml" manual_combined.xml: $(SRC) xmllint --nonet --noent --xinclude manual.xml > manual_combined.xml manual.epub: images-epub manual_combined.xml cp manual_combined.xml epub/manual.xml make -C epub images-epub: images epub/fig install intro/fig/*.png epub/fig install gui/fig/*.png epub/fig epub/fig: mkdir epub/fig html: images-html cp manual_combined.xml html/manual.xml make -C html images-html: images html/fig manual_combined.xml install intro/fig/*.png html/fig install gui/fig/*.png html/fig html/fig: mkdir html/fig clean: make -C intro/fig clean make -C epub clean make -C html clean rm -f manual.pdf manual.fo manual_combined.xml ================================================ FILE: doc/manual/cli/callsign.xml ================================================
Writing the call-sign DB The command line interface also allows to write a call-sign DB (also known as Talker Alias) to the radio if the radio supports it. This can be done with the write-db command. This command behaves similar to the call-sign DB upload in qdmr. That is, it tries to select the call-signs being written automatically. Although many radios provide a huge amount of memory for the call-sign DB, they cannot hold the entire list of assigned DMR IDs. Therefore, a selection of relevant call-signs must be done. qdmr and dmrconf do that based on the DMR ID of the radio. DMR IDs are not random. They follow a pattern similar to land-line phone numbers. DMR IDs within a certain continent, country and region share a common prefix. This way, it is possible to select DMR IDs that are close by selecting IDs that share a common prefix. qdmr and dmrconf use the default DMR ID of the radio to select the call-signs to be written. This ID is obtained by qdmr from the codeplug. dmrconf does not require a codeplug to be present for the upload. Consequently, it requires the explicit specification of the ID to base the selection on. The ID can be specified using the or options. It is also possible to just specify the prefix instead of an ID. This example writes a call-sign DB to the connected radio using the DMR prefix 2621 (for the region Berlin/Brandenburg in Germany). The radio will fill the available space with as many call-signs as possible. So for the previous example, dmrconf will not only program all IDs with the prefix 2621 but as many as possible starting with those close to that prefix. It is possible to limit the number of call-signs encoded using the or option. Unfortunately, there is always an exception to a rule: Some countries have several prefixes or you may program the call-signs of several countries that are not necessarily close in terms of their prefixes. For these cases, it is possible to specify a list of prefixes to the option. This example will select and write up to 10000 (given the radio can hold that amount) call-signs staring with those closest to the prefixes 262 and 263 (both prefixes for Germany).
Specifying own databases With version 0.11.3, it is possible, to specify a user-crafted JSON database. Then, this file will be used for selecting the call signs for writing the call-sign DB. Here, up to 10000 call signs are written from the my_db.json JSON file starting with those IDs closest to the prefix 262. Specifying a user-curated call-sign DB, simply replaces the public one and thus all methods described above still work.
Encoding a call-sign DB Like for the binary codeplug, it is also possible to generate and store the binary representation of the call-sign DB using the encode-db command. This command is only useful for debugging purposes as the binary representation of the call-sign DB cannot be written to the device. The encode-db takes the same arguments as the write-db command but additionally needs a filename to store the encoded DB into as well as the radio to encode for using the option. Like the previous example, this one encodes up to 10000 call-signs starting with those closest to the prefixes 262 and 263 for a AnyTone AT-D878UV radio and stores the result into callsigns.dfu.
================================================ FILE: doc/manual/cli/codeplug.xml ================================================
Reading and writing codeplugs The major feature of the command line tool is certainly the ability to read and write codeplugs from and to the device. The majority of the action happens automatically. Like the detection of the radio. If something goes wrong, an error message will be written to stderr. A more detailed logging can be enabled by passing the flag.
Reading a codeplug To read a codeplug, the read command is used. The codeplug can be stored in several formats. The human readable extensible codeplug format (YAML) and as a binary memory dump of the codeplug memory on the device. dmrconf detects the format based on the file extension or by means of an additional flag. The latter is particularly important if the read codeplug should be written to stdout for piping it to another program for further processing. Will simply read the codeplug from the detected device and stores it in the extensible codeplug format in the file codeplug.yaml. The format was detected by the file extension yaml which refers to the extensible codeplug format using YAML. To store the memory dump of the codeplug memory of the radio, the file extension should be dfu. As mentioned above, it is also possible to dump the decoded codeplug to stdout allowing to pipe the codeplug into another program for processing. This can be done by omitting the output filename. Then, however, the output format is not specified anymore. In this case, one the explicit format flags or must be used to specify in which format the codeplug should be written. These flags can also be used to store a codeplug in a particular format in arbitrarily named files. This example reads the codeplug from the connected device and decodes it. The decoded codeplug is then piped to the python script my_script.py.
Decoding binary codeplugs It is also possible decode binary codeplugs that has been read from the device earlier and stored as a memory dump (i.e., in a dfu file). This step is actually the second step automatically performed during reading. When reading a codeplug, in a first step the memory dump of the codeplug is read from the device. In a second step, the read binary codeplug is then decoded and dumped in a human readable format. The decode command performs that second step. To do that, it needs two additional information: The radio type, from which this codeplug was read and the format to write the decoded format to. Like for the read command, the latter can be passed by the output filename extension or via an additional flag. This example performs the same actions like a simple read command (assuming a TyT MD-UV390 is connected). It first downloads the binary codeplug. This time, the memory dump is stored in a binary form (dfu file). The second command then decodes the binary codeplug into the extensible codeplug format (yaml file), assuming that the binary codeplug stems from a TyT MD-UV390. Since version 0.8.1, it is now also possible to decode some manufacturer binary codeplug files as they are produced by the manufacturer CPS. To signal the decode command to treat the file as a manufacturer CPS file, you need to pass the or and the option. The latter tells the decode command the format of the file. That is, the call Will decode the manufacturer CPS file manufacturer_cps_file.rdt assuming it is a file generated by the CPS for the TyT MD-UV390. Like for the normal decoding the output format must be specified either by file extension or flag.
Debugging the codeplug decoding Under normal circumstances, it makes no sense to first read the binary codeplug from the device and then decoding it in a separate step as the read command will do that for you. However, if there is a bug in dmrconf that gets triggered by your codeplug on the device, the binary codeplug is an invaluable resource for debugging the application. Consider filing an issue at the bug tracker and include the binary codeplug as an attachment. If you like, you can also send me your codeplug directly. I'll keep it confidentially.
Writing a codeplug To write a codeplug into the device, the write command is used. The codeplug can be read from several formats. The extensible codeplug format (yaml file) as well as the old table based format (conf file). It is not possible to write binary codeplugs without decoding them first. Like for the read command, dmrconf will detect the format based on the file extension or by passed flags. This example will write the codeplug stored in the extensible codeplug format in codeplug.yaml to the connected device. Before writing the codeplug to the device, the connected device gets detected and the codeplug gets verified. If the verification step fails, one or more error messages are written to stderr describing the issue with the codeplug. One verification step is the check whether all channel frequencies are within the frequency limits specified by the manufacturer. The latter check can be disabled using the flag. There are also some flags controlling the assembly of the binary codeplug. When the flag is set, the codeplug will be generated from scratch using default values for all options not explicitly specified in the codeplug file. This might be used to initialize a brand new radio. However, any changes made to the radio are lost. When this option is not set, the codeplug gets encoded and written in a two-step process. First the current binary codeplug is downloaded from the radio. Then the codeplug file is used to update the binary codeplug. The result is then written back to the device. This ensures that all settings made in the radio are kept, unless they are explicitly set in the codeplug file. The and flags will tell dmrconf to enable the GPS or roaming feature whenever any of the programmed channels use the GPS or a roaming zone. (This depends also on the ability of the radio.)
Verify a codeplug The aforementioned verification of the codeplug file can also be performed separately using the verify. This command also needs to know against which radio the codeplug should be verified. The radio must be specified using the option. This command will verify the codeplug stored in codeplug.yaml in the extensible codeplug format against an AnyTone AT-D878UV. Like for the write, any issues are written to stderr. Like for the write command, the verification can be altered using the flag.
Encoding codeplugs Is is also possible to perform the encoding step of the codeplug separately. This can be done with the encode command. Like for the verify command, the encode command also needs the radio for which the codeplug should be encoded. The input format of the codeplug is again specified by either the file extension of the codeplug file or by flags. This call will encode the codeplug codeplug.yaml specified in the extensible codeplug format for a radio running the OpenGD77 firmware and stores the resulting binary codeplug in codeplug.dfu. Like for the write command, the encoding can be controlled using the and flags.
================================================ FILE: doc/manual/cli/commandline.xml ================================================ The <command>dmrconf</command> command line tool Beside the graphical user interface provided by qdmr (see ), there is also a command line tool allowing to read/write codeplugs from and to the radios. It is based on the same library called libdmrconf and thus provides the same features like qdmr. This chapter will briefly describe the command line tool and how it can be used to handle codeplugs from the command line. The command line tool might be helpful in cases, where the codeplug file (see ) gets assembled by a script. Then the same script may upload the codeplug to the radio using the command line tool. Additionally to the the features of the GUI (see ), the command line tool provides some features to analyze the memory representation of the binary codeplugs as well as debugging their implementation. ================================================ FILE: doc/manual/cli/dangerzone.xml ================================================
Danger zone You are about to enter the land of pain. Continue on your own risk. Some radios are actually identical to others. They also identify themselves as a different radio. An example for such a radio is the Retevis RT3S, this radio is simply a relabeled TyT MD-UV390. The RT3S actually identifies itself as a MD-UV390. From the perspective of the CPS, these two radios are indistinguishable. Consequently, qdmr and dmrconf will always identify the RT3S as a MD-UV390. There are, however, virtually identical radios. These are radios that actually identify themselves as different models but the firmware, communication protocol and codeplug is basically identical to another model. An example for such virtually identical models are the AnyTone AT-D868UV and the BTech DMR-6X2. Each model identifies itself correspondingly and thus is distinguished by the CPS. Some of these relationships between virtually identical models are known to qdmr and dmrconf. In these cases, the CPS will treat these radios as identical. Some of these close relationships between models are not known to dmrconf. In these cases, dmrconf will stop with an error that a radio is unknown although it actually supported as a different model. In these rare cases, it is possible to override the radio detection using the option. This option is usually used to specify the type whenever the radio model is not detected. This option also overrides the model detection and thus allows to handle virtually identical radios. For example, if the relation ship between the AT-D868UV and the DMR-6X2 would have not been known to dmrconf, a codeplug could read anyway from the device by calling Here the radio detection (resulting the detection of a DMR-6X2) gets overridden and the radio is handled as a AT-D868UV. If you know of such virtually identical radios that dmrconf does not recognize, consider filing an issue at the bug tracker. Of cause, handling a radio differently as it identifies itself may cause permanent damage to the radio. So you should be very sure that the radios are actually identical when overriding the radio detection routines.
================================================ FILE: doc/manual/cli/various.xml ================================================
Various features of <command>dmrconf</command> Beside reading and writing codeplugs or writing the call-sign DB, there are some more commands and features that mainly concern the debugging of the codeplug and call-sign DB encoding and decoding. If you are interested in the codeplug internals, you may use these commands to study them.
Getting help As usual for command line tools, a brief help text about the commands and options gets written to stdout when the or option is passed. No other commands passed are executed then. Similar to the option, it is possible to print the version number of dmrconf using the or option. Like for the , no other commands passed get executed. To get a list of keys identifying radio models when specified using the option, can be passed. This will print a small table to stdout that lists the keys for each known radio as well as the model and manufacturer name.
Detecting the radio type The command detect solely detects the radio. No data is written or read from the device (except of the radio model information). This command can be used to check whether a radio is detected correctly. This command will try to detect the connected radio. If a known radio is found, the model and manufacturer name is written to stdout. If no radio is detected or if the model is unknown or unsupported, an error message is written to stderr.
Inspecting binary codeplugs The encode, encode-db and read commands can store the codeplug and call-sign DB in binary form in a DFU file. The generated file is a valid DFU (device firmware update) file, that can be handled with other DFU tools. A DFU file may contains several so-called images. Each image may contains several so-called elements. The latter represents a segment of memory with an associated memory address. The info command produces a hex-dump of the DFU file that is written to stdout. It can then be inspected using more or less. This example will generate a hex dump of the encoded codeplug in the specified DFU file codeplug.dfu. The result is piped to less for easy reading. The hex dump also prints some information about the file structure as well as memory addresses. It also collapses repetitive memory sections (similar to hexdump -C). To this end, this command is a helpful tool for debugging the encoding of codeplugs and call-sign DBs.
================================================ FILE: doc/manual/codeplug/anytone/aprs.xml ================================================
FM APRS settings extension This extensions allows to specify some additional settings for the FM APRS System. As AnyTone devices allow only for one FM APRS system, these settings can be considered radio wide. The AnyTone FM APRS extension is a mapping named anytone. It contains the device specific settings for that FM APRS system. AnyTone devices only allow for a single FM APRS system.
FM APRS system attributes FM APRS system extension fields txDelay A delay before the start of the transmission. Specified with a resolution of 20ms. The default value is 60ms. preWaveDelay A delay between the start of the carrier and the actual APRS transmission. Default 0ms. passAll Basically disables the CRC check on received APRS messages. reportPositionreportMicEreportObject reportItemreportMessagereportWeather reportNMEAreportStatusreportOther A bunch of flags indicating what is being reported (?!?). frequencies While AnyTone only allows for a single set of parameters for the FM APRS transmission and reception, it allows for several frequencies. The default frequency is specified by the revert channel of the FM APRS system. Seven additional frequencies might be specified through this list. Each entry is a mapping, specifying a unique ID for the frequency, a name and the actual frequency. The id can then be used to reference this FM APRS frequency in the AnyTone channel extension.
================================================ FILE: doc/manual/codeplug/anytone/channel.xml ================================================
Channel extensions This extensions allows to set some device specific channel settings for AnyTone devices. There are some general settings, valid for FM and DMR channels as well some FM and DMR specific settings. Examples of the <trademark>AnyTone</trademark> extensions for FM and DMR channels.
Common channel attributes This section describes the common channel attributes. These attributes are shared between FM and DMR channels. frequencyCorrection Specifies some sort of correction to the frequency in some unknown units. Consider contacting me, if you know more details about it. handsFree If true, the hands-free feature is enabled for this channel. The actual configuration of the hands-free feature happens in the general settings extension. This setting is only applicable to the AT-D578UV device. fmAPRSFrequency Optional alternative FM APRS frequency specified in the AnyTone extension to the APRS settings. aprsPTT Enables/specifies if and when the position is send via APRS whenever the PTT is pressed. Possible values are Off, Start and End. If a DMR APRS system is associated with this channel, Start and End are synonymous. If and FM APRS system is associated with this channel, the position is send at the beginning (Start) or end (End) of a transmission. If Off is set, the position will not be send along with every transmission. In this case, the position might be send periodically, depending on the settings of the position reporting system.
FM channel attributes These attributes are only valid for FM channels. rxCustomCTCSS If true, the custom CTCSS frequency (see customCTCSS) is used to control the squelch. txCustomCTCSS If true, the custom CTCSS frequency (see customCTCSS) is transmitted. customCTCSS Specifies a custom CTCSS frequency in Hz. The frequency can be specified with a resolution of 0.1Hz. squelchMode Specifies the squelch mode. Must be one of Carrier, SubTone, OptSig, SubToneAndOptSig or SubToneOrOptSig. scrambler If true, the analog scrambler is enabled. It is not legal to use scramblers in HAM radio.
DMR channel attributes This section describes the attributes, applicable to DMR channels on AnyTone devices. adaptiveTDMA If true, the adaptive TDMA mode is enabled. This makes only sense, if simplexTDMA is enabled too. In this case, the radio is able to receive both simplex TDMA as well as normal simplex DMR on the channel. throughMode If true, the through mode is enabled. What ever that means. If you know more about it, consider extending this section.
================================================ FILE: doc/manual/codeplug/anytone/contact.xml ================================================
DMR contact extension This extensions allows to specify some DMR contact attributes for AnyTone devices. This extension is only applicable to DMR (digital) contacts. The AnyTone contact extension is a mapping named anytone. It contains the device specific settings for that DMR contact.
Contact attributes For now, there is only one attribute allowing to override the alert type for a DMR contact. Channel extension fields alertType The alert type specifies the notification type when a call from that contact is received. To this end, this field overrides the ring setting of the common contact settings. This attribute is either None, Ring or Online. If None is set, the notification is disabled.
================================================ FILE: doc/manual/codeplug/anytone/extensions.xml ================================================
<trademark>AnyTone</trademark> Codeplug Extensions This chapter documents the extensions and settings specific to AnyTone radios. Please note, that not all attributes are applicable to all devices.
================================================ FILE: doc/manual/codeplug/anytone/settings.xml ================================================
Settings extension This extensions allows to specify some device specific settings for AnyTone devices. As there are a myriad of settings for these devices, the settings extension is split into several sub-extensions, grouping the settings by domain. The AnyTone settings extension is a mapping named anytone. It contains the device specific settings for AnyTone devices.
General settings attributes General settings extension fields subChannel If true, the sub-channel is enabled. selectedVFO Specifies the currently selected VFO. Must be one of A or B, selecting VFO A and B, respectively. modeA, modeB Specifies the VFO mode for VFOs A and B. Must be one of Memory or VFO. The former selects the memory/channel mode while the latter selects the VFO mode. zoneA, zoneB Specifies the current zones for VFO A and B. Must be a reference to a zone. vfoScanType Specifies scan-type for the VFO mode. Must be one of Time, Carrier or Stop. Default is Time. If Time is selected, the scan is stops for a specified period. If Carrier is selected, the scan stops as long as a carrier is detected. If Stop is selected, the scan stops entirely. minVFOScanFrequencyVHF, maxVFOScanFrequencyVHF, minVFOScanFrequencyUHF, maxVFOScanFrequencyUHF Specifies the frequency ranges for the VFO scan in the VHF and UHF bands. These frequency can be specified arbitrarily. If a unit-less floating point value is given, it is interpreted as in MHz. If a unit-less integer value is given, it is interpreted as in Hz. keepLastCaller If true, the last caller ID is kept even, when the channel is changed. Seriously, who needs that option? Just keep it or don't. Why is it so important? AnyTone codeplugs are full of these unnecessary settings. vfoStep Specifies the VFO tuning step-size. steType, steFrequency, steDuration Specifies the squelch-tail elimination type, frequency and duration. The type must be one of Off, Silent, Deg120, Deg180 or Deg240. The frequency is specified as a floating point number in Hz. The duration is specified in ms. This option is applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV devices. tbstFrequency Specifies TBST/pilot-tone frequency used to enable/open a repeater. This should be one of the common frequencies like 1000, 1450, 1750 and 2100 Hz. This option is applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV devices. proMode If true, enables the professional mode. If enabled, some menu options are hidden. This option is applicable to AT-D878UV, AT-D878UV II and DMR-6X2UV devices. maintainCallChannel If true, maintains the call-channel. Whatever that now means. If you find it out, let it me know. This option is applicable to AT-D878UV, AT-D878UV II and DMR-6X2UV devices. fan Specifies, what controls the fan of the D578UV. The must be one of PTT (default), Temperature or Both. bootSettings, powerSaveSettings, keySettings, toneSettings, displaySettings, audioSettings, menuSettings, autoRepeaterSettings, dmrSettings, roamingSettings, gpsSettings, simplexRepeaterSettings Many more funny settings, grouped by domain. For details, see the descriptions below.
Boot settings Collects some settings, that control the startup of the radio. These settings are collected under the bootSettings key within the anytoneSettings entry. Boot settings extension fields bootDisplay Specifies what is seen on the display during boot. Must be one of Default, CustomText or CustomImage. bootPasswordEnabled, bootPassword Enables/disables and specifies the boot password. The boot password is a string of up to 8 number chars. defaultChannel, zoneA, channelA, zoneB, channelB If true, defaultChannel enables the specified zones and channels to be set on startup. zoneA and zoneB specify the startup zone for VFOs A and B, respectively, and must reference a defined zone. While channelA and channelB specify the startup channels for each VFO. If no channels are defined, the VFO is chosen. priorityZoneA, priorityZoneB Specifies the priority zones for each VFO. These must reference a defined zone. defaultRoamingZone Specifies the startup roaming zone. Must refer to a defined roaming zone. This option is applicable to AT-D878UV, AT-D878UV II and AT-D578UV devices. gpsCheck Enables the GPS check on boot. This option is applicable to AT-D878UV and AT-D878UV II devices. reset Enables a MCU reset on boot. This option is applicable to AT-D878UV and AT-D878UV II devices.
Power-save settings Collects some settings, that configure the power-save features. These settings are collected under the powerSaveSettings key within the anytoneSettings entry. Power-save setting extension fields autoShutdown Specifies the time-out for the automatic shut down. If 0, the automatic shut-down is disabled. resetAutoShutdownOnCall If true, the automatic showdown timer is reset on every call. powerSave Specifies the power-save mode. Must be one of Off, Save50 or Save66. The latter two will save 50% or 66%, respectively. atpc Enables the ATPC (Adaptive Transmission Power Control). It reduces the transmission power, if a strong signal is received.
Keypad settings Collects some settings, that configure the keypad, lock etc. These settings are collected under the keySettings key within the anytoneSettings entry. This extension also allows to assign specific functions to the programmable function keys on the radio. These are the side-keys, top-key and the front buttons labeled P1 and P2. The following list shows all functions, that can be assigned to each of these buttons. Some function are not available on all devices. Key functions Off No function at all. Voltage Shows the battery voltage. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Power Toggles though the power settings. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Repeater Toggles the talk-around feature. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Reverse Swaps the RX and TX frequencies. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Encryption Shows the encryption key selection. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Call Performs an FM call with the configured DMTF/two-tone/five-tone ID. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. VOX Shows the VOX level selection. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II and DMR-6X2UV. ToggleVFO Toggles between VFO and memory/channel mode. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV. SubPTT PTT for the sub channel. Only applicable for PF1, PF2 and PF3 keys. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II and DMR-6X2UV. Scan Starts/stops a scan. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. WFM Toggles the WFM (or Air band) receiver. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Alarm Starts/stops an alarm call. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. RecordSwitch Enables/disables recoding function. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Record Start/stops a recoding. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. SMS Start writing an SMS. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Dial Start manual dial. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. GPSInformation Applicable to AT-D868UV, AT-D578UV and DMR-6X2UV. Monitor Enables the (FM) monitor. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. ToggleMainChannel Toggles between VFOs A and B for the main channel. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. HotKey1, HotKey2, HotKey3, HotKey4, HotKey5, HotKey6 Triggers one of the programmed hot-key function. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. WorkAlone Toggles the lone-worker mode. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. SkipChannel During a scan, use this function to skip the current channel. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. DigitalMonitor Toggles the digital monitor. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. SubChannel Enables/disables the sub-channel. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. PriorityZone Change to the priority zone. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. VFOScan Toggles the VFO scan. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. MICSoundQuality Toggle enhanced sound mode in DMR mode. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. LastCallReply Allows to answer the last call, after the hang-time has passed. Applicable to AT-D868UVE, AT-D878UV, AT-D578UV and DMR-6X2UV. ChannelType Change the channel type. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. SimplexRepeater Enables/disables the simplex repeater feature. Applicable to DMR-6X2UV. Ranging Applicable to AT-D868UV, AT-D578UV and DMR-6X2UV Roaming Manually starts roaming. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. ChannelRanging Applicable to AT-D868UV, AT-D578UV and DMR-6X2UV MaxVolume Shows the maximum volume setting. Applicable to AT-D868UV, AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Slot Changes the time-slot, AT-D868UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. Squelch Shows squelch settings. Applicable to AT-D578UV and DMR-6X2UV. APRSTypeSwitch Applicable to AT-D578UV. Zone Shows the zone selection dialog. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. RoamingSet Shows the roaming settings(?). Applicable to AT-D878UV, AT-D878UV II and DMR-6X2UV. APRSSet Opens APRS settings. Applicable to DMR-6X2UV. Mute Mutes the radio for a specified interval. Applicable to AT-D878UV, AT-D878UV II and DMR-6X2UV. MuteA, MuteB Mutes the VFO A/B for a specified interval. Applicable to AT-D578UV. XBandRepeater Enables the cross-band repeater function. Applicable to AT-D578UV. Speaker Applicable to AT-D578UV. CtcssDcsSet Shows the CTCSS/DCS settings for FM channels. Applicable to AT-D878UV, AT-D878UV II and DMR-6X2UV. TBSTSend Sends the TBST tone. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV. Bluetooth Enables/disables bluetooth function. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV. GPS Enables/disables GPS. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. ChannelName Toggle channel name and frequency display. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV. CDTScan Starts CTCSS/DCS scan for FM channel. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV. APRSSend Transmits the positioning information. Applicable to AT-D878UV, AT-D878UV II and AT-D578UV. APRSInfo Shows received APRS information. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. GPSRoaming Applicable to AT-D578UV. NoiseReductionTX Applicable to AT-D578UV. DIMShut Unknown function. Applicable to DMR-6X2. SatPredict Starts the prediction of passes of programmed satellites. Applicable to DMR-6X2. The following list contains all keypad setting fields. Keypad settings extension fields funcKey1Short, funcKey1Long, funcKey2Short, funcKey2Long, funcKey3Short, funcKey3Long, funcKey4Short, funcKey4Long, funcKey5Short, funcKey5Long, funcKey6Short, funcKey6Long Specifies the functions associated with the function keys P1-P6 for a short and long press. Must be one of the items in the key function list above. The function keys 1-6 are labeled P1-P6 and are located either below or next to the display. funcKeyAShort, funcKeyALong Specifies the functions associated with the programmable function key PF1 or A. This button is located right below the PTT button (on handheld radios) or labeled A on the hand microphone (AT-D578UV). funcKeyBShort, funcKeyBLong Specifies the functions associated with the programmable function key PF2 or B. This button is the second one below the PTT button (on handheld radios) or labeled B on the hand microphone (AT-D578UV). funcKeyCShort, funcKeyCLong Specifies the functions associated with the programmable function key PF3 or C. THis button is the colorful one on the top (on handheld radios) or labeled C on the hand microphone (AT-D578UV). funcKeyDShort, funcKeyDLong Specifies the functions associated with the programmable function key D. This button is located on the hand microphone (AT-D578UV only). funcKnobShort, funcKnobLong Specifies the functions associated with the programmable channel/frequency knob. (AT-D578UV only). longPressDuration Specifies the minimum duration of a long press on one of the programmable function keys. upDownKeys Specifies the functions associated with the up/down buttons on the top of the handset. This must either be Channel or Volume. (AT-D578UV only). autoKeyLock Enables/disables the automatic key lock. knobLock, keypadLock, sideKeysLock Specifies, whether the knob, keypad and side-keys are locked, when the key-lock is enabled. forcedKeyLock Don't use. Prevents the manual key-lock release. Only applicable to commercial applications.
Tone settings Collects some settings, that configure signal tones. These settings are collected under the toneSettings key within the anytoneSettings entry. Tone settings extension fields keyTone Enables the key-pad tones. Should be disabled. keyToneLevel Specifies the key-tone level, 0 means user adjustable. smsAlert Enables the SMS alert tone. callAlert Enables the call alert tone. See also callMelody. dmrTalkPermit, fmTalkPermit Enables the DMR talk permit tone. dmrReset Enables the DMR reset tone. See also resetMelody. dmrIdle, fmIdle Enables the idle tone (for DMR and FM channels). See also idleMelody. startup Enables a startup tone. tot Transmit time-out warning tone. Applicable to DMR-6X2 PRO. wxAlarm Enables the weather alarm. Applicable to DMR-6X2 PRO. callMelody, idleMelody, resetMelody, callEndMelody Specifies the melody played on each of these occasions. These melodies can contain up to 5 notes (and pauses). For the sake of simplicity, qdmr supports the LilyPond musical notation for these melodies. E.g., a8 b e2 des4 d.
Display settings Collects all display settings. These settings are collected under the displaySettings key within the anytoneSettings entry. Display settings extension fields displayFrequency If true, the channel frequency is shown instead of the channel name. brightness Specifies the brightness of the backlight in a range 1-10. backlightDuration, backlightDurationRX, backlightDurationTX Specifies the back-light duration for several occasions. Not all devices support all settings. None is supported by all devices. It is a mess. backlightDuration specifies how long the backlight stays on, after any event/user interaction. Usually, after a button has been pressed. This value can be set to infinity to specify, that the backlight stays on indefinitely. The feature is supported by AT-D868UVE, AT-D878UV, AT-D878UV II, DMR-6X2 and DMR-6X2 PRO backlightDurationRX specifies, how long the backlight stays on during reception. This value can be set to infinity to specify, that the backlight stays on during the entire reception period. This feature is supported by AT-D878UV, AT-D878UV II, DMR-6X2 and DMR-6X2 PRO. backlightDurationTX specifies, how long the backlight stays on during transmission. This value can be set to o to disable the backlight during transmission. This feature is supported by AT-D878UV, AT-D878UV II, AT-D578UV and AT-D578UV III. customChannelBackground If true, the custom channel background is enabled. volumeChangePrompt, callEndPrompt If true the volume change and call-end prompts are shown. showClock, showCall, showContact, showChannelNumber, showColorCode, showTimeSlot, showChannelType, showLastHeard Enables/disables various display elements. showGlobalChannelNumber If enabled, the global channel number is shown. If disabled, the channel number within the zone is shown. Applicable to DMR-6X2 PRO. lastCallerDisplay Specifies how the last caller is shown. Must be one of Off, ID, Call or Both callColor Specifies the call color. Must be one of White, Black, Orange, Red, Yellow, Green, Turquoise, Blue. Please note, that not all devices allow for all colors. Especially the AT-D868UV has only limited colors. standbyTextColor, standbyBackgroundColor Specifies the text or background color in stand-by. Must be one of White, Black, Orange, Red, Yellow, Green, Turquoise, Blue. Please note, that not all devices allow for all colors. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. channelNameColor, channelBNameColor Specifies the color of the channel name. Must be one of White, Black, Orange, Red, Yellow, Green, Turquoise, Blue. Please note, that not all devices allow for all colors. Applicable to AT-D878UV, AT-D878UV II and AT-D578UV. zoneNameColor, zoneBNameColor Specifies the color of the channel name. Must be one of White, Black, Orange, Red, Yellow, Green, Turquoise, Blue. Please note, that not all devices allow for all colors. Applicable to AT-D878UV, AT-D878UV II and AT-D578UV. language Specifies the UI language. Must be one of English or German. Applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV. dateFormat Specifies the date format. Must be one of YearFirst (i.e., yyyy/mm/dd) or DayFirst (i.e., dd/mm/yyyy). Obviously, they got everything covered.
Audio settings This entry collects all audio-related settings. These settings are collected under the audioSettings key within the anytoneSettings entry. Audio settings extension fields voxDelay An interval specifying the delay to activate the VOX. Stored in ms resolution. voxSource Specifies the source for the VOX. Must be one of Internal, External or Both. recording If true, recording is enabled. enhance If true, enabled some TX audio enhancement. I don't know what it does nor if it is reasonable to enable. muteDelay Specifies some muting delay in minutes. maxVolume, maxHeadPhoneVolume Specifies the maximum speaker and headphone volume. enableFMMicGain, fmMicGain Enables and sets a separate microphone gain for FM. If disabled, the global mic gain is used for both, DMR and FM. speaker Specifies, which speaker are enabled. Must be one of Handset, Radio (default) or Both. This only applies to AT-D578UV. handsetSpeaker Specifies the source for the handset speaker, if it is enabled. Must be one of MainChannel or SubChannel. This only applies to AT-D578UV handsetType Specifies the type of handset. This must be one of Anytone or Generic. This only applies to AT-D578UV.
Menu settings This entry collects all menu-related settings (there aren't many). These settings are collected under the menuSettings key within the anytoneSettings entry. Menu settings extension fields duration Specifies the duration, the menu is shown. separator If true, the menu items are separated by a line. Again WTF AnyTone? Why the hack is an option needed for this? For me, this is just another useless option to check on every firmware update.
Auto-Repeater settings The auto-repeater settings of anytone-devices is quiet complex. It allows to specify rules, in which frequency ranges some auto-repeater settings are applied referencing a particular offset for that rule. These offsets are specified within a separate list of frequency offsets. These settings are needlessly complicated. It would be sufficient to specify a range and offset. AnyTone, however, decided that this would be way to simple and user friendly. However, fitting their concept into qdmr's concept of references to objects, makes it even worse. You will see. The auto-repeater settings are specified within the autoRepeaterSettings item within the anytoneSettings item. Auto-repeater extension fields directionA, directionB Specifies the direction of the repeater offset for VFO A and B. I have absolutely no clue, why this depends on the VFO and not on the BAND (VHF/UHF) or even per offset. Only the wise people at AnyTone will know the answer. However, must be one of Off, Positive or Negative. vhfMin, vhfMax, uhfMin, uhfMax, vhf2Min, vhf2Max, uhf2Min, uhf2Max Specifies the VHF and UHF frequency range for which the auto-repeater feature is defined. These can be any frequencies by the way. The vhf2 and uhf2 variants are only present for AT-D878UV, AT-D878UV II and AT-D578UV radios and allow for a second pair of frequency ranges with a different offset. vhf, uhf, vhf2, uhf2 References the offset for the VHF and UHF frequency range specified above. This must be a reference to one of the auto-repeater offsets defined below. The vhf2 and uhf2 references are only present for AT-D878UV, AT-D878UV II and AT-D578UV radios and allow for a different offset. offset A list of auto-repeater offsets. As qdmr only allows references to named objects, they must be relatively complex. Each entry has an ID (to reference the offset), name (because qdmr objects have names) and the actual offset frequency. See below for a description of these elements.
Auto-repeater offsets As mentioned above, qdmr requires any object referenced within the codeplug to be a proper object. That is with an ID and name. This makes sense, as almost all referenced elements within the codeplug are complex objects and have a display name. However, AnyTone decided to reference a mere offset frequency. Consequently, you have to give names to them in qdmr. Sorry. The below you find a list with the properties of the offset frequencies. Offset frequency fields id Specifies the unique ID of the offset frequency. Used to reference it. name Specifies the name of the offset frequency. Any string is valid and not needed. However, references are shown in qdmr with their name. So don't keep that field empty. offset Specifies the actual frequency offset.
An example for two auto-repeater frequency ranges active only on VFO A.
DMR related settings Some DMR related settings. Some of these should not be touched. groupCallHangTime, privateCallHangTime, manualGroupCallHangTime, manualPrivateCallHangTime Specifies the group and private call hang time. That is, the time, within you could answer a call by pressing the PTT, even if the group or private call is not the default transmit contact of the channel. preWaveDelay, wakeHeadPeriod Don't touch. Should both be 100ms. filterOwnID If true, your own ID is not shown in the call lists. smsFormat Specifies the SMS format. Must be one of Motorola, Hytera, DMR. monitorSlotMatch Specifies if and how the DMR monitor will match the time slot of the current channel. Must be one of Off, Single, Both. monitorColorCodeMatch If true, the DMR monitor will match the color code of the current channel. monitorIDMatch If true, the DMR monitor will match the DMR IDs to the group list of the current channel. monitorTimeSlotHold If true, the DMR monitor will hold the time slot first received on. sendTalkerAlias If enabled, sends the radio name as talker alias over the air. takerAliasSource Specifies the source for the talker alias display. Must be one of Off, UserDB or Air. talkerAliasEncoding Specifies the encoding of the taker alias. Must be one of ISO8, ISO7 or Unicode.
GPS settings This section collects settings concerning GPS position reporting. These settings are collected under the gpsSettings key within the anytoneSettings entry. timeZone Specifies the time-zone for the device. Any IANA time-zone ID is valid here. E.g., Europe/Paris or UTC+5. reportPosition If true, GPS position reporting is enabled. updatePeriod Specifies the update/reporting interval in seconds. Any interval specification is valid here. E.g., 5 min or 360 s.
Roaming settings This section collects settings concerning the roaming. These settings are collected under the roamingSettings key within the anytoneSettings entry. autoRoam If true, the auto-roaming is enabled. If false, the roaming must be started manually. autoRoamPeriod Specifies the auto-roaming period in minutes. Any interval specification is valid here. E.g., 30 min or 1 h. autoRoamDelay Specifies some additional delay in seconds before the auto-roaming is started. Any interval specification is valid here like 60 s or 1 min. roamStart,roamReturn Specifies the roaming start/end condition. Must be one of Periodic or OutOfRange. rangeCheck If true, the periodic range check is enabled. checkInterval Specifies the range check period in seconds. Any interval specification is valid here. E.g., 3 min or 180 s. retryCount Number of retries to connect to a repeater before giving up and starting the roaming. outOfRangeAlert Specifies the alert type for the out-of-range notification. Must be one of Off, Bell or Voice notification If true, the repeater check notification is enabled. notificationCount The number of repeater check notifications. defaultZone Specifies the default roaming zone. Must be a roaming zone ID or null. gpsRoaming If true, GPS based roaming is enabled.
Bluetooth settings This section collects settings concerning bluetooth feature. These settings are collected under the bluetoothSettings key within the anytoneSettings entry. enabled If true, the bluetooth feature is enabled. This option is applicable to DMR-6X2UV PRO devices. pttLatch If true, the bluetooth PTT button latches. This option is applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV PRO devices. pttSleepTimer Specifies the timeout for the sleep mode of the bluetooth PTT button. This option is applicable to AT-D878UV, AT-D878UV II, AT-D578UV and DMR-6X2UV PRO devices. internalSpeaker, internalMic If true, the internal speaker and or mic stays active, if Bluetooth is enabled. This option is applicable to DMR-6X2UV PRO devices. micGain, speakerGain Specifies the bluetooth mic and speaker gain. The actual range may depend on the device. For the DMR-6X2 PRO valid values are 0-4. This option is applicable to DMR-6X2UV PRO devices. hold, holdDelay Specifies the bluetooth hold duration or delay. Unfortunately, I have absolutely no clue, that this setting is all about. This option is applicable to DMR-6X2UV PRO devices.
Bluetooth Handset <productname>BT-01</productname> The AT-D578UV supports a bluetooth handset, that can control the radio. It is called BT-01 and looks a bit like a small AT-D878UV. There are plenty of settings for this device, stored in the AT-D578UV codeplug. They are collected as a sub-setting under handset within the bluetooth settings. volumeLevelA, volumeLevelB Specifies the volume for VFO A and B respectively. Must be a value in [0, 10], where 0 means off. micGain Specifies the microphone gain setting for the handset. Must be a value in [1,10]. squelch Specifies the squelch sensitivity. Must be a value in [0,10], where 0 means open. txNoiseReduction Specifies the transmit noise reduction (whatever that means). Must be a value in [0, 10], where 0 means disabled. voxLevel Specifies the VOX sensitivity level. Must be a value in [0,10], where 0 disables the VOX. voxDelay Specifies the VOX delay between triggering the VOX and start of transmission in arbitrary units. Default 0ms. funcKeyAShort, funcKeyBShort, funcKeyCShort, funcKeyALong, funcKeyBLong, funcKeyCLong, funcKeyAVeryLong, funcKeyBVeryLong, funcKeyCVeryLong Specifies the functions for the programmable side keys for short, long and very long key presses. The function must be one of the functions described in . shutdown If true, the handset will shutdown along with the radio. backlight Specifies the duration, the backlight remains on after any event (keypress, call, etc.). This can be set in arbitrary units an also to infinity.
Repeater settings Collects all settings related to the DMR-6X2UV and AT-D578UV repeater feature. These settings are collected under the repeaterSettings key within the anytoneSettings entry. enabled If true, the simplex repeater function is enabled. monitor If true, the traffic can be monitored on the speaker. This only applies to DMR-6X2UV. timeSlot, secTimeSlot Specifies the time-slot of the repeater. This must be one of Any, TS1, TS2 or Channel. The latter implies, that the repeater uses the time-slot of the channel. colorCode Specifies, how color codes are matched. If Ignore ignores the color code, VFOA and VFOB specifies that the color code must match the one of VFO A or B, respectively. This only applies to AT-D578UV.
<productname>DMR-6X2UV</productname> Simplex Repeater Mode The BETECH DMR-6X2UV series provides a simplex repeater feature. Enabling the repeater feature, will record any transmission received and retransmit it on the same channel. Consequently, the record feature must be enabled. The only relevant options for the DMR-6X2UV are enabled, monitor and timeSlot. The latter specifies, which time slot to listen and transmit on. This can either be TS1, TS2 or Channel. The latter requires any transmission to match the CC of the channel.
<productname>AT-D578UV</productname> Repeater The AnyTone AT-D578UV provides a proper cross-band or cross-timeslot repeater. That is, in contrast to the DMr-6X2UV the received audio is re-transmitted (almost) immediately. In principle, the repeater works using VFO A for reception and VFO B for transmission or vice-versa. If the AT-D578UV is set up as an FM repeater, the two channels must be on different bands. If set up as a DMR repeater, VFO A and VFO B must be on different time slots. This is controlled via the timeSlot setting. If set to TS1, the repeater receives on time slot 1 and transmits on time slot 2. If set set to TS2 the reverse it true. If set to Any, the radio switches automatically between the two schemes.
Satellite mode settings Collects all settings related to the satellite mode. This extension is only relevant for the DMR-6X2UV series. power Specifies the transmit power setting in satellite mode. This must be one of Min, Low, Mid, High or Max. squelch Specifies the FM squelch level in satellite mode. This must be an integer in [0,1-10], where 0 means an open squelch.
================================================ FILE: doc/manual/codeplug/anytone/zone.xml ================================================
Zone extension This extensions allows to specify some zone attributes for AnyTone devices. The AnyTone zone extension is a mapping named anytone. It contains the device specific settings for that zone.
Zone attributes For now, there is only one attribute allowing to hide the zone from the zone list in the radio. Zone extension fields hidden If true, the zone is hidden in the zone list on the radio.
================================================ FILE: doc/manual/codeplug/aprs.xml ================================================
Positioning Systems Some radios allow to send the current position using DMR or analog APRS. Consequently, there are two types of positioning systems dmr and aprs. As an example, consider these two position reporting systems: The first specifies a digital (DMR) positioning system while the latter defines a APRS system.
Common attributes The following attributes apply to both, analog APRS and digital DMR position reporting systems. Common fields id Specifies the ID of the system. This can be used reference this system in . Any unique string is valid here. name Specifies the name of the position reporting system. Any string is valid here. period Specifies the update period in seconds. If omitted or set to 0, the system will not send any updates periodically.
DMR position reporting system attributes The following attributes apply only to digital (DMR) position reporting systems. DMR position reporting system fields contact Specifies the digital contact, the GPS information is sent to. This must be a reference to a digital contact. revert Specifies the revert channel. That is, the channel the data is sent on. If set, it must be a reference to a digital channel. If omitted or set to !selected, the currently active channel will be used to send the GPS information.
APRS attributes The following attributes apply only for APRS position reporting systems. Analog position reporting system (APRS) fields revert Specifies the revert channel. That is, the channel the APRS information is sent on. This must be a reference to an analog channel. Usually a dedicated analog APRS channel is defined and referenced here. If omitted or set to !selected, the currently active channel will be used to send the position information. This, however, is not supported by all radios. Usually, an explicit APRS revert channel must be specified. destination Specifies the destination call and SSID, the information is sent to. This must be a string in the form CALL-SSID. Please note that the call must match the format defined by the AX.25 protocol. That is, it may only contain letters and numbers and cannot exceed the length of 6. source Specifies the source call and SSID. See destination for details. path Specifies the optional packet path. If set, this must be a list of CALL-SSID strings. icon Specifies the icon name to use. The icon name will be fuzzy matched. See for a complete list of icon names. message Specifies an optional message sent along with the position information.
================================================ FILE: doc/manual/codeplug/channels.xml ================================================
Channels The channels list contains all channels defined within the codeplug. Usually, DMR radios support at least two different channel types. DMR channels and analog FM channels. Some radios (e.g., running OpenRTX) also support M17 channels. To distinguish these three types, each entry of the channel list contains a map with a single entry. The key specifies the type (either dmr, fm or m17) while the value contains the actual channel definition. As an example consider the following two channel definitions: Channel definition The first channel is a DMR channel. Its ID is ch5 and its name BB DB0LDS TS2. The second channel is an analog FM channel with ID ch76 and name DB0LDS. Please note, that there are some common attributes like rxFrequency, txFrequency, power, timeout, rxOnly, vox and scanList but also type-specific settings like timeSlot or rxTone which apply only to digital and analog channels respectively.
Common attributes The following attributes are common for all channel types. Common channel fields id Specifies the ID of the channel, this can be used later to reference this channel in and . Any unique string is valid here. name Specifies the name of the channel. Any string is valid here. rxFrequency, txFrequency Specifies the RX or TX frequency in MHz. Any floating point number is valid here. power Specifies the transmit power of the channel. Must be one of Min, Low, Mid, High or Max. timeout Specifies the transmit timeout in seconds. Any integer is valid here. Omitting this field or setting it to 0 will disable the timeout. Setting it to !default, the global transmit timeout will be used. See . rxOnly If set to true, the channel can only receive. Omitting it or setting it to false will allow transmission on the channel. vox Specifies the VOX sensitivity for this channel. If set to !default, the global VOX sensitivity will be used. See . Omitting this value or setting it to 0, will disable the VOX for this channel. scanList Specifies the optional scan list for this channel. If set, this must be a reference to a .
DMR channel attributes The following attributes apply only to DMR channels. DMR channel fields admit Specifies the admit criterion for the channel. This must be one of Always, Free or ColorCode. colorCode Specifies the color code of the channel, any number between 0-16 is valid here. timeSlot Specifies the time slot of the channel. Must be one of TS1 or TS2. groupList Specifies the RX group list for this channel. This must be a reference to a group list. See above. contact Specifies the default transmit contact. This must be a reference to a digital contact. See above. If omitted, no default transmit contact is associated with the channel. Some radios require all channels to have a transmit contact. aprs Specifies the positioning system for this channel. If set, this must be a reference to a positioning system (DMR or APRS). See below. roaming Specifies the roaming zone for this channel. If set it must be a reference to a roaming zone. See below. To use the default roaming zone here, consider using the !default tag instead of a direct reference to a specific zone. If omitted, no roaming zone (also not the default one) is associated with the channel. radioID Specifies the radio ID for this channel. If set, it must be a reference to one of the radio DMR IDs. See . To use the default radio ID here, consider using the !default tag instead of a direct reference to a specific ID. If omitted the default radio ID is assumed.
M17 channel attributes The following attributes apply only to M17 digital channels. M17 channel fields mode Specifies the channel mode. That is, one of Voice, Data and VoiceAndData. accessNumber Specifies the channel access number. Similar to the color code of DMR channels, this must match the repeater access number. A value between 0-15. contact Specifies the M17 transmit contact. This must be a reference to a M17 contact. gpsEnabled Specifies wether position information is send along with voice and data. encryptionMode Specifies the encryption mode. That is, one of None, Scrambled and AES256.
FM channel attributes The following attributes apply only to FM channels. FM channel fields admit Specifies the admit criterion for the channel. Must be one of Always, Free or Tone. squelch Specifies the squelch level for the channel. Must be an integer in the range of [1-10]. If set to 0, the squelch gets disabled. If set to !default, the global squelch setting is used. See above. bandwidth Specifies the bandwidth of the channel. Must either be Narrow or Wide for 12.5kHz or 25kHz respectively. rxTone Specifies the receive sub-tone setting for this channel. That is, the squelch will only open when a certain subtone is received along with the signal. As there are two common subtone standards, this attribute is a map with a single entry. The key specifies the type (either ctcss or dcs) while the value specifies the actual subtone. For CTCSS tones, the value is the subtone frequency in Hz. For DCS it is the code number as an integer. For inverted DCS codes, use negative numbers. txTone Specifies the transmit sub-tone setting for this channel. For details see rxTone above. aprs Specifies the APRS positioning system of this channel. If set, it must be a reference to an analog APRS system.
Extended channel settings All channels have some extended settings section. This section allows to specify usually unused settings that are common among many of the supported radios. The extended settings are stored under the extended key.
Common extended channel settings There is only one setting common to all channel types. The talkaround flag. talkaround For a repeater channel, if enabled, the radio transmits on the RX frequency. This allows to bypass the repeater and answer directly on the RX frequency.
Extended FM channel settings Additional to the common extended channel settings, this section describes all extended settings applicable to FM channels. reverseBurst Enables the CTCSS phase-reversal at the end of the transmission. This can be used to mitigate an issue with the CTCSS squelch, where a short burst of noise may heard at the end of the transmission.
Extended DMR channel settings Additional to the common extended channel settings, this section describes all extended settings applicable to FM channels. privateCallConfirm, smsConfirm, dataConfirm Enables confirmation of SMS, data and private call reception. dcdm Enables the dual-capacity direct mode. For simplex operation, allows to utilize both time-slots. This requires one radio to dictate a clock to define TS1 and TS2. Not used in amateur radio. sms Enables SMS reception. loneWorker Enables lone-worker feature of some radios.
================================================ FILE: doc/manual/codeplug/commercial/channel.xml ================================================
Channel extension This extension allows to specify some channel settings specific for commercial applications of DMR. For now, it only allow for specifying the encryption key for a digital channel. The commercial channel extension is a mapping named commercial. It contains the settings for commercial features for that channel.
Channel attributes For now, there is only one attribute specifying the encryption key associated with the channel. Channel extension fields encryptionKey References a encryption key defined within the commercial codeplug extension. See .
================================================ FILE: doc/manual/codeplug/commercial/encryption.xml ================================================
Encryption extension This extension allows to configure the commercial encryption features of DMR. Almost all DMR radios implement means for encrypting the traffic. This feature however, is usually illegal when used within amateur radio. This extension is a simple list of keys held in the global commercial extension. Each key must either be a DMR (basic), RC4 (enhanced) or AES (advanced) key. To differentiate these key types, each list entry must be a mapping with a single entry. The name specifies the type (i.e., either dmr, rc4 or aes). The value then specifies the properties of the key.
Common key attributes As only the key size differs between the different key types, there are common attributes. Specifically, the ID and name of the key. Common key fields id Specifies the ID of the key. This ID can then be used to reference the key within the commercial digital channel extension. See . name Specifies the name of the key. This property is usually not encoded in the binary codeplug.
DMR (basic) key attributes DMR key fields key Specifies the key as a HEX string. It must be at least 8bit but can be of variable size. The actual size depends on the device. Usually, a fixed size of 16 or 32bit is supported. Some devices, however, support variable sized keys.
RC4 (enhanced) key attributes RC4 key fields key Specifies the key as a HEX string. This key is fixed to a size of 40bit. That is, the hex string must be of length 10.
AES (advanced) key attributes AES key fields key Specifies the key as a HEX string. Also this key can be of variable size. Usually, these keys are 128 or 256bit. The actual size depends on the device.
================================================ FILE: doc/manual/codeplug/commercial/extensions.xml ================================================ Commercial Codeplug Extensions This section collects all extensions that configure commercial features present in all radios. DMR was developed as a digital replacement of the analog trunked radio systems used in commercial context. To this end, there are many features of that are irrelevant or outright forbidden in an amateur radio context. However, some HAM operators use their hand-held radios also for their day job and thus may need to configure some features that are only applicable in a commercial context. To this end, qdmr allows to set some of the commercial features through extensions. Some commercial features defined within the DMR standard are illegal to use in an amateur radio context. Check your local regulations! Example commercial extension defining an encryption key. All commercial extensions are held under the commercial key within the codeplug. For now, there is only one extension, the encryption extension listing the encryption keys described below. ================================================ FILE: doc/manual/codeplug/contacts.xml ================================================
Contacts The contacts element specifies a list of all contacts. Each contact is a map that contains a single entry to specify the contact type. The key specifies the type name and the value specifies the actual contact definition. Currently there are three possible contact types. As an example consider this contact list, containing 4 contact definitions. One for each type. Contact definitions
DMR Contacts (<token>dmr</token>) A DMR contact is a simple object and is usually defined in one line. Each contact contains an optional id that will be used to reference this contact throughout the codeplug (e.g., in channels, group lists, etc.). DMR Contact Fields id Specifies the identifier of the contact. This identifier can later be used to reference the contact. Any unique string is valid. name Specifies the name of the contact. Any string is valid. type Specifies the type of the contact. Must be one of PrivateCall, GroupCall or AllCall. number Specifies the DMR ID for this contact. That is, any integer between 0 and 16777215. This element is mandatory for all types except for the all-call. For the all-call, the default number 16777215 will be used. ring If true, the radio will ring whenever a call from this contact is received (if supported by the radio). Optional, if omitted set to false.
M17 Contacts (<token>m17</token>) An M17 contact is a simple object and is usually defined in one line. Each contact contains an optional id that will be used to reference this contact throughout the codeplug (e.g., in M17 channels). For now, M17 is only supported by radios running the OpenRTX firmware. M17 Contact Fields id Specifies the identifier of the contact. This identifier can later be used to reference the contact. Any unique string is valid. call Specifies the call (M17 ID) of the contact. This must be a string of not more than 9 chars, containing only A-Z, 0-9, ., / & -. See M17 specification for details. broadcast Specifies whether the contact is the M17 broadcast destination. Default is false. If true, the call is ignored.
Analog DMTF Contacts (<token>dtmf</token>) An analog DTMF contact can be used to store commonly used DTMF sequences. For example, it may be used to control the EchoLink feature of a repeater. id Specifies the identifier of the contact. This identifier can later be used to reference the contact. Any unique string is valid. name Specifies the name of the contact. Any string is valid. number Specifies the DTMF ID for this contact. That is any combination of numbers 0-9 and symbols A-D, *, #. used. ring If true, the radio will ring whenever a call from this contact is received (if supported by the radio). Optional, if omitted set to false.
================================================ FILE: doc/manual/codeplug/extensions.xml ================================================ Device specific extensions The sole reason for introducing a new YAML based codeplug format was the ability to extend the file format with device specific settings without breaking the format. This was simply impossible with the old table based text files. The present YAML based codeplug file format is extensible at almost any level. That is, device specific elements can be added to single codeplug elements like channels, zones or contacts but also to the codeplug itself. The latter allows to extend the codeplug with new elements. ================================================ FILE: doc/manual/codeplug/format.xml ================================================ Extensible Codeplug File Format This document describes the extensible codeplug file format using YAML. If you are unfamiliar with YAML, consider reading a YAML introduction first. The documentation for the old table based conf-file format, can be found in section . The introduction of device specific settings (with version 0.9.0) required an extensible codeplug file format. The old table based format did not allow for any extension without braking backward compatibility. The new YAML based format allows for exactly that: Some means to extend the format for device specific settings without breaking the format while maintaining some degree of readability. (Yes, some users use the command line tool and edit their codeplug in a text editor.) There are several levels, at which device specific extensions may appear within the codeplug. There are global extensions that apply to the entire codeplug. These extensions are located at the top level. There might also be extensions to single channels, contacts, zones, etc. These extensions are then located under the specific element that gets extended. ================================================ FILE: doc/manual/codeplug/grouplists.xml ================================================
Group lists A group list collects several digital (DMR) contacts that should be received on a channel associated with this group list. Consequently, a group list consists of a name and a list of contact IDs. The groupLists element is then a list of several group list definitions. As an example, consider the two group lists below: Group list definition The first group list has the internal ID grp1. This ID can then be used later to reference this group list. The name is set to DL. The list of contacts of the group list is then defined as a list containing the IDs of the referenced contacts. Group list fields id Specifies the ID of the group list. This ID can then be used to refer to the group list within digital channels. Any unique string is possible here. name Specifies the name of the group list. Any string is possible here. contacts A list of digital contact IDs. See above.
================================================ FILE: doc/manual/codeplug/opengd77/channel.xml ================================================
Channel extension This extension allows to specify some channel settings specific for devices running the OpenGD77 firmware. This extension can be added to any channel, analog and digital. For now, this extension only allows to specify the power for the channel in more detail. The OpenGD77 channel extension is a mapping named openGD77. It contains the device specific settings for that channel.
Channel attributes For now, there are only few attributes specifying scan behavior for the channel. Channel extension fields scanZoneSkip scanAllSkip In contrast to the original GD-77 firmware, OpenGD77 does not implement scanning by scan lists. Instead, either all channels defined in the radio or all channels within the current zone can be scanned. To exclude some channels from these scans, the scanZoneSkip and scanAllSkip flags can be used. When enabled, the channel will be excluded from the respective scan. beep If enabled (default), the channel beeps are played. powerSave Enables the power save feature for this channel (default). location Allows to specify a fixed location to be used for this channel and the associated APRS system. talkerAliasTS1 talkerAliasTS2 Specifies what to be send as the talker alias, depending on the current timeslot. This must be one of None, APRS, Text or Both.
================================================ FILE: doc/manual/codeplug/opengd77/contact.xml ================================================
DMR contact extension This extensions allows to specify some DMR contact attributes for devices running the OpenGD77 firmware. This extension is only applicable to DMR (digital) contacts. The OpenGD77 contact extension is a mapping named openGD77. It contains the device specific settings for that DMR contact.
Contact attributes For now, there is only one attribute allowing to override the time slot of a channel whenever this contact is selected as the destination contact for that channel. Channel extension fields timeSlotOverride OpenGD77 allows to override the time slot settings for each channel on the bases of the selected transmit contact. If the contact has a time slot override set, this time slot is used instead of the channel time slot. This attribute is either None, TS1 or TS2. If None is set, the channel time slot will not be overridden.
================================================ FILE: doc/manual/codeplug/opengd77/extensions.xml ================================================
OpenGD77 Codeplug Extensions This chapter documents the extensions and settings specific to radios running the OpenGD77 firmware.
================================================ FILE: doc/manual/codeplug/radioddity/extensions.xml ================================================
<trademark>Radioddity</trademark> Codeplug Extensions This section collects all device specific extensions to the codeplug for Radioddity and some Baofeng devices. These extensions are applicable to Radioddity GD77, GD73 and Baofeng/Radioddity RD-5R radios.
================================================ FILE: doc/manual/codeplug/radioddity/generalradiosettings.xml ================================================
General radio settings This section describes the top-level generic radio settings for Radioddity devices. Radio settings extension example showing default values. Radio settings fields monitorType Specifies the monitor type. Must be either Open or Silent. Default Open. loneWorkerResponseTime When lone worker is enabled, specifies the time in minutes before the radio will start to remind the user. Default 1min. See also lone worker. loneWorkerReminderPeriod Specifies the period in seconds for the lone worker reminder. Default 10s. See also lone worker. groupCallHangTime Specifies the group-call hang time in milliseconds. This is the time-period the user can answer a received group call by pressing PTT. After this time has passed, a press on the PTT button will call the default contact on the selected channel. Default 3000ms. See also hang time. privateCallHangTime Specifies the private-call hang time in milliseconds. This is the time-period the user can answer a received private call by pressing PTT. After this time has passed, a press on the PTT button will call the default contact on the selected channel. Default 3000ms. See also hang time. downChannelModeVFO If true, the channel-up button will tune the VFO. If false, it will step through the channels. Default false. upChannelModeVFO If true, the channel-donw button will tune the VFO. If false, it will step through the channels. Default false. powerSaveMode Puts the radio is a sleep mode when in idle state (no traffic on the channels). This allows for some power saving. However, the radio will need some time to wake up. Consequently, all other radios in the network need to transmit a wake-up preamble. Default true. wakeupPreamble Enables the transmission of a short wake-up preamble allowing receiving radios to wake-up in time for the actual transmission. Default true. preambleDuration This sets the preamble duration in milliseconds. Default 360ms. powerSaveDelay Specifies the delay, before an idle radio enters power save mode. (GD-73 only) allLEDsDisabled If true, all LEDs are disabled. Default false. quickKeyOverrideInhibited If true, allows the user to transmit on a busy channel irrespective of the channels admit criterion by double pressing the PTT. Default false. txInterrupt WTF?!? txOnActiveChannel If true, the radio will transmit on the currently active channel (if double-wait) is enabled. Default false. scanMode Specifies the scan mode. Must be one of Time, Carrier or Search. Default Time. repeaterEndDelay Specifies the delay after the end of a repeater transmission in seconds. Default 0s, off. repeaterSTE Specifies the repeater STE (what ever that means) in seconds. Default 0s, off. language Specifies the UI language. Must be one of Chinese or English.
================================================ FILE: doc/manual/codeplug/radioddity/radiobootsettings.xml ================================================
Boot settings This section collects some settings related to booting the radio. Boot settings extension example showing default values. Boot settings fields display Specifies what to display during boot. Must be one of None, Text or Image. bootPassword, progPassword Specifies the boot and programming passwords. The former (usually only numbers) must be entered, when the radio boots. The latter must be entered in the CPS to program or read the codeplug.
================================================ FILE: doc/manual/codeplug/radioddity/radiobuttonsettings.xml ================================================
Button settings This section describes how the buttons are configured for Radioddity devices. Button settings extension example showing default values. Button settings fields longPressDuration Sets the duration, a button must be pressed, to be considered as a long press. This interval is usually expressed in ms. E.g., 1000ms. funcKey1Short, funcKey1Long Short and long-press functions for the programmable function key 1. This is the side key 1 on the GD77 and RD-5R and the P1 button on the GD73. funcKey2Short, funcKey2Long Short and long-press functions for the programmable function key 2. This is the side key 2 on the GD77 and RD5R and the P2 button on the GD73. funcKey3Short, funcKey3Long Short and long-press functions for the programmable function key 3. This is the top key on the GD77 and RD5R This button is not present on the GD73. Button functions None Disables the button. No function is associated with it. ToggleAllAlertTones Enables or disables all alert tones. Only present in GD77 and RD5R radios. EmergencyOn, EmergencyOff Why not toggle? Either enables or disables an emergency. ToggleMonitor Enables/toggles the monitor. This is device specific, on some radios the monitor function latches, on most not. Then, the monitor is enabled, as long as the button is pressed. OneTouch1, OneTouch2, OneTouch3, OneTouch4, OneTouch5, OneTouch6 Triggers one specific one-touch action. Not all radios have 6 of these. The GD-77 and RD-5R have 6 one-touch actions, while the GD-73 has only 5. ToggleTalkaround Enables/disables the talkaround feature for repeater channels. The radio then also transmits on the RX frequency. Consequently bypassing the repeater. ToggleScan Enables/disables the scan. ToggleEncryption Enables/disables the encryption for the channel, if configured. ToggleVox Enables/disables the VOX for the channel, if configured. ZoneSelect Brings up the zone selection dialog. BatteryIndicator Shows the battery charge indicator. ToggleLoneWorker Enables/disables lone-worker feature, if configured. PhoneExit WTF?!? ToggleFlashLight Enables/Disables the flash light. Not all devices have one. ToggleFMRadio Enables/disables the FM broadcast radio. RadioCheck,RadioDisable, RadioEnable If configured, the radio will transmit tones, that cause other radios --- if configured to do so --- to either response, disable or re-enable themselves. This allows to control other radios remotely. TBST Sends the TBST tone (usually 1750Hz). Some radios have a fixed button combo for that. CallSwell WTF?!?
================================================ FILE: doc/manual/codeplug/radioddity/radiosettings.xml ================================================
Radio settings extension This extension allows to set the device-specific general settings for Radioddity radios. It extends the settings section of the codeplug and is split into several sub-extensions.
================================================ FILE: doc/manual/codeplug/radioddity/radiotonesettings.xml ================================================
Tone settings This section collects some settings relates to tones and other audio stuff. Tone settings extension example showing default values. Tone settings fields lowBatteryWarn Enables the low battery-charge warning. This can either be a notification on the screen or a warning tone. The warning interval and tone-volume might be set by lowBatteryInterval and lowBatteryWarnVolume. lowBatteryWarnInterval Specifies the interval, at which low battery warning are issued. lowBatteryWarnVolume Specifies the volume of the low-battery warning tone in a range from 1 to 10. keyTone, keyToneVolume If true, the key-pad tones are enabled. Don't do it. The volume of these tones might be set using keyToneVolume in a range from 1 to 10.
================================================ FILE: doc/manual/codeplug/radioids.xml ================================================
Radio IDs The radioIDs-element specifies a list of radio IDs. Each radio ID is a map that contains a single entry to specify the type. The key of the entry specifies the type name and the value specifies the actual radio ID definition. Currently only the type dmr is supported. As an example consider this radio ID list, containing only a single ID. Radio ID definitions This DMR radio ID got the identifier id1, name DM3MAT and number 2621370.
DMR Radio IDs (<token>dmr</token>) The DMR radio ID definition consist of an optional id (necessary to reference that ID later), a name and the DMR ID number. DMR Radio ID Fields id Specifies the identifier of the radio ID. This identifier can later be used to associate the radio ID to channels. Any unique string is valid. name Specifies the name of the radio ID. This name may also be used as the radio name. Any non-empty string is valid. number Specifies the DMR ID for this radio ID. That is any integer between 0 and 16777215.
================================================ FILE: doc/manual/codeplug/radiosettings.xml ================================================
Radio settings The radio settings section contains all radio-wide settings. For example the microphone level, boot text etc. For sake of readability, they are organized in groups. E.g., boot options, audio settings, ... As an example, consider the following general settings General radio-wide settings. Here, the microphone amplification is set to 6, the speech synthesis is disabled, the two boot text lines are set to qDMR and DM3MAT respectively and the default DMR radio ID is set to id1. The latter is the id of a radio ID defined below. Also, the radio-wide default squelch, VOX and power level is set. These values can be referenced later in channels. Also, some radios do not allow for these settings to be applied on a per-channel basis. For these radios, these values are used. Radio-wide Setting Fields introLine1 introLine2 Sets the two boot text lines (if supported by the radio). These text lines will show up on the boot of the radio. If the radio is set to show a picture during boot, these lines are not shown. defaultID Specifies which radio ID will be used as the default DMR ID (see below). If none is specified, the first defined DMR radio ID will be used. power Specifies the default transmit power. This value may be referenced in channels or represent the radio-wide power setting. Possible values are Min, Low, Mid, High and Max.
Boot settings Some settings relevant for the boot process. display Specifies what is shown during the boot process. This must be one of Logo, Image or text. Where Logo is usually the manufacturer logo and the default passwordEnabled, password Enables and specifies the boot password. This is usually a 8-digit number that must be entered into the radio to boot it. defaultChannel, zoneA, channelA, zoneB, channelB Enables and specifies the channels and zones that are selected after boot. If disabled, usually the last used channels/zones are selected. If specified, the channel must be member of the zone. To select a zone/channel assign the corresponding zone/channel id. allowFactoryReset If true, allows to factory reset the device during reboot. This can be used to clear an incompatible codeplug after the firmware has been updated. Sometime, this must be enabled to enter the firmware programming mode.
Audio settings This section groups all global audio settings. Some of these can be overridden by channel settings. squelch Specifies the default squelch level. This value may be referenced in channels or represent the radio-wide squelch setting. Any value in [0-10] is valid, where 0 implies an open squelch (if supported by the radio). dmrSquelch Specifies a separate squelch level for DMR channels. If set to none, the default squelch is used. micGain Specifies the microphone amplification. Must be an integer between 1 and 10. fmMicGain, m17MicGain Specifies a separate microphone gain for FM and DMR channels. If set to none, the default mic gain setting will be used. maxSpeakerVolume, maxHeadphoneVolume Specifies the maximum speaker/headphone volume setting. This can be used to limit the maximum volume, when the radio is used indoors. vox Specifies the default VOX sensitivity. This value may be referenced in channels or represent the radio-wide VOX sensitivity. Any value in [0-10] is valid here, where 0 disables the VOX. voxDelay Specifies the delay between voice detection and start of transmission. speech Enables/disables the speech synthesis. Some radios can announce the current channel etc. for the visually impaired. To enable that feature (if supported by the radio) set this field to true. Must be a boolean value.
GNSS settings This section collects some global GNSS settings that many radio support. Especially, it allows to specify a fixed location that can be used by the radio, if it does not support GPS. Radio-wide GNSS Setting Fields fixedLocationEnabled, fixedLocation Specifies and enables a fixed location. This location can be used instead of a GNSS position. The location is specified in terms of a locator. systems Specifies the GNSSs to use in terms of a list of GPS, Glonass, Galileo, Beidou. units Specifies the units to use to show distances and speeds. Must be one of Metric or Archaic.
DMR settings This section collects some global DMR settings that many radio support. Like sending a talker alias and specifying the SMS format. Radio-wide DMR Setting Fields privateCallMatch, groupCallMatch Enable private and group call matches. Must either be true or false. If private-call match is enabled, the radio only receives private calls for the radio. all other private calls are ignored. If group-call match is enabled, the radio only receives group calls that are in the group list of the current channel. All other group calls are ignored. privateCallHangTime, groupCallHangTime Specifies, how long a private and group call can be answered directly by pressing PTT. That is, how long the call hangs around, before the default transmit contact for the current channel gets active again. The duration must be specified with units. By default, the duration for private calls is 5s while the duration for group calls is 3s. sendTalkerAlias, talkerAliasEncoding Enables sending the talker alias -- that is, the radio name along with the DMR ID. This enables receives to show your radio name, if the receiver does not contain your DMR ID in its call-sign DB. Current DMR networks inject the talker alias based on the global DMR ID database. Consequently, this feature is of little use. It might be relevant for commercial applications and simplex connections. If used, talkerAliasEncoding specifies the encoding of the alias. It must be one of Iso7, Iso8 or Unicode. preamble Specifies the duration of the DMR transmit preamble. The duration must be specified with units. Usually this is set to 100ms.
================================================ FILE: doc/manual/codeplug/roaming.xml ================================================
Roaming The roaming configuration consists of a list of roaming channels and roaming zones. While roaming channels specify those channel settings, that might be overridden during roaming, the roaming zone specifies a collection of roaming channels, that can be used to maintain a connection to a particular talk group.
Roaming Channels Roaming channels contain those settings of a DMR channel, that are specific for a particular repeater. This allows to override these settings in a channel, when the repeater of that channel gets out of range. Then, the radio may switch to a different repeater to maintain the connection to the current talk group. The example above shows the definition of two roaming channels. Both are used to stay in contact with a regional talk group within the Brandmeister network. While the repeater DB0LDS is located within this region, DB0AFZ is not. Consequently, the time-slot of the channel needs to be overridden. Roaming channel fields id Specifies the ID of the roaming channel. This ID can then be used to refer to this channel within a roaming zone. Any unique string is valid here. name Specifies the name of the roaming channel. Any string is valid here. rxFrequency, txFrequency Specifies the receive and transmit frequencies of the channel. colorCode Specifies the color code of the channel. If set, overrides the color code of the current channel. If omitted, the color code of the selected channel is used during roaming. timeSlot Specifies the time slot of the channel. If set, overrides the time slot of the current DMR channel. If omitted, the time slot of the selected channel is used during roaming.
Roaming Zones Roaming zones are collections of roaming channels, that are scanned for the strongest signal to maintain connection to a particular network or talk group. As an example, consider the following roaming zone: This zone has the ID roam1 and the name Berlin/Brand. This group collects all roaming channels that provide access to the Berlin/Brandenburg talk group. Roaming zone fields id Specifies the ID of the roaming zone. This ID can then be used to refer to this zone. Any unique string is valid here. name Specifies the name of the roaming zone. Any string is valid here. channels Specifies the member channels for this roaming zone. This must be a list of references to roaming channels.
================================================ FILE: doc/manual/codeplug/scanlists.xml ================================================
Scan lists Scan lists are simple lists of channels to scan. A scan list might be associated with a channel. As an example, consider the following scan list: This scan list has the ID scan1, the name KW and contains several channels (both analog and digital). Scan list fields id Specifies the ID of the scan list. This ID can then be used to reference this scan list in . name Specifies the name of the scan list. Any string is valid here. primary Specifies the primary priority channel. Usually this channel is scanned very frequently. If set, this must be a reference to a channel. If the tag !selected is used here, the channel from which the scan got started is used as the primary priority channel. secondary Specifies the secondary priority channel. Usually this channel is scanned frequently. If set, this must be a reference to a channel. If the tag !selected is used here, the channel from which the scan got started is used as the secondary priority channel. revert Specifies the revert channel. That is, the channel to transmit on irrespective of the current channel being scanned. If set, this must be a reference to a channel. If the tag !selected is used here, the channel from which the scan got started is used as the transmit channel. If omitted the radio will transmit on the currently scanned channel. channels Specifies the list of channels to scan. Must be a list of channel IDs.
================================================ FILE: doc/manual/codeplug/smsextension.xml ================================================
SMS Extension This extension allows for configuring the SMS (text message) feature of all DMR radios. For now, it is implemented as an extension, but might be changed into a common core setting in the future. This extension only has two elements: the SMS format and a list of predefined text messages. The latter is important for devices that do not have a keypad and thus do not allow for entering any messages. E.g., the Radioddity GD-73. As this extension configures a common feature, the extension is always present. Example of a single SMS pre-defined text or template.
Common SMS settings format Specifies the SMS format to be used. This must be one of Motorola, Hytera or DMR. If an incompatible format is chosen, you cannot receive or send text messages.
Message templates The templates key defines a list of SMS templates to be programmed onto the radio. Each template is a config object with a name and ID. The latter can be used to reference the template somewhere else in the codeplug. Each template has the following properties: id Specifies the unique ID of the message. This can be used to reference the message. name Specifies the display name of the message. Please note, that not all radios store descriptive names alongside with the preset messages. message The actual preset message. The length might differ but is usually limited to 144 chars.
================================================ FILE: doc/manual/codeplug/tyt/buttonsettings.xml ================================================
Button settings extension This extension to the codeplug allows to specify the function for each of the programmable buttons on the radio. Please note that not all TyT radios have all buttons described here. Button settings fields sideButton1Short sideButton1Long sideButton2Short sideButton2Long sideButton3Short (Retevis RT84, Baofeng DM-1701) sideButton3Long (Retevis RT84, Baofeng DM-1701) progButton1Short (Retevis RT84, Baofeng DM-1701) progButton1Long (Retevis RT84, Baofeng DM-1701) progButton2Short (Retevis RT84, Baofeng DM-1701) progButton2Long (Retevis RT84, Baofeng DM-1701) Specifies the different functions for each button press. Must be one of: Disabled, ToggleAllAlertTones, EmergencyOn, EmergencyOff, PowerSelect, MonitorToggle, OneTouch1-OneTouch6, RepeaterTalkaroundToggle, ScanToggle, SquelchToggle, PrivacyToggle, VoxToggle, ZoneIncrement, BatteryIndicator, LoneWorkerToggle, RecordToggle, RecordPlayback, RecordDeleteAll, Tone1750Hz, SwitchUpDown, RightKey, LeftKey or ZoneDecrement. longPressDuration Specifies the long-press duration in milliseconds.
================================================ FILE: doc/manual/codeplug/tyt/channel.xml ================================================
Channel extension This extension to the codeplug allows to set device specific channel settings for many TyT radios (and therefore also many Retevis radios). Not all settings are present in all radios. Unsupported settings are ignored during encoding or set to the default value during decoding. Example channel settings specifying the default values for <trademark>TyT</trademark> <trademark>Retevis</trademark> devices. Common channel setting fields autoScan If true, the auto-scan feature is enabled. emergencyAlarmConfirmed Enables the confirmation of emergency calls. These fields are usually disabled as the radio will first establish the connection before the actual call can be started. displayPTTId If true, the received analog PTT will be shown. rxRefFrequency rxRefFrequency Specifies some weird reference frequency setting for RX and TX. By default the value Low is used. Possible values are Low, Medium and High. Channel settings for <trademark>TyT</trademark> <productname>MD-390</productname> and <trademark>Retevis</trademark> <productname>RT8</productname> tightSquelch If set to true, the silent squelch is used. compressedUDPHeader Some unknown flag. Usually disabled. Channel settings for <trademark>TyT</trademark> <productname>MD-UV390</productname> and <trademark>Retevis</trademark> <productname>DM-2017</productname> killTone Specifies the kill tone. Possible values are Off, Tone259_2Hz and Tone55_2Hz. Disabling or setting the kill tone to 259.2Hz or 55.2Hz, respectively. inCallCriterion Specifies the in-call criterion. Possible values are Always, AdmitCriterion and TXInterrupt. allowInterrupt Enables/disables interrupts if inCallCriterion is set to TXInterrupt. dcdmLeader If DCDM is enabled, this flag specifies whether this radio is the channel leader. That is, if this radio provides the clock for the time-slots. dmrSquelch Allows to set the squelch level for DMR channels. This prevents the LED to light all the time. The radio, however, remains silent irrespective of this setting.
================================================ FILE: doc/manual/codeplug/tyt/extensions.xml ================================================
<trademark>TyT</trademark> Codeplug Extensions This section collects all device specific extensions to the codeplug for TyT, Retevis and some Baofeng devices. These extensions are applicable to TyT MD-390, MD-UV380, MD-UV390, MD-2017, Retevis RT8, RT3S, RT82, RT84 and Baofeng DM-1701 radios.
================================================ FILE: doc/manual/codeplug/tyt/menusettings.xml ================================================
Menu settings extension This extension to the codeplug allows to specify which menu items are enabled. This feature is frequently used in commercial applications to restrict the user to change certain settings in the radio. Menu settings fields hangtimeIsInfinite hangTime Specify the menu hang time. That is the duration, the menu is shown without any user action. If hangtimeIsInfinite is true, the menu is shown indefinitely, irrespective of the hangTime setting. If hangtimeIsInfinite is false, the hangTime specifies the menu hang time in seconds. textMessage Enables the text-messaging menu item. callAlert Enables the call alert menu item. contactEditing Enables the contact editing menu item. manualDial Enables manual dial. remoteRadioCheck Enables the remote radio-check menu item. remoteMonitor Enables the remote monitor menu item. remoteRadioEnable remoteRadioDisable Enables the remote radio enable and disable menu items. scan scanListEditing Enables the scan and scan list editing menu items. callLogMissed callLogAnswered callLogOutgoing Enables the menu items for the list of missed, answered and outgoing calls. talkaround Enables the talkaround menu item. alertTone Enables the alert tone settings menu item. power Enables the power settings menu item. backlight Enables the backlight settings menu item. bootScreen Enables the boot-screen settings menu item. keypadLock Enables the keypad lock settings menu item. ledIndicator Enables the LED indicator settings menu item. squelch Enables the squelch settings menu item. vox Enables the VOX settings menu item. password Enables the password settings menu item. displayMode Enables the display mode settings menu item. radioProgramming Enables radio programming from the keypad. E.g., editing channels etc. gpsInformation Enables the GPS information menu item. This setting has only an effect on radios supporting GPS.
================================================ FILE: doc/manual/codeplug/tyt/radiosettings.xml ================================================
Radio settings extension This extension allows to set the device-specific general settings for TyT and Retevis radios. It extends the settings section of the codeplug. Radio settings extension example showing default values. Radio settings fields montitorType Specifies the monitor type. Possible values are Open and Silent. This setting only affects analog channels. If Open is specified, the squelch will open if monitoring is enabled. allLEDsDisabled If enabled, all LEDs are disabled. talkPermitToneDigital If enabled, a talk-permit tone will sound on digital channels. talkPermitToneAnalog If enabled, a talk-permit tone will sound on analog channels. passwordAndLock Enables and disables the passwords and keypad locks globally. They can also be enabled/disabled individually below. channelFreeIndicationTone If enabled, a tone will sound after a transmission has ended to indicate that the channel is free again. allTonesDisabled If enabled, all tones (talk permit, channel free, etc.) are disabled globally. powerSaveMode Puts the radio is a sleep mode when in idle state (no traffic on the channels). This allows for some power saving. However, the radio will need some time to wake up. Consequently, all other radios in the network need to transmit a wake-up preamble. wakeupPreamble Enables the transmission of a short wake-up preamble allowing receiving radios to wake-up in time for the actual transmission. bootPicture If enabled, a picture is shown during boot rather than the boot text (see introLine1 and introLine2 in the general settings section). channelMode, channelModeA, channelModeB Controls the mode of the radio, VFO A and VFO B. If channelMode is true, the entire radio operates in channel mode (memory mode). This also overrides the channelModeA and channelModeB settings. If channelMode is set to false, the channelModeA and channelModeB specify the mode for the VFO A and B, respectively. txPreambleDuration Specifies the transmit preamble duration in milliseconds. If this preamble is the wake-up preamble, is unknown. channelHangTime Specifies the channel hang time in milliseconds. groupCallHangTime, privateCallHangTime Specifies the group-call hang time in milliseconds. This is the time a group or private call can be answered directly even if that group call is not the default contact of the channel. See also hang time. lowBatteryWarnInterval Specifies the period of the low-battery warn tone in seconds. callAlertToneContinuous If enabled, a call-alert tone will sound until the operator reacts. This setting overrides the callAlertToneDuration setting. callAlertToneDuration Specifies the call alert-tone duration in seconds. If callAlertToneContinuous is enabled, the alert tone will be continuous irrespective of this setting. loneWorkerResponseTime Sets the lone-worker response time in minutes. See also lone worker. loneWorkerReminderTime Sets the lone-worker reminder time in minutes. See also lone worker. digitalScanHangTime, analogScanHangTime Specifies the time in milliseconds, the radio will continue monitoring a DMR or FM channel after the transmission on that channel ended. backlightAlwaysOn If enabled, the backlight will stay on. backlightDuration Specifies the backlight duration in seconds. keypadLockManual If enabled, the keypad lock is enabled manually. If not, the keypad lock gets enabled automatically after a specified period (see keypadLockTime). keypadLockTime Specifies the time, after which the keypad lock is engaged automatically unless keypadLockManual is enabled. powerOnPasswordEnabled Enables the power-on password. powerOnPassword Specifies the power-on password. An 8-digit number. This password must then be entered when booting the radio. radioProgPasswordEnabled Enables the radio-programming password. radioProgPassword Sets the radio-programming password. An 8-digit number. This password must then be entered when making any changes to the radio/channel settings through the keypad. pcProgPassword Specifies the PC programming password. This password is then needed when programming the radio through the CPS. An empty string disables this password. privateCallMatch, groupCallMatch If true, the private and group call IDs must match.
================================================ FILE: doc/manual/codeplug/tyt/scanlist.xml ================================================
Scan-list settings extension This extension to the codeplug allows to specify device specifics settings for scan-lists. Scan-list settings example showing the default values. Scan-list settings fields holdTime Specifies the hold time in ms. prioritySampleTime Specifies the sample-time in ms for priority channels.
================================================ FILE: doc/manual/codeplug/zones.xml ================================================
Zones The zones element collects all zones defined within the codeplug. It is just a list of zone definitions. Each zone has an ID, name and one or two lists of channels. One for VFO A and one for VFO B. Depending on the radio, a zone will be split automatically into two zones if the radio handles separate zones for each VFO. As an example, consider the following zone: Zone definition This zone has the name KW and contains two lists of channels. One for each VFO. On radios, where each VFO is assigned a zone individually, this zone will be split into two: KW A and KW B to match the radios configuration. Zone fields id Specifies the ID of the zone. For now, there are no codeplug elements that refer to zones. name Specifies the name of the zone. Any string is valid here. A Specifies the channel list for VFO A. This must be a list of references to channels. B Optional channel list for VFO B. If present, must be a list of references to channels.
================================================ FILE: doc/manual/conf/analogchannels.xml ================================================
Analog channel table The analog channel table collects all analog (FM) channels. As digital channels have some different options compared to analog channels, they are not defined within the same table. However, they share the same IDs. So be careful not to assign the same identifier to analog and digital channels. The analog channel table has the form The analog channel table starts with the "Analog" keyword and ends with an empty line. The remaining keywords right after "Analog" (i.e., "Name", "Receive", "Transmit", "Power", "Scan", "TOT", "RO", "Admit", "Squelch", "RxTone", "TxTone" and "Width") are ignored but are part of the self-documentation of the config file. Each line within the table specifies a single channel. The first column specifies the unique ID of the channel. This ID can by any number that is unique among analog AND digital channels. The second (Name) column specifies the name of the channel as a string. Any string can be used here. The third (Receive) column specifies the RX frequency of the channel in MHz. The fourth (Transmit) column specifies the TX frequency in MHz or alternatively, an offset relative to the receive frequency in MHz by prefixing "+" or "-". The 5th (Power) column specifies the transmit power. This must be either the "High" or "Low" keyword. The 6th (Scan) column specifies the scan-list ID for this channel or "-" if there is no scan-list assigned to the channel. A scan-list (see below) is just a collection of channels that gets scanned whenever scanning is started on a particular channel. The 7th (TOT) column specifies the transmit time-out in seconds or "-" if disabled. The 8th (RO) column specifies whether this channel is receive-only with either "-" meaning disabled and "+" enabled. If enabled, it is impossible to transmit on that channel. The 9th column specifies the admit criterion on that channel. This must be either "-" meaning that there is no restriction when to send on that channel, the keyword "Free" meaning that the channel must be free to transmit or the keyword "Tone" meaning that the channel must be free and the RxTone must match. The 10th (Squelch) column specifies the squelch level for the channel. This must be a number between [0-10]. The larger the value, the stronger the signal must be to open the squelch. The value 0 disables the squelch. The 11th (RxTone) specifies the receive CTCSS tone frequency in Hz. The squelch will then only open, if the signal is strong enough (see previous column) and the specified tone is received. If set "-" the RX tone is disabled and the squelch will open if the signal is strong enough. The 12th (TxTone) column specifies the CTCSS tone to transmit in Hz or "-" if disabled. This feature is used by some repeaters to open their squelch and to start repeating to avoid conflicts between repeaters operating on the same frequency (e.g., in case of DX conditions). The 13th (Width) column specifies the bandwidth of the channel in kHz. This can be 12.5kHz narrow-band or 25kHz wide-band. Finally, the 14th column specifies the APRS system ID to use or "-" for APRS disabled.
================================================ FILE: doc/manual/conf/aprssystems.xml ================================================
APRS Systems The APRS system list specifies the various information for transmitting your position using analog APRS. As digital channels may use either DMR or analog APRS for position reporting, this list shares a namespace with the GPS system list. That is, the ID must be unique across both lists. The first column specifies the ID of the APRS positioning system. This must be unique across APRS and DMR position reporting systems. The second column specifies the name of the system as a string. the third column specifies the revert channel. That is, the analog channel the APRS information is transmitted on. The 4th column specifies the period with which the position gets reported. The 5th and 6th columns specify the source and destination calls and SSIDs respectively. The 7th column specifies the path string. This is list of calls and SSIDs stored as a string without any separators. The 8th column specifies the map icon name. The name does not need to match the official icon name exactly. The icon is identified as the closes matching icon name with respect to the Levenshtein distance between the given and all icon names. That is, jogger and jogging will select the same icon. Finally, the 9th column specifies a freely selectable text to be sent with the position report.
================================================ FILE: doc/manual/conf/contacts.xml ================================================
Contact table The contact table is a list of DMR contacts like These contacts can be personal contacts like DM3MAT, so-called all-calls and group calls. The contact table starts with the "Contact" keyword and ends with an empty line. The remaining keywords ("Name", "Type", "ID", "RxTone") are ignored, however, they are part of the self-documentation of the config file. Following the "Contact" keyword, each line represents a single contact in the contact list. The first column represents a unique internal ID for the contact. It must not necessarily be in ascending order, any unique number will do. The second column is the name of the contact. Any string can be used here. The third column specifies the type of the contact. This must be one of the keywords "Private", "Group" or "All", meaning private, group or all-calls, respectively. The fourth column specifies the DMR ID for the contact. Please note, that an all-call requires the specific DMR ID 16777215 to work as an all-call. The last column specifies, whether an incoming call from this contact will cause a ring-tone. Here "+" means enabled/yes and "-" disabled/no.
================================================ FILE: doc/manual/conf/digitalchannels.xml ================================================
Digital channel table The digital channel table defines all digital DMR channels. As digital channels have some different options compared to analog channels, they are not defined within the same table. However, they share the same IDs. So be careful not to assign the same identifier to analog and digital channels. The digital channel table has the form The digital-channel table starts with the keyword "Digital" and ends with an empty line. The next keywords (Name, Receive, Transmit, Power, Scan, TOT, RO, Admit, CC, TS, RxGL and TxC, GPS, Roam, ID) are ignored and are maintained for the self-documentation of the configuration file. Each channel is defined within a single line. The first column is the unique channel identifier (any unique number among analog AND digital channels). The second column specifies the channel name as a string. The third column specifies the RX frequency in MHz and the fourth column the TX frequency in MHz. Alternatively, a TX frequency can also be specified in terms of an offset relative to the RX frequency. In this case, the offset must be prefixed with either "+" or "-". The 5th (Power) column specifies the power level to use. Here, either the "High" or "Low" keyword must be used. The 6th (Scan) column specifies the ID of the scan-list (see below) attached to the channel. This list will be used whenever a scan is started on this channel. The 7th column (TOT) column specifies the TX time-out-timer in seconds or "-", if disabled. The 8th column (RO) specifies whether the channel is RX only ("+") or not ("-"). If enabled, you cannot transmit on that particular channel. The 9th (Admit) column specifies the TX admit criterion for the channel. This must be either "-" or one of the keywords "Free" and "Color". "-" indicates that there is no restriction in transmitting on that channel. The radio will transmit whenever PTT is pressed. The "Free" keyword indicates that the radio will only transmit if the channel is free. The "Color" keyword indicates that the radio will only transmit if the channel is free and the color-code of the repeater matches the specified color-code of the channel (see next column). The 10th (CC) column specifies the color-code of the channel. To avoid interference between neighboring radios and repeaters on the same frequency (in case of DX conditions), the repeater and radio will only react to transmissions on a channel with the matching color-code. The color-code can be any number between 0 and 15. The 11th (TS) column specifies the time-slot for this channel. Due to the audio compression used in DMR, it is possible to operate two independent channels on a single frequency by using time-slicing. DMR uses two time-slots. This option specifies which of the two time-slots is used for the channel. On simplex channels, this time-slicing is irrelevant, as there is no central instance (the repeater) that defines what time-slot 1 or 2 is. The 12th (GPS) column specifies the GPS or APRS system (see below) to use on that channel. The 13th (Roam) column specifies the roaming zone. This can either be '-' meaning roaming disabled or an ID of a roaming zone specified below. Finally, the 14th column (ID) specifies the DMR ID to use with this channel. That is either '-' for default ID or an index (0-based) of the ID list above.
================================================ FILE: doc/manual/conf/format.xml ================================================ Table Based Codeplug Format This chapter describes the old and deprecated table based conf text-file format. This format is already incomplete and will remains so. This documentation is maintained for the sake of completeness but you should not use that format in future. qdmr will still be able to import this format but it will not save codeplugs in that format anymore. The new codeplug format is documented in . This configuration file format represents a generic configuration for a wide variety of radios. It is a simple text file containing simple key-value definitions like the DMR ID as well as tables like the table of channels, contacts, etc. The aim of this config format is to be human-readable and writable. This would allow users to write config file by hand and share them easily, as well as enable users to modify shared configurations using a text editor. To this end, the format must be intuitive and to some degree self-documenting. Within the following sections, I will describe that text format in some detail.
Line comments To document your configuration, you may use so-called line-comments. These comments start with the character # and end at the end-of-line.
================================================ FILE: doc/manual/conf/gpssystems.xml ================================================
GPS Systems The GPS system list just specifies the contact to which some positional information is sent to (which usually gets forwarded to the APRS system) and at which period this information is sent. The first column specifies the ID of the GPS system. This can be any number >0. The second column (Name) specifies the name of the GPS system. The third column specifies the destination contact ID (see Contacts above), the position information is sent to. The fourth column (Period) specifies the update period in seconds. The fifth column (Revert) specifies the revert channel. In amateur radio, this can be left blank ("-").
================================================ FILE: doc/manual/conf/grouplists.xml ================================================
Group list table Group lists are simple named lists of one or more contacts. These lists may include group, all or even private calls. Group lists are assigned to channels. They form a group of contacts (e.g., talk groups) you may want to listen to on a particular channel. Usually these group lists form a collection of talk groups that are specific for a particular region. Group lists are defined within the config file like The group list table starts with the keyword "Grouplist". The following keywords (Name & Contacts) are ignored, but form a kind of self-documentation for the config file. Following the "Grouplist" keyword, each group list is defined by a single line. The first column specifies the internal unique ID for the group list. This can be any number as long as it is unique. The second column contains the name of the group list as a string. This can be any non-empty string. The third column contains the comma-separated list of contact IDs that form that group list.
================================================ FILE: doc/manual/conf/radiosettings.xml ================================================
General configuration The general configuration settings of some radios can be overly complex with a huge amount of options. The vast majority of these settings, however, are useless for ham-radio purposes. Thus the possible settings for the general configuration of the radio are reduced to 6 key-value pairs. The DMR ID of cause, is absolutely necessary and specifies your personal DMR number. Keep in mind, that you do NOT need to get a unique DMR ID for each radio you own! All your radios can share the same DMR ID. The DMR ID is specified using the "ID" keyword as In rare situations, where you actually need several different radio IDs (e.g., if you use the same radio for HAM and commercial applications), you may specify them as a comma separated list. The first ID in the list will be handled as the default ID for the radio. The radio name is a string, that the radio may display somewhere on the screen. It does not have any effect on the behavior of the radio or gets transmitted. You may set this entry to your call-sign. For example: The two intro lines might be shown on the screen of your radio on startup. You may set these to any string you like. They are also cosmetic and don't have any effect on the behavior of your radio. For example The microphone sensitivity/amplification can also be set (on some radios) using the MicLevel entry. This entry is a number between 1 and 10. The larger the level, the larger the microphone amplification. This value may vary heavily from model to model. The "Speech" option enables the speech synthesis of the radio if supported. Possible settings are "on" and "off".
================================================ FILE: doc/manual/conf/roaming.xml ================================================
Roaming Zones Roaming zones allow to stay in contact with a particular talk group when moving round and the current repeater gets out of range. In this case, the radio will search for the strongest repeater in a list (the so-called roaming zone) and switch to this repeater. Therefore, a roaming zone is a simple channel list. The first column specifies the ID of the zone. This ID can be used in the digital channel table to associate a channel with a specific roaming zone. The second column specifies the name of the zone and the third column holds the comma-separated list of channel in each zone.
================================================ FILE: doc/manual/conf/scanlists.xml ================================================
Scan lists A scan list is list of channels, that are scanned whenever scanning is started on a channel, the scan list is associated with. A single scan list might be associated with several channels. For example, all channels within that scan list. The list of scan lists has the following form The list of scan lists starts with the "Scanlist" keyword and ends with an empty line. The remaining keywords (Name, PCh1, PCh2 & Channels) are ignored but part of the self-documentation of the configuration file format. A scan list is defined with every other line. The first column specifies the unique identifier of the scan list. The second (Name) column specifies the name of the scan list as a string. Any string will do. The third and fourth columns specify the first and second priority channels for the scan list respectively. These priority channels are visited more frequently during the scan. That is, the first priority channel is visited 50% of the time while the second is visited 25% of the time. These channels might also be set to "-" indicating that there is no priority channel. The 5th column specifies the transmit channel during the scan. Possible options are "Last", "Sel" and any valid channel index. The "Sel" keyword implies that the radio will transmit on the selected channel when the scan started. The "Last" keyword implies that the radio will transmit on the channel at which the scan stopped on, while specifying any channel index implies, that the radio will transmit on that channel. Finally the 6th column specifies the comma-separated list of channels that form the scan list.
================================================ FILE: doc/manual/conf/zones.xml ================================================
Zone lists Zones are just collections of channels. Typical radios can hold thousands of channels. To keep large numbers of channels manageable, they can be organized into zones. Usually, these zones represent a geographical area and all repeaters in that area are then grouped into zones. Of cause, a single channel can be added to multiple zones. Please note that for many radios, channels can only be accessed via a zone. That means, a channel that is not a member of any zone may not be accessible. The zone table is defined within the configuration file as The zone table starts with the keyword "Zone" and ends with an empty line. The remaining keywords (Name and Channels) are ignored but are part of the self-documentation of the configuration file. The first column specifies an unique identifier for each zone. This can be any integer as log as it is unique. The second (Name) column specifies the name of the zone as a string. Any string is valid here. The third column specifies the VFO (either A or B) for that zone. This allows to specify different channels for the two VFOs of the radio. For example, it allows to specify a list of repeater channels for VFO A and some simplex and calling frequencies on VFO B. The fourth column contains the comma-separated list of channel IDs for the zone and VFO. A reference to any channel-type can be used here, analog and digital.
================================================ FILE: doc/manual/epub/Makefile ================================================ all: manual.epub %.epub: %.xml pandoc -f docbook -t epub3 -o $@ $< %.xml: clean: rm -f manual.epub rm -f manual.xml rm -rf fig/ ================================================ FILE: doc/manual/gui/aprs.xml ================================================
Setup GPS/APRS Position Reporting The GPS/APRS tab allows to specify several so-called positioning systems. A positioning system is a collection of settings on how GPS information is sent to APRS network. Screen-shot of the GPS or APRS settings. The list GPS/APRS systems.
Edit/Create GPS Systems Double-clicking a GPS system or clicking on the Add GPS System button will open the GPS system edit dialog. The digital APRS editing dialog. This dialog allows to specify how the GPS information is sent through the DMR network. That is the Name field specifies the name of the GPS system. The Destination field specifies the private-call contact, the information is sent to. For example 262999 in the Brandmeister network. The Update Period specifies the period in which the current GPS information is sent to the contact. The Revert Channel specifies to which channel the radio should switch to, to send the position information. This should always be Selected. That is, the radio will always send the information on the currently selected channel. If Show Commercial Features is enabled in the settings dialog (see ), a tab bar is shown at the top. There you can also access the device specific settings for the GPS system.
Edit/Create APRS System Double-clicking an APRS system or clicking on the Add APRS System button will open the APRS system dialog. Screen-shot of the edit APRS dialog. The analog APRS editing dialog. This dialog allows to specify how the GPS information is sent through the APRS network. That is the Name field specifies the name of the APRS system. The Channel field specifies on which channel the APRS information should be send. This must be an analog channel with one of the typical APRS frequencies. The Source field specifies your call and the source SSID, while the Destination field specifies the destination call and SSID. Path is an optional string containing the APRS path for the packet. The Icon combo-box allows to select an icon for the packets. These icons are then shown on APRS maps. The Update Period specifies how frequently an APRS packet should be send. Finally, the Message field allows to set an optional text message for the packet. If Show Commercial Features is enabled in the settings dialog (see ), a tab bar is shown at the top. There you can also access the device specific settings for the APRS system.
================================================ FILE: doc/manual/gui/channels.xml ================================================
Creating Channels Creating the list of channels for the DMR radio is the most cumbersome task. Remember, each repeater has two time-slots with possibly multiple talk-groups assigned to each time slot. For the sake of convenience, it is reasonable to define a channel for each talk-group you are interested in on every repeater. Thus, instead of a single channel per FM repeater, you will likely define at least 3-4 channels per DMR repeater. To ease the burden of creating a lot of channels, qdmr implements some features that should help you in creating these channel. One feature is the automatic retrieval of repeater input and output frequencies from repeaterbook.com. This is a world-wide database of ham-radio repeaters. Both, FM and DMR. When you enter your locator into the settings dialog (see ), qdmr will provide you with a list of nearby repeater and fill in the input and output frequencies. This feature works for both, FM and DMR repeaters. Screen-shot of the list of channels. List of channels. The Channels tab shows the list of all defined channels, irrespective of whether they are FM or DMR channels. You may add an analog or digital channel by clicking on the Add FM Channel or Add DMR Channel button on the bottom, respectively. The Clone Channel button allows for cloning of a selected channel. This enables one to create a set of channels that differ only in the default transmit contact but share the same remaining settings. You can also delete a channel, by selecting that channel in the list and clicking on the Delete Channel button at the bottom. You may move a channel up or down the list by selecting that channel and clicking on the arrow-up or -down button to the right, respectively. Finally you can edit a channel by double-clicking it in the list. The number of channels usually grow fast and it becomes hard to find channels within the list. To search the list for any channel name or frequency, just hit Ctrl+F to open a search box. This search feature is present in all lists. The channel list, however, is likely the largest. Similar to the search function, all tables have a filter and sort mode. Hit CtrlShiftL to toggle modes. Within the filter and sort mode, moving channels around is disabled but you can sort all channels by any column and also filter channels.
Edit DMR channels When you double-click on a DMR channel or click on the Add DMR Channel button, the digital channel editor dialog will be shown. This dialog allows you to edit or create digital channels. Screen-shot of the DMR channel editor dialog. The DMR channel editor. The dialog is limited to the DMR-channel settings that are relevant for amateur radio. Thus, it is much smaller that the typical dialogs to edit DMR channels in commercial CPSs. However, if you enable the Show device extensions option in the settings dialog (see ), an additional tab will appear called Extensions. There all implemented device-specific settings are hidden. They can be used to set all options you also find in the manufacturer CPS. See also . The Name, Rx and Tx Frequency fields contain the chosen name of the channel as well as the transmit and receive frequencies. The latter can be set automatically by using the repeater auto-completion feature: Start to enter the call-sign of a repeater. A list of matching nearby repeaters is shown. Select a repeater from this list and the RX/TX frequencies will be set using the information from repeaterbook.com. For simplex-channels RX and TX frequencies must be identical. The retrieval of the repeater information may time some moments. Hence, the auto-completion list of repeaters near by may not appear immediately. The found repeaters are cached locally for faster retrieval later on. Once the RX frequency is specified, it is also possible to specify the transmit frequency implicitly via an offset. First specify the offset direction and then the offset frequency. The transmit frequency will be updated accordingly. The Power setting specifies the power used on that channel. For a nearby repeater, you may reduce the power. If you check the Default box, the global power setting will be used. See above. Tx Timeout (TOT) specifies the transmit timeout in seconds. This limits the continuous transmission time to this period. A value of Off disables the timeout. If you check the Default box, the global TOT setting will be used. See above. VOX Level specifies the sensitivity of the VOX for this channel. If OFF is selected, the VOX is disabled for this channel. If you check the Default box, the global VOX setting will be used. See above. Checking Rx Only will disable transmission on this channel. Scan List allows to specify the scan list associated with this channel. If a scan is started on this channel, this scan list will be used. Each channel may have a different scan list. The DMR ID field allows to select the Radio DMR ID for this channel. Some radios allow to program several DMR IDs to be used with one radio. This option makes not sense for HAM-radio usage. However, if you use the same radio for HAM as well as commercial applications, you may need to set your HAM DMR ID for HAM-radio channels and your commercial ID on commercial channels. The Tx Admit field specifies the Admit Criterion, under which you are allowed to transmit on the channel. For DMR repeater channels this should be set to Color Code. This means that you may only transmit if the radio received the correct color code of the repeater before. On simplex channels Channel Free should be chosen and thus the radio will only transmit on the channel if the channel is free. The Color Code specifies the color code of the repeater. For simplex channels, this should be set to 1. For repeater channels, this must match the color code of the repeater. If the auto-completion feature is used, the color code is set automatically. The Time Slot specifies the time-slot of the repeater for this channel. All repeaters have two time slots but different talk groups might be associated with each time slot. You may need to consult the webpage of the repeater. In the Brandmeister network, usually the time-slot 2 is for local/regional communication while time-slot 1 is for DX. The Group List specifies the list of group-calls you want to receive on this channel. See above for details. The Tx Contact specifies the default Transmit Contact you want to call on this channel when pressing the PTT button. The Positioning System (DMR ARPS) specifies how you location information is send over this channel (selecting None disables GPS for this channel). Please note, that this setting is ignored for radios without GPS. The Roaming allows to associate a roaming zone (see below) with this channel. If the radios has the roaming feature, this zone is then used to search for a another channel of another repeater that is reachable once this repeater get out-of-range. If Show Commercial Features is enabled in the settings dialog (see ), a tab bar is shown at the top. There you can also access the device specific settings for the channel.
Edit FM channels When you double-click on an analog channel or click on the Add FM Channel button, the analog-channel-editor dialog will be shown. This dialog allows for editing or creating FM channels. Screen-shot of the FM channel editor dialog. The FM channel editor. The left column of the editor are identical to the DMR channel dialog, that is Name, Rx and Tx Frequency, Power, Tx Timeout, VOX Level Rx Only and Scan List. Like for DMR channels, analog channels may also have an Tx Admit criterion. Possible options are Always, Channel Free and Tone. For FM repeaters the Always option should be chosen to allow for a quick turn-around in a QSO. For simplex channels Channel Free should be chosen, as it only allows to transmit when the simplex channel is free. Selecting Channel Free on repeater channels, would prevent transmission while the repeater is active although the last transmission already ended. The Squelch field specifies the squelch threshold. If Open is selected, the squelch is disabled. If Default is enabled, the global squelch level is used. See above. RX and TX Tone specify the CTCSS/DCS sub-tones for this channel/repeater. The RX Tone specifies the sub-tone that is needed to open the squelch. The TX Tone specifies the sub-tone that gets transmitted (e.g., to open the repeater). If the auto-completion feature is used, these tones are set automatically. Bandwidth specifies the band-width of the transmission. Possible values are 12.5kHz (Narrow) or 25kHz (Wide). APRS allows to specify an APRS system to be used on this channel. If the radio supports analog APRS, your position will be sent using the frequency, destination and path specified in this APRS system. If None is selected, APRS is disabled for this channel. If your radio does not support APRS, this setting is ignored. If Show Commercial Features is enabled in the settings dialog (see ), a tab bar is shown at the top. There you can also access the device specific settings for the channel.
================================================ FILE: doc/manual/gui/contacts.xml ================================================
Creating Contacts The second tab is the Contact List. Here all DMR contacts are defined, irrespective of their type. It is not only possible to define digital DMR contacts (i.e., private, group and all calls) but also DTMF contacts (and in future two-tone, five-tone contacts too). This eases the control of the EchoLink feature of repeaters. Screen-shot of the contact list tab. The contact list. You may add a contact by clicking on the Add Contact button at the bottom. You can also delete a contact by selecting the contact in the list and clicking on the Delete Contact button at the bottom. You may also reorder the contacts by selecting a contact in the list and use the arrow-up and arrow-down buttons on the right to move the contact up and down the list, respectively.
Editing DMR contacts When you create a code-plug, the contact list should contain all talk groups and reflectors you are interested in, as well as a so-called All Call contact to the number 16777215. Additionally you may add private calls to several operators you know, as well as some service numbers. Screen-shot new/edit DMR contact dialog. The new/edit DMR contact dialog. When you click on the Add Contact button or when you double-click a contact entry in the list, the Edit Contact dialog will appear. The first drop-down box allows to choose the type of the call. The possible options are Private Call, Group Call and All Call. The second entry is the name of the contact. Here any text can be entered. The third entry is the number of the contact. This entry gets disabled when All Call is selected as the call-type. Finally, if the last option Rx Tone is enabled, you will hear a ring-tone whenever this contact calls you. qdmr tries to download the current list of all registered user DMR IDs. The contact dialog will use this information (once downloaded) to resolve call-signs to DMR IDs. Just start entering the call-sign into the name field and matching call-signs are shown. The same holds true for Brandmeister Talkgroup names. That is, start typing the name of a talk group and a drop-down list of talk group names will appear for auto-completion. Select the talk group and its DMR ID gets set. If Show device extensions is enabled in the settings dialog (see ), a tab bar is shown at the top. There you can also access the device specific settings for contacts.
Editing DTMF contacts Screen-shot new/edit DTMF contact dialog. The new/edit DTMF contact dialog. Beside DMR contacts, the contact list may also contain analog DTMF contacts. You can add a new DTMF contact by clicking on the Add DTMF contact button. This will open the Edit DTMF contact dialog (see above). This is a rather simple dialog. Name specifies the name of the DTMF contact, while Number specifies the DTMF number. There, any DTMF number can be entered, this includes symbols like *, #, A-D. The Ring option specifies whether the radio should ring if a call by this contact is received.
================================================ FILE: doc/manual/gui/extensions.xml ================================================
Edit Device Specific Extensions Since version 0.9.2, qdmr supports the editing of so-called device-specific extensions. These extensions were introduced with version 0.9.0, to allow for configuring all features of a radio, although not represented within the common codeplug structure used by qdmr, to program all supported radios. These extensions can be added to every element of a codeplug. For example contacts, channels, zones etc. They are stored in a common way to allow for an unified method for manipulating them. All extensions are displayed in a so-called Extension View, a tree-like structure present for all codeplug elements. As qdmr is intended to be a clean and easy-to-use CPS for all radios, these device-specific settings are normally hidden. To show them, you need to enable the Show device extensions option in the settings dialog (see ). As this extension view is common to all codeplug elements, this section will discuss the usage on the codeplug extensions. When Show device extensions is enabled in the settings dialog, the codeplug extensions are shown in the last tab called Extensions. Screen shot of the codeplug extensions. Extension view for the codeplug extensions. Unless a codeplug is read from a device, the extensions are usually not set (see screen shot above). In these cases, the Value column of the extension view shows [None]. You can add these extensions to the codeplug by selecting the corresponding extension and click on the Create button at the bottom of the extension view. Similarly, an extension can be removed from the codeplug by selecting it and clicking on the Remove button at the bottom of the extension view. Screen shot of the codeplug extensions for TyT devices. Example for codeplug extensions for TyT devices. Once an extension is added to the codeplug, it can be expanded to view and edit the contained settings. To edit a particular setting, simply double-click on the value.
================================================ FILE: doc/manual/gui/fig/Makefile ================================================ all: ================================================ FILE: doc/manual/gui/grouplists.xml ================================================
Assembling Group Lists RX group lists or simply Group Listss are just lists of Group Calls you may wish to receive on a channel. Of cause, you may want to receive calls to multiple talk groups on one channel, hence you have to create these lists of talk groups beforehand. Screen-shot of the RX group lists. The list of group lists. The Group Lists tab is just a simple list of all group lists you created. You may add a group list by clicking on the Add RX Group button at the bottom. You can delete a group list by selecting it in the list and clicking on the Delete RX Group button there. You can also edit a RX group list by double-clicking on that group in the list. Screen-shot of the group list edit dialog. The group list dialog. When creating a new group list or when editing one, the Edit Group List dialog will open. Within this dialog, you can change the name of the group at the top. In the center of the dialog, you will find the list of group calls of this group list. You can add group call to the list by clicking on the Add Contact button on the bottom. You can also remove contacts from the list by selecting the contact and clicking on the Remove Contact button. When you are done editing the group list, click on the Ok button. The Cancel button will discard all changes and closes the dialog. Screen-shot of the group call selection dialog. Selecting the group calls to add. When adding contacts to the group list, the a dialog appears, that allows for selecting the group calls to add to the group list. Some radios (in particular the OpenGD77 firmware) also allow to add private calls to group lists. To this end, the Show private calls option allows to add private calls too. All other devices do not allow for private calls in group lists. They will simply ignored by qdmr when the codeplug is generated for these devices. If Show Commercial Features is enabled in the settings dialog (see ), a tab bar is shown at the top. There you can also access the device specific settings for the group list.
================================================ FILE: doc/manual/gui/gui.xml ================================================ The Graphical User Interface This chapter describes the graphical user interface (GUI) in some detail. qdmr aims at being a device and manufacturer independent CPS. To this end, the GUI reflects a device independent code plug. This means, that not all of the shown features are present in all radios. For example, not all supported devices implement APRS or roaming. These settings are then ignored when generating the device specific binary code plug. Some radios provide settings, that are important but also specific to a single model or family of models. qdmr aims at supporting these settings through so-called extensions. These extensions can be attached to all elements of a codeplug. For example to channels, contacts, zones, etc. ================================================ FILE: doc/manual/gui/programradio.xml ================================================
Programming the radio Once the code plug is finished, it can be programmed onto the radio. Just select the write cod plug button in the tool-bar at the top of the window or select write from the device menu. In a first step, qdmr will try to detect a connected radio. This will be done automatically (unless disabled in the settings menu) if there is only one radio connected and it is safe to access the USB device. Some radios use some generic USB to serial chips in their cable instead of connecting the micro controller of the radio directly to the computer. This way, it is not possible for qdmr to safely assume, that the found serial port is actually a radio. If such a generic interface is detected, qdmr will ask which interface the radio is connected to. Once a radio is found it will verify the code plug with that radio. That is, it will check whether any limits are exceeded. For example the number of channels, contacts, group lists, etc. There are several levels of issues that can be detected when verifying a code plug with a radio. The lowest level is the Information. These are just messages generated to inform you about minor changes made to the code plug to fit it into the specific radio. For example when zones are split. These information are usually ignored and qdmr will proceed writing the code plug. Warnings are one level more severe. They are issued if changes are made that may change the behavior of the code plug. The result, however, will still be a working code plug. They are usually issued when names are too long. When warnings are issued, qdmr will not automatically proceed writing the code plug. The user, however, can ignore the warnings and continue. In the application settings (see ), you may choose to always ignore verification warnings. In this case, qdmr will write the code plug automatically, even if there were some warnings. Finally Errors are the most severe verification issues. They simply prevent writing the code plug to the device. The user cannot ignore errors as they would result in invalid and even damaging code plugs being written to the device. If, however, everything fits into the radio, qdmr will start writing the binary code plug to the device. Writing the code plug is a two-step process. First, the current code plug is read from the radio. This includes all settings. Then the device-specific code plug is updated and then re-uploaded to the device. This two-step process will maintain all device-specific settings made earlier unless explicitly set within qdmr. During the reading or writing, the qdmr GUI will turn gray (inactive) to prevent any changes to the code plug during the transfer. However, a progress-bar is shown in the bottom-right to indicate the progress.
Permissions When running qdmr or dmrconf under Linux, you may need to change the permissions to access USB devices. Along with the software, a udev rules file was installed. This file specifies that members of the dialout group have access to the radios. Consequently, you need to be a member of this group. You can check your group membership with groups. This command lists all groups your user is a member of. This list should contain dialout. If your user is not yet a member of the dialout group, you can add your user to it by calling sudo adduser YOUR_USER dialout
================================================ FILE: doc/manual/gui/radiosettings.xml ================================================
General configuration The figure below shows the General Configuration tab of qdmr. This tab is divided into Five subtabs: Boot Settings, Audio Settings, Tone Settings, Channel Default Values and Extensions. Screen shot of the general configuration tab showing the boot settings. General settings tab.
Boot settings Within the Boot Settings, the Intro Line 1 & 2 specify the text that some radios show on startup. You may enter any text here. Some radios show an image during boot. For those radios, these settings have no effect.
Audio Settings Screen shot of the audio settings. Audio settings tab. Within the Audio Settings, the Default Microphone Amplification specifies (by default) the common mic gain for all modes. This must be a value between 1 and 10, where 1 is the smallest amplification and 10 the loudest. A separate FM Microphone Amplification can be specified below. If Default is selected, the default microphone gain is used instead. The Default Squelch specifies the common squelch level for all modes. This must either be Open or a value between 1 and 10. The larger the value, the stronger the station must be to open the squelch. A squelch level is usually only relevant for FM channels. Some radios allow to set a separate DMR squelch level. This can be done with the DMR Squelch field. If Default is checked, the default (FM) squelch is used. The VOX Sensitivity field specifies the sensitivity of the VOX (voice-operated-switch). If set to Off, the VOX is disabled. Otherwise, a value between 1 and 10 must be selected. The larger the value, the louder you have to talk into the radio, to trigger the VOX. There is also VOX Delay field, specifying a delay before the VOX is triggered. This can be used to avoid unintentional transmission of short loud bursts, for example in some working environments. The Maximum Speaker and Maximum Headphone Volumes allow for limiting the loudness the speaker or headphone. This can be used to adapt the range of volume knob on the radio to the current working environment. The Speech Synthesis option allows for enabling speech synthesis by the radio. Some radios support speech synthesis to help visually impaired operators to handle the radio and navigate its menus. If this option is checked, the speech-synthesis will be enabled if the radio supports this feature.
Tone Settings Screen shot of the tone settings. Tone settings tab. The tone settings control some of the sounds generated by the radio. Especially, some radios allow to disable all sounds globally. Or to enable/disable or set the volume of the key tones. The SMS tone, Ringtone and Talk permit fields allow to enable/disable tones sounding, when an SMS, a private all is received or if it is allowed to transmit on the currently active channel according to the admit criterion set for that channel. The SMS and private call tones can only be enabled or disabled globally, while the talk-permit tone can be enabled on a by-channel-type basis. To enable/disable the tone for channel types, click on the pen icon. The remaining tone-settings concern the boot, call-start, call-end, channel-idle and call-reset melodies. These can either be enabled/disabled globally (e.g., boot and call-reset melodies) or enabled for channel types individually. Again, click on the pen button to select the channel types. Some radios allow for programming custom melodies for these tones. These melodies can be specified in LilyPond format and speed in BPM (beats per minute). The play-button on the right plays the specified melody. qdmr uses a simplified LilyPond format. It consists of a simple, white-space separated list of notes and pauses. The general format is Name[Length][Ocave][.] Name must be a note name (i.e., c, cis, d, dis, e, f, fis, g, gis, a, ais or b) or r for a pause (rest). The length specifies the length of the note or pause. It must be one of 1, 2, 4, 8, 16 or 32, specifying a whole, half, quarter, ... note. Adding a dot (.) to the end of the note extends it by half of its length. I.e. a4. will be 3/8th long. The length can also be omitted. In this case, the length of the previous not is re-used. Consequently, the first note need an explicit length. Finally, by adding one or more ' the octave can be increased, while adding one or more , the octave of a note can be decreased. It is not possible to specify the octave of the entire melody.
Channel Default Values Screen shot of the channel default settings. Channel default settings tab. The Channel Default Values block allows to specify some default values that can be referenced by channels (see below). This serves two use cases. First, it allows to set some channel properties for all channels that reference these values at once. More importantly, however, some radios do not allow to set these options on a per-channel basis. For those radios, these values are used for all channels.
Extensions Screen shot of the settings extensions. Settings extensions tab. Finally, the tab labeled Extensions contains extensions. This view allows to add, remove and edit device specific radio settings. All device specific settings are handled and displayed in the same way. See for some description of this process.
================================================ FILE: doc/manual/gui/roaming.xml ================================================
Roaming Roaming is a feature that allows DMR radios to select an alternative repeater once you leave the range of the currently selected one. To do that, you have to specify so-called roaming zones. Within these zones, you collect all repeaters that provide access to a particular talk group. When roaming is enabled, the radio will check periodically, whether the current repeater is still reachable. If not, the strongest repeater from the selected roaming zone will then be selected instead. In order to stay connected to a particular talk group, not the entire channel settings must be changed. But only those properties, that are specific to the repeater we change to. These are the RX and TX frequencies, color codes and sometimes the time-slot. The latter is necessary, as those repeaters may be located in other regions. Usually — at least for Brandmeister repeaters — the time slot 2 is reserved for regional communication while time slot 1 is intended for inter-regional communication. So you may need to override the time-slot of the active channel, whenever you roam outside of the region associated with the talk group. Some talk groups, however, are over-regional anyway. For example, the world-wide talk group 91. Here, the time slot will always be TS 1 and thus does not need to be overridden. As only some properties of the current DMR channel needs to be overridden, there are special channels called roaming channels that contain only those settings, that are changed during roaming.
Roaming Channels The Roaming Channels tab collects all defined roaming channels. Screen-shot of the list of roaming channels. List of roaming channels. Each roaming channel has a name, for easy reference in the roaming zones. This should be the call of the repeater. Additionally, each roaming channel has a transmit (TX) and receive (RX) frequency overriding the TX and RX frequencies of the current channel on roaming. There are two optional settings, that may override the time slot (TS) and color code (CC) of the current channel. If [Selected] is show, the time slot and color code of the current channel is used instead during roaming.
Roaming Zones The Roaming Zones tab collects all defined roaming zones. Screen-shot of the list of roaming zones. List of roaming zones. There are two ways to create a new roaming zone. The easiest way is to generate a zone and the associated roaming channels automatically. Click on Generate roaming zone, to generate a roaming zone automatically or Add Roaming Zone to assemble one manually.
Creating a Roaming Zone When generating a roaming zone automatically, a dialog will you to select a talk group or several talk groups to create a roaming zone for. That is, the talk group you want to stay connected to in case of losing contact to the repeater. Screen-shot of the talk-group-selection dialog when generating a roaming zone automatically. Selecting talk groups to generate a roaming zone. Once a talk group is selected, a roaming channel created for each DMR channel, which has the selected talk group as its transmit contact. These newly created channels are then collected into a new roaming zone. The roaming zone editing dialog is then opened, allowing you to edit the newly created zone. Alternatively, you may create a roaming zone manually. Simply click the Add Roaming Zone button and the roaming zone editing dialog will open. There you can add roaming channels manually.
Editing Roaming Zones Double-clicking on the roaming zone or clicking on the Add Roaming Zone button will open the Roaming Zone Editor. Screen-shot of the edit-roaming-zone dialog. The roaming-zone editing dialog. Here you can edit the name of the roaming zone as well as adding the roaming channels that are part of this zone. However, you may also add DMR channels to this zone by clicking on the Add DMR Channel button. This will not add DMR channel directly to the roaming zone, but will create a new roaming channel from this DMR channel. If Show Commercial Features is enabled in the settings dialog (see ), a tab bar is shown at the top. There you can also access the device specific settings for the roaming zone.
================================================ FILE: doc/manual/gui/satellite.xml ================================================
Orbital Elements and Transponder Some radios support the prediction of satellite passes and also ease satellite operations by correcting doppler shifts of up- and downlink frequencies. To this end, this feature turns a cheap handheld radio in a fully fledged satellite station. The satellite editor dialog allows to select which satellites will be tracked by the device. The dialog also allows to specify the up- and downlink frequencies for the chosen satellites. Screen shot of the satellite editor dialog. The satellite editor dialog. To add a new satellite, click the Add button at the bottom of the dialog. You will be presented with a selection dialog of all known satellites. Screen shot of the satellite selection dialog. The satellite selection dialog. The list of all known (amateur radio) satellites is huge. You may search for a name using the Ctrl-F shortcut. Once all satellites are added you are interested in, you can edit the transponder frequencies. In qdmr, a satellite may have an FM transponder, an APRS transponder and a beacon. At least one of these features should be configured. The uplink (from earth to satellite) or transmit frequency as well as the downlink (from satellite to search) or receive frequency can be entered directly or selected from a known set of frequencies. To change a frequency, simply double-clock the field in the table. Detail screen shot of the satellite editor dialog, selecting a downlink frequency. The satellite editor dialog: Selecting a downlink frequency. Some satellites also require a sub-tone (CTCSS). These tones can be selected from the dropdown menu for up- and downlink separately. Once all satellites and their transponders are set up, you can write the configuration to the radio using Write satellites from the Device menu.
================================================ FILE: doc/manual/gui/scanlists.xml ================================================
Assembling Scan Lists Scan lists are simple lists of channels that are scanned sequentially, when scanning is started. A ScanList may be associated with an analog or digital channel (see ). For many radios, you need to associate a scan list with a channel (see above) in the analog or digital channel edit dialog. This determines which scan list is used, when a scan is started on a particular channel. Screen-shot of the list of scan lists. The list of scan lists. A new scan list can be created by clicking on the Add Scan List button. A scan list can be deleted by selecting the scan list and clicking on the Delete Scan List button. The order of the scan list can be changed by selecting a list and moving it up/down using the up and down buttons on the right. Double-clicking on a scan list or clicking the Add Scan List button will open the scan-list edit-dialog. This dialog allows to alter/assemble the scan list by adding, removing or reordering the channels in the scan list.
Edit Scan Lists Screen shot of the edit scan-list dialog. The scan-list editor. By double-clicking an existing scan list or clicking on the Add Scan List button, the scan-list dialog opens. The Name field allows to specify the name of the scan list. The optional Primary and Secondary Channel fields allow to specify channels that are visited more frequently. The Primary Channel will be visited 50% of the time. That is, after a channel of the scan list was visited, the primary channel is visited again, and after that, the next channel from the scan list is visited. In the end, the primary channel is scanned half of the time. The secondary channel is similar but gets visited only 25% of the time. The drop down list allows to select none, any channel or the Selected channel. The latter refers to the channel, the scan started on. The optional Transmit Channel or Revert Channel specifies the channel to transmit on during a scan. Here none, any channel, the selected channel and also the Last channel can be chosen. The Last channel refers to the last active channel on the scan list. This allows to answer a heard call during a scan. Channels can be added to the scan list by clicking on the Add Channel button at the bottom. Similarly, channels can be removed by selecting them and clicking on the Remove Channel button. Like for all other lists, the channels can be moved around within the list by selecting channels and using the up and down buttons to the right. If Show Commercial Features is enabled in the settings dialog (see ), a tab bar is shown at the top. There you can also access the device specific settings for the scan list.
================================================ FILE: doc/manual/gui/settingsdialog.xml ================================================
Application Settings Dialog The application settings dialog controls the behavior of qdmr. The dialog is divided into 2 sections, accessible via the tabs on the top.
Data Sources The Data Sources tab collects the settings for the location service and repeater database. Screen shot of the application settings dialog for the data sources. The settings dialog: Data sources The first section concerns the location of the user. You may enter your Maidenhead Locator here or you may enable System location. The latter tries to obtain the current location from the operating system. This information is then used in the channel editors (see ) to provide auto-completion for repeaters nearby. Some data sources restrict search queries to a radius around some position. This radius is set in Search radius. This is radius only limits the results obtained from specific sources, not the auto-completion by qdmr. The second section allows to set the sources for the repeater database. This enables qdmr to automatically complete some information for a repeater if its callsign is entered as the name, when creating a new channel. As several source may have different settings, each source has its own settings tab. repeaterbook.com For some weird reason, Repeater Book provides two identical APIs for North America and the rest of the World. You must select your source accordingly, also you need to request a user-API access token and paste it here. repeatermap.de repeatermap.de also requires a user-API access token. See API access page from repeatermap.de.
Radio programming settings The second tab controls, how the radios are programmed. That is, how the codeplug is assembled and also, how the callsign database gets curated. Screen shot of the application settings dialog for programming the radios. The settings dialog: Programming The Radio Interfaces section contains settings, controlling, how the radios are accessed. For now, there is only one setting, called disable auto-detect. This will disable all means to detect and identify connected radios. You will then have to select an interface and choose a radio model, every time you access it. The second section specifies how codeplugs are assembled. The first option Update codeplug specifies whether a codeplug is generated from scratch or whether the codeplug currently programmed on the radio gets updated. qdmr does not implement all settings possible for all radios, consequently Update code plug should be chooses to maintain all settings of the radio that are not touched by qdmr. For some radios, the GPS and roaming functionality must be enabled explicitly. The Auto-enable GPS and Auto-enable roaming options can be used to automatically enable GPS or roaming. If selected, whenever any channel has a GPS/APRS system or a roaming zone associated with it, the GPS and/or roaming gets enabled globally. As described in , the upload of a code plug will be paused if some verification warnings are issued. The Ignore verification warnings option allows to continue silently even in the presence of verification warnings. The may be needed for some radios with some rather short communication timeout. The radio may reset the connection to the computer while the warnings are shown. To prevent this, this option might be used. The Ignore frequency limits option does exactly what it says. Usually, programming a channel outside of the radios frequency range would issue an error. However, many radios are able to receive and even transmit outside of the frequency range specified by the manufacturer. But be aware, that transmitting outside the declared frequency range may destroy the radio! The Call-Sign DB section collects options that control the automatic curation of the call-sign DB. Many radios allow to write a large database of call-signs and DMR IDs to the radio. These DBs are then used to resolve DMR IDs to call-signs, names etc. and display them. Usually, curating these databases is a cumbersome task. qdmr tries to automate this task. Usually, qdmr will select as many call-signs from the global database it can fit into the radio. Although modern radios will provide a huge amount memory, not all registered IDs can be programmed. In these cases, qdmr will select only the closest IDs to your DMR ID (default Radio DMR ID, see ). The DMR IDs are compared by the longest matching prefix. This makes sense as DMR IDs are not random. They share the same prefix for countries and regions. This way, qdmr will first select all IDs from the same region followed by all IDs from the same country etc. Of cause, there is no rule without any exceptions. Some countries have several prefixes assigned. The Limit number of DB entries option and Number of DB entries field allow to limit the number of DB entries written to the device. If the Limit number of DB entries option is disabled, as many entries are written to the device as it can hold. The Select using my DMR ID option and the Select using prefixes field can be used to control the selection of entries. If the Select using my DMR ID option is enabled, the aforementioned algorithm is used to select the entries based on the default DMR ID. If this option is disabled, a list of prefixes must be specified in the Select using prefixes field. This must be a list of comma-separated prefixes like 262, 263. Whitespace are ignored. Then the DMR IDs closest to these prefixes are used to assemble the final call-sign DB.
================================================ FILE: doc/manual/gui/zones.xml ================================================
Assembling Zones You may program a myriad of different channels for you radio. To organize them in handy chunks, zones are used. That is, a zone is just a named list of channels that are relevant for a particular area or a particular situation. The list of zones. The Zones tab just lists all defined zones. You may add a Zone using the Add Zone button or you may delete one by selecting the zone in the list and clicking on Delete Zone. You may also alter the ordering of the zones by selecting one from the list and using the up and down buttons on the right. How zones are implemented differs from radio to radio. For example, some radios allow to set a different zone for each VFO (A or B), consequently these zones are simple lists of channels. Other radios allow to select a single zone for both VFOs. For these radios, a zone consists of two lists of channels. One for each VFO. qdmr zones follow the second approach. That is, a zone consists of two lists. One of each VFO. When programming radios that support only one channel list per zone, the zone is split into two (unless the second list is empty). One for each VFO and the A/B label is added to the name. Double-clicking a zone or clicking on the Add Zone button will open the zone editor dialog. Screen-shot of the edit-zone dialog. The zone editor. This dialog allows for adding or removing channels, and changing the order of the channels within the zone using the up- and down-buttons on the right.
================================================ FILE: doc/manual/html/Makefile ================================================ all: manual.xml xsltproc dm3mat.darc.de_manual.xsl manual.xml clean: rm -rf fig/ rm -f manual.xml rm -f *.html rm -f docbook.css ================================================ FILE: doc/manual/html/dm3mat.darc.de_manual.xsl ================================================ appendix nop article toc,title book toc,title chapter toc part nop preface nop qandadiv nop qandaset nop reference toc,title section nop set toc #include virtual="/menu.html" Up Index
================================================ FILE: doc/manual/html/manual.css ================================================ main > nav { margin-top:20px; } dl.variablelist { margin-left: 2em; } dl.toc { margin-left: 2em; margin-right: 2em; background-color: #f2f2f2; overflow: auto; padding: 1em; border: 0px solid black; border-radius: 5px; font-weight: bolder; } pre.programlisting { background-color: #f2f2f2; overflow-x: auto; padding: 1em; margin-left: 16px; margin-right: 16px; } table { margin-left: 16px; margin-right: 16px; } thead td { font-weight: bold; } tbody td { vertical-align: top; } .token { font-family: 'Courier New', Courier, monospace; } ================================================ FILE: doc/manual/intro/codeplug.xml ================================================
Codeplug Assembly After the basic concepts and technical details of the DMR mode has been discussed, it is time to consider the actual configuration of the DMR radios. This usually not done via the keypad of the radio but with the help of a separate software. The so-called CPS or codeplug programming software. Before we can start, we need like any other participant in the DMR network a unique number, the DMR ID. You can get your personal and unique DMR ID from radioid.net. There you need to verify that you are a licensed ham operator. You will receive your personal DMR ID usually within 24h per Mail. Once you've got your ID, you can start. As this script is intended for the beginners, it is very likely that you do not own a top-shelf Motorola device but rather one of the cheap devices of the common manufacturers. If you do not own a DMR device yet but consider to by one, you should explicitly check whether it supports DMR Tier I and IIAs usual, DMR is not a single standard but a family of standards. Tier I describes the simplex operation while Tier II considers the repeater operation and time-slots. You will therefore need a device that also implements Tier II to be able to work with repeaters.. Ignore any marketing BS and check the technical description of the product for Tier I & II. If it is not mentioned there, simply skip that product. This is particularly true for the Baofeng MD-5R but not the RD-5R. The manufacturer of the device of your choice will provide the CPS for download you need to program your radio. Usually you will also find there firmware updates for your device. The manufacturers usually provide a separate CPS version for every device and even firmware revision. So please check whether you've got the correct CPS version. The configuration of the device differs from device to device and even more from manufacturer to manufacturer. The basic setup, however, remains the same. When you start the CPS for the first time, you will likely note two things. First, that the user experience stems from the last millennium (about Windows 3.11). And second, that there are a tone of obscure and badly translated options for your device. These options are usually named cryptic and are not documented. The configuration of your device usually happens in five to six steps: General settings, creating contacts, assembling group lists, creating channels, assembling zones and optionally assembling scan lists. Within the following sections, I want to guide you through these steps.
General radio-wide settings. The single most important options within the general settings is your DMR ID and your call sign. These options are usually located under the label Radio Settings or General SettingsThe actual name may vary from manufacturer to manufacturer.. Your DMR ID is entered in the field name Radio ID. Many radios support to enter several DMR IDs. This feature is usually not used in ham radio. In fact you will only always need a single DMR ID even with several radios. Your call can be entered in the Radio Name field.
Creating Contacts Once you have made these basic settings, you may create some contacts in your contact list. This list should contain all talk groups you are interested in, some private contacts to OM you know as well as some service numbers for the echo-service, SMS service etc. A sample is shown in . Example contacts for germany
Name Typ Nummer Name Typ Nummer
Local group call 9 Ham/SlHo group call 2622
Regional group call 8 NiSa/Bre group call 2623
TG99 group call 99 NRW group call 2624
All call all call 16777215 RhPf/Saar group call 2625
World wide group call 91 Hessen group call 2626
Europe group call 92 BaWü group call 2627
D-A-CH group call 920 Bay group call 2628
Germany group call 262 Sa/Th group call 629
Austria group call 232 Echo Test private call 262997
Switzerland group call 228 SMS Serv. private call 262993
EMCOM EU group call 9112 DAPNET private call 262994
EMCOM WW group call 9911 APRS GW private call 262999
MeVo/SaAn group call 2620 DM3MAT private call 2621370
Ber/Bra group call 2621 ... ... ...
Of cause there are much more talk groups. There are also talk groups for specific topics which are not necessarily targeted at a specific region. A rather complete list can be found in the Brandmeister Wiki.
Assemble group lists The next step is to assemble so-called Group Lists. These are simple lists of talk groups that you want to receive on a particular channel. As mentioned in the introduction, the network does not know which talk groups you are interested in. This must be programmed into the radio. Group lists do exactly that: The specify which talk groups you want to receive. All others are ignored. You should at least create two group lists. One for the simplex operation, one for regional communication and optionally one for the trans-regional communication. You should also create one for each region you frequently visit. The simplex group list is theoretically not necessary as simplex calls should always use the so-called All Call. Frequently, however, also the talkgroups TG99, TG9 and TG8 are used in simplex operation. Hence a group list with these talk groups is needed for simplex operation. Your trans-regional talk group should include the talk groups for the entire world TG91, your continent (e.g., Europe TG92), your country (e.g., Germany TG262) and also the emergency talk group (e.g., 9112 in Europe). Finally the talk group for the local/regional communication should contain the local TG9, regional TG8 and the talk group for your region (e.g., TG 2621 for my region Berlin/Brandenbug). As I am also frequently in saxony, I also created a group list for that region. My group list settings are shown in . Example group lists
Name Group calls
Simplex Local, Regional, TG99
WW/EU/DL World wide, Europe, D-A-CH, Germany, EMCOM EU
Ber/Bra Local, Regional, Ber/Bra
Sa/Th Local, Regional, Sa/Th
Creating channels Before we start assembling any channels, I should mention that DMR radios are also able to transmit and receive analog FM. Thus, you can also use them for classic FM simplex and repeater operation. In this section, I describe the configuration of digital DMR channels usually called digital channels. The configuration of analog FM channels is not described. To create a DMR channel, you have to select digital for the channel type, for FM channels analog. When you already have some experience with the analog FM repeater operation, the configuration of DMR channels may appear quiet weird. For analog FM repeaters, you usually configure exactly one channel. For DMR repeaters you will configure at least two (one for each time slot), but usually many more. To cut a long story short, let me explain it with a concrete example.
Creating Simplex Channels Example simplex channel configuration
Name RX Freq. TX Freq. TS CC TX Contact Grp.List
DMR S0 433.4500 MHz 433.4500 MHz 1 1 all call simplex
DMR S1 433.6125 MHz 433.6125 MHz 1 1 all call simplex
... ... ... ... ... ... ...
In an example for a simplex channel configuration is shown. Of cause, you should extend it to all 8 simplex channels. The first column simply specifies the name of the channel. The second and third columns specify the transmit and receive frequencies for these channels. For simplex channels, RX and TX frequencies are the same. In simplex operation, there is no repeater. That is, no instance that dictates a beat. To this end the choice of the time slot (TS) is irrelevant and usually TS1 is chosen. The color code, however, matters. Repeater as well as your radio will ignore calls with a mismatching color code. For simplex channels, the color code 1 has been established. The sixth column specifies the default transmit contact. For simplex channels, the so-called All Call should be chosen, to ensure that really everyone can receive the call irrespective of the receivers group list settings. The default transmit contact specifies the contact (private, group or all call) that is called whenever the PTT is pressed. As mentioned earlier, there is an exception to that rule. Whenever you directly answer a call within in the so-called Hang Time, you will answer with the same call you received. The last column specifies the to so-called Group List. This list specifies which talk groups are received on that channel. As mentioned earlier, no entry should be needed here, as the all-call should be used for the transmit contact on simplex channels. Unfortunately, it is not uncommon to find several talk groups being used as transmit contacts on simplex channels like TG9, TG8, TG99. For these cases, a group list simplex was created earlier. Within your CPS, you will find many more options for channels. The majority can be left untouched. At the end of this section, I will describe some of these settings briefly. Many of these settings are quite uncommon in amateur radio or even straight illegal. The Admit Criterion specifies under which conditions your radio is allowed to transmit. For simplex channels, the option channel free should be chosen. This configures the radio to only transmit if the channel is currently free.
Creating repeater channels Creating repeater channels is slightly more complex than creating simplex channels, as we need to create several channels per repeater. Before you can create any channels, you need to know which DMR repeaters are near to you. A good overview provides the repater book. There you can also filter for DMR repeaters and you get all information you need to configure the DMR repater channels. That is input and output frequencies and the color code of the repeater. Example channels for a single repeater DB0LDS
Name RX Freq. (output) TX Freq. (input) TS CC TX Contact Grp.List
DB0LDS TS1 439.5625 MHz 431.9625 MHz 1 1 - WW/EU/DL
DB0LDS DL TS1 439.5625 MHz 431.9625 MHz 1 1 Germany WW/EU/DL
DB0LDS Sa/Th TS1 439.5625 MHz 431.9625 MHz 1 1 Sa/Th Sa/Th
DB0LDS TG9 TS2 439.5625 MHz 431.9625 MHz 2 1 TG9 Ber/Bra
DB0LDS TG8 TS2 439.5625 MHz 431.9625 MHz 2 1 TG8 Ber/Bra
DB0LDS BB TS2 439.5625 MHz 431.9625 MHz 2 1 Ber/Bra Ber/Bra
I think, it is the best to explain the creation of repeater channels using a concrete example for a repeater near to me shown in . This repeater has the call DB0LDS and has the input frequency 431.9625 MHz and output frequency 439.5625 MHz. According to repeater book, this repeater expects the color code 1. These are the elementary information you need to set for all channels using this repeater. Many CPSs allow to copy or clone channels. This way you only need to enter this basic information once. At the end of , I mentioned that trans-regional communication is happening on time slot 1 while regional communication is happening on time slot 2. This is visible in this example. The repeater is located in the region Berlin/Brandenbug (Ber/Bra), consequently all channels with within-region talk-groups have the time slot 2, all others have the time slot 1. The first channel DB0LDS TS1 is a generic channel for the time slot 1. There is no default transmit contact defined for this channel. This channel can be used to perform arbitrary direct and group calls by selecting a contact or talk group from the contact list. This means, that a call cannot be started by simply pressing PTT on that channel. First, a contact must be selected that should get called. The second channel DB0LDA DL TS1 is almost identical to the first except for the default transmit contact. Here Germany (TG262) is selected. This means, if this channel is selected and the PTT is pressed, the talk group 262 is called. By configuring a separate channel for this talk group allows to start a call to it without having to search for it in the contact list. This also allows to temporarily subscribe this talk group on a repeater easily, by simply pressing PTT briefly. Irrespective of the default transmit contact, you can always answer to a call within the hang time. The third channel DB0LDS Sa/Th TS1 is also similar to the first two. Here the default transmit contact is the talk group for Saxony/Thuringia (TG2629) to be able to subscribe that talk group at my local repeater and call it easily. Please not that for this channel the time slot 1 is used. The repeater is located in Brandenburg and therefore any communication with Saxony is inter-regional and should happen on the time slot 1. The group list contains only the talk group for Saxony/Thuringia and thus other inter-regional talk groups are not received on that channel. Channels four, five and six are for repeater-local (TG9), regional (TG8) and the talk group Berlin/Brandenburg (TG2621) calls. As this is all regional communication, it happens on the time slot 2. Also they all have the group list Ber/Bra set (see ). Therefore, all regional talk groups (TG8, TG9, TG2621) are received on that channel. As the default transmit contact, the corresponding talk group is set. If the channel DB0LDS TG9 TS2 is selected and the PTT is pressed, a call to TG9 is repeated only by the repeater DB0LDS. If the channel DB0LDS BB TS2 is selected and the PTT is pressed, a call to TG2621 is repeater by almost all repeaters in the region Berlin/Brandenbug. Therefore, chose a talk group that is sufficient for you intended communication. On any channel, you can start an arbitrary call (group, privat, all) by either selecting the contact from the contact list of even simply entering the DMR number into the keypad of the radio. This is independent from the default contact on the current channel. In the end, the default transmit contact is a convenience feature. With the default contact, channels for frequently used contacts can be created. The so-called Admit Criterion should be set to Color Code for DMR repeater channel. This means, that the radio will only transmit if the channel is free and the color code of the repeater matches the color code of the channel.
Other channel options The user interface of the manufacturer CPS where you configure the channels, is usually very extensive. There is a huge amount of options that control the behavior of the channel. The majority of these options are not used in ham radio applications. Some of these, however, I want to describe here briefly. The Admit Criterion was mentioned before. It controls under which conditions the radios can transmit. There are usually three options. Always does exactly what it says: it allows to transmit always. This option should be chosen for analog FM repeater channels. Channel free means that the radio will only transmit if the current channel is free. This option should be choses for simplex channels. When Color code is selected, the radio will only transmit if the channel is free and the color code of the repeater matches the color code of the channel. This option should be chosen for DMR repeater channels. The TOT setting or transmit timeout specifies the maximum duration of continuous transmission. After that period of continuous transmission, the radio will stop the transmission automatically. The feature is used in commercial applications to avoid the blocking of a channel or talk group by a participant. This option has little sense in amateur radio and can be set to infinity. The Emergency System is a method to signal an alarm or an emergency situation. Also this feature is not used in amateur radio. The option Privacy Group or Encryption Key refers to a built-in method of encrypting the traffic. This is actually forbidden in amateur radio. The flags Emergency Alarm Confirmed, Private Call Confirmed and Data Call Confirmed specify how the radio starts these calls. The radio will first establish a call to the destination and will signal once the call is confirmed. Once the confirmation is received, the actual call starts. These options are not used in amateur radio and should be disabled as they may interfere with the normal operation. The option Talkaround allows to operate simplex on a repeater channel. That is the radio transmits and receives on the repeater input frequency. This allows to bypass the repeater and to communicate directly with other participants on the same repeater channel. Also this option makes little sense in amateur radio. When the RX Only flag is enabled, the radio cannot transmit on that channel. This may be useful for out-of-band monitoring channels where you are not allowed to transmit. The VOX feature is actually used in ham radio. It stands for voice operated switch and allows to start a call using the voice without the need to press PTT. Some radios allow to enable this option on a per-channel bases others only radio-wide. The Power option allows to specify the transmit power level. This can usually be set in predefined steps like Low, Middle, High. Some radios may also allow a fine grained setting of the power level. The Scan List specifies a list of channel that are scanned if a scan is started on that channel. This feature might be used as an alternative to a missing roaming feature (see ).
Assembling zones Once you have assembled all channels of interest, you may notice that the list is quite long. Hence all DMR radios organize the channels in so-called Zones. Zones are simple lists of channels that group them into relevant sets, usually based on the location. You may therefore collect all channels for Home, Work and Holidays into one zone each. How you organize your zones is up to you. You may also organize these channels by talk groups. This way you may implement some kind of a manual roaming. Once you left the range of a repeater you may search for another one in the same zone. This way you stay connected to a particular talk group. In contrast to the automatic roaming, you have to select the repeater by hand. Channels that are not assigned to any zone are usually not selectable by the radio. It is, however, perfectly fine to assign a channel to several zones.
Assembling scan lists Scan Lists are simple lists of channels. When the scan is started on a particular channel, the channels scan list is used. The radio will then step though that list and may stop on a channel that shows activity. It is then possible to answer the received call. This function allows for observing several channels. Additionally, it is usually possible to specify one or more priority channels for a scan list. This channel is then visited more frequently and thus monitored more intensively.
================================================ FILE: doc/manual/intro/fig/Makefile ================================================ IMG = fm_simplex_a.png fm_simplex_b.png fm_duplex_a.png fm_duplex_b.png fm_echolink_a.png fm_echolink_b.png fm_echolink_c.png \ trunk_net_ex1.png trunk_net_ex2.png trunk_net_ex3.png trunk_net_ex4a.png trunk_net_ex4b.png \ simplex_allcall.png simplex_groupcall.png simplex_privatecall.png \ repeater_local.png repeater_privatecall.png \ talkgroup_ex1a.png talkgroup_ex1b.png talkgroup_ex1c.png \ timeslot.png #.PRECIOUS: %.pdf # keep PDFs all: $(IMG) %.png: %.pdf convert -density 300 -depth 8 -quality 85 $< $@ %.pdf: %.tex pdflatex $< clean: rm -f $(IMG) rm -f *.aux *.log ================================================ FILE: doc/manual/intro/fig/fm_duplex_a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DL0LDS}; \user{U2}{6,0}{DL2XYZ}; \path[->] (U1) edge node[above,rotate=17] {$431.9625 MHz$} (R1) ; \path[->] (R1) edge node[above,rotate=-17] {$439.5625 MHz$} (U2); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/fm_duplex_b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DL0LDS}; \activeuser{U2}{6,0}{DL2XYZ}; \path[->] (U2) edge node[above,rotate=-17] {$431.9625 MHz$} (R1) ; \path[->] (R1) edge node[above,rotate=17] {$439.5625 MHz$} (U1); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/fm_echolink_a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DB0SP}; \repeater{R2}{6,1}{DB0LDS}; \user{U2}{9,0}{DL2XYZ}; \path[->] (U1) edge node[above,rotate=17] {$DTMF: 662699$} node[below,rotate=17]{$431.825 MHz$} (R1) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/fm_echolink_b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DB0SP}; \activerepeater{R2}{6,1}{DB0LDS}; \user{U2}{9,0}{DL2XYZ}; \path[->] (U1) edge node[above,rotate=17] {$431.825 MHz$} (R1) ; \path[->,dashed] (R1) edge node[above] {via Echolink} (R2) ; \path[->] (R2) edge node[above,rotate=-17] {$439.150 MHz$} (U2) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/fm_echolink_c.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{U1}{0,0}{DM3MAT}; \activerepeater{R1}{3,1}{DB0SP}; \activerepeater{R2}{6,1}{DB0LDS}; \activeuser{U2}{9,0}{DL2XYZ}; \path[->] (R1) edge node[above,rotate=17] {$439.425 MHz$} (U1) ; \path[->,dashed] (R2) edge node[above] {via Echolink} (R1) ; \path[->] (U2) edge node[above,rotate=-17] {$431.875 MHz$} (R2) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/fm_simplex_a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{U1}{0,0}{DM3MAT}; \user{U2}{6,0}{DL2XYZ}; \path[->] (U1) edge node[above] {$144.500 MHz$} (U2) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/fm_simplex_b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{U1}{0,0}{DM3MAT}; \activeuser{U2}{6,0}{DL2XYZ}; \path[->] (U2) edge node[above] {$144.500 MHz$} (U1) ; \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/repeater.tex ================================================ \newcommand{\repeater}[3]{% \node ({#1}) at ({#2}) {% \begin{tikzpicture}% \draw [black,thick] (-.25,0) -- (0,0.5) -- (0.25,0) -- (-0.25,0);% \draw [black,thick,domain=-45:225] plot ({0.2*cos(\x)}, {0.5+0.2*sin(\x)});% \draw [black,thick,domain=-45:225] plot ({0.4*cos(\x)}, {0.5+0.4*sin(\x)});% \node (xxx) at (0,-.2) {{#3}};% \end{tikzpicture}% } % } \newcommand{\activerepeater}[3]{% \node ({#1}) at ({#2}) {% \begin{tikzpicture}% \draw [black,thick] (-.25,0) -- (0,0.5) -- (0.25,0) -- (-0.25,0);% \draw [red,thick,domain=-45:225] plot ({0.2*cos(\x)}, {0.5+0.2*sin(\x)});% \draw [red,thick,domain=-45:225] plot ({0.4*cos(\x)}, {0.5+0.4*sin(\x)});% \node (xxx) at (0,-.2) {{#3}};% \end{tikzpicture}% } % } \newcommand{\user}[3]{% \node ({#1}) at ({#2}) {% \begin{tikzpicture}% \draw [black,fill=black] (-.25,0) -- (0,0.5) -- (0.25,0) -- (-0.25,0);% \draw [black,fill=black] (0,.5) circle (.2); % \node (xxx) [text width=0.6cm, align=center] at (-.35cm,-.4) {{#3}};% \end{tikzpicture}% } % } \newcommand{\activeuser}[3]{% \node ({#1}) at ({#2}) {% \begin{tikzpicture}% \draw [red,fill=red] (-.25,0) -- (0,0.5) -- (0.25,0) -- (-0.25,0);% \draw [red,fill=red] (0,.5) circle (.2); % \node (xxx) [text width=0.6cm, align=center] at (-.35cm,-.4) {{#3}};% \end{tikzpicture}% } % } ================================================ FILE: doc/manual/intro/fig/repeater_local.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 2,0}{DL1XYZ}; \user{u3}{ 4,0}{DL2XYZ}; \draw[dotted] (5,4) -- (5,-1); \activeuser{u4}{ 6,0}{DL3XYZ}; \user{u5}{ 8,0}{DL4XYZ}; \user{u6}{10,0}{DL5XYZ}; \activerepeater{R1}{1,3}{DB0ABC}; \repeater{R2}{3,3}{DB0DEF}; \activerepeater{R3}{7,3}{DB0GHI}; \activerepeater{R4}{9,3}{DB0JKL}; \draw[->] (u1) -- node[above,rotate=70]{GC: TG9} (R1); \draw[->] (R1) -- node[above,rotate=-70]{GC: TG9} (u2); \draw[->] (u4) -- node[above,rotate=70]{GC: TG8} (R3); \draw[->] (R3) -- node[above,rotate=-70]{GC: TG8} (u5); \draw[->] (R4) -- node[above,rotate=-70]{GC: TG8} (u6); \path[->] (R3) edge[dashed,bend left] node[above]{via network} (R4); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/repeater_privatecall.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT 2621370}; \activerepeater{R1}{1,3}{DB0ABC}; \draw[dotted] (2,4) -- (2,-1); \user{u2}{ 4,0}{I/DL2XYZ\\2621234}; \activerepeater{R2}{3,3}{I0ABC}; \draw[->] (u1) -- node[above,rotate=70]{PC: 2621234} (R1); \draw[->] (R2) -- node[above,rotate=-70]{PC: 2621234} (u2); \path[->] (R1) edge[dashed,bend left] node[above]{via network} (R2); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/simplex_allcall.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 6,2}{DL1XYZ, TG99}; \user{u3}{ 6,0}{DL2XYZ, TG99}; \user{u4}{ 6,-2}{DL3XYZ}; \path[->] (u1) edge[bend left] node[above, rotate=10]{$433.450 MHz$} node[below, rotate=10]{All Call} (u2); \path[->] (u1) edge node[above]{$433.450 MHz$} node[below]{All Call} (u3); \path[->] (u1) edge[bend right] node[above, rotate=-10]{$433.450 MHz$} node[below, rotate=-10]{All Call} (u4); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/simplex_groupcall.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 6,2}{DL1XYZ, TG99}; \user{u3}{ 6,0}{DL2XYZ, TG99}; \user{u4}{ 6,-2}{DL3XYZ}; \path[->] (u1) edge[bend left] node[above, rotate=10]{$433.450 MHz$} node[below, rotate=10]{GC: TG99} (u2); \path[->] (u1) edge node[above]{$433.450 MHz$} node[below]{GC: TG99} (u3); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/simplex_privatecall.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \activeuser{u2}{ 6,2}{DL1XYZ, TG99}; \user{u3}{ 6,0}{DL2XYZ, TG99}; \user{u4}{ 6,-2}{DL3XYZ}; \path[->] (u1) edge[bend left] node[above, rotate=10]{$433.450 MHz$} node[below, rotate=10]{PC: DL1XYZ} (u2); \path[->] (u2) edge[bend left] node[above, rotate=10]{$433.450 MHz$} node[below, rotate=10]{PC: DM3MAT} (u1); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/talkgroup_ex1a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 2,0}{DL1XYZ}; \user{u3}{ 6,0}{DL2XYZ}; \draw[dotted] (7,4) -- (7,-1); \user{u4}{10,0}{I/DL3XYZ}; \activerepeater{R1}{1,3}{DB0ABC, TG2621}; \activerepeater{R2}{5,3}{DB0DEF, TG2621}; \repeater{R3}{9,3}{I0ABC}; \path[->] (u1) edge node[above,rotate=70]{GC: TG2621} (R1); \path[->] (R1) edge node[above,rotate=-70]{GC: TG2621} (u2); \path[->] (R2) edge node[above,rotate=-70]{GC: TG2621} (u3); \path[->] (R1) edge[bend left] node[above]{GC: TG2621} (R2); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/talkgroup_ex1b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{u1}{ 0,0}{DM3MAT}; \user{u2}{ 2,0}{DL1XYZ}; \user{u3}{ 6,0}{DL2XYZ}; \draw[dotted] (7,4) -- (7,-1); \activeuser{u4}{10,0}{I/DL3XYZ}; \activerepeater{R1}{1,3}{DB0ABC, TG2621}; \activerepeater{R2}{5,3}{DB0DEF, TG2621}; \activerepeater{R3}{9,3}{I0ABC, (TG2621)}; \path[->] (u4) edge node[above,rotate=-70]{GC: TG2621} (R3); \path[->] (R1) edge node[above,rotate=70]{GC: TG2621} (u1); \path[->] (R1) edge node[above,rotate=-70]{GC: TG2621} (u2); \path[->] (R2) edge node[above,rotate=-70]{GC: TG2621} (u3); \path[->] (R3) edge[bend right] node[below]{GC: TG2621} (R2); \path[->] (R3) edge[bend right] node[above]{GC: TG2621} (R1); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/talkgroup_ex1c.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{u1}{ 0,0}{DM3MAT}; \user{u2}{ 2,0}{DL1XYZ}; \user{u3}{ 6,0}{DL2XYZ}; \draw[dotted] (7,4) -- (7,-1); \user{u4}{10,0}{I/DL3XYZ}; \activerepeater{R1}{1,3}{DB0ABC, TG2621}; \activerepeater{R2}{5,3}{DB0DEF, TG2621}; \activerepeater{R3}{9,3}{I0ABC, (TG2621)}; \path[->] (u1) edge node[above,rotate=70]{GC: TG2621} (R1); \path[->] (R1) edge node[above,rotate=-70]{GC: TG2621} (u2); \path[->] (R2) edge node[above,rotate=-70]{GC: TG2621} (u3); \path[->] (R3) edge node[above,rotate=-70]{GC: TG2621} (u4); \path[->] (R1) edge[bend left] node[below]{GC: TG2621} (R2); \path[->] (R1) edge[bend left] node[above]{GC: TG2621} (R3); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/timeslot.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \usetikzlibrary{patterns,snakes} \input{repeater} \begin{document} \begin{tikzpicture} \draw[|-,dotted, semithick] (-1,-0.2) -- (0,-0.2); \draw[|-,semithick] (0,-0.2) -- (1,-0.2); \draw[|-,semithick] (1,-0.2) -- (2,-0.2); \draw[|-,semithick] (2,-0.2) -- (3,-0.2); \draw[|-,semithick] (3,-0.2) -- (4,-0.2); \draw[|-,semithick] (4,-0.2) -- (5,-0.2); \draw[|-,semithick] (5,-0.2) -- (6,-0.2); \draw[|->,dotted,semithick] (6,-0.2) -- (7,-0.2); \node at (7, -.5) {$t$}; \draw [thick,decoration={brace,mirror},decorate] (0,-0.4) -- (1,-0.4) node [pos=0.5, anchor=north,yshift=-0.55] {$30\ ms$}; \fill[red!30] (0.1,0) -- (0.1,1) -- (0.9,1) -- (0.9,0) -- cycle; \node at (0.5,0.5) {TS 1}; \fill[blue!30] (1.1,0) -- (1.1,1) -- (1.9,1) -- (1.9,0) -- cycle; \node at (1.5,0.5) {TS 2}; \fill[red!30] (2.1,0) -- (2.1,1) -- (2.9,1) -- (2.9,0) -- cycle; \node at (2.5,0.5) {TS 1}; \fill[blue!30] (3.1,0) -- (3.1,1) -- (3.9,1) -- (3.9,0) -- cycle; \node at (3.5,0.5) {TS 2}; \fill[red!30] (4.1,0) -- (4.1,1) -- (4.9,1) -- (4.9,0) -- cycle; \node at (4.5,0.5) {TS 1}; \fill[blue!30] (5.1,0) -- (5.1,1) -- (5.9,1) -- (5.9,0) -- cycle; \node at (5.5,0.5) {TS 1}; \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/trunk_net_ex1.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \user{r1}{ 0,0}{Clean 1}; \user{r2}{ 2,0}{Clean 2}; \draw[dotted] (3,4) -- (3,-1); \user{s1}{ 4,0}{Security 1}; \user{z} { 6,0}{HQ}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Security 2}; \user{r3}{10,0}{Clean 3}; \repeater{R1}{1,3}{Terminal 1, TG: C,S}; \repeater{R2}{5,3}{Terminal 2, TG: C,S}; \repeater{R3}{9,3}{Apron, TG: S}; \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/trunk_net_ex2.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{r1}{ 0,0}{Clean 1}; \user{r2}{ 2,0}{Clean 2}; \draw[dotted] (3,4) -- (3,-1); \activeuser{s1}{ 4,0}{Security 1}; \activeuser{z} { 6,0}{HQ}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Security 2}; \activeuser{r3}{10,0}{Clean 3}; \activerepeater{R1}{1,3.5}{Terminal 1, TG: C,S}; \activerepeater{R2}{5,3.5}{Terminal 2, TG: C,S}; \activerepeater{R3}{9,3.5}{Apron, TG: S}; \draw[->] (r1) -- node[above,rotate=74] {PC: Clean 3} (R1); \path[->,dashed] (R1) edge [bend left] node[above] {via network} (R3); \draw[->] (R3) -- node[above,rotate=-74] {PC: Clean 3} (r3); \draw[->] (z) -- node[above,rotate=-74] {PC: Security 1} (R2); \draw[->] (R2) -- node[above,rotate=74] {PC: Security 1} (s1); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/trunk_net_ex3.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{r1}{ 0,0}{Clean 1}; \activeuser{r2}{ 2,0}{Clean 2}; \draw[dotted] (3,4) -- (3,-1); \user{s1}{ 4,0}{Security 1}; \activeuser{z} { 6,0}{HQ}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Security 2}; \user{r3}{10,0}{Clean 3}; \activerepeater{R1}{1,3}{Terminal 1, TG: C,S}; \activerepeater{R2}{5,3}{Terminal 2, TG: C,S}; \repeater{R3}{9,3}{Apron, TG: S}; \draw[->] (z) -- node[above,rotate=-74] {TG: C} (R2); \path[->,dashed] (R2) edge [bend right] node[above] {via network} (R1); \draw[->] (R1) -- node[above,rotate=74] {TG: C} (r1); \draw[->] (R1) -- node[above,rotate=-74] {TG: C} (r2); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/trunk_net_ex4a.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{r1}{ 0,0}{Clean 1}; \activeuser{r2}{ 2,0}{Clean 2}; \draw[dotted] (3,4) -- (3,-1); \user{s1}{ 4,0}{Security 1}; \user{z} { 6,0}{HQ}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Security 2}; \user{r3}{10,0}{Clean 3}; \activerepeater{R1}{1,3}{Terminal 1, TG: C,S}; \repeater{R2}{5,3}{Terminal 2, TG: C,S}; \activerepeater{R3}{9,3}{Apron, TG: S,(C)}; \draw[->] (r3) -- node[above,rotate=-74] {TG: C} (R3); \path[->,dashed] (R3) edge [bend right] node[above] {via network} (R1); \draw[->] (R1) -- node[above,rotate=74] {TG: C} (r1); \draw[->] (R1) -- node[above,rotate=-74] {TG: C} (r2); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/fig/trunk_net_ex4b.tex ================================================ \documentclass{standalone} \usepackage{tikz} \usetikzlibrary{shapes.geometric} \input{repeater} \begin{document} \begin{tikzpicture}[every node/.style={scale=.8}] \activeuser{r1}{ 0,0}{Clean 1}; \activeuser{r2}{ 2,0}{Clean 2}; \draw[dotted] (3,4) -- (3,-1); \user{s1}{ 4,0}{Security 1}; \activeuser{z} { 6,0}{HQ}; \draw[dotted] (7,4) -- (7,-1); \user{s2}{ 8,0}{Security 2}; \activeuser{r3}{10,0}{Clean 3}; \activerepeater{R1}{1,3}{Terminal 1, TG: C,S}; \activerepeater{R2}{5,3}{Terminal 2, TG: C,S}; \activerepeater{R3}{9,3}{Apron, TG: S,(C)}; \draw[->] (z) -- node[above,rotate=-74] {TG: C} (R2); \path[->,dashed] (R2) edge [bend right] node[above] {via Netzwerk} (R1); \path[->,dashed] (R2) edge [bend left] node[above] {via Netzwerk} (R3); \draw[->] (R1) -- node[above,rotate=74] {TG: C} (r1); \draw[->] (R1) -- node[above,rotate=-74] {TG: C} (r2); \draw[->] (R3) -- node[above,rotate=-74] {TG: C} (r3); \end{tikzpicture} \end{document} ================================================ FILE: doc/manual/intro/foreknowledge.xml ================================================
Basics: Repeater operations This section briefly describes the common amateur radio FM-repeater operation. The majority of all licensed operators will be familiar with this topic. If you are not yet licensed and interested into amateur radio, consider reading this section. Otherwise, skip right to The majority of connections between HAM operators are made in the so-called simplex mode. That is, the two operators transmit and receive alternately on the same frequency This is actually called semi-duplex, however the term simplex stuck. The term simplex actually refers to the situation, where there is only one transmitter and possibly many receivers (e.g., broadcast). and the connection is direct. This works very well on HF where world-wide direct connections can be made. Typical FM simplex operation For this example, DM3MAT transmits directly to DL2XYZ on the frequency 144.500 MHz. The latter then also answers directly on the same frequency. On higher frequencies, however, radio waves behave more like light and it gets increasingly difficultAlso on VHF and UHF, larger distances can be bridged using an elevated location and larger antennas. to bridge significant distances beyond the horizon. This fact limits the operating range of simple hand-held radios. To still cover a large area, repeaters can be used. Repeaters are autonomous amateur radio stations that are usually located on a mountain, hill or high tower. This allows them to easily cover a large area. They receive signals from HAM operators and retransmit them at the same time. To do that, they cannot send and receive on the same frequency (otherwise they would interfere with themselves). Therefore, repeaters operate in the so-called duplex mode. That is, the repeater receives on one frequency (the so-called input frequency) and transmits the received signal on another frequency (the output frequency) simultaneously. Simple FM repeater operation For this example, DM3MAT sends on the input frequency 431.9625 MHz to the repeater DB0LDS. The repeater transmits the received signal on its output frequency 439.5625 MHz. On that frequency, DL2XYZ receives the original call. The shows a common repeater operation on UHF. Here, the operator DM3MAT transmits its call to DL2XYZ not directly but on the input frequency of the repeater DL0LDS (431.9625 MHz). The repeater receives the call and transmits it simultaneously on its output frequency (439.5625 MHz). This signal is then received by DL2XYZ. Consequently, the call has reached its destination, although DM3MAT and DL2XYZ may not be able to communicate directly. The reply of DL2XYZ to DM3MAT follows the same path. Here DL2XYZ transmits on the repeater input frequency, and DM3MAT receives that call on the repeater output frequency. This way the two operators can communicate with each other even if they are not able to reach each other directly.
Echolink However, there are situations, where two operators are far away and they cannot reach the same repeater. For these cases, it is possible to connect two repeaters via the EchoLink network. Repeater operation with Echolink DM3MAT connects repeaters DB0SP (near Berlin) and DB0LEI (near Leipzig) via EchoLink. Now, they are able to communicate with each other. This network allows to link FM repeaters via internet or to connect directly to a remote repeater via internet. Many FM repeaters are connected to the EchoLink network, allowing for world-wide communication with simple hand-held radios. Frequently, it is also possible to control a repeater over-the-air and to connect that repeater to some other repeater via EchoLink. Usually, this can be done by sending the EchoLink number of the destination repeater using DTMF to the local repeater. This is shown in above. There, DM3MAT sends the EchoLink number 662699 of the repeater DB0LEI near Leipzig to his local repeater DB0SP near Berlin. Then the local repeater (DB0SP) will link with the destination repeater (DB0LEI) via the EchoLink network. For some limited time, both repeaters will act like one logical repeater. That is, everything that is received by one repeater will also be transmitted by the other. This way, the two operators DM3MAT and DL2XYZ can communicate although they cannot reach a common repeater. Once two repeaters are linked via EchoLink, they behave like a single repeater. All over the world, there are FM repeater that are part of the EchoLink network. Therefore, it is possible to communicate world-wide at any time using simple hand-held radios that are as cheap as 40€ or even less.
================================================ FILE: doc/manual/intro/introduction.xml ================================================ Introduction The chapter tries to provide an introduction to DMR (digital mobile radio) targeted at the unexperienced operator and anyone interested in this topic. I try to hide details until it gets absolutely necessary to explain them. The majority of DMR introductions I've found, are more or less extensive glossaries (if you are interested in that, see ). They are hard to comprehend, unless one has at least some experience with DMR. The perceived complexity of DMR comes from its origin as a radio standard for commercial applications at large events and companies (i.e., trunked radio). Therefore, I will first describe an example how DMR is used commercially before I describe how it is used in amateur radio. I hope that this approach will make some of the weird terms and concepts of DMR clearer. ================================================ FILE: doc/manual/intro/local.xml ================================================
Local Repeater Operation One central objective of DMR is to be repeater transparent. That is, it does not matter which repeater you use. You will always be able to reach the same groups and be always reachable through the same means (private or group call). This concept is violated by the talk groups 8 & 9. The are the regional and local talk groups. Two regions with two repeaters each. The talk group 9 (TG9) is the so-called local talk group. Group calls to that talk group are not forwarded though the network and only retransmitted locally. Usually locally means only the repeater. Sometimes, however, these calls are also forwarded to other repeaters nearby. This case is shown in on the left side. Here DM3MAT sends a group call to TG9 via the repeater DB0ABC. This call is not forwarded to any other repeater and thus is only received in the local area around the repeater. DL1XYZ is in that local area and may receive that call if he configured his radio to receive calls from the TG9. The talk group 8 (TG8) is the so-called regional talk group. A call to that talk group is usually forwarded to all repeaters within a specific region. Which repeaters are part of a region, is a decision of the repeater administrators. So it is hard to predict to which repeaters a regional call gets forwarded. In on the right side, DL3XYZ sends a group call to TG8 to the repeater DB0GHI. This call gets forwarded to all repeaters in the region. In this case, also to the repeater DB0JKL within the same region. Therefore, all participants within that region are able to receive the group call if they configured their radios accordingly. In this example, not only DL4XYZ received the call but also DL5XYZ who is not close to the repeater DB0GHI and would have missed a group call to the local talk group.
================================================ FILE: doc/manual/intro/networks.xml ================================================
DMR Networks Within the previous sections, I tried to outline the concepts and some technical details of a DMR network and how a codeplug might be assembled. These concepts, however, apply only to the so-called Brandmeister network. This is the network in the background that routes your private and group calls, connects repeaters etc. In Germany, this is the dominant network. But also world wide, it connects the majority of repeaters (about 5000). However, there are also other networks. There is the DMR-MARC network and the DMR+ network. Which network you are likely to encounter, depends on your location. In countries like France, Spain, BeNeLux, Poland, Czech Republic and Slovakia, almost all repeaters are connected to the Brandmeister network, while in Denmark the DMR+ network dominates. In the USA and Austria, DMR-MARC repeaters aren't rare. All these networks do not differ on the technical level. That is, you DMR-ID is valid in all of these networks and you can use any DMR Tier II radio. The concepts however, in particular how group calls are performed, depends heavily on the network. This means, that you need to configure the repeater channels for a DMR+ repeater in a different way compared to a Brandmeister repeater.
Reflectors (DMR+) Reflectors play an important role in the DMR+ network. They represent a talk group within the DMR+ network. The major difference between a reflector and a talk group is, that they cannot be simply called using a group call. They are subscribed to a local repeater by a private call to the reflector. Then all repeaters subscribed to that reflector behave like a single repeater. You will then participate on that reflector by performing a group call to TG9, the local talk group. Your call will then be sent to the reflector as well as to all other reflectors currently subscribed on the repeater and consequently to all repeaters also subscribed to that reflector. This has the advantage of a much simpler codeplug assembly, as only two channels are configured for each repeater. One for each time slot. The default transmit contact will always be the local talk group TG9. To subscribe a reflector, a private call is started to the reflector from the contact list. This implies that the contact list should contain all reflectors you are interested in. This concept is also much closer to the semi-analog concepts of EchoLink. However, advanced features like roaming are not possible this way. Also the repeater transparency gets lost. Instead of simply starting a group call to the destination talk group, the local repeater needs to be configured. Once that configuration is done, the communication will happen on the local talk group TG9, even if the communication is not local anymore.
================================================ FILE: doc/manual/intro/origin.xml ================================================
DMR Introduction & Origin DMR (digital mobile radio) is a digital radio standard to transmit speech and data. That is, the speech is not directly modulated on the carrier but is first digitalized and compressed using a lossy compression codec (VOCODER). The compressed speech is then transmitted as data packets. The latter allows to attach meta-information to the data packet like source and destination of the packet. DMR was designed to be the digital replacement for analog trunking networks in commercial applications. A classic example for such a commercial application of DMR would be a civil air port. With this, I do not mean the air-traffic radio but all the communication of the ground staff in and around the actual air port buildings. At such an imaginary air port, there is a huge staff with a wide variety of tasks. For example (without any claim of completeness): The cleaning crew, technicians, security staff, apron staff for refueling, luggage and catering, the fire brigade and the headquarters. All these people carry a radio and should be able to Directly call the headquarters. All staff should be able to call the headquarters directly. Direct communication between members of the same group, without interfering with other groups. For example, the cleaning staff should be able to communicate with each other, without interfering with the fire brigade. Each person should be able to call a complete group. For example, the headquarters may call the entire fire brigade or one member of the security staff may call the entire security for help. An air port, however, is a rather large area. Consequently, not all staff members are able to reach all others. Therefore, some repeaters must be installed to cover the entire air port including all interiors. If you compare this scenario with the classic FM repeater networks (see ), it gets clear that is hard to implement these concepts using analog FM repeaters. Especially, if several repeaters are connected though a network. In this case, a single call on one repeater may block the entire network There are means to implement this concept on analog repeater networks using tone-signaling techniques (e.g., DTMF, five-tone etc.).. Certainly, it would be much better if only those repeaters get activated that are actually required for the communication between two parties. Then, the remaining repeaters are still available for the rest of the staff. This routing, however, should happen automatically. An operator may not know, which repeater to use to reach a particular person. DMR was designed to implements such complex communication networks without requiring from every participant to have detailed knowledge about the structure of the network. That is, the knowledge about where every repeater is installed and which participants are reachable on which repeater. DMR is more similar to a phone network that to classic FM repeater networks. Speaking of phone networks: Each participant and thus his/her radio is uniquely identified by a number. The DMR-ID. This is a number between 1 and 16777215. And like for any other phone network, each participant may call any other directly using this number. This call is called Private Call. And there are groups. Each of these so-called Talk Groups is also assigned a unique number. A talk group can be used to group all staff with a specific task (e.g., the security, fire brigade, etc.). It is then possible to reach all members of this group at once, by performing a Group Call to that talk group. The network, however, does not know which participant is member of which group. Consequently, the radio of the participant needs to know which group calls to accept and which to ignore. This point is important to remember: The DMR network does not know which participant is member of which group. The radio needs to be configured to react on specific group calls. Simplified air-port network There are 3 cleaning staff, two security and one headquarters. To cover the entire area, three repeaters are required. One in terminal 1, one in Terminal 2 and one on the apron. is a simplified air port network (in reality, it is much larger and way more complex). Consider the situation, where a cleaning stuff 1 & 3 want to communicate. At the same time, the headquarters want to talk to security 1. In an simple analog network, the call between cleaning 1 & 3 would block the entire network and therefore the call between the headquarters and security 1. Simultaneous calls Two Simultaneous private calls in the example network between cleaning 1 & 3 as well as between headquarters and security 1. Private calls in DMR networks only use those repeaters, that are actually required to establish the communication. This is shown in : Cleaning 1 starts a private call to cleaning 3. As the DMR network knows that cleaning 3 was last active on the apron repeater, this call gets routed only though repeaters terminal 1 and apron. The repeater in terminal 2, however, is not affected. Consequently, this repeater remains available for the call between the headquarters and security 1. The network only knows at which repeater each participant was last active. The network will therefore try to establish a connection though that repeater to the participant. During the call between cleaning 1 & 3, the repeaters in terminal 1 and apron are blocked. This means, that the headquarters may not be able to reach cleaning 2 and security 2 immediately. This sounds worse than it actually is. In contrast to classic phone networks, a direct call is considered interrupted, once the calling participant releases the PTT button. To this end, the headquarters may use the pauses between calls to reach the other participants. In the next , the headquarters want to reach all cleaning staff. Therefore, they start a group call to the talk group cleaning (C for cleaning and S for security). With this call, it can reach cleaning 1 & 2 immediately. However, cleaning 3 does not receive that call. This is due to the fact, that the DMR network does not know which participants are members of which groups. As the cleaning crew is usually not on the apron, the apron repeater has not subscribed the talk group cleaning. Therefore, it does not forward group calls to that talk group. Temporary subscription of talk groups. To remain reachable for group calls, cleaning 3 needs to temporarily subscribe the apron repeater to the cleaning talk group. This can be done by starting a group call to that talk group on the apron repeater. Then the repeater will temporarily subscribe to that talk group for a limited amount of time (usually between 10-30min). During that time, the repeater will forward group calls to that talk group and cleaning 3 remains reachable via that repeater. This temporary subscription will be renewed every time a participant starts a group call to that talk group on this repeater. With these examples, the most basic terms of DMR (DMR-ID, talk group, private and group calls as well as talk group subscriptions where introduced and explained on an example network. The following sections will concern the use DMR in ham radio.
================================================ FILE: doc/manual/intro/private.xml ================================================
Private calls Private calls allow to call other participants directly without interfering with other calls (except for using the repeater). In the introduction into DMR above, the private call over several repeaters has been described. I consider this aspect of DMR particularly interesting. With the exception of TG 8 & 9, private and group calls are transparent with respect to the repeaters used. It simply does not matter which repeaters are actually used to establish a connection. Therefore, I do not need ot know where the other participants are located. Direct calls between countries. Consider the typical vacation situation: An OM at his holiday location may want to participate in his local afternoon net. He can do that by simply starting a group call to the nets talk group over the repeater at his holiday location. Now the local holiday repeater has subscribed to the local talk group at home and the OM can participate as usual in the net. The other participants in the net may not even recognize that the OM is at his holiday location. In a similar fashion, private calls can be started and received at the holiday location. The DMR network knows the repeater a participant was last active on. By briefly pressing the PTT at the holiday repeater, the OM is registered and can now receive private calls at his holiday location. The friends at home may not even know where the OMs current location is nor which repeater to use to reach him. The network takes care of that. shows such a private call between countries. DM3MAT starts a private call to DL2XYZ using his local repeater DB0ABC. DL2XYZ, however, is at his holiday location in Italy. As he had registered himself at that repeater, the private call gets forwarded to the holiday repeater I0ABC in Italy, where DL2XYZ can receive it. DM3MAT does not need to know the location of the callee nor which repeaters are near to him. This automatic routing of calls (group and private calls) is a major advantage over the analog FM repeater network and EchoLink. For the latter, the ID of the destination repeater needs to be known.
================================================ FILE: doc/manual/intro/roaming.xml ================================================
Roaming Usually all repeater within a region will subscribe the same talk groups. This allows to operate in these talk groups in a repeater transparent way. Therefore, it does not matter which repeater is being used within the region, the same talk group remains reachable. In the region Berlin & Brandenburg, this it the TG2621. It therefore makes sense to enter all repeaters into one list that have the same talk groups subscribed. If the radio now would automatically select a reachable repeater, one could drive around in the region and would stay connected to these talk groups irrespective of the own position in the region. This feature exists in many radios and is called Roaming. Many of the slightly more expensive devices support this feature (e.g., AnyTone). The cheapest ones usually do not This is somewhat weird, as this feature is a pure firmware feature and does not need any additional hardware.. To use this feature, all channels with a certain talk group should be added to a list. The so-called Roaming Zone. This could actually be done automatically, but the programming software for these devices is usually not very user friendly. If the signal strength of the currently selected repeater falls below a certain threshold (usually -105dBm), the radio will start to search the roaming zone for a repeater which is stronger than this threshold. This only happens if the radio is in standby. That is, if neither something is received nor transmitted. If the radio finds a stronger repeater in the roaming zone, it automatically changes to that repeater. The new repeater does not necessarily needs to be the strongest in the zone. It only needs to be stronger than the threshold. If no stronger repeater is found, the radio remains on the currently selected one. This roaming can also be set to manual. That is, the roaming search will only start if the signal strength is lower than the threshold and the PTT is pressed or the search is started from the menu.
================================================ FILE: doc/manual/intro/simplex.xml ================================================
DMR Simplex Operation The most simple form of a DMR QSOQSO is a code for call or connection between two amateur radio stations. is the simplex QSO. That is a direct connection between two two DMR radios. Like for the DMR repeater operations, this could be a private, group or so-called All Call. Simplex private call. In a simple simplex private call from DM3MAT to DL1XYZ is shown as well its reply. Both operators transmit and receive on the same frequency (here the DMR calling channel at 433.450 MHz). Although other operators are in the area (DL2XYZ & DL3XYZ) which receive the signal, their radios remain silent. This is because this is a private call to a specific operator and only the radio of that operator will receive the call. All other radios will ignore the call. The channel, however, remains occupied during that call. At that point it is worth mentioning, that if DL1XYZ answers directly to the initial call by pressing the PTT, he will answer with a private call to DM3MAT. He does not need to search for number of DM3MAT in his address book. The direct answering to calls is only possible for several seconds after the end of the initial call. After that period (called Hang Time) a press on the PTT will start a call to the default contact (see ) associated with the simplex channel. Simplex group call It is not only possible to call single operators in simplex mode. Also groups can be called using group calls. A common talk group for the simplex mode has the number 99, (TG99, for talk group 99). These group calls are then received by all radios that are configured accordingly. Like for the repeater operation, also in simplex mode, the radio needs to know which groups the operator belongs to and therefore, which talk groups to receive on which channels. This is done using so-called Group Lists, which are discussed later. In such a simplex group call is shown. There DM3MAT calls the talk group TG99. As DL1XYZ as well as DL2XYZ configured their radios to receive that call on simplex channels, they do so. DL3XZY did not, so he does not receive the call. DL1XYZ & DL2XYZ can now respond to that call by pressing on the PTT within the hang time irrespective of their default contact for the channel. Simplex all call To be sure that a simplex call gets received by all operators in the area, a so-called All Call should be used. This is a special call type to the reserved number 16777215, that gets received by all radios irrespective of their configuration. For the , the all call by DM3MAT gets received by all operators including DL3XYZ. By directly answering within the hang time, all participants are able to respond to that call with an all call as well. In short: A DMR channel consists of a transmit and receive frequency (identical on simplex channels), a default contact that gets called whenever the PTT button is pressed and a list of group calls the radio will receive on that channel. DMR simplex frequencies
The list of eight common DMR simplex channels. The channel S0 is the calling channel.
NameFrequencyNameFrequency
S0 (call)433.4500 MHz S4433.6500 MHz
S1433.6125 MHz S5433.6625 MHz
S2433.6250 MHz S6433.6750 MHz
S3433.6375 MHz S7433.6875 MHz
lists the common simplex channel frequencies. The channel S0 is the calling channel. Especially in densely populated areas, you should switch to another channel for the actual QSO and use S0 only for the initial call.
================================================ FILE: doc/manual/intro/talkgroup.xml ================================================
Talk Group Operation As mentioned in , DMR aims at being repeater transparent. That is, it does not matter which repeater is used for the operation. This is also true for talk groups. In this section, I'll continue with the example of an OM at his holiday location, who wants to participate in his afternoon net. For example, the afternoon net is happening on the talk group 2621 (Berlin/Brandenbug, BB). This talk group is usually statically subscribed on all repeaters in the states Berlin and Brandenbug. That means that the local afternoon net can be performed in this talk group without any additional action in this area. (see top image). Talk group operation between countries For the OM at his holiday location, this it not true. An Italian repeater will certainly not subscribe the BB talk group statically. Therefore, the OM at the holiday location will not hear his afternoon net. He knows, however, when this net starts. So he can perform a group call to the that TG shortly before the net starts (see second image). This will subscribe the BB talk group temporarily at the holiday repeater (I0ABC). Now, the OM can hear and participate in the afternoon net. The subscription will be renewed whenever he starts a group call to that TG. Once the OM subscribed the TG to the holiday repeater, he can participate normally at his afternoon net. All other participants will not even notice that he is not in the area and is participating from an italien repeater. Some talk groups
Name Talk group number
Global 91
Europe 92
Germany 262
Mecklenburg-Vorpommern & Sachsen-Anhalt 2620
Berlin & Brandenburg 2621
Hamburg & Schleswig-Holstein 2622
Niedersachsen & Bremen 2623
Nordrhein-Westfalen 2624
Rheinland-Pfalz & Saarland 2625
Hessen 2626
Baden-Württemberg 2627
Bayern 2628
Sachsen & Thüringen 2629
Cluster In contrast to the regional talk group TG8, all other talk groups are reachable from everywhere in the DMR network. This means, that the OM at his holiday location can easily participate in his afternoon net from everywhere as described above. If the net, however, is happening in the regional TG8, the OM at his holiday location cannot participate. This talk group is only reachable from within the region. If the OM starts a call to TG8, he would only reach the region in italy and not the region at home. For this reason, some regional clusters of repeaters are linked to a so-called Cluster. These clusters provide normal talk groups for the repeater within a region. These clusters are then also reachable from the outside. A list of regional clusters and their associated talk group numbers can be obtained under bm262.de/cluster/.
================================================ FILE: doc/manual/intro/technicalbackground.xml ================================================
Technical background In the previous sections, I tried to explain the basic concepts of the DMR operation. That is, the repeater independent private and group calls. This section concerns the more technical details of the DMR mode. In particular the Time Slot and Color Code.
Time Slots As mentioned before, DMR is a digital mode. The speech signal is first digitized and compressed with a lossy compression codec. The latter is also called VOCODER. Modern codecs are very efficient and allow to transfer two independent speech signals within a single 12.5kHz wide channel. This is exploited in DMR using a technique called TDMA. TDMA means time-division media access and describes that two independent calls can happen simultaneously on one physical channel. To achieve this, each call is assigned a Time Slot (time slot 1 & 2) and both are transmitting and receiving only within their assigned time slot. These time slots are very short. For DMR they are only 30ms long. This short time, however, is sufficient to transfer audio for at least 60ms. Therefore, DMR allows for two independent and simultaneous calls on a single physical channel. When time slot 1 or 2 happens, is determined by the repeater. The repeater defines the beat. This also implies that time slots are irrelevant for the simplex operation. Thus you can ignore the time slot settings when programming simplex channels. (see ). What happens on each time slot, is a convention defied by the repeater. A general suggestion is that regional communication happens on the time slot 2 while trans-regional communication should happen on time slot 1.
Color Codes Color Codes are a technical tool to avoid conflicts between repeaters operating on the same frequency. This usually happens in commercial applications of DMR. One company usually gets only a small number of frequencies assigned. To cover the entire campus, much more repeaters are needed than frequencies are available. Consequently, overlapping repeater ranges on the same frequency will occur and two repeaters may receive the call of a participant. Then, the color code allows the repeater to detect whether a call was intended for it. Only if the color code of a call matches the color code of the repeater, the repeater will react on that call. This issue usually does not arise in amateur radio applications. We just have enough channels. Hence the color code is usually set to 1. In very densely populated areas, however, overlapping repeater ranges may still occur and different color codes might be used there. To use a repeater, you not only need to know the input and output frequencies but also the color code of the repeater.
================================================ FILE: doc/manual/intro/textmessages.xml ================================================
Data services As DMR is a digital mode that transports digitalized speech, it is possible to transfer other data too. Consequently, there are some other digital services provided with DMR. First, there are text messages similar to the one provided by mobile phones. It is also possible to forward the own GPS position to the APRS network.
Textmessages (SMS) With this service, you can transfer short text messages to other participants (like a private call) or to a talk group (like a group call). The latter is rather uncommon and should be avoided. In principle a text messages works like a private call. If the destination is reachable, the text message will be routed to it. There are also service numbers (free of charge). If messages are sent to them, certain information can be retrieved to forwarded to other networks (e.g. to the DAPNET). In Germany (and other countries) there are: 262993 -- GPS and weather Send help and you will receive a list of commands. Send wx and you will receive the weather at the location of the repeater you used. Send wx CITY and you will receive the weather at the specified city. Send gps and the last GPS position is returned that you have sent to the DMR network. With gps CALL you can retrieve the last position sent by the specified call. Send rssi and you will receive a signal report from the repeater. 262994 -- Repeater information & pager messages Send rpt to receive a list of static an dynamic subscribed talk groups at the repeater. Send CALL MESSAGE to send the given message to the given destination call in the DAPNET.
Position reporting to the APRS network As mentioned in the previous section, it is possible to report the own position via the DMR network to the APRS network. This position can then be tracked at for example aprs.fi. To do that, a radio with a built-in GPS receiver is required. But not even these devices are expensive anymore. Simple DMR hand-helds with built-in GPS receivers are available for about 120€. Beside the text message services, it is also possible to transmit the position to the DMR network using a special service number 262999. How the radio is configured to do that, depends heavily on the device being used. qdmr helps with the configuration by providing common means for these settings across all supported radios.
================================================ FILE: doc/manual/manual.code-workspace ================================================ { "folders": [ { "path": "." } ], "settings": {} } ================================================ FILE: doc/manual/manual.xml ================================================ qdmr - User Manual A universal codeplug programming tool for Linux and MacOS X. April 1st, 2026 2022-2026 Hannes Matuschek This document covers qdmr version 0.14.1 This document is released under the conditions of the Creative Commons Attribution-ShareAlike 3.0. ================================================ FILE: doc/manual/manual_fo.debian.xsl ================================================ 7pt always appendix nop article toc,title book toc,title chapter toc part nop preface nop qandadiv nop qandaset nop reference toc,title section nop set toc ================================================ FILE: doc/manual/meta/abstract.xml ================================================ About This Document This document contains a brief manual for the qdmr graphical user interface (GUI) application to program DMR radios. The aim of this application is to provide a simple to use, platform and vendor independent and well documented codeplug programming software (CPS) for popular DMR radios. This document is available in several formats: HTML, PDF and EPUB 3. ================================================ FILE: doc/manual/meta/authors.xml ================================================ Hannes Matuschek dm3mat@darc.de OV Y07 OV Y07, DARC ================================================ FILE: doc/manual/meta/glossary.xml ================================================ Glossary Admit Criterion The admit criterion specifies a criterion under which transmissions on a channel are allowed. These criteria can be defined for both, analog and DMR channels. Irrespective of the channel type, the admit criteria always and channel free can be selected. The former simply always allows to transmit. The former requires the channel to be free. For analog channels, usually there is also the criterion tone. This criterion would only allow transmissions if the matching CTCSS tone or DCS code has been received. For analog repeater operation, the criterion channel free should not be used as this would prevent any transmissions while the repeater is active. For digital channels, usually there is also the criterion color code. This criterion would only allow transmissions if the matching color code has been received. To this end, this criterion is similar to the tone criterion for analog channels. All Call An all-call is a special type of an DMR call to a special number (16777215). All radios are supposed to receive this call irrespective of their particular configuration. This call type is usually used in emergency situations or on simplex channels. Unfortunately, the local talk group (number 9) is frequently used on simplex channel although only those radios will receive calls to this talk group if it is in the group list assigned to the simplex channel. To be on the safe side, use the all-call for simplex calls. Automatic Packet Reporting System APRS The Automatic Packet Reporting System is a protocol that uses single broadcast frequencies to transmit small amounts of information though a network of repeaters. Usually but not limited to position reports. This allows other services (e.g., aprs.fi to display that information on a map. The usual APRS frequencies are 144.390 MHz (North America), 144.800 MHz (Europe) and 145.175 MHz (Australia). Codeplug The term codeplug is rather loosely defined. Usually it refers to the binary representation of the configuration of a radio that is written to the radio to configure it. It contains all contacts, channels, zones, settings, etc that form the configuration of the radio. Color Code A color code is some additional information attached to each DMR call. It is used to prevent interference between repeaters with overlapping ranges that operate at the same frequencies. In amateur radio, this may only happen in very densely populated areas. It is much more frequent in commercial applications, where a single company may got only a few frequencies assigned but needs much more repeaters to cover a campus reliably. The color code is a number between 0 and 16. A radio or repeater may only react to calls with matching color codes. Hence beside the actual input and output frequencies, the color code of a repeater must be known to be able to access it. Continuous Tone Coded Subaudio Squelch CTCSS Means to control the squelch of a radio or to open a repeater, that does not rely on the strength of a carrier. Instead a tone is transmitted along with the audio, which is usually filtered out (e.g., less than 300Hz) by the receiver. There are a set of common CTCSS tone frequencies, but may radios allow for specifying arbitrary sub-tone frequencies. DCS Decentralized Amateur Paging Network DAPNET DAPNET, short for Decentralized Amateur Paging Network does exactly what it says. It is a network of transmitters that transmit pager messages in the UHF band, usually at 439.9875 MHz. As the old pager operated at a frequency near by, they can be modified to operate at that frequency and can therefore be used in amateur radio. The DAPNET is particularly popular with emergency operators as it allows for an convenient and reliable multicast notification. DCDM Stands for dual capacity direct mode. This allows for two independent connections on a single simplex channel by TDMA. Lacking a single central repeater, one of the participants must act as the so-called leader. This radio then defines the clock and thus time slot 1 and 2. All remaining participants must act as so-called follower. They must synchronize to the clock of the leader. This obviously requires that all participants can reach the leader directly. Digital Coded Squelch DCS Similar to the CTCSS tones, the DCM allows to open the squelch or repeater by transmitting sub-tones along with the audio. These tones are usually filtered out by the receiver (e.g., below 300Hz). In contrast to CTCSS tones, DCS uses these tones to repeatedly transmit a digital code. EchoLink EchoLink is a network of analog FM repeaters that allows to link repeaters within this network temporarily. That is, two FM repeaters that are linked via EchoLink behave like a single repeater irrespective of their location. This network also allows to access the repeaters directly though the internet. This is particularly helpful if one cannot reach any repeater. Group Call A group call is simply a call to a talk group. That is, not to a single participant but rather to a group of participants. Every participant that has this talk group in their group list can receive this call. See also Group List and Talk Group. Group List A group list is a simple list of talk groups. A group list is then assigned to a channel to specify which group calls to receive on that channel. If a talk group is not listed in the group list of a channel, the radio will ignore calls to this talk group on that channel. The DMR network cannot know which talk groups you are interested in. You have to tell your radio using group lists. Hang Time The hang time specifies the time period, a DMR call remains active after it ended. For this time period, the call will replace the default transmit contact on that channel. This allows to directly answer a call received on a channel, even if that call is not the default transmit contact on the channel. Maidenhead Locator The Maidenhead locator, also known as QTH locator or IARU locator is a means for transferring a geo-location over radio. Using a so-called grid-square system. Lone Worker Lone worker refers to a feature of many DMR radios used in some commercial settings, where a participant in the network needs to report regularly to some other station. This feature is certainly not very useful for the HAM radio context. Private Call A DMR call to an individual participant. Usually, radios do not receive private calls not addressed to the radio. This therefore can be used to talk to a specific HAM over a repeater without disturbing other participants. The repeater however, will be blocked during that time. Revert Channel A revert channel is a designated channel the radio resorts to, to transfer some information or the radio switches to, when the PTT is pressed. This is usually used in the context of APRS (i.e., the channel to switch to send the position) or scan lists (i.e., the channel the radio will switch to, when the PTT is pressed during the scan). Roaming Channel Specifies which settings of the current channel needs to be overridden to stay in contact with a particular talk group. These settings are usually those, specific for a repeater. That is, receive and transmit frequency and color code. The time-slot might also be overridden. Roaming Zone A collection of roaming channels usually sharing the same transmit contact. When roaming is enabled (and supported by the radio) this list of channels is scanned for a reachable channel once the current repeater gets out-of-range. This way, one can stay in contact with a particular talk group when being mobile. Secondary Station Identifier SSID AX.25 addresses (and thus APRS) consists of a call sign and a so-called SSID (0-15). This number can be used as a replacement for the lack of ports in AX.25 or to provide some additional information about the station. Talkaround Talk Group TG The term Talk Group refers to a DMR ID, which represents a virtual contact. DMR repeater may subscribe to one or more talk groups. Every call to these talk groups are then transmitted by those repeaters, that are subscribed to these talk groups. Usually, it is possible to subscribe a repeater to any talk group temporarily by simply starting a transmission to this talk group on a repeater channel. For some time period after the initial transmission, the repeater will transmit all calls to this now subscribed talk group. The subscription gets renewed with every further call the talk group. Group Call Time-Division Multiple Access TDMA Time Slot Due to the lossy compression of audio data, it is possible to send more than twice the amount of audio data through a single 12.5kHz wide channel than needed. This allows to provide two independent audio streams within a single physical channel. There are basically two ways to separate these two audio streams. Either by frequency, effectively splitting the single 12.5kHz channel into two 6.25kHz wide ones. Alternatively, one can split the channel in time. That is, the band width remains 12.5kHz but each of the two simultaneous participants sends one after the other. One in time slot 1 and the other in time slot 2. This is called TDMA and DMR uses this technique to provide two independent audio and data streams within a single physical channel. For this TDMA to work, one participant needs to define the clock to specify when time slot 1 or 2 happen. This role is usually taken by the repeater but on direct simplex connections, one of the participants might provide that clock. This is then called DCDM (Dual-Capacity Direct Mode). This mode, however, makes little sense in amateur radio context. Transmit Contact A transmit or default contact can be specified for each DMR channel. Some radios actually require that each channel has an transmit contact. This contact can be any contact you want to call whenever the PTT is pressed on that channel. It is, however, possible to answer any received call directly by pressing the PTT button within the so-called hang time, although the call may not match the transmit contact. These hang times can be set within the radio settings, usually for private and group calls separately. It is also possible to call any contact by selecting it from the contact list or by entering the contacts DMR ID into the keypad. Transmit Timeout TOT Specifies the time of continuous transmission, after which the transmission is interrupted automatically. This prevents the accidental blocking of a repeater or talk group by transmitting continuously. Scan List A scan list is just a list of analog and digital channels that one wants to scan. A scan list can be associated with each channel. This scan list is then used whenever a scan is started on that channel. Zone As there are usually several channels defined per repeater (at least two), the number of defined channels grows rapidly. To organize this large number of channels, zones are used. These zones usually collect all relevant channels for a region. That is all (analog and digital) channels associated with the local repeaters. The relevant zone is then selected via the keypad on the radio and only those channels in that zone are then accessible. A defined channel that is not member of any zone is usually not accessible in the radio. Some radios allow to specify a different zones for each VFO. Others set a zone globally. In the latter case, the zone consists of two channel lists. One for each VFO. ================================================ FILE: doc/manual/meta/preface.xml ================================================ Foreword Before you start programming your DMR radio, get familiar with this digital mode. I have written a brief introduction into DMR too. You will find it in (also available in german). If you are already familiar with DMR, skip this chapter and head directly to , where I describe how qdmr is used. If you are a fan of command line tools, have a look at . Digital mobile radio (DMR) was not invented for the use in amateur radio. It was rather designed to be a radio standard for commercial applications in large companies (e.g., airports etc.). Therefore many features of this standard are of no use for ham radio or are even illegal (e.g., encryption). This complexity of the standard makes the programming of the radios cumbersome. Moreover, the resulting configuration (codeplug) is highly device-dependent. These codeplugs cannot be shared between different devices let alone between different vendors. For commercial applications, this is not a big problem, as a company will likely buy identical radios at once from one company. Thus codeplugs can be shared between all radios. For ham radio applications, this incompatibility is a real issue. Since assembling a decent codeplug for one region is hard enough, doing the same work all over again for different models of different vendors is not manageable. Finally, the typical code-plug programming software (CPS), particularly those for cheap Chinese DMR radios, is by no means user-friendly and seldom documented completely. Many options are named cryptic and it is not possible to identify which options are necessary for basic DMR operation. Moreover, the CPSs provided by the vendors usually only run under Windows. The aim of the qdmr project is to overcome these shortcomings of typical CPSs. It has a reduced feature set only supporting those options necessary of amateur radio usage. It tries to be user-friendly by finding repeaters nearby and importing their input and output frequencies. Moreover, it stores the final codeplug not in a device-specific binary format but in a human-readable text format that is device independent and can therefore be shared across multiple device and even across vendors. Finally, I try to keep the application well documented. This manual is part of this effort. It is a guide on how a codeplug is set up using qdmr. ================================================ FILE: doc/manual/reveng/codeplug.xml ================================================
Reverse engineering of the code plug format Once the communication protocol is known, it is time to focus on the code plug format. Depending on the code plug complexity, this can be a very cumbersome task. Moreover, depending on the device, it can be pretty hard to access the binary representation of the configuration (code plug) that is actually written onto the device. In general, one cannot expect, that the binary file, created by the manufacturer, does relate to the binary code plug written onto the device at all. To figure the relationship out, between the code plug file and the actual code plug written onto the device, the code plug must first be extracted from the Wireshark capture. As we already know the protocol, this can be done easily using Python and pyshark. When doing so, be careful with the addresses, data is written to or read from. The binary code plug might not be written sequentially and with gaps. A best practice is to extract the data read or written along with its addresses and sort it with respect to the address. Then dump everything as a hex-dump. Once the code plug is extracted from the Wireshark capture, compare it to the corresponding code plug file created by the CPS. If you are lucky (usually for very cheap DMR radios), the CPS code plug file is simply the binary code plug written to the device (e.g., for GD77, RD5R). Then it is very easy to reverse engineer the code plug format, as you can study changes to the CPS code plug file. If they are not identical, you will need to somehow extract the binary code plug written to the device from the manufacturer CPS. If the protocol is based on Serial-over-USB, you may be able to write an emulator for the device, knowing the communication protocol. This then allows to trick the CPS to talk to the emulator instead of the device and dump the code plug. If not, you are likely stuck with writing a lot of code plugs to the real device and extracting the code plug from the Wireshark captures. This can be considered the worst-case scenario.
Differential analysis Irrespective on how you gain access to the binary code plug written onto the device, the analysis of this code plug is always the same. This is called differential analysis. The idea is, to change a single option in the CPS and compare the resulting binary code plugs bit by bit. Ideally, only a single place in the binary code plug will change too and this change then reflects the encoding of the particular setting touched. This is then repeated for all possible settings in the CPS until the entire code plug structure is known. There will be some bits and bytes in the code plug that will never changed. These are usually reserved bits/bytes. There may also be some undocumented and hidden options in the code plug, that cannot be set via the (normal) CPS. Before you can start reverse engineering the code plug, you need to create a base code plug. That is, one small simple code plug, that touches every feature of the radio. It should not be overly complex with hundreds of channels, but should contain everything the radio provides. For example, it should contain the APRS settings, if the radio has GPS or a roaming zone if the radio supports roaming. You should also create two of every kind. That is, at least two channels, contacts, group lists, zones and scan lists. Otherwise, it will be hard to figure the index scheme out. Once you created the base-code plug, get its binary representation (e.g., from file, emulation or Wireshark capture). Change something simple. For example the name of the first contact. Then get the binary representation of this modified code plug and compare the two binary code plugs. As an example, consider the following difference between two hex-dumps of the binary code plugs for a BTech DMR-6X2UV Pro. Here, the name of the first contact was changed. Difference between two binary code plugs for a DMR-6X2UV Pro. Only the name of the first contact was changed. 02680000 : 01 56 65 72 79 20 6c 6f 6e 67 20 6e 61 6d 65 2e | .Very long name. > 02680010 : 2e 00 00 55 6e 6b 6e 6f 77 6e 00 00 00 00 00 00 | ...Unknown......]]> This difference already shows some very important information about the code plug encoding. First, the list of contacts appears to start at address 2680000h. This is very important for the coarse structure of the binary code plug. We also learn about the structure of the encoded contacts, that the name starts at offset 01h. So, the first byte is still unknown. The length of the name is limited to 16 bytes and is terminated/padded with 0-bytes. However, we still don't know how large the encoded contact element in the code plug actually is. To figure out the size of the contact element, one can use the fact, that all elements very likely have identical sizes. Hence the offset from one name to the name of the next contact will be the size of the contact element. We already know, that the contact list starts at the address 2680000h. Hence, we can look in the code plug directly at that address. Codeplug hex-dump at the contact-list address.
The offset between the two names Contact 1 and Contact 2 is exactly 100 bytes (64h). With this, we know the exact size of the contact element. However, wo only know what 16 of these 100 bytes mean. So there is a lot to figure out. The differential analysis, however, makes it quiet easy. First we may change the call type of the first contact. From group call to private call and study the differences in the code plug. 02680000 : 00 43 6f 6e 74 61 63 74 20 31 00 00 00 00 00 00 | .Contact 1......]]> We see, that only one byte has changed. The one at offset 0. It changed from 01h to 00h. We may conclude that this byte encodes the contact type, where 00h means private call and 01h means group call. Analogously, we find out that 03h means all-call. The next step is to find the encoding and offset of the contact DMR ID. To find it, we reload the base code plug and change the DMR ID of the first contact from 1 to something more complex. E.g., to 1234567 and compare the binary . 02680020 : 00 00 00 01 23 45 67 00 00 00 00 00 00 00 00 00 | ....#Eg.........]]> Here we find some changes at the offset 23h. The content changed from 00000001h to 01234567h. This is somewhat weird, as the binary representation of 1234567 in hex is actually 12d687h. However, for DMR code plugs, it is quiet common to store integers in so-called BCD. Here each digit is stored in 4bits. This has the consequence, that the decimal digits are readable in hex. But this storage is inefficient, as the DMR ID is actually a 24bit number, that can be stored in 3 bytes. Its BCD encoding requires 4 bytes. The last setting for contacts remaining in the CPS is the call-alert. This setting is only valid for private calls. Changing the contact 1 from group to private call, changing the call alert from None to Ring and comparing the code plugs, one gets: 02680000 : 00 43 6f 6e 74 61 63 74 20 31 00 00 00 00 00 00 | .Contact 1...... < 02680020 : 00 00 00 00 00 00 01 00 00 00 00 00 00 00 00 00 | ................ > 02680020 : 00 00 00 00 00 00 01 01 00 00 00 00 00 00 00 00 | ................]]> Of cause, we find two changes. The change of the first byte, indicating the change from group to private call, as well as one byte changing at offset 27h from 00h to 01h. This is the only unexplained change and thus must reflect the alert-type setting. Consequently, the alert type None is represented by 00h and Ring by 01h. Analogously, we find that alert-type Online is encoded as 02h. Obviously, we still don't know the majority of the 100 bytes of the contact element. Lets have a look at the structure of the contact element. There are some additional Pad bytes (00h) that terminate ASCII strings. This is nothing, we found by the differential analysis, but it is some common scheme, found in many binary code plugs. Obviously, the firmware developer did not knew the strnlen C function. The remaining bytes of the contact element appears to be empty, unused space. This is also quiet frequent in code plugs. They contain a lot of unused bytes that might be reserved for future uses. This allows the firmware developer to extend the code plug, while maintaining backward compatibility with older versions of the CPS. Having reverse engineered the contacts, does not mean that you are done. There are a lot more elements to decipher. Did I mentioned that it is a lot of work? But the general scheme of things remain the same. Change a single setting in the CPS and compare the resulting binary code plugs. Document your findings. Step by step, you will figure out the entire code-plug structure.
================================================ FILE: doc/manual/reveng/protocol.xml ================================================
Reverse engineering communication protocols There is no one ultimate way to successfully reverse engineer a communication protocol. But some methods are very common. First, one needs to get some captures of the communication between the manufacturer codeplug programming software (CPS) and the radio of interest. The majority of these radios will communicate via USB. Hence, some means are needed to capture USB traffic. There is an application, called Wireshark (). This application is usually used to capture and analyze network traffic. It can, however, also capture traffic from USB. How this is done, depends a bit on the operating system you are using. If you want to capture and analyze the communication under Windows, read the instructions at . This makes sense, as you need a windows installation, to run the manufacturer CPS. I personally prefer to work under Linux, to this end, I've installed Windows in a virtual machine, but capture the USB traffic under the host Linux system. To do that, the usbmon kernel module must be loaded. If loaded, Wireshark will show USB as a capture source. Once, capturing USB traffic works, connect the radio to the host. You may need to allow the guest system to have access to the USB device. Then fire up the manufacturer CPS, start capturing in Wireshark and start a codeplug read. Stop capturing once the read is complete. Save the captured data and restart the capturing. Then write the codeplug back to the device, stop the capture and save it in another file. These files now contain all packets send and received by the PC over USB. In a next step, the packets must be filtered and inspected. To do that, I personally prefer to write short Python scripts. The pyshark package provides means to read packages from the capture file and access their content. This allows to filter those packages to and from the device, that contain the actual payload to and from the device. Please note, that may radios may misuse some already existing protocols over USB. Some Radioddity and Baofeng devices use the HID specification to transfer data to and from the device. Others may use the DFU specification (TyT, Retevis), mass storage (CSi) or simply serial-over-USB (AnyTone). Irrespective of the underlying specification used to send data to the device, the payload of these packages must be extracted. To do that, use Wireshark to inspect these packages by hand. Search for a packet, that was definitely send to or by the device and note the USB (URB) device address. This is the best way to filter all packets to and from the device. Some simple example to filter packages by device address and destination/source. This example shows how to filter only those packets to and from a specific device and also dispatch depending on the destination and source of the packet. That is, if the packet was send by the host or the device. In a next step, the captured payload must be inspected. If the packet contains any additional payload data, the usb.data_length field will be set. Then, there is a field called usb.capdata_raw. Unfortunately, that field name contains a dot, so you need to access it differently. That is The extracted data is hex string. Now comes the difficult part. Staring at these packets and making sense of them. Usually, the communication is performed in a strict command-response structure. That is, the host sends a request packet and the device responses with a single packet. This eases the analysis a lot, as the communication is always initiated by the host and the association between the request and the response packets are trivial.
Identifying the communication structure A typical communication with the device is performed in three steps: First, the radio is identified and brought into a programming mode. Several packets might be needed to perform this task. In that state, the radio usually displays some message on the screen or blinks an LED. The second step consists of the actual codeplug transfer. There, a large amount of packets are send. This step is usually easy to find within the captured packets, as a large number of equally sized packets are exchanged. Sometimes, a relatively large amount of data is read or written at once. This can be spotted easily, as basic control commands of the first step are usually pretty short. Frequently, these packets contain an address field or sequence number used to specify where the codeplug data chunk is written to or read from. These fields are of upmost importance and need to be identified. They are, however, easy to spot, as this field is likely constantly increasing, with a fixed increment from packet to packet. The final step usually is to reboot the radio or leaving the programming mode. This is commonly performed by a single packet. Sometimes, the device reboots immediately and no response to the command is received by the host. Once the protocol structure is identified, that is, the purpose of the single packets is known, the actual work of identifying the packet structure can start.
Identifying the packet format The actual packet format will be much harder to reverse engineer. I cannot give any general suggestions on how to figure out the meaning of each byte in a packet. However, I can give you some examples. This might help in reverse engineering new protocols as the structure of the packets will be different, but the concepts remain the same. For example, read and write commands must somehow specify an address to read from and write to as well as the amount of data to read or write. This might be helpful Let us inspect some packets send and received from an AnyTone device. Capture of a codeplug read from an AnyTone AT-D578UV 50 52 4f 47 52 41 4d | PROGRAM < 51 58 06 | QX. > 02 | . < 49 44 35 37 38 55 56 00 12 56 31 31 30 00 00 06 | ID578UV..V110... > 52 02 64 00 00 10 | R.d... < 57 02 64 00 00 10 fe ff ff ff ff ff ff ff ff ff | W.d............. | ff ff ff ff ff ff 65 06 | ......e. > 52 02 64 00 10 10 | R.d... < 57 02 64 00 10 10 ff ff ff ff ff ff ff ff ff ff | W.d............. | ff ff ff ff ff ff 76 06 | ......v. > 52 02 64 00 20 10 | R.d. . < 57 02 64 00 20 10 ff ff ff ff ff ff ff ff ff ff | W.d. ........... | ff ff ff ff ff ff 86 06 | ........ ... > 52 01 64 08 80 10 | R.d... < 57 01 64 08 80 10 00 ff ff ff ff ff ff ff ff ff | W.d............. | ff ff ff ff ff ff ee 06 | ........ > 52 02 48 02 00 10 | R.H... < 57 02 48 02 00 10 01 08 00 00 ff ff ff ff ff ff | W.H............. | ff ff ff ff ff ff 59 06 | ......Y. ... ]]> The shows a capture of the data exchanged between the host and an AnyTone AT-D578UV during a codeplug read. Everything starting with > is sent from the host to the device, while everything starting with < is sent by the device to the host. The first packet send, is the ASCII string PROGRAM. This causes the device to enter the programming mode. A message will be shown on the screen of the device. This is then acknowledged by the device with a short message QX followed by a single 06h byte. The last byte appears to be weird, but it will always be present on any response from the device. This might be seen as an end-of-packet byte. The next command send by the host is pretty simple. It consists of a single 02h byte. The device responds with a 15 bytes long ID string, identifying the device name and hardware version followed by the 06h end-of-packet byte. After this, the actual codeplug read appears to start. The host sends a series of equally size commands, each starting with an R followed by 5 bytes of payload. As mentioned above, this payload must contain some sort of memory address and length field, to specify how much to read and from where. To figure that out, one may study subsequent read requests. The payloads of the first tree read read requests are The only difference of these requests is the fourth byte. Hence, one may assume, that this byte is part of the address field. However, we do not know which bytes are part of the address as well. The responses to these requests also contain these bytes as well as 18 more bytes of payload. It is reasonable that the transfer size is a power of two. So the request likely reads 16 bytes from the device. 16 in hex is 10h, hence one may assume that the last byte specifies the amount of data to read. Hence we identified the size field as the fifth byte of the read request payload data. As the address increases by 10h from request to request, one may infer, that the read request specifies the address to read from, not the sequence or block number of a read from. As the entire Codeplug will not fit into 256 bytes, the address field must be larger. Even 64kb will be too small, to hold the entire codeplug, the address is likely a 32bit integer. This does not need to be true. Some radios will implement special commands to select the memory bank beforehand. Then each 64kB bank can be accessed with a 16bit address. To verify our assumption, we will study some read requests, that appear much later and check if the first bytes change as well. And indeed, much later, we observe read request payloads like Obviously, these bytes change too. Consequently, we may assume that the address is stored as a 32bit unsigned integer in big-endian byte-order. Finally, we may summarize the read request format as Where N is the number of bytes to read. Now, it is time to study the structure of the read response. As we already reverse engineered the read request, the read response is then easy to understand. Each of these responses start with a W char, followed by the same address and length as send by the request. That is 52 02 64 00 00 10 < 57 02 64 00 00 10 fe ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff 65 06]]> Obviously the 16 byte data read, follows the length byte. The last byte is the common 06h end-of-packet byte, we have already seen. So a single unknown 65h byte is still unknown. This byte might be some sort of checksum over the payload. Checksums are notoriously difficult to figure out. Frequently, common techniques like CRC16 are used. Here, a single checksum byte is used, hence one of the common checksums is unlikely. Now starts some guesswork. We may compare some very similar read responses to get an idea, how this checksum may work. For example The only difference between these two responses is one byte in the address, the remaining payload is identical. The address also only differs by 10h like the checksum. So one may assume, that the checksum is simply the sum over the payload bytes. Summing all bytes of the latter up to the checksum gets 10ddh. Of cause, this is not the checksum directly. The checksum is a single byte. But the least significant byte of the sum (ddh), however, does not match. Maybe, the sum is not taken over the entire payload, but over the part that actually matters. That is, the address, length and data fields. This time, the sum is 1086h and this time, the least significant byte (86h) matches the checksum. Now, we can summarize the read response format as where the CRC is the least significant byte of the sum over the address, length and data payload.
================================================ FILE: doc/manual/reveng/reverseengineering.xml ================================================ Reverse engineering The majority of the development time needed for qdmr, consists of reverse engineering the code plug and communication protocols of the radios. Fortunately, many radios share the same communication protocol, in particular those from the same manufacturer. Sometimes these protocols are even used by other manufacturers. Even if the protocol is already implemented in qdmr, you may need to reverse engineer the code plug format. This step has to be performed always, even if an already implemented code plug is reused, you will need to verify that format. Any mistake here, may brick your device. Before you attempt to reverse engineer anything, consider to invest some significant time in research. Reverse engineering is an cumbersome and frequently frustrating task, there is no need to waste your valuable time on something, that someone else has already done. Also, consider documenting and publishing your results, so others can find it, even if it is incomplete. Someone else might pickup your work and complete it. In the following sections, I attempt to describe, how I approach reverse engineering the communication protocols and code plugs. To this end, I hope it might help you in your reverse engineering work. And if you like, you may contribute your work to qdmr. ================================================ FILE: doc/qdmr.in.xml ================================================ qdmr 1 qdmr ${PROJECT_VERSION} User Commands Hannes Matuschek dm3mat@darc.de Main author qdmr Graphical tool for programming DMR radios. qdmr Description qdmr is a graphical tool to program DMR radios. That is, reading, editing, generating and uploading codeplugs to these radios. The configuration of these radios is usually stored in a highly vendor and device specific binary codeplug. qdmr stores this configuration in a human-readable and device independent format, thus allowing for sharing codeplugs across devices. Options If the optional codeplug file is passed to qdmr, it gets loaded on startup. Otherwise, the application starts with an empty codeplug. This option allows to set a Qt style for the application. This can be used to alter the style of the widgets. So call qdmr -style=windows if you fancy. However, this option is of real use if you want to use a dark theme. By installing a dark Qt theme like kvantum-dark and starting qdmr by passing this theme to the style option. That is qdmr -style=kvantum-dark You may set the Qt style for all Qt applications using the QT_STYLE_OVERRIDE environment variable. This option allows for specifying a style sheet to alter the appearance of some or all widgets of qdmr. This option allows for specifying the log level for messages printed to stderr. This does not affect the log level of messages written to the log file. Must be one of debug, info, warning, error or fatal. Default is info. Logging qdmr writes a lot of debug and error messages to the log file. This can be used to inspect some issues during reading/writing the a codeplug to the device. The log file containing these messages is usually found at ~/.local/share/DM3MAT/qdmr/qdmr.log. Bugs This program is still under development and may contain bugs that may cause harm to the radios and may even destroy them. Hence you may use this software on your own risk. If you want to have guaranties, consider using the CPS (code-plug programming software) supplied with your radio. Writing a single application supporting several radios of different manufacturers is a hard task. To this end, there are plenty of bugs to be expected. If you stumble across one of them, consider opening an issue at . ================================================ FILE: doc/reveng/README.md ================================================ # How to reverse engineer code-plugs? This question is certainly not easy to answer. It heavily depends on the particular device. In general, reverse engineering the code-plug requires two steps. First, the communication protocol between the host and the device must be known. This step can be skipped if the protocol is already known (e.g., for Radioddity, Baofeng, Tytera, Retevis or AnyTone devices) or when the protocol is actually an industry standard (e.g., DFU). Once you know how the host communicates with the device, the actual code-plug can be tackled: The general approach is a so-called differential analysis. That is, using the original CPS, you start with a compact code-plug that covers all features for the radio. Then you change only ONE thing using the original CPS and compare the two results. Sometimes, the manufacturer is nice to you and the binary code-plug saved by the original CPS is basically a one-to-one image of the memory content written to the device. This is true for the Radioddity GD-77 code-plugs and to some lesser extend for the RD-5R, TyT MD-UV390 code-plugs. Sometimes, however, the manufacturer CPS saves something that has basically nothing to do with what is written to the device. For example the AnyTone D-878UV. In these cases, the content being written to the device must be captured. This can be done using wireshark on a linux host. That is, fire up a virtual machine running the original CPS. Start wireshark on the host system and monitor the USB interface using *usbmon*. Then, write a small script that extracts the actual data written to the device from the capture files. These can then be compared to perform the differential analysis. An example script including captured traffic and extracted data can be found in the anytone directory. ================================================ FILE: doc/reveng/anytone/README.md ================================================ # Reverse engineering of AnyTone codeplugs The transfer protocol of the AnyTone devices is based on the USB-Serial protocol (ACM-CDC) and is well known. To this end, it is possible to *emulate* the AnyTone devices using a simple python script. You will find a matching script in every subdirectory. ## Emulation under windows These scripts expect a serial loopback device to be present. Under windows, such a device can be created using a *virtual null-modem cable*. For example [com0com](http://com0com.sourceforge.net/). This will create a pair of COM ports (e.g., COM5 and COM6). One will be used by the emulation and the other will be used by the manufacturer CPS. First, start the emulation ``` python3 at_dxxx_emulator.py COM5 ``` Here, the COM5 port will be used by the emulation. Then, start the CPS and select `COM6` as the port to be used. ## Emulation under Linux It might be possible to emulate the device under Linux and using Wine to run the CPS. First, a pair of *serial ports* (pts) must be created. This can be done using `socat` by calling ``` socat -d -d pty,raw,echo=0,b4000000 pty,raw,echo=0,b4000000 ``` This call creates a pair of pseudo-terminals, for example `/dev/pts/3` and `/dev/pts/4`. The emulation may then use one of them by calling ``` python3 at_dxxx_emulator.py /dev/pts/3 ``` Now, the second must be associated with a COM port in the Wine emulation. This is done using the registry editor. Run `wine regedit` and change ``` HKEY_LOCAL_MACHINE/Software/Wine/Port/COM1 /dev/pts/4 ``` You may need to create that key first. This will bind the Wine emulated COM port to the second pseudo-terminal. Now, start the manufacturer CPS under Wine and select COM1. ## Differential Codeplug Analysis Create a small codeplug containing at least two channels, two contacts, a group list and a zone. Save that codeplug and write it to the device. The emulator will save that codeplug as a hex dump in a file called `codeplug_0000.hex`. Any further codeplug write will be saved as a separate hex dump with an incremented number. The differential analysis of the codeplug allows to figure out how and where a specific setting is encoded within the binary codeplug. To do that, make a single change in the codeplug. E.g. changing the first radio ID. Then write that changed codeplug to the emulation. The hexdump of the codeplug is then save as `codeplug_0001.hex`. You can then use a diff tool to inspect the changes. E.g. ``` $ diff codeplug_0000.hex codeplug_0001.hex 1530c1530 < 02580000 : 01 23 45 67 00 52 61 64 69 6f 20 31 00 00 00 00 | .#Eg.Radio 1.... --- > 02580000 : 00 12 34 56 00 52 61 64 69 6f 20 31 00 00 00 00 | ..4V.Radio 1.... ``` Here the first radio ID 1234567 was changed to 123456. The only differences between the codeplugs are at the address 2580000. Apparently, the first radio ID is stored there together with the radio name. The first 4 bytes at this address have changed from `01 23 45 67` to `00 12 34 56`. As the numbers are readable in hex, they are stored in BCD and the most-significant number is stored first. So the radio ID is stored in an 8-digit BCD in big-endian. This step is then repeated for all settings within the CPS to figure out the entire codeplug. ================================================ FILE: doc/reveng/anytone/d578uv/at_d578uv_emulator.py ================================================ #!/usr/bin/env python3 # # Emulate anytone d878uv radio to customer programming software. # Send intercepted data stream over network to server script for further investigation. # # This script connects to a virtual com port COM26 which is connected via a virtual # null modem cable to the virtual com port COM18 which is used by the programming software. # This virtual ports and cable can be provided by the COM0COM tool. # # Linux users can use # socat -d -d pty,raw,echo=0,b4000000 pty,raw,echo=0,b4000000 # for emulating a virtual null modem cable. import serial import time import sys import struct # config filebase = 'codeplug' filecount = 0 comport = 'COM6' # connected to COM18 with com0com. use COM18 in CPS def hexDump(s): h = " ".join(map("{:02x}".format, s)) t = "" for i in range(len(s)): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." return( h + " | " + t) # parameters? if len(sys.argv) == 2: comport = sys.argv[1] elif len(sys.argv) >= 3: print("Usage: " + sys.argv[0] + ' [comport]') exit() # open serial port serialPort = None try: print("Trying comport " + comport) serialPort = serial.Serial(port = comport, baudrate=4000000, bytesize=8, timeout=1, stopbits=serial.STOPBITS_ONE) # 115200 921600 4000000 except (err): print('ERR: Could not open port ' + comport) print("Usage: " + sys.argv[0] + ' servername [comport]: ' + err) exit() out = None nextaddr = None # wait for data try: while 1: command = '' command = serialPort.read() while serialPort.in_waiting > 0: command += serialPort.read() # respond to command on com port if ( len(command) == 0 ): pass elif ( command == b'PROGRAM'): print("Program session requested.") resp = b'QX\x06' serialPort.write(resp) filename = "{}_{:04}.hex".format(filebase, filecount) out = open(filename, "w") nextaddr = None elif ( command == b'\x02' ): print("Device info requested.") resp = b'ID578UV\x00\x00V110\x00\x00\x06' serialPort.write(resp) elif ( command == b'R\x02\xfa\x00\x20\x10' ): print("Read special memory request.") resp = b'W\x02\xfa\x00\x20\x10\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x24\x06' serialPort.write(resp) elif ( command[0:4] == b'R\x02\xfa\x00' and command[5] == 16 ): # 0x02fa00.. print("Read local information.") resp = b'W\x02\xfa\x00' + bytes([command[4]]) + b'\x10' if ( command[4] == 0x00 ): resp += b'\x00\x00\x00\x03\x01\x01\x01\x00\x00\x01\x01\x20\x20\x20\x20\xff' elif ( command[4] == 0x10 ): # Radio Type resp += b'\x44\x38\x37\x38\x55\x56\x00\x01\x00\xff\xff\xff\xff\xff\xff\xff' elif ( command[4] == 0x30 ): # Serial Number resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x40 ): # Production Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x50 ): # Manucfacture Code resp += b'\x31\x32\x33\x34\x35\x36\x37\x38\xff\xff\xff\xff\xff\xff\xff\xff' elif ( command[4] == 0x60 ): # Maintained Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x70 ): # Dealer Code resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x80 ): # Stock Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x90 ): # Sell Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xa0 ): # Seller resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xb0 ): # Maintained Description resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xc0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xd0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xe0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xf0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' else: resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' resp = resp + bytes( [sum(resp[1:]) & 0xff] ) + b'\x06' #print(resp.hex()) serialPort.write(resp) elif ( command[0] == ord('W') ) : resp = b'\x06' # just ack serialPort.write(resp) res = struct.unpack(">cIB16sBB", command) addr = res[1] if nextaddr != addr: out.write((8+3+16*3+16+2)*"-" + "\n") out.write("{:08X} : {}\n".format(addr, hexDump(res[3]))) nextaddr = addr+16 elif ( command == b'END' ): print("End session.") resp = b'\x06' # just ack serialPort.write(resp) out.close() filecount += 1 elif ( command == b'UPDATE' ): # for firmware update the device has to be switched on while pressing PF3 (blue button on top) and PTT keys print("Start Firmware Update. Only useful if device is in update receiving mode. (Switch on while pressing PF3 (blue button on top) and PTT keys)") resp = b'\x06' # just ack serialPort.write(resp) elif ( command == b'\x18' ): print("Firmware Update Send Complete. Switch device on while pressing PF2 (top left side) and PTT keys to start installer.") resp = b'\x06' # just ack serialPort.write(resp) elif ( command[0] == 0x01 ): print("Firmware data.") resp = b'\x06' # just ack serialPort.write(resp) else: #print("> " + str(command)) pass finally: print('QRT') serialPort.close() ================================================ FILE: doc/reveng/anytone/d868uve/at_d868uv_emulator.py ================================================ #!/usr/bin/env python3 # # Emulate anytone d878uv radio to customer programming software. # Send intercepted data stream over network to server script for further investigation. # # This script connects to a virtual com port COM26 which is connected via a virtual # null modem cable to the virtual com port COM18 which is used by the programming software. # This virtual ports and cable can be provided by the COM0COM tool. # # Linux users can use # socat -d -d pty,raw,echo=0,b4000000 pty,raw,echo=0,b4000000 # for emulating a virtual null modem cable. import serial import time import sys import struct # config filebase = 'codeplug' filecount = 0 comport = 'COM6' # connected to COM18 with com0com. use COM18 in CPS def hexDump(s): h = " ".join(map("{:02x}".format, s)) t = "" for i in range(len(s)): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." return( h + " | " + t) # parameters? if len(sys.argv) == 2: comport = sys.argv[1] elif len(sys.argv) >= 3: print("Usage: " + sys.argv[0] + ' [comport]') exit() # open serial port serialPort = None try: print("Trying comport " + comport) serialPort = serial.Serial(port = comport, baudrate=4000000, bytesize=8, timeout=1, stopbits=serial.STOPBITS_ONE) # 115200 921600 4000000 except (err): print('ERR: Could not open port ' + comport) print("Usage: " + sys.argv[0] + ' servername [comport]: ' + err) exit() out = None nextaddr = None # wait for data try: while 1: command = '' command = serialPort.read() while serialPort.in_waiting > 0: command += serialPort.read() # respond to command on com port if ( len(command) == 0 ): pass elif ( command == b'PROGRAM'): print("Program session requested.") resp = b'QX\x06' serialPort.write(resp) filename = "{}_{:04}.hex".format(filebase, filecount) out = open(filename, "w") nextaddr = None elif ( command == b'\x02' ): print("Device info requested.") resp = b'ID868UVE\x00V102\x00\x00\x06' serialPort.write(resp) elif ( command == b'R\x02\xfa\x00\x20\x10' ): print("Read special memory request.") resp = b'W\x02\xfa\x00\x20\x10\xff\xff\xff\xff\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x26\x06' serialPort.write(resp) elif ( command[0:4] == b'R\x02\xfa\x00' and command[5] == 16 ): # 0x02fa00.. print("Read local information.") resp = b'W\x02\xfa\x00' + bytes([command[4]]) + b'\x10' if ( command[4] == 0x00 ): resp += b'\x00\x00\x00\x00\x01\x01\x01\x00\x00\x01\x01\x20\x20\x20\x20\xff' elif ( command[4] == 0x10 ): # Radio Type resp += b'\x44\x38\x37\x38\x55\x56\x00\x01\x00\xff\xff\xff\xff\xff\xff\xff' elif ( command[4] == 0x30 ): # Serial Number resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x40 ): # Production Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x50 ): # Manucfacture Code resp += b'\x31\x32\x33\x34\x35\x36\x37\x38\xff\xff\xff\xff\xff\xff\xff\xff' elif ( command[4] == 0x60 ): # Maintained Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x70 ): # Dealer Code resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x80 ): # Stock Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x90 ): # Sell Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xa0 ): # Seller resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xb0 ): # Maintained Description resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xc0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xd0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xe0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xf0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' else: resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' resp = resp + bytes( [sum(resp[1:]) & 0xff] ) + b'\x06' #print(resp.hex()) serialPort.write(resp) elif ( command[0] == ord('W') ) : resp = b'\x06' # just ack serialPort.write(resp) res = struct.unpack(">cIB16sBB", command) addr = res[1] if nextaddr != addr: out.write((8+3+16*3+16+2)*"-" + "\n") out.write("{:08X} : {}\n".format(addr, hexDump(res[3]))) nextaddr = addr+16 elif ( command == b'END' ): print("End session.") resp = b'\x06' # just ack serialPort.write(resp) out.close() filecount += 1 elif ( command == b'UPDATE' ): # for firmware update the device has to be switched on while pressing PF3 (blue button on top) and PTT keys print("Start Firmware Update. Only useful if device is in update receiving mode. (Switch on while pressing PF3 (blue button on top) and PTT keys)") resp = b'\x06' # just ack serialPort.write(resp) elif ( command == b'\x18' ): print("Firmware Update Send Complete. Switch device on while pressing PF2 (top left side) and PTT keys to start installer.") resp = b'\x06' # just ack serialPort.write(resp) elif ( command[0] == 0x01 ): print("Firmware data.") resp = b'\x06' # just ack serialPort.write(resp) else: #print("> " + str(command)) pass finally: print('QRT') serialPort.close() ================================================ FILE: doc/reveng/anytone/d878uv/README.md ================================================ # How to reverse engineer the AnyTone code-plugs Unfortunately, AnyTone CPSs store the code-plug in a completely different format on the disc compared to the content being written to the device. This requires to observe the communication between the host running the original CPS and the device. Moreover, a complete code-plug must be programmed to the device for every option in the CPS, to be able to reverse engineer the format. So to reverse engineer the code-plug format on the device, you first need to get the original CPS running in a virtual machine on a Linux host (I use VirtualBox for that, but any other will do too). Then the *usbmon* kernel module must be loaded to be able to grab all traffic between the host and the radio. So call ``` $ sudo modprobe usbmon ``` Then fire up wireshark ``` $ sudo wireshark ``` and start monitoring on the *usbmon* device. * Then start to upload a base code-plug to the device. Wireshark will capture everything that gets sent to the device. * Once the code-plug is written to the device, stop capturing in wireshark and save the data under a recognizable filename, e.g. *base.pcapng*. * Change a single thing within the CPS, e.g., enable GPS. Remember, we are doing a differential analysis, so change only one setting at a time. * Restart capturing in wireshark and upload the modified code-plug to the device. * Once the code-plug is written, stop capturing and save the data in another file, e.g. *gps_enabled.pcapng*. * Extract the data written to the device using the *extract.py* script with `python3 extract.py PCAPNG_FILE > HEX_FILE` for both captured code-plugs. * Using *diff* you can now compare the two hex files. As only one feature of the code-plug was changed, the difference between the two hex files should be tiny. In fact if only a single setting is changed, frequently only a single bit/byte will change in the code-plug. Repeat the steps above over and over again to find the memory location of each option, setting and feature of the original CPS. Over time, you will be able to reverse engineer the entire code-plug. But keep in mind, that you will not be able to reverse engineer every single bit of the code-plug. At the end, there will be some reserved, fixed or hidden options left. ## An example In this directory, you will find two examples. The captured and extracted base code-plug (*capture_base.pcapng* and *d878uv_base.hex*) as well as a captured and extracted code-plug, where only a single setting was changed (*capture_set_gps_on.pcapng* and *d878uv_set_gps_on.hex*). In this example, the GPS was enabled in the CPS. The *diff* of the two hex files reveals ``` 3029c3029 < 02500020 : 01 00 00 00 00 00 04 01 00 01 01 00 00 01 02 01 | ................ --- > 02500020 : 01 00 00 00 00 00 04 01 01 01 01 00 00 01 02 01 | ................ ``` This tells me, that at the address 0x02500020 + 8, a single byte changed from 00 to 01. The entire remaining code-plug (about 100kB) stayed the same. Comparing the address 0x02500028 with the [coarse memory layout](https://dm3mat.darc.de/qdmr/libdmrconf/class_d878_u_v_codeplug.html) of the code-plug tells me, that byte 40 (hex 0x28) of the general settings section of the code-plug enables or disables the GPS. ================================================ FILE: doc/reveng/anytone/d878uv/cpsfileformat.md ================================================ # AnyTone CPS codeplug file format The fileformat used by the CPS to store the codeplug in binary format is unfortunately not directly related to the binary representation of the codeplug written to the device (in contrast to the fileformat for the GD77, RD5R and MD-UV390). This document tries to collect my knowledge about this file format. ## General layout The geneal layout of the file consists of a header followed by the sequence of elements (usually lists) that form the codeplug. Each list starts with the number of elements in this list. At the end, a footer is added to the file. The order of the elements in the file is important. Due to the variable size of each element, the file-offsets differ from codeplug to codeplug. The order of elements in the file is * Header (fixed size) * Channel list * Radio ID list * Zone list * List of scan lists * List of analog contacts The format of each element is documented below. ## Header ``` 0 8 16 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | CPS version | Content size | Model name | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 | 0 0 0 | HW version | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 30 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 40 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 50 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 60 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 70 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 80 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 90 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ a0 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ b0 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ c0 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ d0 | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ e0 | | +---+---+ ``` | Field | Description | |---|---| | CPS Version | Version number of the CPS as a string E.g., "V1287" | | Content size | Content size in bytes, little-endian. | | Model name | Model name as a string. E.g., "D878UV" | | HW Version | Hardware version as a string. E.g., "V100" | ## Channel list The channel list consists of a simple list of all channels concatenated together preceded by the number of channels encoded as little endian. ``` +---+---+---+---+---+---+ ... + | #Chan | Channel data ... | +---+---+---+---+---+---+ ... + ``` ### Channel encoding Each channel is encoded in a variable size binary blob as ``` 0 8 16 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 00 | ChIdx | RX frequency |RPT| TX offset |TYP|PWR|BWD| 0 |RXO| +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 10 |CAC|TKA| RXCTC | | TXCTC | | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ 20 |ADM| |CLC| |TSL|IGS| |WAL| | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+...+---+ 30 | Channel name N chars ... 0 | +---+---+---+---+---+---+...+---+ +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 |RNG| ? | 0 0 |APT| 0 |FreqCor| 0 |SMC|ExR| 0 0 0 0 | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | 0 |DAK| 0 0 | +---+---+---+---+ ``` | Field | Description | |---|---| | ChIdx | Specifies the channel index in little endian, 0-based. | | RX frequency | RX frequency in 10Hz, little endian. | | RPT | Repeater type, 0=simplex, 1=positive, 2=negative | | TX offset | TX offset frequency in 10Hz, little endian. Sign determined by RPT field. | | TPY | Channel type, 1=digital, 0=analog, ... | | PWR | Power setting 0=low, 1=mid, 2=high, 3=turbo. | | BWD | Analog band width 0=12.5kHz, 1=25kHz. | | RXO | RX only flag. | | CAC | Call confirm flag. | | TKA | Talkaround flag. | | RXCTC | RX CTCSS/DSC settings. | | TXCTC | RX CTCSS/DSC settings. | | ADM | Admit criterion, 0=always, 1=, 2=, 3=same color code. Also busy lock 1=repeater, 2=busy.| | COC | Color code. | | TSL | Time slot, 0=TS1, 1=TS2 | | IGS | Ignore time slot flag. | | WAL | Work alone flag. | | Channel name | Channel name of max 16 ASCII chars. Variable size (max. 16b?). Zero padded. | | RNG | Ranging flag. | | APT | APRS report type, 0=none, 1=analog, 2=digital. | | FreqCor | Frequency correction in Hz, little endian. | | SMC | SMS confirmation flag. | | ExR | Exclude from roaming flag. | | DAK | Data ACK flag. | ## RadioID list As usual, the radio ID list starts with the number of defined IDs (little endian) and is followed by the actual list of IDs. ``` +---+---+---+---+---+ ... + |#ID| RadioID data ... | +---+---+---+---+---+ ... + ``` ### Radio ID encoding ``` +---+---+---+---+---+---+ ... +---+ |IDX| DMR ID | Name 0 | +---+---+---+---+---+---+ ... +---+ ``` | Field | Description | |---|---| | IDX | Specifies the radio ID index in little endian, 0-based. | | DMR ID | The DMR ID as a 24bit little endian integer. | | Name | Variable length radio ID name. ASCII, 0-terminated. | ## Zone List As usual, the zone starts with the number of defined zones and is followed by the actual list of zones. ``` +---+---+---+---+ ... + |#ZO| Zone data ... | +---+---+---+---+ ... + ``` ### Zone encoding ``` +---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+ ... +---+ |ZIX|NCH| Ch00 | Ch01 | ... | Chn A | Chn B | Zone name ... 0 | +---+---+---+---+---+---+ ... +---+---+---+---+---+---+---+ ... +---+ ``` | Field | Description | |---|---| | ZIX | Zone index. | | NCH | Number of channel in zone. | | ChXX | List of 16bit, little endian, 0-based channel indices | | Chn A | Zone channel A | | Chn B | Zone channel B | | Zone name | Name of the zone, ASCII 0-terminated. | ## Scan lists As usual, the scan lists starts with the number of scan lists and is followed by the actual scan lists. ``` +---+---+---+---+---+ ... + |#SL| Scan list data ... | +---+---+---+---+---+ ... + ``` ### Scan list encoding ``` +---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+ |SLI| Name ... 0 | 0 |PCS| PC1 | PC2 |RVC|LBA|LBB|DOD|DWT| +---+---+---+ ... +---+---+---+---+---+---+---+---+---+---+---+---+ +---+---+---+---+---+---+---+ ... + |NCH| 0 0 | Ch00 | Ch01 | ... | +---+---+---+---+---+---+---+ ... + ``` | Field | Description | |---|---| | SLI | Scan list index. | | Name | Name of the scan list, max 16 x ASCII, 0-terminated. | | PCS | Priority channel select, 0=Off, 1=PC1, 2=PC2, 3=Both | | PC1 | Priority channel 1, 0=Off, 1=Current, else channel index + 2, little endian. | | PC2 | Priority channel 2, 0=Off, 1=Current, else channel index + 2, little endian. | | RVC | Revert channel, 0=Selected, 1=Selected+Talkback, 2=Last called, 3=Last used. | | LBA | Look back time A in 100ms. | | LBB | Look back time B in 100ms. | | DOD | Dropout delay in 100ms. | | DWT | Dwell time in 100ms. | | NCH | Number of channels in scan list. | ## Analog contacts As usual, the analog contact list starts with the number of contacts and is followed by the actual contact list. ``` +---+---+---+---+---+---+ ... + |#AC| Analog contact data ... | +---+---+---+---+---+---+ ... + ``` ### Analog contact encoding ``` +---+---+---+---+ ... +---+---+ ... +---+ |IDX|NoL| Number ... | Name ... 0 | +---+---+---+---+ ... +---+---+ ... +---+ ``` | Field | Description | |---|---| | IDX | Analog contact index. | | NoL | Number length. | | Name | Name of the contact, max 16 x ASCII, 0-terminated. | ================================================ FILE: doc/reveng/anytone/d878uv/d878uv_base.hex ================================================ ----------------------------------------------------------------------------- 00800000 : 43 95 62 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.bP.v.......... 00800010 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800020 : 01 00 00 44 42 30 4c 44 53 20 54 53 31 00 00 00 | ...DB0LDS TS1... 00800030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800040 : 43 95 62 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.bP.v.......... 00800050 : cf 09 00 00 07 00 00 00 00 00 03 ff 02 00 00 00 | ................ 00800060 : 01 00 00 53 61 2f 54 68 20 44 42 30 4c 44 53 20 | ...Sa/Th DB0LDS 00800070 : 54 53 31 00 00 00 00 00 00 00 00 00 00 00 00 00 | TS1............. 00800080 : 43 95 62 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.bP.v.......... 00800090 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008000A0 : 01 01 00 54 47 38 20 44 42 30 4c 44 53 20 54 53 | ...TG8 DB0LDS TS 008000B0 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 008000C0 : 43 95 62 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.bP.v.......... 008000D0 : cf 09 00 00 05 00 00 00 00 00 03 00 01 00 00 00 | ................ 008000E0 : 01 01 00 54 47 39 20 44 42 30 4c 44 53 20 54 53 | ...TG9 DB0LDS TS 008000F0 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800100 : 43 95 62 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.bP.v.......... 00800110 : cf 09 00 00 06 00 00 00 00 00 03 00 01 00 00 00 | ................ 00800120 : 01 01 00 42 42 20 44 42 30 4c 44 53 20 54 53 32 | ...BB DB0LDS TS2 00800130 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800140 : 43 88 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800150 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800160 : 01 00 00 44 4d 30 54 5a 4e 20 54 53 31 00 00 00 | ...DM0TZN TS1... 00800170 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800180 : 43 88 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800190 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008001A0 : 01 01 00 54 47 38 20 44 4d 30 54 5a 4e 20 54 53 | ...TG8 DM0TZN TS 008001B0 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 008001C0 : 43 88 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 008001D0 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008001E0 : 01 01 00 54 47 39 20 44 4d 30 54 5a 4e 20 54 53 | ...TG9 DM0TZN TS 008001F0 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800200 : 43 88 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800210 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800220 : 01 01 00 42 42 20 44 4d 30 54 5a 4e 20 54 53 32 | ...BB DM0TZN TS2 00800230 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800240 : 43 84 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800250 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800260 : 01 00 00 44 42 30 4c 4f 53 20 54 53 31 00 00 00 | ...DB0LOS TS1... 00800270 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800280 : 43 84 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800290 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008002A0 : 01 01 00 52 2f 54 47 39 20 44 42 30 4c 4f 53 20 | ...R/TG9 DB0LOS 008002B0 : 54 53 32 00 00 00 00 00 00 00 00 00 00 00 00 00 | TS2............. 008002C0 : 43 90 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 008002D0 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008002E0 : 01 00 00 52 2f 54 47 39 20 44 4d 30 54 54 20 54 | ...R/TG9 DM0TT T 008002F0 : 53 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | S1.............. 00800300 : 43 90 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800310 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800320 : 01 01 00 54 47 38 20 44 4d 30 54 54 20 54 53 32 | ...TG8 DM0TT TS2 00800330 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800340 : 43 90 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800350 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800360 : 01 01 00 54 47 39 20 44 4d 30 54 54 20 54 53 32 | ...TG9 DM0TT TS2 00800370 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800380 : 43 90 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800390 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008003A0 : 01 01 00 42 42 20 44 4d 30 54 54 20 54 53 32 00 | ...BB DM0TT TS2. 008003B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008003C0 : 43 95 37 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.7P.v.......... 008003D0 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008003E0 : 01 00 00 54 47 39 20 44 42 30 4b 4b 20 54 53 31 | ...TG9 DB0KK TS1 008003F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800400 : 43 95 37 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.7P.v.......... 00800410 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800420 : 01 01 00 42 42 20 44 42 30 4b 4b 20 54 53 32 00 | ...BB DB0KK TS2. 00800430 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800440 : 43 86 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800450 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800460 : 01 00 00 44 42 30 4f 55 44 20 54 53 31 00 00 00 | ...DB0OUD TS1... 00800470 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800480 : 43 86 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800490 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008004A0 : 01 01 00 54 47 38 20 44 42 30 4f 55 44 20 54 53 | ...TG8 DB0OUD TS 008004B0 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 008004C0 : 43 86 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 008004D0 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008004E0 : 01 01 00 54 47 39 20 44 42 30 4f 55 44 20 54 53 | ...TG9 DB0OUD TS 008004F0 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800500 : 43 86 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800510 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800520 : 01 01 00 42 42 20 44 42 30 4f 55 44 20 54 53 32 | ...BB DB0OUD TS2 00800530 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800540 : 43 95 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800550 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800560 : 01 00 00 54 47 39 20 44 42 30 54 55 20 54 53 31 | ...TG9 DB0TU TS1 00800570 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800580 : 43 95 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800590 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008005A0 : 01 01 00 42 42 20 44 42 30 54 55 20 54 53 32 00 | ...BB DB0TU TS2. 008005B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008005C0 : 43 95 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 008005D0 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 008005E0 : 01 00 00 44 4d 30 4d 4f 54 20 54 53 31 00 00 00 | ...DM0MOT TS1... 008005F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800600 : 43 95 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800610 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800620 : 01 01 00 54 47 38 20 44 4d 30 4d 4f 54 20 54 53 | ...TG8 DM0MOT TS 00800630 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800640 : 43 95 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800650 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800660 : 01 01 00 54 47 39 20 44 4d 30 4d 4f 54 20 54 53 | ...TG9 DM0MOT TS 00800670 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800680 : 43 95 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800690 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008006A0 : 01 01 00 42 42 20 44 4d 30 4d 4f 54 20 54 53 32 | ...BB DM0MOT TS2 008006B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008006C0 : 43 94 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 008006D0 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 008006E0 : 01 00 00 44 42 30 54 41 20 54 53 31 00 00 00 00 | ...DB0TA TS1.... 008006F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800700 : 43 94 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800710 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800720 : 01 01 00 54 47 38 20 44 42 30 54 41 20 54 53 32 | ...TG8 DB0TA TS2 00800730 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800740 : 43 94 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800750 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800760 : 01 01 00 54 47 39 20 44 42 30 54 41 20 54 53 32 | ...TG9 DB0TA TS2 00800770 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800780 : 43 94 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800790 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008007A0 : 01 01 00 42 42 20 44 42 30 54 41 20 54 53 32 00 | ...BB DB0TA TS2. 008007B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008007C0 : 43 94 50 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.P..v.......... 008007D0 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 008007E0 : 01 00 00 44 42 30 46 58 20 54 53 31 00 00 00 00 | ...DB0FX TS1.... 008007F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800800 : 43 94 50 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.P..v.......... 00800810 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800820 : 01 01 00 54 47 38 20 44 42 30 46 58 20 54 53 32 | ...TG8 DB0FX TS2 00800830 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800840 : 43 94 50 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.P..v.......... 00800850 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800860 : 01 01 00 54 47 39 20 44 42 30 46 58 20 54 53 32 | ...TG9 DB0FX TS2 00800870 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800880 : 43 94 50 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.P..v.......... 00800890 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008008A0 : 01 01 00 42 42 20 44 42 30 46 58 20 54 53 32 00 | ...BB DB0FX TS2. 008008B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008008C0 : 43 84 00 00 00 76 00 00 89 00 00 00 00 00 00 00 | C....v.......... 008008D0 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 008008E0 : 01 00 00 44 42 30 50 44 4d 20 54 53 31 00 00 00 | ...DB0PDM TS1... 008008F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800900 : 43 84 00 00 00 76 00 00 89 00 00 00 00 00 00 00 | C....v.......... 00800910 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800920 : 01 01 00 54 47 38 20 44 42 30 50 44 4d 20 54 53 | ...TG8 DB0PDM TS 00800930 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800940 : 43 84 00 00 00 76 00 00 89 00 00 00 00 00 00 00 | C....v.......... 00800950 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800960 : 01 01 00 54 47 39 20 44 42 30 50 44 4d 20 54 53 | ...TG9 DB0PDM TS 00800970 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800980 : 43 84 00 00 00 76 00 00 89 00 00 00 00 00 00 00 | C....v.......... 00800990 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 008009A0 : 01 01 00 42 42 20 44 42 30 50 44 4d 20 54 53 32 | ...BB DB0PDM TS2 008009B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008009C0 : 43 94 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 008009D0 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 008009E0 : 01 00 00 44 42 30 42 52 42 20 54 53 31 00 00 00 | ...DB0BRB TS1... 008009F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800A00 : 43 94 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800A10 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800A20 : 01 01 00 54 47 38 20 44 42 30 42 52 42 20 54 53 | ...TG8 DB0BRB TS 00800A30 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800A40 : 43 94 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800A50 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800A60 : 01 01 00 54 47 39 20 44 42 30 42 52 42 20 54 53 | ...TG9 DB0BRB TS 00800A70 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800A80 : 43 94 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800A90 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800AA0 : 01 01 00 42 42 20 44 42 30 42 52 42 20 54 53 32 | ...BB DB0BRB TS2 00800AB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800AC0 : 43 94 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800AD0 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800AE0 : 01 00 00 44 42 30 53 50 4e 20 54 53 31 00 00 00 | ...DB0SPN TS1... 00800AF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800B00 : 43 94 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800B10 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800B20 : 01 00 00 52 2f 54 47 39 20 44 42 30 53 50 4e 20 | ...R/TG9 DB0SPN 00800B30 : 54 53 32 00 00 00 00 00 00 00 00 00 00 00 00 00 | TS2............. 00800B40 : 43 95 12 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800B50 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800B60 : 01 00 00 44 42 30 4e 4c 53 20 54 53 31 00 00 00 | ...DB0NLS TS1... 00800B70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800B80 : 43 95 12 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800B90 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800BA0 : 01 01 00 52 2f 54 47 39 20 44 42 30 4e 4c 53 20 | ...R/TG9 DB0NLS 00800BB0 : 54 53 32 00 00 00 00 00 00 00 00 00 00 00 00 00 | TS2............. 00800BC0 : 43 82 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800BD0 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800BE0 : 01 00 00 44 42 30 4c 53 20 54 53 31 00 00 00 00 | ...DB0LS TS1.... 00800BF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800C00 : 43 82 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800C10 : cf 09 00 00 04 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800C20 : 01 01 00 54 47 38 20 44 42 30 4c 53 20 54 53 32 | ...TG8 DB0LS TS2 00800C30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800C40 : 43 82 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800C50 : cf 09 00 00 05 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800C60 : 01 01 00 54 47 39 20 44 42 30 4c 53 20 54 53 32 | ...TG9 DB0LS TS2 00800C70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800C80 : 43 82 25 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.%..v.......... 00800C90 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800CA0 : 01 01 00 42 42 20 44 42 30 4c 53 20 54 53 32 00 | ...BB DB0LS TS2. 00800CB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800CC0 : 43 93 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800CD0 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800CE0 : 01 00 00 44 4d 30 4c 43 20 54 53 31 00 00 00 00 | ...DM0LC TS1.... 00800CF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800D00 : 43 93 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800D10 : cf 09 00 00 04 00 00 00 00 00 03 ff 02 00 00 00 | ................ 00800D20 : 01 01 00 54 47 38 20 44 4d 30 4c 43 20 54 53 32 | ...TG8 DM0LC TS2 00800D30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800D40 : 43 93 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800D50 : cf 09 00 00 05 00 00 00 00 00 03 ff 02 00 00 00 | ................ 00800D60 : 01 01 00 54 47 39 20 44 4d 30 4c 43 20 54 53 32 | ...TG9 DM0LC TS2 00800D70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800D80 : 43 93 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800D90 : cf 09 00 00 07 00 00 00 00 00 03 ff 02 00 00 00 | ................ 00800DA0 : 01 01 00 53 61 2f 54 68 20 44 4d 30 4c 43 20 54 | ...Sa/Th DM0LC T 00800DB0 : 53 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | S2.............. 00800DC0 : 43 93 87 50 00 76 00 00 89 00 00 00 00 00 00 00 | C..P.v.......... 00800DD0 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800DE0 : 01 00 00 42 42 20 44 4d 30 4c 43 20 54 53 31 00 | ...BB DM0LC TS1. 00800DF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800E00 : 43 95 37 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.7P.v.......... 00800E10 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800E20 : 01 00 00 44 42 30 46 4c 57 20 54 53 31 00 00 00 | ...DB0FLW TS1... 00800E30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800E40 : 43 95 37 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.7P.v.......... 00800E50 : cf 09 00 00 04 00 00 00 00 00 03 ff 02 00 00 00 | ................ 00800E60 : 01 01 00 54 47 38 20 44 42 30 46 4c 57 20 54 53 | ...TG8 DB0FLW TS 00800E70 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800E80 : 43 95 37 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.7P.v.......... 00800E90 : cf 09 00 00 05 00 00 00 00 00 03 ff 02 00 00 00 | ................ 00800EA0 : 01 01 00 54 47 39 20 44 42 30 46 4c 57 20 54 53 | ...TG9 DB0FLW TS 00800EB0 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00800EC0 : 43 95 37 50 00 76 00 00 89 00 00 00 00 00 00 00 | C.7P.v.......... 00800ED0 : cf 09 00 00 07 00 00 00 00 00 03 ff 02 00 00 00 | ................ 00800EE0 : 01 01 00 53 61 2f 54 68 20 44 42 30 46 4c 57 20 | ...Sa/Th DB0FLW 00800EF0 : 54 53 32 00 00 00 00 00 00 00 00 00 00 00 00 00 | TS2............. 00800F00 : 43 95 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800F10 : cf 09 00 00 00 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800F20 : 01 00 00 44 42 30 4c 45 20 54 53 31 00 00 00 00 | ...DB0LE TS1.... 00800F30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800F40 : 43 95 75 00 00 76 00 00 89 00 00 00 00 00 00 00 | C.u..v.......... 00800F50 : cf 09 00 00 05 00 00 00 00 00 03 ff 02 00 00 00 | ................ 00800F60 : 01 01 00 52 2f 54 47 39 20 44 42 30 4c 45 20 54 | ...R/TG9 DB0LE T 00800F70 : 53 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | S2.............. 00800F80 : 43 99 75 00 00 94 00 00 89 00 00 00 00 00 00 00 | C.u............. 00800F90 : cf 09 00 00 02 00 00 00 00 00 03 ff 00 00 00 00 | ................ 00800FA0 : 01 00 00 44 4c 20 44 42 30 41 46 5a 20 54 53 31 | ...DL DB0AFZ TS1 00800FB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00800FC0 : 43 99 75 00 00 94 00 00 89 00 00 00 00 00 00 00 | C.u............. 00800FD0 : cf 09 00 00 06 00 00 00 00 00 03 ff 01 00 00 00 | ................ 00800FE0 : 01 00 00 42 42 20 44 42 30 41 46 5a 20 54 53 31 | ...BB DB0AFZ TS1 00800FF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801000 : 43 99 75 00 00 94 00 00 89 00 00 00 00 00 00 00 | C.u............. 00801010 : cf 09 00 00 05 00 00 00 00 00 03 ff 04 00 00 00 | ................ 00801020 : 01 01 00 54 47 39 20 44 42 30 41 46 5a 20 54 53 | ...TG9 DB0AFZ TS 00801030 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00801040 : 43 99 75 00 00 94 00 00 89 00 00 00 00 00 00 00 | C.u............. 00801050 : cf 09 00 00 09 00 00 00 00 00 03 ff 04 00 00 00 | ................ 00801060 : 01 01 00 48 65 73 20 44 42 30 41 46 5a 20 54 53 | ...Hes DB0AFZ TS 00801070 : 32 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | 2............... 00801080 : 43 34 50 00 00 00 00 00 09 00 00 00 00 00 00 00 | C4P............. 00801090 : cf 09 00 00 0a 00 00 00 00 00 01 ff 05 00 00 00 | ................ 008010A0 : 01 00 00 44 4d 52 20 53 30 00 00 00 00 00 00 00 | ...DMR S0....... 008010B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008010C0 : 43 36 12 00 00 00 00 00 09 00 00 00 00 00 00 00 | C6.............. 008010D0 : cf 09 00 00 0a 00 00 00 00 00 01 ff 05 00 00 00 | ................ 008010E0 : 01 00 00 44 4d 52 20 53 31 00 00 00 00 00 00 00 | ...DMR S1....... 008010F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801100 : 43 36 25 00 00 00 00 00 09 00 00 00 00 00 00 00 | C6%............. 00801110 : cf 09 00 00 0a 00 00 00 00 00 01 ff 05 00 00 00 | ................ 00801120 : 01 00 00 44 4d 52 20 53 32 00 00 00 00 00 00 00 | ...DMR S2....... 00801130 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801140 : 43 36 38 00 00 00 00 00 09 00 00 00 00 00 00 00 | C68............. 00801150 : cf 09 00 00 0a 00 00 00 00 00 01 ff 05 00 00 00 | ................ 00801160 : 01 00 00 44 4d 52 20 53 33 00 00 00 00 00 00 00 | ...DMR S3....... 00801170 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801180 : 43 36 50 00 00 00 00 00 89 00 00 00 00 00 00 00 | C6P............. 00801190 : cf 09 00 00 0a 00 00 00 00 00 01 ff 05 00 00 00 | ................ 008011A0 : 01 00 00 44 4d 52 20 53 34 00 00 00 00 00 00 00 | ...DMR S4....... 008011B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008011C0 : 43 36 63 00 00 00 00 00 09 00 00 00 00 00 00 00 | C6c............. 008011D0 : cf 09 00 00 0a 00 00 00 00 00 01 ff 05 00 00 00 | ................ 008011E0 : 01 00 00 44 4d 52 20 53 35 00 00 00 00 00 00 00 | ...DMR S5....... 008011F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801200 : 43 36 75 00 00 00 00 00 09 00 00 00 00 00 00 00 | C6u............. 00801210 : cf 09 00 00 0a 00 00 00 00 00 01 ff 05 00 00 00 | ................ 00801220 : 01 00 00 44 4d 52 20 53 36 00 00 00 00 00 00 00 | ...DMR S6....... 00801230 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801240 : 43 36 88 00 00 00 00 00 09 00 00 00 00 00 00 00 | C6.............. 00801250 : cf 09 00 00 0a 00 00 00 00 00 01 ff 05 00 00 00 | ................ 00801260 : 01 00 00 44 4d 52 20 53 37 00 00 00 00 00 00 00 | ...DMR S7....... 00801270 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801280 : 43 95 62 50 00 76 00 00 88 05 01 01 00 00 00 00 | C.bP.v.......... 00801290 : cf 09 00 00 00 00 00 00 00 10 00 ff ff 00 00 00 | ................ 008012A0 : 00 00 00 44 42 30 4c 44 53 00 00 00 00 00 00 00 | ...DB0LDS....... 008012B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008012C0 : 43 93 00 00 00 76 00 00 88 00 00 00 00 00 00 00 | C....v.......... 008012D0 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008012E0 : 00 00 00 44 42 30 52 41 47 00 00 00 00 00 00 00 | ...DB0RAG....... 008012F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801300 : 43 85 75 00 00 76 00 00 88 05 01 01 00 00 00 00 | C.u..v.......... 00801310 : cf 09 00 00 00 00 00 00 00 10 00 ff ff 00 00 00 | ................ 00801320 : 00 00 00 44 42 30 4c 55 44 00 00 00 00 00 00 00 | ...DB0LUD....... 00801330 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801340 : 14 56 00 00 00 06 00 00 88 00 00 00 00 00 00 00 | .V.............. 00801350 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801360 : 00 00 00 44 42 30 53 50 2d 32 00 00 00 00 00 00 | ...DB0SP-2...... 00801370 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801380 : 43 94 25 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.%..v.......... 00801390 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008013A0 : 00 00 00 44 42 30 53 50 2d 37 30 00 00 00 00 00 | ...DB0SP-70..... 008013B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008013C0 : 14 56 62 50 00 06 00 00 88 00 00 00 00 00 00 00 | .VbP............ 008013D0 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008013E0 : 00 00 00 44 42 30 5a 4f 44 2d 32 00 00 00 00 00 | ...DB0ZOD-2..... 008013F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801400 : 43 87 25 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.%..v.......... 00801410 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801420 : 00 00 00 44 42 30 5a 4f 44 2d 37 30 00 00 00 00 | ...DB0ZOD-70.... 00801430 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801440 : 14 57 25 00 00 06 00 00 88 00 00 00 00 00 00 00 | .W%............. 00801450 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801460 : 00 00 00 44 42 30 42 52 4c 00 00 00 00 00 00 00 | ...DB0BRL....... 00801470 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801480 : 43 92 75 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.u..v.......... 00801490 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008014A0 : 00 00 00 44 42 30 42 4c 4f 00 00 00 00 00 00 00 | ...DB0BLO....... 008014B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008014C0 : 43 90 50 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.P..v.......... 008014D0 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008014E0 : 00 00 00 44 42 30 53 58 00 00 00 00 00 00 00 00 | ...DB0SX........ 008014F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801500 : 43 91 25 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.%..v.......... 00801510 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801520 : 00 00 00 44 42 30 54 41 00 00 00 00 00 00 00 00 | ...DB0TA........ 00801530 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801540 : 14 56 75 00 00 06 00 00 88 00 00 00 00 00 00 00 | .Vu............. 00801550 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801560 : 00 00 00 44 42 30 50 44 4d 00 00 00 00 00 00 00 | ...DB0PDM....... 00801570 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801580 : 43 88 50 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.P..v.......... 00801590 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008015A0 : 00 00 00 44 42 30 43 42 53 00 00 00 00 00 00 00 | ...DB0CBS....... 008015B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008015C0 : 43 86 37 50 00 76 00 00 88 00 00 00 00 00 00 00 | C.7P.v.......... 008015D0 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008015E0 : 00 00 00 44 42 30 41 46 00 00 00 00 00 00 00 00 | ...DB0AF........ 008015F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801600 : 43 89 50 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.P..v.......... 00801610 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801620 : 00 00 00 44 42 30 4e 46 4c 00 00 00 00 00 00 00 | ...DB0NFL....... 00801630 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801640 : 43 93 00 00 00 76 00 00 88 00 00 00 00 00 00 00 | C....v.......... 00801650 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801660 : 00 00 00 44 42 30 4c 4d 4d 00 00 00 00 00 00 00 | ...DB0LMM....... 00801670 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801680 : 43 91 50 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.P..v.......... 00801690 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008016A0 : 00 00 00 44 4d 30 4c 45 49 00 00 00 00 00 00 00 | ...DM0LEI....... 008016B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008016C0 : 14 57 75 00 00 06 00 00 88 00 00 00 00 00 00 00 | .Wu............. 008016D0 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008016E0 : 00 00 00 44 42 30 4c 45 49 00 00 00 00 00 00 00 | ...DB0LEI....... 008016F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801700 : 43 92 75 00 00 76 00 00 88 00 00 00 00 00 00 00 | C.u..v.......... 00801710 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801720 : 00 00 00 44 42 30 53 4d 4c 00 00 00 00 00 00 00 | ...DB0SML....... 00801730 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801740 : 14 56 62 50 00 06 00 00 88 00 00 00 00 00 00 00 | .VbP............ 00801750 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801760 : 00 00 00 44 42 30 4c 53 41 00 00 00 00 00 00 00 | ...DB0LSA....... 00801770 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801780 : 43 87 87 50 00 76 00 00 88 00 00 00 00 00 00 00 | C..P.v.......... 00801790 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 008017A0 : 00 00 00 44 42 30 4c 45 00 00 00 00 00 00 00 00 | ...DB0LE........ 008017B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008017C0 : 14 46 75 00 00 00 00 00 48 00 00 00 00 00 00 00 | .Fu.....H....... 008017D0 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 008017E0 : 00 00 00 44 4f 4b 20 59 30 37 00 00 00 00 00 00 | ...DOK Y07...... 008017F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801800 : 14 46 50 00 00 00 00 00 08 00 00 00 00 00 00 00 | .FP............. 00801810 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801820 : 00 00 00 44 4f 4b 20 44 32 30 00 00 00 00 00 00 | ...DOK D20...... 00801830 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801840 : 14 53 75 00 00 00 00 00 08 00 00 00 00 00 00 00 | .Su............. 00801850 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801860 : 00 00 00 44 4f 4b 20 44 32 33 00 00 00 00 00 00 | ...DOK D23...... 00801870 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801880 : 14 47 25 00 00 00 00 00 08 00 00 00 00 00 00 00 | .G%............. 00801890 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 008018A0 : 00 00 00 44 4f 4b 20 53 33 31 00 00 00 00 00 00 | ...DOK S31...... 008018B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008018C0 : 14 53 00 00 00 00 00 00 08 00 00 00 00 00 00 00 | .S.............. 008018D0 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 008018E0 : 00 00 00 53 32 30 00 00 00 00 00 00 00 00 00 00 | ...S20.......... 008018F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801900 : 14 55 00 00 00 00 00 00 08 00 00 00 00 00 00 00 | .U.............. 00801910 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801920 : 00 00 00 32 6d 20 4d 6f 62 69 6c 00 00 00 00 00 | ...2m Mobil..... 00801930 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801940 : 43 35 00 00 00 00 00 00 08 00 00 00 00 00 00 00 | C5.............. 00801950 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801960 : 00 00 00 37 30 63 6d 20 4d 6f 62 69 6c 00 00 00 | ...70cm Mobil... 00801970 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801980 : 14 48 00 00 00 00 00 00 08 00 00 00 00 00 00 00 | .H.............. 00801990 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 008019A0 : 00 00 00 41 50 52 53 00 00 00 00 00 00 00 00 00 | ...APRS......... 008019B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008019C0 : 14 58 25 00 00 00 00 00 88 00 00 00 00 00 00 00 | .X%............. 008019D0 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 008019E0 : 00 00 00 49 53 53 20 41 50 52 53 00 00 00 00 00 | ...ISS APRS..... 008019F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801A00 : 43 70 00 00 29 10 10 00 88 04 01 00 00 00 00 00 | Cp..)........... 00801A10 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801A20 : 00 00 00 49 53 53 20 46 4d 20 52 65 70 00 00 00 | ...ISS FM Rep... 00801A30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801A40 : 43 72 00 00 29 28 50 00 88 00 00 00 00 00 00 00 | Cr..)(P......... 00801A50 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801A60 : 00 00 00 43 41 53 2d 33 48 20 46 4d 00 00 00 00 | ...CAS-3H FM.... 00801A70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801A80 : 14 59 00 00 29 16 00 00 48 00 00 00 00 00 00 00 | .Y..)...H....... 00801A90 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801AA0 : 00 00 00 50 4f 2d 31 30 31 20 46 4d 00 00 00 00 | ...PO-101 FM.... 00801AB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801AC0 : 43 67 90 00 29 09 40 00 88 04 01 00 00 00 00 00 | Cg..).@......... 00801AD0 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801AE0 : 00 00 00 53 4f 2d 35 30 20 46 4d 00 00 00 00 00 | ...SO-50 FM..... 00801AF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801B00 : 14 58 80 00 28 94 70 00 48 04 01 00 00 00 00 00 | .X..(.p.H....... 00801B10 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801B20 : 00 00 00 41 4f 2d 39 32 20 46 4d 00 00 00 00 00 | ...AO-92 FM..... 00801B30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801B40 : 14 59 60 00 28 92 90 00 48 04 01 00 00 00 00 00 | .Y`.(...H....... 00801B50 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801B60 : 00 00 00 41 4f 2d 39 31 20 46 4d 00 00 00 00 00 | ...AO-91 FM..... 00801B70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801B80 : 43 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | C............... 00801B90 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801BA0 : 00 00 00 55 30 00 00 00 00 00 00 00 00 00 00 00 | ...U0........... 00801BB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801BC0 : 43 02 25 00 00 00 00 00 40 00 00 00 00 00 00 00 | C.%.....@....... 00801BD0 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801BE0 : 00 00 00 55 31 00 00 00 00 00 00 00 00 00 00 00 | ...U1........... 00801BF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801C00 : 43 02 50 00 00 00 00 00 00 00 00 00 00 00 00 00 | C.P............. 00801C10 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801C20 : 00 00 00 55 32 00 00 00 00 00 00 00 00 00 00 00 | ...U2........... 00801C30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801C40 : 43 02 75 00 00 00 00 00 00 00 00 00 00 00 00 00 | C.u............. 00801C50 : cf 09 00 00 00 00 00 00 00 00 00 ff ff 00 00 00 | ................ 00801C60 : 00 00 00 55 33 00 00 00 00 00 00 00 00 00 00 00 | ...U3........... 00801C70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801C80 : 44 60 06 25 00 00 00 00 40 00 00 00 00 00 00 00 | D`.%....@....... 00801C90 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801CA0 : 00 00 00 50 4d 52 20 31 00 00 00 00 00 00 00 00 | ...PMR 1........ 00801CB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801CC0 : 44 60 18 75 00 00 00 00 40 00 00 00 00 00 00 00 | D`.u....@....... 00801CD0 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801CE0 : 00 00 00 50 4d 52 20 32 00 00 00 00 00 00 00 00 | ...PMR 2........ 00801CF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801D00 : 44 60 31 25 00 00 00 00 40 00 00 00 00 00 00 00 | D`1%....@....... 00801D10 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801D20 : 00 00 00 50 4d 52 20 33 00 00 00 00 00 00 00 00 | ...PMR 3........ 00801D30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801D40 : 44 60 43 75 00 00 00 00 00 00 00 00 00 00 00 00 | D`Cu............ 00801D50 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801D60 : 00 00 00 50 4d 52 20 34 00 00 00 00 00 00 00 00 | ...PMR 4........ 00801D70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801D80 : 44 60 56 25 00 00 00 00 00 00 00 00 00 00 00 00 | D`V%............ 00801D90 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801DA0 : 00 00 00 50 4d 52 20 35 00 00 00 00 00 00 00 00 | ...PMR 5........ 00801DB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801DC0 : 44 60 68 75 00 00 00 00 40 00 00 00 00 00 00 00 | D`hu....@....... 00801DD0 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801DE0 : 00 00 00 50 4d 52 20 36 00 00 00 00 00 00 00 00 | ...PMR 6........ 00801DF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801E00 : 44 60 81 25 00 00 00 00 40 00 00 00 00 00 00 00 | D`.%....@....... 00801E10 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801E20 : 00 00 00 50 4d 52 20 37 00 00 00 00 00 00 00 00 | ...PMR 7........ 00801E30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801E40 : 44 60 93 75 00 00 00 00 40 00 00 00 00 00 00 00 | D`.u....@....... 00801E50 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801E60 : 00 00 00 50 4d 52 20 38 00 00 00 00 00 00 00 00 | ...PMR 8........ 00801E70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801E80 : 44 61 06 25 00 00 00 00 00 00 00 00 00 00 00 00 | Da.%............ 00801E90 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801EA0 : 00 00 00 50 4d 52 20 39 00 00 00 00 00 00 00 00 | ...PMR 9........ 00801EB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801EC0 : 44 61 18 75 00 00 00 00 00 00 00 00 00 00 00 00 | Da.u............ 00801ED0 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801EE0 : 00 00 00 50 4d 52 20 31 30 00 00 00 00 00 00 00 | ...PMR 10....... 00801EF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801F00 : 44 61 31 25 00 00 00 00 40 00 00 00 00 00 00 00 | Da1%....@....... 00801F10 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801F20 : 00 00 00 50 4d 52 20 31 31 00 00 00 00 00 00 00 | ...PMR 11....... 00801F30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801F40 : 44 61 43 75 00 00 00 00 00 00 00 00 00 00 00 00 | DaCu............ 00801F50 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801F60 : 00 00 00 50 4d 52 20 31 32 00 00 00 00 00 00 00 | ...PMR 12....... 00801F70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801F80 : 44 61 56 25 00 00 00 00 00 00 00 00 00 00 00 00 | DaV%............ 00801F90 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801FA0 : 00 00 00 50 4d 52 20 31 33 00 00 00 00 00 00 00 | ...PMR 13....... 00801FB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00801FC0 : 44 61 68 75 00 00 00 00 00 00 00 00 00 00 00 00 | Dahu............ 00801FD0 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00801FE0 : 00 00 00 50 4d 52 20 31 34 00 00 00 00 00 00 00 | ...PMR 14....... 00801FF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802080 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802090 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008020A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008020B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008020C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008020D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008020E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008020F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802100 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802110 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802120 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802130 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802140 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802150 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802160 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802170 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802180 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802190 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008021A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008021B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008021C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008021D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008021E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008021F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802200 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802210 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802220 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802230 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802240 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802250 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802260 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802270 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802280 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802290 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008022A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008022B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008022C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008022D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008022E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008022F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802300 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802310 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802320 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802330 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802340 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802350 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802360 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802370 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802380 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802390 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008023A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008023B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008023C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008023D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008023E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008023F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802400 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802410 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802420 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802430 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802440 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802450 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802460 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802470 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802480 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802490 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008024A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008024B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008024C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008024D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008024E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008024F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802500 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802510 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802520 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802530 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802540 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802550 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802560 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802570 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802580 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802590 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008025A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008025B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008025C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008025D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008025E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008025F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802600 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802610 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802620 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802630 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802640 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802650 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802660 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802670 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802680 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802690 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008026A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008026B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008026C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008026D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008026E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008026F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802700 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802710 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802720 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802730 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802740 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802750 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802760 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802770 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802780 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802790 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008027A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008027B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008027C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008027D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008027E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008027F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802800 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802810 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802820 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802830 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802840 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802850 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802860 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802870 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802880 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802890 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008028A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008028B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008028C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008028D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008028E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008028F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802900 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802910 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802920 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802930 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802940 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802950 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802960 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802970 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802980 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802990 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008029A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008029B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008029C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008029D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008029E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008029F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802A90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802AA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802AB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802AC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802AD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802AE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802AF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802B90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802BA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802BB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802BC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802BD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802BE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802BF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802C90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802CA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802CB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802CC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802CD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802CE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802CF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802D90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802DA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802DB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802DC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802DD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802DE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802DF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802E90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802EA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802EB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802EC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802ED0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802EE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802EF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802F90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802FA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802FB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802FC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802FD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802FE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00802FF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803080 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803090 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008030A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008030B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008030C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008030D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008030E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008030F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803100 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803110 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803120 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803130 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803140 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803150 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803160 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803170 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803180 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803190 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008031A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008031B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008031C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008031D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008031E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008031F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803200 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803210 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803220 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803230 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803240 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803250 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803260 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803270 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803280 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803290 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008032A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008032B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008032C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008032D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008032E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008032F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803300 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803310 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803320 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803330 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803340 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803350 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803360 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803370 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803380 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803390 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008033A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008033B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008033C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008033D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008033E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008033F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803400 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803410 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803420 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803430 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803440 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803450 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803460 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803470 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803480 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803490 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008034A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008034B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008034C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008034D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008034E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008034F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803500 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803510 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803520 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803530 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803540 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803550 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803560 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803570 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803580 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803590 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008035A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008035B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008035C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008035D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008035E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008035F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803600 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803610 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803620 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803630 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803640 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803650 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803660 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803670 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803680 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803690 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008036A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008036B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008036C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008036D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008036E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008036F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803700 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803710 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803720 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803730 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803740 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803750 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803760 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803770 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803780 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803790 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008037A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008037B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008037C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008037D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008037E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008037F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803800 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803810 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803820 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803830 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803840 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803850 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803860 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803870 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803880 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803890 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008038A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008038B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008038C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008038D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008038E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008038F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803900 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803910 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803920 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803930 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803940 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803950 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803960 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803970 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803980 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803990 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008039A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008039B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008039C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008039D0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008039E0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 008039F0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803A90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803AA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803AB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803AC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803AD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803AE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803AF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803B90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803BA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803BB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803BC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803BD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803BE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803BF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803C90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803CA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803CB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803CC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803CD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803CE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803CF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803D90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803DA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803DB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803DC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803DD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803DE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803DF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803E90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803EA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803EB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803EC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803ED0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803EE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803EF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803F90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803FA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803FB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803FC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803FD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803FE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00803FF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 00840000 : 44 61 81 25 00 00 00 00 00 00 00 00 00 00 00 00 | Da.%............ 00840010 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00840020 : 00 00 00 50 4d 52 20 31 35 00 00 00 00 00 00 00 | ...PMR 15....... 00840030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00840040 : 44 61 93 75 00 00 00 00 40 00 00 00 00 00 00 00 | Da.u....@....... 00840050 : cf 09 00 00 00 00 00 00 00 00 01 ff ff 00 00 00 | ................ 00840060 : 00 00 00 50 4d 52 20 31 36 00 00 00 00 00 00 00 | ...PMR 16....... 00840070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 00842000 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00842010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00842020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00842030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00842040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00842050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00842060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00842070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 00FC0800 : 43 51 25 00 00 01 00 00 1d 00 15 15 13 00 13 00 | CQ%............. 00FC0810 : 26 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | &............... 00FC0820 : 01 00 00 43 68 61 6e 6e 65 6c 20 56 46 4f 20 41 | ...Channel VFO A 00FC0830 : 00 00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 | ................ 00FC0840 : 14 51 25 00 00 01 00 00 1d 00 15 15 13 00 13 00 | .Q%............. 00FC0850 : 26 05 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | &............... 00FC0860 : 01 00 00 43 68 61 6e 6e 65 6c 20 56 46 4f 20 42 | ...Channel VFO B 00FC0870 : 00 00 00 00 08 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 00FC2800 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00FC2810 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00FC2820 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00FC2830 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00FC2840 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00FC2850 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00FC2860 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 00FC2870 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01000000 : 00 00 03 00 04 00 01 00 05 00 07 00 08 00 09 00 | ................ 01000010 : 0a 00 0b 00 0d 00 0e 00 4a 00 4b 00 4c 00 4d 00 | ........J.K.L.M. 01000020 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000040 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000050 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000060 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000070 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000080 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000090 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010000A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010000B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010000C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010000D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010000E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010000F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000100 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000110 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000120 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000130 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000140 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000150 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000160 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000170 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000180 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000190 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010001A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010001B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010001C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010001D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010001E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010001F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000200 : 5f 00 63 00 64 00 65 00 42 00 43 00 44 00 45 00 | _.c.d.e.B.C.D.E. 01000210 : 46 00 47 00 48 00 49 00 ff ff ff ff ff ff ff ff | F.G.H.I......... 01000220 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000230 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000240 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000250 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000260 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000270 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000280 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000290 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010002A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010002B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010002C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010002D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010002E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010002F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000300 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000310 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000320 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000330 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000340 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000350 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000360 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000370 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000380 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000390 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010003A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010003B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010003C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010003D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010003E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010003F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000400 : 6e 00 6f 00 70 00 71 00 ff ff ff ff ff ff ff ff | n.o.p.q......... 01000410 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000420 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000430 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000440 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000450 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000460 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000470 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000480 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000490 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010004A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010004B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010004C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010004D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010004E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010004F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000500 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000510 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000520 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000530 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000540 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000550 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000560 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000570 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000580 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000590 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010005A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010005B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010005C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010005D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010005E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010005F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000600 : 3e 00 40 00 41 00 ff ff ff ff ff ff ff ff ff ff | >.@.A........... 01000610 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000620 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000630 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000640 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000650 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000660 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000670 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000680 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000690 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000700 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000710 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000720 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000730 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000740 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000750 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000760 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000770 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000780 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000790 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000800 : 64 00 42 00 43 00 44 00 45 00 46 00 47 00 48 00 | d.B.C.D.E.F.G.H. 01000810 : 49 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff | I............... 01000820 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000830 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000840 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000850 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000860 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000870 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000880 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000890 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000900 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000910 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000920 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000930 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000940 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000950 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000960 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000970 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000980 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000990 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A00 : 0e 00 10 00 14 00 16 00 1a 00 1e 00 22 00 4d 00 | ............".M. 01000A10 : 4e 00 4f 00 50 00 51 00 52 00 53 00 54 00 ff ff | N.O.P.Q.R.S.T... 01000A20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C00 : 60 00 61 00 64 00 65 00 42 00 43 00 44 00 45 00 | `.a.d.e.B.C.D.E. 01000C10 : 46 00 47 00 48 00 49 00 ff ff ff ff ff ff ff ff | F.G.H.I......... 01000C20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E00 : 23 00 24 00 25 00 26 00 27 00 28 00 29 00 2a 00 | #.$.%.&.'.(.).*. 01000E10 : 4d 00 4e 00 55 00 ff ff ff ff ff ff ff ff ff ff | M.N.U........... 01000E20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000ED0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001000 : 64 00 65 00 42 00 43 00 44 00 45 00 46 00 47 00 | d.e.B.C.D.E.F.G. 01001010 : 48 00 49 00 ff ff ff ff ff ff ff ff ff ff ff ff | H.I............. 01001020 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001040 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001050 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001060 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001070 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001080 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001090 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001100 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001110 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001120 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001130 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001140 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001150 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001160 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001170 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001180 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001190 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001200 : 2b 00 2c 00 2d 00 2e 00 2f 00 30 00 31 00 32 00 | +.,.-.../.0.1.2. 01001210 : 56 00 57 00 58 00 ff ff ff ff ff ff ff ff ff ff | V.W.X........... 01001220 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001230 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001240 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001250 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001260 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001270 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001280 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001290 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001300 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001310 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001320 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001330 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001340 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001350 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001360 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001370 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001380 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001390 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001400 : 64 00 65 00 42 00 43 00 44 00 45 00 46 00 47 00 | d.e.B.C.D.E.F.G. 01001410 : 48 00 49 00 ff ff ff ff ff ff ff ff ff ff ff ff | H.I............. 01001420 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001430 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001440 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001450 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001460 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001470 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001480 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001490 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001500 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001510 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001520 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001530 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001540 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001550 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001560 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001570 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001580 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001590 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001600 : 33 00 35 00 36 00 37 00 38 00 3a 00 3b 00 3c 00 | 3.5.6.7.8.:.;.<. 01001610 : 3d 00 59 00 5a 00 5b 00 5c 00 5d 00 5e 00 ff ff | =.Y.Z.[.\.].^... 01001620 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001630 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001640 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001650 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001660 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001670 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001680 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001690 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001700 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001710 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001720 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001730 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001740 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001750 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001760 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001770 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001780 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001790 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001800 : 62 00 64 00 65 00 42 00 43 00 44 00 45 00 46 00 | b.d.e.B.C.D.E.F. 01001810 : 47 00 48 00 49 00 ff ff ff ff ff ff ff ff ff ff | G.H.I........... 01001820 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001830 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001840 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001850 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001860 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001870 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001880 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001890 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001900 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001910 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001920 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001930 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001940 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001950 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001960 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001970 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001980 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001990 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A00 : 68 00 6d 00 6c 00 6b 00 6a 00 69 00 ff ff ff ff | h.m.l.k.j.i..... 01001A10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C00 : 67 00 68 00 6d 00 6c 00 6b 00 6a 00 69 00 ff ff | g.h.m.l.k.j.i... 01001C10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E00 : 72 00 73 00 74 00 75 00 76 00 77 00 78 00 79 00 | r.s.t.u.v.w.x.y. 01001E10 : 7a 00 7b 00 7c 00 7d 00 7e 00 7f 00 80 00 81 00 | z.{.|.}.~....... 01001E20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001ED0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002000 : 72 00 73 00 74 00 75 00 76 00 77 00 78 00 79 00 | r.s.t.u.v.w.x.y. 01002010 : 7a 00 7b 00 7c 00 7d 00 7e 00 7f 00 80 00 81 00 | z.{.|.}.~....... 01002020 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002040 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002050 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002060 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002070 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002080 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002090 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002100 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002110 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002120 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002130 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002140 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002150 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002160 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002170 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002180 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002190 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ ----------------------------------------------------------------------------- 01040000 : 44 00 00 00 43 99 00 00 ff ff 00 00 00 00 00 00 | D...C........... 01040010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01040020 : 00 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 | ................ 01040030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01040040 : 00 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 | ................ 01040050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01040060 : 00 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 | ................ 01040070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01042000 : 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01042010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01042080 : 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01043000 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01043010 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01043020 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01043030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01043040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01043050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01043060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01043070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01080000 : 00 01 00 00 ff ff 0f 00 19 00 1d 00 1d 00 00 4b | ...............K 01080010 : 57 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | W............... 01080020 : 03 00 04 00 4a 00 4b 00 5f 00 64 00 42 00 6e 00 | ....J.K._.d.B.n. 01080030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080040 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080050 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080060 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080070 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080080 : ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01080200 : 00 00 ff ff ff ff 0f 00 19 00 1d 00 1d 00 00 42 | ...............B 01080210 : 65 72 42 72 61 00 00 00 00 00 00 00 00 00 00 00 | erBra........... 01080220 : 04 00 08 00 0e 00 10 00 14 00 16 00 1e 00 22 00 | ..............". 01080230 : 1a 00 26 00 2a 00 32 00 ff ff ff ff ff ff ff ff | ..&.*.2......... 01080240 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080250 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080260 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080270 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080280 : ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01640000 : 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640010 : 00 00 02 01 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640020 : 00 00 03 02 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640030 : 00 00 04 03 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640040 : 00 00 ff 04 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01640800 : 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff | ................ 01640810 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640820 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640830 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640840 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640850 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640860 : ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640870 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640880 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140000 : 48 65 6c 6c 6f 21 00 00 00 00 00 00 00 00 00 00 | Hello!.......... 02140010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140080 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140090 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021400A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021400B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021400C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140100 : 57 65 6c 63 6f 6d 65 21 00 00 00 00 00 00 00 00 | Welcome!........ 02140110 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140120 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140130 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140140 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140150 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140160 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140170 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140180 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140190 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021401A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021401B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021401C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140200 : 54 68 61 6e 6b 20 79 6f 75 21 00 00 00 00 00 00 | Thank you!...... 02140210 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140220 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140230 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140240 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140250 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140260 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140270 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140280 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140290 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021402A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021402B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021402C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140300 : 47 6f 6f 64 20 62 79 65 21 00 00 00 00 00 00 00 | Good bye!....... 02140310 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140320 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140330 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140340 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140350 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140360 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140370 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140380 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140390 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021403A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021403B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021403C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140400 : 48 61 70 70 79 20 65 76 65 72 79 20 64 61 79 21 | Happy every day! 02140410 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140420 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140430 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140440 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140450 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140460 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140470 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140480 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140490 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021404A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021404B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021404C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02480000 : 01 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02480200 : 01 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02480210 : 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02480220 : 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 024C0000 : 00 00 0e 46 12 34 56 78 9a bc de 00 00 00 00 00 | ...F.4Vx........ 024C0010 : 00 00 00 00 00 00 00 00 00 20 20 20 20 20 20 00 | ......... . ----------------------------------------------------------------------------- 024C0C80 : 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 024C0D00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0ED0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 024C1000 : c0 5d 68 29 50 2d 9c 31 b0 36 c4 3b 3c 41 7c 47 | .]h)P-.1.6.;.@.A........... 01000610 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000620 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000630 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000640 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000650 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000660 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000670 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000680 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000690 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010006F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000700 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000710 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000720 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000730 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000740 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000750 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000760 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000770 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000780 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000790 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010007F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000800 : 64 00 42 00 43 00 44 00 45 00 46 00 47 00 48 00 | d.B.C.D.E.F.G.H. 01000810 : 49 00 ff ff ff ff ff ff ff ff ff ff ff ff ff ff | I............... 01000820 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000830 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000840 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000850 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000860 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000870 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000880 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000890 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010008F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000900 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000910 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000920 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000930 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000940 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000950 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000960 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000970 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000980 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000990 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010009F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A00 : 0e 00 10 00 14 00 16 00 1a 00 1e 00 22 00 4d 00 | ............".M. 01000A10 : 4e 00 4f 00 50 00 51 00 52 00 53 00 54 00 ff ff | N.O.P.Q.R.S.T... 01000A20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000A90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000AF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000B90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000BF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C00 : 60 00 61 00 64 00 65 00 42 00 43 00 44 00 45 00 | `.a.d.e.B.C.D.E. 01000C10 : 46 00 47 00 48 00 49 00 ff ff ff ff ff ff ff ff | F.G.H.I......... 01000C20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000C90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000CF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000D90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000DF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E00 : 23 00 24 00 25 00 26 00 27 00 28 00 29 00 2a 00 | #.$.%.&.'.(.).*. 01000E10 : 4d 00 4e 00 55 00 ff ff ff ff ff ff ff ff ff ff | M.N.U........... 01000E20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000E90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000ED0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000EF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000F90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01000FF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001000 : 64 00 65 00 42 00 43 00 44 00 45 00 46 00 47 00 | d.e.B.C.D.E.F.G. 01001010 : 48 00 49 00 ff ff ff ff ff ff ff ff ff ff ff ff | H.I............. 01001020 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001040 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001050 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001060 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001070 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001080 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001090 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010010F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001100 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001110 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001120 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001130 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001140 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001150 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001160 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001170 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001180 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001190 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010011F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001200 : 2b 00 2c 00 2d 00 2e 00 2f 00 30 00 31 00 32 00 | +.,.-.../.0.1.2. 01001210 : 56 00 57 00 58 00 ff ff ff ff ff ff ff ff ff ff | V.W.X........... 01001220 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001230 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001240 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001250 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001260 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001270 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001280 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001290 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010012F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001300 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001310 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001320 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001330 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001340 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001350 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001360 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001370 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001380 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001390 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010013F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001400 : 64 00 65 00 42 00 43 00 44 00 45 00 46 00 47 00 | d.e.B.C.D.E.F.G. 01001410 : 48 00 49 00 ff ff ff ff ff ff ff ff ff ff ff ff | H.I............. 01001420 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001430 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001440 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001450 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001460 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001470 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001480 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001490 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010014F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001500 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001510 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001520 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001530 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001540 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001550 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001560 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001570 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001580 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001590 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010015F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001600 : 33 00 35 00 36 00 37 00 38 00 3a 00 3b 00 3c 00 | 3.5.6.7.8.:.;.<. 01001610 : 3d 00 59 00 5a 00 5b 00 5c 00 5d 00 5e 00 ff ff | =.Y.Z.[.\.].^... 01001620 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001630 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001640 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001650 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001660 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001670 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001680 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001690 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010016F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001700 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001710 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001720 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001730 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001740 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001750 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001760 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001770 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001780 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001790 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010017F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001800 : 62 00 64 00 65 00 42 00 43 00 44 00 45 00 46 00 | b.d.e.B.C.D.E.F. 01001810 : 47 00 48 00 49 00 ff ff ff ff ff ff ff ff ff ff | G.H.I........... 01001820 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001830 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001840 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001850 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001860 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001870 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001880 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001890 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010018F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001900 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001910 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001920 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001930 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001940 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001950 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001960 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001970 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001980 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001990 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010019F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A00 : 68 00 6d 00 6c 00 6b 00 6a 00 69 00 ff ff ff ff | h.m.l.k.j.i..... 01001A10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001A90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001AF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001B90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001BF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C00 : 67 00 68 00 6d 00 6c 00 6b 00 6a 00 69 00 ff ff | g.h.m.l.k.j.i... 01001C10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001C90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001CF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001D90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001DF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E00 : 72 00 73 00 74 00 75 00 76 00 77 00 78 00 79 00 | r.s.t.u.v.w.x.y. 01001E10 : 7a 00 7b 00 7c 00 7d 00 7e 00 7f 00 80 00 81 00 | z.{.|.}.~....... 01001E20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001E90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001ED0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001EF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F00 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F10 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F20 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F30 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F40 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F50 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F60 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F70 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F80 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001F90 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FA0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FB0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FC0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FD0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FE0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01001FF0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002000 : 72 00 73 00 74 00 75 00 76 00 77 00 78 00 79 00 | r.s.t.u.v.w.x.y. 01002010 : 7a 00 7b 00 7c 00 7d 00 7e 00 7f 00 80 00 81 00 | z.{.|.}.~....... 01002020 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002040 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002050 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002060 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002070 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002080 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002090 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010020F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002100 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002110 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002120 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002130 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002140 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002150 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002160 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002170 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002180 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01002190 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021A0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021B0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021C0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021D0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021E0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 010021F0 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ ----------------------------------------------------------------------------- 01040000 : 44 00 00 00 43 99 00 00 ff ff 00 00 00 00 00 00 | D...C........... 01040010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01040020 : 00 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 | ................ 01040030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01040040 : 00 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 | ................ 01040050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01040060 : 00 00 00 00 00 00 00 00 ff ff 00 00 00 00 00 00 | ................ 01040070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01042000 : 0f 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01042010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01042080 : 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01043000 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01043010 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01043020 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01043030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01043040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01043050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01043060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01043070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01080000 : 00 01 00 00 ff ff 0f 00 19 00 1d 00 1d 00 00 4b | ...............K 01080010 : 57 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | W............... 01080020 : 03 00 04 00 4a 00 4b 00 5f 00 64 00 42 00 6e 00 | ....J.K._.d.B.n. 01080030 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080040 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080050 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080060 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080070 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080080 : ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01080200 : 00 00 ff ff ff ff 0f 00 19 00 1d 00 1d 00 00 42 | ...............B 01080210 : 65 72 42 72 61 00 00 00 00 00 00 00 00 00 00 00 | erBra........... 01080220 : 04 00 08 00 0e 00 10 00 14 00 16 00 1e 00 22 00 | ..............". 01080230 : 1a 00 26 00 2a 00 32 00 ff ff ff ff ff ff ff ff | ..&.*.2......... 01080240 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080250 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080260 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080270 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01080280 : ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01640000 : 00 00 01 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640010 : 00 00 02 01 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640020 : 00 00 03 02 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640030 : 00 00 04 03 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640040 : 00 00 ff 04 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 01640800 : 00 00 00 00 00 ff ff ff ff ff ff ff ff ff ff ff | ................ 01640810 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640820 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640830 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640840 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640850 : ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff | ................ 01640860 : ff ff ff ff 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640870 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 01640880 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140000 : 48 65 6c 6c 6f 21 00 00 00 00 00 00 00 00 00 00 | Hello!.......... 02140010 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140020 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140030 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140040 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140050 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140060 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140070 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140080 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140090 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021400A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021400B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021400C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140100 : 57 65 6c 63 6f 6d 65 21 00 00 00 00 00 00 00 00 | Welcome!........ 02140110 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140120 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140130 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140140 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140150 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140160 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140170 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140180 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140190 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021401A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021401B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021401C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140200 : 54 68 61 6e 6b 20 79 6f 75 21 00 00 00 00 00 00 | Thank you!...... 02140210 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140220 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140230 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140240 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140250 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140260 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140270 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140280 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140290 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021402A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021402B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021402C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140300 : 47 6f 6f 64 20 62 79 65 21 00 00 00 00 00 00 00 | Good bye!....... 02140310 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140320 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140330 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140340 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140350 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140360 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140370 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140380 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140390 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021403A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021403B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021403C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02140400 : 48 61 70 70 79 20 65 76 65 72 79 20 64 61 79 21 | Happy every day! 02140410 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140420 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140430 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140440 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140450 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140460 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140470 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140480 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02140490 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021404A0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021404B0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 021404C0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02480000 : 01 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 02480200 : 01 08 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02480210 : 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 02480220 : 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 024C0000 : 00 00 0e 46 12 34 56 78 9a bc de 00 00 00 00 00 | ...F.4Vx........ 024C0010 : 00 00 00 00 00 00 00 00 00 20 20 20 20 20 20 00 | ......... . ----------------------------------------------------------------------------- 024C0C80 : 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 024C0D00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0D90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DD0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0DF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E00 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E10 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E20 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E30 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E40 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E50 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E60 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E70 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E80 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0E90 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EA0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EB0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EC0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0ED0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EE0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ 024C0EF0 : 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 | ................ ----------------------------------------------------------------------------- 024C1000 : c0 5d 68 29 50 2d 9c 31 b0 36 c4 3b 3c 41 7c 47 | .]h)P-.1.6.;=0x20 and c<0x7f: t += chr(c) else: t += "." return( h + " | " + t) def isWriteCommand(p): if not isDataPacket(p): return False data = getData(p) return 'W' == chr(data[0]) def isDataPacket(p): return ("host" == p.usb.src) and ("USB.CAPDATA" in p) def getData(p): if not isDataPacket(p): return None return binascii.a2b_hex(p["USB.CAPDATA_RAW"].value) def dumpWriteCommand(p, nextaddr): if not isWriteCommand(p): return data = getData(p) res = struct.unpack(">cIB16sBB", data) addr = res[1] if nextaddr != addr: print((8+3+16*3+16+2)*"-") print("{:08X} : {}".format(addr, hexDump(res[3]))) return addr+16 cap = pyshark.FileCapture(sys.argv[1], include_raw=True, use_json=True) nextaddr = 0 for p in cap: if isWriteCommand(p): nextaddr = dumpWriteCommand(p, nextaddr) ================================================ FILE: doc/reveng/anytone/d878uv2/at_d878uv2_emulator.py ================================================ #!/usr/bin/env python3 # # Emulate anytone d878uv radio to customer programming software. # Send intercepted data stream over network to server script for further investigation. # # This script connects to a virtual com port COM26 which is connected via a virtual # null modem cable to the virtual com port COM18 which is used by the programming software. # This virtual ports and cable can be provided by the COM0COM tool. # # Linux users can use # socat -d -d pty,raw,echo=0,b4000000 pty,raw,echo=0,b4000000 # for emulating a virtual null modem cable. import serial import time import sys import struct # config filebase = 'codeplug' filecount = 0 comport = 'COM6' # connected to COM18 with com0com. use COM18 in CPS def hexDump(s): h = " ".join(map("{:02x}".format, s)) t = "" for i in range(len(s)): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." return( h + " | " + t) # parameters? if len(sys.argv) == 2: comport = sys.argv[1] elif len(sys.argv) >= 3: print("Usage: " + sys.argv[0] + ' [comport]') exit() # open serial port serialPort = None try: print("Trying comport " + comport) serialPort = serial.Serial(port = comport, baudrate=4000000, bytesize=8, timeout=1, stopbits=serial.STOPBITS_ONE) # 115200 921600 4000000 except (err): print('ERR: Could not open port ' + comport) print("Usage: " + sys.argv[0] + ' servername [comport]: ' + err) exit() out = None nextaddr = None # wait for data try: while 1: command = '' command = serialPort.read() while serialPort.in_waiting > 0: command += serialPort.read() # respond to command on com port if ( len(command) == 0 ): pass elif ( command == b'PROGRAM'): print("Program session requested.") resp = b'QX\x06' serialPort.write(resp) filename = "{}_{:04}.hex".format(filebase, filecount) out = open(filename, "w") nextaddr = None elif ( command == b'\x02' ): print("Device info requested.") resp = b'ID878UV2\x00V100\x00\x00\x06' serialPort.write(resp) elif ( command == b'R\x02\xfa\x00\x20\x10' ): print("Read special memory request.") resp = b'W\x02\xfa\x00\x20\x10\xff\xff\xff\xff\x00\x00\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x26\x06' serialPort.write(resp) elif ( command[0:4] == b'R\x02\xfa\x00' and command[5] == 16 ): # 0x02fa00.. print("Read local information.") resp = b'W\x02\xfa\x00' + bytes([command[4]]) + b'\x10' if ( command[4] == 0x00 ): resp += b'\x00\x00\x00\x00\x01\x01\x01\x00\x00\x01\x01\x20\x20\x20\x20\xff' elif ( command[4] == 0x10 ): # Radio Type resp += b'\x44\x38\x37\x38\x55\x56\x00\x01\x00\xff\xff\xff\xff\xff\xff\xff' elif ( command[4] == 0x30 ): # Serial Number resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x40 ): # Production Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x50 ): # Manucfacture Code resp += b'\x31\x32\x33\x34\x35\x36\x37\x38\xff\xff\xff\xff\xff\xff\xff\xff' elif ( command[4] == 0x60 ): # Maintained Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x70 ): # Dealer Code resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x80 ): # Stock Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x90 ): # Sell Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xa0 ): # Seller resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xb0 ): # Maintained Description resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xc0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xd0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xe0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xf0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' else: resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' resp = resp + bytes( [sum(resp[1:]) & 0xff] ) + b'\x06' #print(resp.hex()) serialPort.write(resp) elif ( command[0] == ord('W') ) : resp = b'\x06' # just ack serialPort.write(resp) res = struct.unpack(">cIB16sBB", command) addr = res[1] if nextaddr != addr: out.write((8+3+16*3+16+2)*"-" + "\n") out.write("{:08X} : {}\n".format(addr, hexDump(res[3]))) nextaddr = addr+16 elif ( command == b'END' ): print("End session.") resp = b'\x06' # just ack serialPort.write(resp) out.close() filecount += 1 elif ( command == b'UPDATE' ): # for firmware update the device has to be switched on while pressing PF3 (blue button on top) and PTT keys print("Start Firmware Update. Only useful if device is in update receiving mode. (Switch on while pressing PF3 (blue button on top) and PTT keys)") resp = b'\x06' # just ack serialPort.write(resp) elif ( command == b'\x18' ): print("Firmware Update Send Complete. Switch device on while pressing PF2 (top left side) and PTT keys to start installer.") resp = b'\x06' # just ack serialPort.write(resp) elif ( command[0] == 0x01 ): print("Firmware data.") resp = b'\x06' # just ack serialPort.write(resp) else: #print("> " + str(command)) pass finally: print('QRT') serialPort.close() ================================================ FILE: doc/reveng/baofeng/d6x2uv/dmr_6x2uv_emulator.py ================================================ #!/usr/bin/env python3 # # Emulate anytone d878uv radio to customer programming software. # Send intercepted data stream over network to server script for further investigation. # # This script connects to a virtual com port COM26 which is connected via a virtual # null modem cable to the virtual com port COM18 which is used by the programming software. # This virtual ports and cable can be provided by the COM0COM tool. # # Linux users can use # socat -d -d pty,raw,echo=0,b4000000 pty,raw,echo=0,b4000000 # for emulating a virtual null modem cable. import serial import time import sys import struct # config filebase = 'codeplug' filecount = 0 comport = 'COM6' # connected to COM18 with com0com. use COM18 in CPS def hexDump(s): h = " ".join(map("{:02x}".format, s)) t = "" for i in range(len(s)): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." return( h + " | " + t) # parameters? if len(sys.argv) == 3: filebase = sys.argv[1] comport = sys.argv[2] elif len(sys.argv) == 2: filebase = sys.argv[1] elif len(sys.argv) >3: print("Usage: " + sys.argv[0] + ' filebase [comport]') exit() # open serial port serialPort = None try: print("Trying comport " + comport) serialPort = serial.Serial(port = comport, baudrate=4000000, bytesize=8, timeout=1, stopbits=serial.STOPBITS_ONE) # 115200 921600 4000000 except (err): print('ERR: Could not open port ' + comport) print("Usage: " + sys.argv[0] + ' servername [comport]: ' + err) exit() out = None nextaddr = None # wait for data try: while 1: command = '' command = serialPort.read() while serialPort.in_waiting > 0: command += serialPort.read() # respond to command on com port if ( len(command) == 0 ): pass elif ( command == b'PROGRAM'): print("Program session requested.") resp = b'QX\x06' serialPort.write(resp) filename = "{}_{:04}.hex".format(filebase, filecount) out = open(filename, "w") nextaddr = None elif ( command == b'\x02' ): print("Device info requested.") resp = b'ID6X2UV\x00\x00V102\x00\x00\x06' serialPort.write(resp) elif ( command == b'R\x02\xfa\x00\x20\x10' ): print("Read special memory request.") resp = b'W\x02\xfa\x00\x20\x10\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x24\x06' serialPort.write(resp) elif ( command[0:4] == b'R\x02\xfa\x00' and command[5] == 16 ): # 0x02fa00.. print("Read local information.") resp = b'W\x02\xfa\x00' + bytes([command[4]]) + b'\x10' if ( command[4] == 0x00 ): resp += b'\x00\x00\x00\x03\x01\x01\x01\x00\x00\x01\x01\x20\x20\x20\x20\xff' elif ( command[4] == 0x10 ): # Radio Type resp += b'\x44\x38\x37\x38\x55\x56\x00\x01\x00\xff\xff\xff\xff\xff\xff\xff' elif ( command[4] == 0x30 ): # Serial Number resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x40 ): # Production Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x50 ): # Manucfacture Code resp += b'\x31\x32\x33\x34\x35\x36\x37\x38\xff\xff\xff\xff\xff\xff\xff\xff' elif ( command[4] == 0x60 ): # Maintained Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x70 ): # Dealer Code resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x80 ): # Stock Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0x90 ): # Sell Date resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xa0 ): # Seller resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xb0 ): # Maintained Description resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xc0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xd0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xe0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' elif ( command[4] == 0xf0 ): resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' else: resp += b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00' resp = resp + bytes( [sum(resp[1:]) & 0xff] ) + b'\x06' #print(resp.hex()) serialPort.write(resp) elif ( command[0] == ord('W') ) : resp = b'\x06' # just ack serialPort.write(resp) res = struct.unpack(">cIB16sBB", command) addr = res[1] if nextaddr != addr: out.write((8+3+16*3+16+2)*"-" + "\n") out.write("{:08X} : {}\n".format(addr, hexDump(res[3]))) nextaddr = addr+16 elif ( command == b'END' ): print("End session.") resp = b'\x06' # just ack serialPort.write(resp) out.close() filecount += 1 elif ( command == b'UPDATE' ): # for firmware update the device has to be switched on while pressing PF3 (blue button on top) and PTT keys print("Start Firmware Update. Only useful if device is in update receiving mode. (Switch on while pressing PF3 (blue button on top) and PTT keys)") resp = b'\x06' # just ack serialPort.write(resp) elif ( command == b'\x18' ): print("Firmware Update Send Complete. Switch device on while pressing PF2 (top left side) and PTT keys to start installer.") resp = b'\x06' # just ack serialPort.write(resp) elif ( command[0] == 0x01 ): print("Firmware data.") resp = b'\x06' # just ack serialPort.write(resp) else: #print("> " + str(command)) pass finally: print('QRT') serialPort.close() ================================================ FILE: doc/reveng/baofeng/dr1801/extract.py ================================================ #!/usr/bin/env python3 import pyshark import sys import binascii from abc import ABCMeta dev_addr = 1 if len(sys.argv) != 2: print("Usage: extract.py PCAPNG_FILE") def hexDump(s: bytes, prefix: str = "", addr: int = 0) -> str: """ Utility function to hex-dump binary data. """ N = len(s) Nb = N//16 if (N%16): Nb += 1 res = "" for j in range(Nb): a,b = j*16, min((j+1)*16,N) h = " ".join(map("{:02x}".format, s[a:b])) h += " "*(16-(b-a)) t = "" for i in range(a,b): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." res += (prefix + "{:08X} ".format(addr+16*j) + h + " | " + t + "\n") return res[:-1] def isFromHost(p): return ("host" == p.usb.src) and ((None==dev_addr) or (dev_addr == int(p.usb.device_address))) def isToHost(p): return (("host" == p.usb.dest) and ((None==dev_addr) or (dev_addr == int(p.usb.device_address)))) def isDataPacket(p): return ("USB.CAPDATA" in p) def getData(p): if not isDataPacket(p): return None return binascii.a2b_hex(p["USB.CAPDATA_RAW"].value) cap = pyshark.FileCapture(sys.argv[1], include_raw=True, use_json=True) nextaddr = 0 for p in cap: if isDataPacket(p): if isFromHost(p): print(hexDump(getData(p), "< ")) else: print(hexDump(getData(p), "> ")) #print(p) ================================================ FILE: doc/reveng/baofeng/dr1801/protocol.md ================================================ # DR-1801A6 (BF1801) - Protocol A new version of the popular DM-1801 by Baofeng. Is sold as DR-1801UV or DR-1801A6 and calls itself BF1801. The communication is based on USB CDC-ACM (serial over USB). This device uses the AUCTUS A6 chip and thus its protocol. For a detailed description, see jhart99s brilliant [article series](https://jhart99.com/a6-hidden-interface/) on this chip. ## Basic structure The first few packets exchanged at a read: ``` > aa 06 01 04 03 bb < aa 07 81 04 01 83 bb > aa 07 00 2b 00 2c bb < aa 07 80 2b 02 ae bb > aa 0a 01 00 00 01 c2 00 c8 bb < aa 15 81 00 01 00 01 dd 90 00 00 00 68 00 02 e6 9e 09 2d ef bb > aa 06 01 01 06 bb < ... only packets from device to host from now on with no structure. Just plain data. ``` The first few packets exchanged at a write: ``` > aa 06 01 04 03 bb < aa 07 81 04 01 83 bb > aa 07 00 2b 00 2c bb < aa 07 80 2b 02 ae bb > aa 12 01 02 00 01 00 01 da 8c 7f f6 00 01 c2 00 0d bb < aa 08 81 02 01 00 8a bb > .... Codeplug is written in many large packets with no structure, just data. < aa 09 81 03 01 00 00 8a bb < 00 | . < 00 | . < 00 | . ``` There appears to be two kind of messages command/response messages and status messages from the device. The status messages do not appear to be triggered by by the host. ## Command/response messages They start with `aah` and end with `bbh`. The general structure of command/response messages are ``` +---+---+---+---+---+...+---+---+---+ |aah|LEN| CMD | Params |CRC|bbh| +---+---+---+---+---+...+---+---+---+ ``` The `LEN` field contains the length of the entire packet, including start and stop bytes. The parameter field is optional and of variable length. The checksum is computed by simply xor-ing the entire payload byte-wise. The command field has the following sub-structure: ``` +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ |C/R| Command 15bit, big-endian | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ``` The most significant bit contains a flag, that is set for responses and cleared for requests. There are many commands already known see jhart99s [list of commands](https://jhart99.com/atecps/#cps-commands). ### 0000h Command -- Identify radio Not used by the manufacturer CPS but handled by the radio. This command is send without any parameters and the radio returns a string, identifying the radio. The response looks like ``` aa 3a 80 00 01 20 2c 42 46 31 38 30 31 2c 41 36 ..... ,B F1801,A6 2d 30 30 30 30 2d 58 58 58 58 2c 70 6f 72 74 61 -0000-XX XX,porta 62 6c 65 2c 31 33 36 4d 2d 31 37 34 4d 2c 34 30 ble,136M -174M,40 30 4d 2d 34 38 30 4d 2c fd bb 0M-480M, .. ``` The response payload starts with a status byte (0x01 success) followed by an ASCII string, containing the information about the radio. The information is separated by comma (,). The first entry is empty (single space), followed by the device name (BF1801), followed by the firmware version (weird number here), device class (portable) and the frequency ranges. There is likely space for three bands, however, the device only supports 136-174MHz and 400-480MHz. ### 0104h Command -- Enter CPS mode (?) First command send without any parameters irrespective of codeplug read or write. Response contains a single parameter `01h`, likely status byte like *success*. ### 002bh Command -- Check Programming Password Checks the programming password. Request contains parameter `00h`. Likely, the length of the password. Needs to be tested with a proper password. The Response contains a single byte `02h`. ### 0100h Command Only send before reading the codeplug. Contains 4bytes of parameters. Seen `00 01 c2 00` = 115200 big endian. Set UART speed?!? Response parameters are quiet long, e.g. `01 00 01 dd 90 00 00 00 68 00 02 e6 9e 09 2d`. 0x0001dd90 is the size of the codeplug file. Then, it appears like two uint32 numbers i.e., 0x00000068 and 0x0002e69e. These numbers do not change. The last two bytes, however, appear to be a checksum. ### 0101h Command Only send right before reading of the codeplug starts. There is no actual response to this request, the radio responds with the codeplug. No parameters send with this command. ### 0102h Command Only send before writing the codeplug. Contains 12bytes of parameters. E.g., `00 01 00 01 da 8c 7f f6 00 01 c2 00`, again contains 0001c200h=115200. As not the entire codeplug file is written to the device, `00 01 da 8c` may encode the amount to be written. Response only contains two bytes. E.g., `01 00`. ### 0103h Command Only seen after codeplug write. Contains 3 parameter bytes `01 00 00`. ================================================ FILE: doc/reveng/cotre/README.md ================================================ # Cotre DMR radio This is likely the cheapest DMR radio out there for about $45. The protocol appears to be a weird one: It is actually a stream of requests and responses that do not alternate. That is, the CPS bombards the radio with requests and the radio responses to it some time later. Consequently, it is harder to correlate a response to its request. Although such a protocol is common in networks (e.g., TCP) it is rather uncommon for USB devices as there is virtually no latency between request and response and thus, the bandwidth is not limited by the latency. Is this what happens when a network engineer writes embedded code? ## Packet format Requests and responses appear to share the same packet format. Please note, that due to the nature of the chosen transport, a packet may be split over two USB transfer packets. ``` 0 8 16 24 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ | Preamble, fixed to 0xad | 16 bit payload length, big endian | Payload, variable size ... +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... However, only a few bytes are actually transferred here. | Checksum? | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ``` ### Checksum The checksum appears to be a simple XOR of all payload bytes. ### Example The very first request sent to the device when reading is ``` ad 00 07 ff 04 03 00 00 00 01 f9 ``` The would then decode into * Preamble `ad` * Length `0007` * Payload `ff 04 03 00 00 00 01` * CRC `f9` = `ff ^ 04 ^ 03 ^ 01` ================================================ FILE: doc/reveng/cotre/extract.py ================================================ #!/usr/bin/env python3 import pyshark import struct import sys import binascii dev_addr = 6 def hexDump(s): h = " ".join(map("{:02x}".format, s)) t = "" for i in range(len(s)): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." return( h + " | " + t) def isFromHost(p): return ("host" == p.usb.src) and ((None==dev_addr) or (dev_addr == int(p.usb.device_address))) def isToHost(p): return ("host" == p.usb.dest) def isDataPacket(p): return ("USB.CAPDATA" in p) def getData(p): if not isDataPacket(p): return None return binascii.a2b_hex(p["USB.CAPDATA_RAW"].value) cap = pyshark.FileCapture(sys.argv[1], include_raw=True, use_json=True) nextaddr = 0 for p in cap: if isDataPacket(p): dump = hexDump(getData(p)) if isFromHost(p): print("> " + dump) else: print("< " + dump) ================================================ FILE: doc/reveng/gd77/callsign-db.md ================================================ # GD77 call-sign DB memory representation The encoding appears to be super simple: ## Header ``` 0 8 +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... + |'I'|'D'|'-'|'V'|'0'|'0'|'1'| 0 | N entries | Entries ... | +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ ... + ``` The first 8 bytes (at address 0x0000) contains the 0-terminated string `ID-V001`, followed by the number of DB entries encoded as a 32bit little endian integer. After this header all entries are just appended. These entries must be sorted by the DMR ID in ascending order. There is no index table written to the device. ## DB Entry Each entry of the table is encoded into 12bytes as ``` 0 8 +---+---+---+---+---+---+---+---+---+---+---+---+ | DMR ID | Name, ASCII | +---+---+---+---+---+---+---+---+---+---+---+---+ ``` where the DMR ID is encoded as 8 BCD numbers in litte-endian and the name is max 8 ASCII chars, 0-terminated and padded. ================================================ FILE: doc/reveng/gd77/dump.py ================================================ #!/usr/bin/env python3 from json import dump from re import S import pyshark import struct import sys import binascii device = None # "1.13.0" def hexDump(s: bytes, prefix="", addr=0) -> str: """ Utility function to hex-dump binary data. """ N = len(s) Nb = N//16 if (N%16): Nb += 1 res = "" for j in range(Nb): a,b = j*16, min((j+1)*16,N) h = " ".join(map("{:02x}".format, s[a:b])) h += " "*(16-(b-a)) t = "" for i in range(a,b): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." res += (prefix + "{:08X} ".format(addr+16*j) + h + " | " + t + "\n") return res[:-1] def isFromHost(p): b = ("host" == p.usb.src) if device: b = b and (device == p.usb.dst) return b def isFromDevice(p): b = ("host" == p.usb.dst) if device: b = b and (device == p.usb.src) return b def isRequest(p): return isFromHost(p) and ("SETUP DATA" == p.highest_layer) and (p.layers[-1].has_field("data_fragment")) def isResponse(p): return isFromDevice(p) and ("USB.CAPDATA" in p) def getData(p): if isRequest(p): return binascii.a2b_hex(p.layers[-1].data_fragment_raw[0]) elif isResponse(p): return binascii.a2b_hex(p["USB.CAPDATA_RAW"].value) return None class RawPayload: def __init__(self, payload): self._payload = payload def __len__(self): return len(self._payload) def dump(self, prefix="", addr=0): return hexDump(self._payload, prefix, addr) class Package: def __init__(self, payload): self._type, self._length = struct.unpack("=4: self._payload = ReadOperation(payload) elif (0x57 == payload[0]) and len(payload)>=4: self._payload = WriteOperation(payload) elif 0x41 == payload[0]: self._payload = ACK(payload) else: self._payload = RawPayload(payload) def type(self): return self._type def payload(self): return self._payload def dump(self, prefix=""): s = "" if 1 == self._type: s += prefix + "Request, len=0x{0:02X}".format(self._length) + "\n" elif 3 == self._type: s += prefix + "Response, len=0x{0:02X}".format(self._length) + "\n" s += self._payload.dump(prefix+" | ") return s class ReadOperation: def __init__(self, payload): self._addr, self._length = struct.unpack(">HB", payload[1:4]) self._payload = None if 4 < len(payload): self._payload = RawPayload(payload[4:]) def dump(self, prefix=""): s = prefix + "Read addr=0x{0:04X}, len=0x{1:02X}".format(self._addr, self._length) if None != self._payload: s += "\n" + self._payload.dump(prefix + " | ") return s class WriteOperation: def __init__(self, payload): self._addr, self._length = struct.unpack(">HB", payload[1:4]) self._payload = None if 4 < len(payload): self._payload = RawPayload(payload[4:]) def payload(self): return self._payload def addr(self): return self._addr def dump(self, prefix=""): s = prefix + "Write addr=0x{0:04X}, len=0x{1:02X}".format(self._addr, self._length) if None != self._payload: s += "\n" + self._payload.dump(prefix + " | ") return s class ACK: def __init__(self, payload): pass def dump(self, prefix=""): return prefix+"ACK" if 2 == len(sys.argv): cap = pyshark.FileCapture(sys.argv[1], include_raw=True, use_json=True) for p in cap: if isRequest(p) and len(getData(p))>=4: P = Package(getData(p)) print(P.dump(" > ")) print("") elif isResponse(p) and len(getData(p))>=4: P = Package(getData(p)) print(P.dump(" < ")) print("") elif isRequest(p): print(hexDump(getData(p), " > ")) elif isResponse(p): print(hexDump(getData(p), " < ")) elif (3 == len(sys.argv)) and ("write" == sys.argv[1]): print("Dump written memory from file {0}:".format(sys.argv[2])) cap = pyshark.FileCapture(sys.argv[2], include_raw=True, use_json=True) addr = 0 for p in cap: if isRequest(p) and len(getData(p))>=4: P = Package(getData(p)) if isinstance(P.payload(), WriteOperation): W = P.payload() if (addr != W.addr()): print("") print ("-"*80) print("") addr = W.addr() print(W.payload().dump(" ", addr)) addr += len(W.payload()) else: print("") print(P.dump(" > ")) print("") else: print("oops") ================================================ FILE: doc/reveng/pinspect/__init__.py ================================================ from packethandler import PacketHandler from devicefilter import DeviceFilter ================================================ FILE: doc/reveng/pinspect/auctus_a6.py ================================================ from devicefilter import DeviceFilter from streamhandler import StreamHandler from cdcacmfilter import CDCACMFilter from rawstreamdump import RawStreamDump from datagram import Datagram, RawDatagramDump, DatagramHandler from struct import unpack from utilities import hexDump from binascii import crc_hqx def crc16_simple_sum8(data:bytearray): return sum(data)&0xffff def crc16_simple_sum16(data:bytearray): s = 0 for i in range(len(data)//2): s += unpack(">H", data[2*i:(2*(i+1))])[0] return s&0xffff def crc16_simple_xor16(data:bytearray): s = 0 for i in range(len(data)//2): s ^= unpack(">H", data[2*i:(2*(i+1))])[0] return s def crc_summary(data:bytearray): print("CRC16-HQX : {:04X}".format(crc_hqx(data, 0))) print("CRC16-SUM8 : {:04X}".format(crc16_simple_sum8(data))) print("CRC16-SUM16: {:04X}".format(crc16_simple_sum16(data))) print("CRC16-XOR16: {:04X}".format(crc16_simple_xor16(data))) class AuctusA6Datagram(Datagram): def __init__(self, command, parameters, crc): self._isRequest = (0 == (command & 0x8000)) self._command = (command & 0x7fff) self._parameters = parameters self._crc = crc def isRequest(self): return self._isRequest def isResponse(self): return not self.isRequest() def command(self): return self._command def parameters(self): return self._parameters def format(self, prefix=""): res = prefix res += "type={}, command={:04X}, CRC={:02X}".format( ("Req" if self._isRequest else "Resp"), self._command, self._crc) if len(self._parameters): res += "\n"+hexDump(self._parameters, prefix=" "*len(prefix)+" |") return res def __repr__(self): return self.format() class AuctusA6Handler(StreamHandler): MODE_PASS = 0x00 MODE_DATAGRAM = 0x01 def __init__(self): super().__init__() self._tobuffer = bytearray() self._frombuffer = bytearray() self._mode = AuctusA6Handler.MODE_DATAGRAM self._dataLeft = None self._handler = [] def attach(self, handler): self._handler.append(handler) return self def handleToHost(self, data:bytearray): self._tobuffer.extend(data) if AuctusA6Handler().MODE_DATAGRAM == self._mode: while AuctusA6Handler().hasDatagram(self._tobuffer): dgram = AuctusA6Handler().popDatagram(self._tobuffer) self._processToHost(dgram) for handler in self._handler: if isinstance(handler, DatagramHandler): handler.handleToHost(dgram) else: self._dataLeft -= len(data) if 0 == self._dataLeft: self._mode = AuctusA6Handler.MODE_DATAGRAM for handler in self._handler: if isinstance(handler, StreamHandler): handler.handleToHost(self._tobuffer) crc_summary(self._tobuffer) self._tobuffer.clear() def handleFromHost(self, data:bytearray): self._frombuffer.extend(data) if AuctusA6Handler().MODE_DATAGRAM == self._mode: while AuctusA6Handler().hasDatagram(self._frombuffer): dgram = AuctusA6Handler().popDatagram(self._frombuffer) self._processFromHost(dgram) for handler in self._handler: if isinstance(handler, DatagramHandler): handler.handleFromHost(dgram) else: self._dataLeft -= len(data) if 0 == self._dataLeft: self._mode = AuctusA6Handler.MODE_DATAGRAM for handler in self._handler: if isinstance(handler, StreamHandler): handler.handleFromHost(self._frombuffer) crc_summary(self._frombuffer) self._frombuffer.clear() @staticmethod def hasDatagram(buffer: bytearray): if not buffer.startswith(b'\xaa'): return False if len(buffer) < 6: return False length, command = unpack(">xBH", buffer[:4]) return len(buffer) >= length @staticmethod def popDatagram(buffer: bytearray): length, command = unpack(">xBH", buffer[:4]) length, command, params, crc = unpack(">xBH{}sBx".format(length-6), buffer[:length]) del buffer[:length] return AuctusA6Datagram(command, params, crc) def _processFromHost(self, dgram:AuctusA6Datagram): if dgram.isRequest() and (0x0102 == dgram.command()): self._dataLeft, baudRate = unpack(">xxIxxI", dgram.parameters()) elif dgram.isRequest() and (0x0101 == dgram.command()): self._mode = AuctusA6Handler.MODE_PASS def _processToHost(self, dgram:AuctusA6Datagram): if dgram.isResponse() and (0x0102 == dgram.command()): self._mode = AuctusA6Handler.MODE_PASS elif dgram.isResponse() and (0x0100 == dgram.command()): self._dataLeft, = unpack(">xI10x", dgram.parameters()) if "__main__" == __name__: import sys stream = DeviceFilter(14).attach( CDCACMFilter().attach( AuctusA6Handler() .attach(RawDatagramDump()) .attach(RawStreamDump()))) stream.process(sys.argv[1]) ================================================ FILE: doc/reveng/pinspect/cdcacmfilter.py ================================================ from packethandler import PacketHandler from streamhandler import StreamHandler from binascii import a2b_hex class CDCACMFilter(PacketHandler): USB_URB_INTERRUPT = 0x01 USB_URB_BULK = 0x03 def __init__(self): super().__init__() pass def attach(self, handler:StreamHandler): self._handler.append(handler) return self def handle(self, packet): if (int(packet.usb.transfer_type,base=16) != CDCACMFilter.USB_URB_BULK) or ("USB.CAPDATA" not in packet): return for handler in self._handler: if PacketHandler.isFromHost(packet): handler.handleFromHost(a2b_hex(packet["USB.CAPDATA_RAW"].value)) else: handler.handleToHost(a2b_hex(packet["USB.CAPDATA_RAW"].value)) ================================================ FILE: doc/reveng/pinspect/datagram.py ================================================ from abc import ABCMeta, abstractmethod class Datagram(metaclass=ABCMeta): """ Base class of all data grams. """ def __init__(self): pass @abstractmethod def format(self, prefix=""): pass class DatagramHandler(metaclass=ABCMeta): def __init__(self): pass @abstractmethod def handleFromHost(self, dgram: Datagram): pass @abstractmethod def handleToHost(self, dgram: Datagram): pass class RawDatagramDump(DatagramHandler): def __init__(self): super().__init__() def handleFromHost(self, dgram:Datagram): print(dgram.format("< ")) def handleToHost(self, dgram:Datagram): print(dgram.format("> ")) ================================================ FILE: doc/reveng/pinspect/devicefilter.py ================================================ from packethandler import PacketHandler class DeviceFilter(PacketHandler): """ Filters by device address. """ def __init__(self, device: int = None): """ :param device: Specifies the USB device address of radio. """ super().__init__() self._source = device def handle(self, packet): if ((None == self._source) or (int(packet.usb.device_address) == self._source)): super().handle(packet) ================================================ FILE: doc/reveng/pinspect/packethandler.py ================================================ #!/usr/bin/env python3 """ THis module implements some basic packet handler, that can filter and process packets captured by wireshark. """ from abc import ABCMeta, abstractmethod from pyshark import FileCapture class PacketHandler(metaclass=ABCMeta): """ Abstract base class for all packet handlers. """ def __init__(self): self._handler = [] def attach(self, handler): self._handler.append(handler) return self @staticmethod def isFromHost(p): return "host" == p.usb.src @staticmethod def isToHost(p): return "host" == p.usb.dest def handle(self, packet): for handler in self._handler: handler.handle(packet) def process(self, filename:str): for packet in FileCapture(filename, include_raw=True, use_json=True): self.handle(packet) ================================================ FILE: doc/reveng/pinspect/rawstreamdump.py ================================================ from utilities import hexDump from streamhandler import StreamHandler class RawStreamDump(StreamHandler): def __init__(self): super().__init__() self._toAddr = 0 self._fromAddr = 0 def handleFromHost(self, data): print(hexDump(data, "< ", self._fromAddr)) self._fromAddr+=len(data) def handleToHost(self, data): print(hexDump(data, "> ", self._toAddr)) self._toAddr+=len(data) ================================================ FILE: doc/reveng/pinspect/streamhandler.py ================================================ from abc import ABCMeta, abstractmethod class StreamHandler(metaclass=ABCMeta): def __init__(self): pass @abstractmethod def handleFromHost(self, data:bytearray): pass @abstractmethod def handleToHost(self, data:bytearray): pass ================================================ FILE: doc/reveng/pinspect/utilities.py ================================================ def hexDump(s: bytes, prefix: str = "", addr: int = 0) -> str: """ Utility function to hex-dump binary data. """ N = len(s) Nb = N//16 if (N%16): Nb += 1 res = "" for j in range(Nb): a,b = j*16, min((j+1)*16,N) h = " ".join(map("{:02x}".format, s[a:b])) h += " "*(16-(b-a)) t = "" for i in range(a,b): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." res += (prefix + "{:08X} ".format(addr+16*j) + h + " | " + t + "\n") return res[:-1] ================================================ FILE: doc/reveng/radioddity/gd73/README.md ================================================ # Reverse engineering of the GD73 serial protocol See `protocol.md` for details. ================================================ FILE: doc/reveng/radioddity/gd73/extract.py ================================================ #!/usr/bin/env python3 import pyshark import struct import sys import binascii device = "1.255" device_in = "{}.2".format(device) device_out = "{}.1".format(device) def hexDump(s: bytes, prefix:str="", addr:int=0, compact:bool=False) -> str: """ Utility function to hex-dump binary data. """ N = len(s) Nb = N//16 if (N%16): Nb += 1 res = "" last_line = None compressing = False for j in range(Nb): a,b = j*16, min((j+1)*16,N) line = s[a:b] if (not compact) or (last_line != line): h = " ".join(map("{:02x}".format, line)) h += " "*(16-(b-a)) t = "" for i in range(a,b): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." res += (prefix + "{:08X} ".format(addr+16*j) + h + " | " + t + "\n") last_line = line compressing = False elif not compressing: res += (prefix + "*\n") compressing = True return res[:-1] def isDataPacket(p): return ("USB.CAPDATA" in p) def isFromHost(p): return (("host" == p.usb.src) and (device_in == p.usb.dst)) def isFromDevice(p): return (("host" == p.usb.dst) and (device_out == p.usb.src)) def getData(p): if not isDataPacket(p): return None return binascii.a2b_hex(p["USB.CAPDATA_RAW"].value) class Packet: def __init__(self, data:bytes): self._flag, self._cmd, self._sub, self._pcrc, size = struct.unpack(" self._crc: self._crc += 0xffff; self._crc -= v if len(data)%2: v = struct.unpack("B", data[-1:])[0] if v > self._crc: self._crc +=0xffff; self._crc -= v; def __repr__(self): if 0 == len(self._payload): return "Packet flag={:02x}, cmd={:02x}, sub={:02x}, crc={:04x}.".format(self._flag, self._cmd, self._sub, self._crc) return "Packet flag={:02x}, cmd={:02x}, sub={:02x}, crc={:04x}\n{}".format(self._flag, self._cmd, self._sub, self._crc, hexDump(self._payload, " ")) cap = pyshark.FileCapture(sys.argv[1], include_raw=True, use_json=True) nextaddr = 0 for p in cap: if isDataPacket(p): print(Packet(getData(p))) ================================================ FILE: doc/reveng/radioddity/gd73/protocol.md ================================================ # GD73 Serial Protocol ## General Request/Response Frame Format ``` 0 1 2 3 4 5 6 7 8 n-2 n-1 bytes +---+---+---+---+---+---+---+---+---+...+---+---+ 00 |68h|FLG|CMD|SUB| CRC? | Size | Payload |10h| +---+---+---+---+---+---+---+---+---+...+---+---+ ``` | Bytes | Value | Description | |:-------|:------|:------------| | 0 | 68 | Start of frame byte. Fixed. | | 1 | 0f | Flags? Fixed. | | 2:3 | 01 04 | First command send on read. Probably *enter prog mode*. No payload. | | | 00 02 | Response to *enter prog mode* | | | 01 02 | Start read. | | | 00 00 | Read response, contains sequence number and 35h bytes codeplug data. | | | 04 01 | Read ACK, triggers read of next segment. | | | 01 00 | Write request, usually 55bytes payload. | | | 00 01 | Write ACK. | | 4:5 | | CRC | | 6:7 | | The payload size in little endian. | | 8:(n-2)| | The request/response payload. | | n-1 | 10 | The end-of-frame byte. | ### CRC The CRC is computed over the entire packet, including start and end bytes. For the computation, the packet is interpreted as a sequence of little-endian unsigned 16-bit integer. If the packet does not align with 16bits, it gets 0-padded to a multiple of 16bit. Let `v[i]` the i-th of `N` unsigned 16bit integer formed by the 0-padded packet, the CRC is then computed as ``` uint32_t crc = 0xffff; for (int i=0; i | | <----- Response 0000 + Data ------- | | --------- ACK seq. 0000 ----------> | | <----- Response 0001 + Data ------- | | --------- ACK seq. 0001 ----------> | ... ... | <----- Response 0a42 + Data ------- | | --------- ACK seq. 0a42 ----------> | | <----- Response 0a43 + Data ------- | ``` ## Write request (0100h) The write request sends a sequence number and codeplug segment to the device. The device then ACKs the written segment. ================================================ FILE: doc/reveng/retevis/rt84/extract.py ================================================ #!/usr/bin/env python3 import struct import sys def hexDump(s: bytes, prefix="", addr=0, compact=False) -> str: """ Utility function to hex-dump binary data. """ N = len(s) Nb = N//16 if (N%16): Nb += 1 res = "" last_line = None compressing = False for j in range(Nb): a,b = j*16, min((j+1)*16,N) line = s[a:b] if (not compact) or (last_line != line): h = " ".join(map("{:02x}".format, line)) h += " "*(16-(b-a)) t = "" for i in range(a,b): c = s[i] if c>=0x20 and c<0x7f: t += chr(c) else: t += "." res += (prefix + "{:08X} ".format(addr+16*j) + h + " | " + t + "\n") last_line = line compressing = False elif not compressing: res += (prefix + "*\n") compressing = True return res[:-1] if 2 > len(sys.argv): print("USAGE: extract.py CODEPLUG.rdt") rdt_filename = sys.argv[1] # list of sections: (file offset, size, destination address) sections = [ (0x002225, 0x03e000, 0x002000), (0x040230, 0x090000, 0x110000), ] # Read all data = open(rdt_filename, "br").read() for section in sections: offset, length, addr = section print(hexDump(data[offset:(offset+length)], "", addr, True)) ================================================ FILE: examples/BER AirBand.yaml ================================================ --- version: 0.14.0 settings: introLine1: Berlin Air introLine2: "" micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 radioIDs: [] contacts: [] groupLists: [] channels: - am: id: ch1 name: BER EM rxOnly: true rxFrequency: 121.5 MHz txFrequency: ~ power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - am: id: ch2 name: BER ATIS rxOnly: true rxFrequency: 123.78 MHz txFrequency: ~ power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - am: id: ch3 name: BER ARR rxOnly: true rxFrequency: 121.13 MHz txFrequency: ~ power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - am: id: ch4 name: BER Tower S rxOnly: true rxFrequency: 118.805 MHz txFrequency: ~ power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - am: id: ch5 name: BER Tower N rxOnly: true rxFrequency: 120.03 MHz txFrequency: ~ power: ! "" timeout: ! "" vox: ! "" squelch: ! "" zones: - id: zone1 name: BER Air A: [ch1, ch2, ch3, ch4, ch5] B: [] commercial: encryptionKeys: [] sms: format: DMR templates: [] ... ================================================ FILE: examples/kw.conf ================================================ # # Configuration generated So. Juni 27 17:49:02 2021 by qdrm, version 0.8.0 # see https://dm3mat.darc.de/qdmr for details. # # Unique DMR ID and name (quoted) of this radio. ID: 2621370 Name: "DM3MAT" # Text displayed when the radio powers up (quoted). IntroLine1: "qDMR" IntroLine2: "DM3MAT" # Microphone amplification, value 1..10: MICLevel: 6 # Speech-synthesis ('On' or 'Off'): Speech: Off # Table of digital channels. # 1) Channel number. # 2) Name in quotes. E.g., "NAME" # 3) Receive frequency in MHz # 4) Transmit frequency or +/- offset in MHz # 5) Transmit power: Max, High, Mid, Low or Min # 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 # 14) GPS System: - or index in GPS table. # 15) Roaming zone: -, + or index in roaming-zone table. # 16) Radio ID: -, index in radio ID list above. - means default ID. # Digital Name Receive Transmit Power Scan TOT RO Admit CC TS RxGL TxC GPS Roam ID 1 "DB0LDS TS1" 439.56250 -7.60000 High - - - Color 1 1 1 6 - + - # Local 2 "Sa/Th DB0LDS TS1" 439.56250 -7.60000 High - - - Color 1 1 3 8 - + - # Sa/Th 3 "TG8 DB0LDS TS2" 439.56250 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 4 "TG9 DB0LDS TS2" 439.56250 -7.60000 High 1 - - Color 1 2 2 6 - + - # Local 5 "BB DB0LDS TS2" 439.56250 -7.60000 High 1 - - Color 1 2 2 7 - + - # Bln/Brb 6 "HRV DB0LDS TS1" 439.56250 -7.60000 High 1 - - Color 1 1 7 11 - + - # HamRadio Village 7 "DM0TZN TS1" 438.82500 -7.60000 High - - - Color 1 1 1 1 - + - # WW 8 "TG8 DM0TZN TS2" 438.82500 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 9 "TG9 DM0TZN TS2" 438.82500 -7.60000 High - - - Color 1 2 2 6 - + - # Local 10 "BB DM0TZN TS2" 438.82500 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 11 "DB0LOS TS1" 438.47500 -7.60000 High - - - Color 1 1 1 6 - + - # Local 12 "R/TG9 DB0LOS TS2" 438.47500 -7.60000 High - - - Color 1 2 2 6 - + - # Local 13 "R/TG9 DM0TT TS1" 439.08750 -7.60000 High - - - Color 1 1 2 6 - + - # Local 14 "TG8 DM0TT TS2" 439.08750 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 15 "TG9 DM0TT TS2" 439.08750 -7.60000 High - - - Color 1 2 2 6 - + - # Local 16 "BB DM0TT TS2" 439.08750 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 17 "TG9 DB0KK TS1" 439.53750 -7.60000 High - - - Color 1 1 2 6 - + - # Local 18 "BB DB0KK TS2" 439.53750 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 19 "DB0OUD TS1" 438.62500 -7.60000 High - - - Color 1 1 1 1 - + - # WW 20 "TG8 DB0OUD TS2" 438.62500 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 21 "TG9 DB0OUD TS2" 438.62500 -7.60000 High - - - Color 1 2 2 6 - + - # Local 22 "BB DB0OUD TS2" 438.62500 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 23 "TG9 DB0TU TS1" 439.57500 -7.60000 High - - - Color 1 1 2 6 - + - # Local 24 "BB DB0TU TS2" 439.57500 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 25 "DM0MOT TS1" 439.52500 -7.60000 High - - - Color 1 1 1 1 - + - # WW 26 "TG8 DM0MOT TS2" 439.52500 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 27 "TG9 DM0MOT TS2" 439.52500 -7.60000 High - - - Color 1 2 2 6 - + - # Local 28 "BB DM0MOT TS2" 439.52500 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 29 "DB0TA TS1" 439.47500 -7.60000 High - - - Color 1 1 1 6 - + - # Local 30 "TG8 DB0TA TS2" 439.47500 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 31 "TG9 DB0TA TS2" 439.47500 -7.60000 High - - - Color 1 2 2 6 - + - # Local 32 "BB DB0TA TS2" 439.47500 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 33 "DB0FX TS1" 439.45000 -7.60000 High - - - Color 1 1 1 6 - + - # Local 34 "TG8 DB0FX TS2" 439.45000 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 35 "TG9 DB0FX TS2" 439.45000 -7.60000 High - - - Color 1 2 2 6 - + - # Local 36 "BB DB0FX TS2" 439.45000 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 37 "DB0PDM TS1" 438.40000 -7.60000 High - - - Color 1 1 1 6 - + - # Local 38 "TG8 DB0PDM TS2" 438.40000 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 39 "TG9 DB0PDM TS2" 438.40000 -7.60000 High - - - Color 1 2 2 6 - + - # Local 40 "BB DB0PDM TS2" 438.40000 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 41 "DB0BRB TS1" 439.48750 -7.60000 High - - - Color 1 1 1 6 - + - # Local 42 "TG8 DB0BRB TS2" 439.48750 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 43 "TG9 DB0BRB TS2" 439.48750 -7.60000 High - - - Color 1 2 2 6 - + - # Local 44 "BB DB0BRB TS2" 439.48750 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 45 "DB0SPN TS1" 439.48750 -7.60000 High - - - Color 1 1 1 6 - + - # Local 46 "R/TG9 DB0SPN TS2" 439.48750 -7.60000 High - - - Color 1 1 2 6 - + - # Local 47 "DB0NLS TS1" 439.51250 -7.60000 High - - - Color 1 1 1 6 - + - # Local 48 "R/TG9 DB0NLS TS2" 439.51250 -7.60000 High - - - Color 1 2 2 6 - + - # Local 49 "DB0LS TS1" 438.22500 -7.60000 High - - - Color 1 1 1 6 - + - # Local 50 "TG8 DB0LS TS2" 438.22500 -7.60000 High - - - Color 1 2 2 5 - + - # Regional 51 "TG9 DB0LS TS2" 438.22500 -7.60000 High - - - Color 1 2 2 6 - + - # Local 52 "BB DB0LS TS2" 438.22500 -7.60000 High - - - Color 1 2 2 7 - + - # Bln/Brb 53 "DM0LC TS1" 439.38750 -7.60000 High - - - Color 1 1 1 6 - + - # Local 54 "TG8 DM0LC TS2" 439.38750 -7.60000 High - - - Color 1 2 3 5 - + - # Regional 55 "TG9 DM0LC TS2" 439.38750 -7.60000 High - - - Color 1 2 3 6 - + - # Local 56 "Sa/Th DM0LC TS2" 439.38750 -7.60000 High - - - Color 1 2 3 8 - + - # Sa/Th 57 "BB DM0LC TS1" 439.38750 -7.60000 High - - - Color 1 1 2 7 - + - # Bln/Brb 58 "DB0FLW TS1" 439.53750 -7.60000 High - - - Color 1 1 1 6 - + - # Local 59 "TG8 DB0FLW TS2" 439.53750 -7.60000 High - - - Color 1 2 3 5 - + - # Regional 60 "TG9 DB0FLW TS2" 439.53750 -7.60000 High - - - Color 1 2 3 6 - + - # Local 61 "Sa/Th DB0FLW TS2" 439.53750 -7.60000 High - - - Color 1 2 3 8 - + - # Sa/Th 62 "DB0LE TS1" 439.57500 -7.60000 High - - - Color 1 1 1 6 - + - # Local 63 "R/TG9 DB0LE TS2" 439.57500 -7.60000 High - - - Color 1 2 3 6 - + - # Local 64 "DL DB0AFZ TS1" 439.97500 -9.40000 High - - - Color 1 1 1 3 - + - # DL 65 "BB DB0AFZ TS1" 439.97500 -9.40000 High - - - Color 1 1 2 7 - + - # Bln/Brb 66 "TG9 DB0AFZ TS2" 439.97500 -9.40000 High - - - Color 1 2 5 6 - + - # Local 67 "Hes DB0AFZ TS2" 439.97500 -9.40000 High - - - Color 1 2 5 10 - + - # Hessen 68 "DMR S0" 433.45000 433.45000 High - - - Free 1 1 6 12 - + - # All Call 69 "DMR S1" 433.61200 433.61200 High - - - Free 1 1 6 12 - + - # All Call 70 "DMR S2" 433.62500 433.62500 High - - - Free 1 1 6 12 - + - # All Call 71 "DMR S3" 433.63800 433.63800 High - - - Free 1 1 6 12 - + - # All Call 72 "DMR S4" 433.65000 433.65000 High - - - Free 1 1 6 12 - + - # All Call 73 "DMR S5" 433.66300 433.66300 High - - - Free 1 1 6 12 - + - # All Call 74 "DMR S6" 433.67500 433.67500 High - - - Free 1 1 6 12 - + - # All Call 75 "DMR S7" 433.68800 433.68800 High - - - Free 1 1 6 12 - + - # All Call # Table of analog channels. # 1) Channel number. # 2) Name in quotes. # 3) Receive frequency in MHz # 4) Transmit frequency or +/- offset in MHz # 5) Transmit power: Max, High, Mid, Low or Min # 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: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 # 11) CTCSS/DCS for receive: frequency (e.g, 67.0), DCS number (e.g., n023 or i023) or '-' to disable # 12) CTCSS/DCS for transmit: frequency (e.g, 67.0), DCS number (e.g., n023 or i023) or '-' to disable # 13) Bandwidth in kHz: 12.5, 25 # 14) APRS system: - or index # Analog Name Receive Transmit Power Scan TOT RO Admit Squelch RxTone TxTone Width APRS 76 "DB0LDS" 439.56250 -7.60000 High - - - - 1 67.0 67.0 12.5 - 77 "DB0RAG" 439.30000 -7.60000 High - - - - 1 - - 12.5 - 78 "DB0LUD" 438.57500 -7.60000 High - - - - 1 67.0 67.0 12.5 - 79 "DB0SP-2" 145.60000 -0.60000 High - - - - 1 - - 12.5 - 80 "DB0SP-70" 439.42500 -7.60000 High - - - - 1 - - 12.5 - 81 "DB0ZOD-2" 145.66250 -0.60000 High - - - - 1 - - 12.5 - 82 "DB0ZOD-70" 438.72500 -7.60000 High - - - - 1 - - 12.5 - 83 "DB0BRL" 145.72500 -0.60000 High - - - - 1 - - 12.5 - 84 "DB0BLO" 439.27500 -7.60000 High - - - - 1 - - 12.5 - 85 "DB0SX" 439.05000 -7.60000 High - - - - 1 - - 12.5 - 86 "DB0TA" 439.12500 -7.60000 High - - - - 1 - - 12.5 - 87 "DB0PDM" 145.67500 -0.60000 High - - - - 1 - - 12.5 - 88 "DB0CBS" 438.85000 -7.60000 High - - - - 1 - - 12.5 - 89 "DB0AF" 438.63750 -7.60000 High - - - - 1 - - 12.5 - 90 "DB0NFL" 438.95000 -7.60000 High - - - - 1 - - 12.5 - 91 "DB0LMM" 439.30000 -7.60000 High - - - - 1 - - 12.5 - 92 "DM0LEI" 439.15000 -7.60000 High - - - - 1 - - 12.5 - 93 "DB0LEI" 145.77500 -0.60000 High - - - - 1 - - 12.5 - 94 "DB0SML" 439.27500 -7.60000 High - - - - 1 - - 12.5 - 95 "DB0LSA" 145.66250 -0.60000 High - - - - 1 - - 12.5 - 96 "DB0LE" 438.78750 -7.60000 High - - - - 1 - - 12.5 - 97 "DOK Y07" 144.67500 144.67500 High - - - Free 1 - - 12.5 - 98 "DOK D20" 144.65000 144.65000 High - - - Free 1 - - 12.5 - 99 "DOK D23" 145.37500 145.37500 High - - - Free 1 - - 12.5 - 100 "DOK S31" 144.72500 144.72500 High - - - Free 1 - - 12.5 - 101 "S20" 145.30000 145.30000 High - - - Free 1 - - 12.5 - 102 "2m Mobil" 145.50000 145.50000 High - - - Free 1 - - 12.5 - 103 "70cm Mobil" 433.50000 433.50000 High - - - Free 1 - - 12.5 - 104 "APRS" 144.80000 144.80000 High - - - Free 1 - - 12.5 2 105 "ISS APRS" 145.82500 145.82500 High - - - Free 1 - - 12.5 - 106 "ISS FM Rep" 437.80000 -291.81000 High - - - - 1 - 67.0 12.5 - 107 "CAS-3H FM" 437.20000 -292.85000 High - - - - 1 - - 12.5 - 108 "PO-101 FM" 145.90000 437.50000 High - - - - 1 - - 12.5 - 109 "SO-50 FM" 436.79000 -290.94000 High - - - - 1 - 67.0 12.5 - 110 "AO-92 FM" 145.88000 435.35000 High - - - - 1 - 67.0 12.5 - 111 "AO-91 FM" 145.96000 435.25000 High - - - - 1 - 67.0 12.5 - 112 "U0" 430.20000 430.20000 Low - - - - 1 - - 12.5 - 113 "U1" 430.22500 430.22500 Low - - - - 1 - - 12.5 - 114 "U2" 430.25000 430.25000 Low - - - - 1 - - 12.5 - 115 "U3" 430.27500 430.27500 Low - - - - 1 - - 12.5 - 116 "PMR 1" 446.00625 446.00625 Low - - - Free 1 - - 12.5 - 117 "PMR 2" 446.01875 446.01875 Low - - - Free 1 - - 12.5 - 118 "PMR 3" 446.03125 446.03125 Low - - - Free 1 - - 12.5 - 119 "PMR 4" 446.04375 446.04375 Low - - - Free 1 - - 12.5 - 120 "PMR 5" 446.05625 446.05625 Low - - - Free 1 - - 12.5 - 121 "PMR 6" 446.06875 446.06875 Low - - - Free 1 - - 12.5 - 122 "PMR 7" 446.08125 446.08125 Low - - - Free 1 - - 12.5 - 123 "PMR 8" 446.09375 446.09375 Low - - - Free 1 - - 12.5 - 124 "PMR 9" 446.10625 446.10625 Low - - - Free 1 - - 12.5 - 125 "PMR 10" 446.11875 446.11875 Low - - - Free 1 - - 12.5 - 126 "PMR 11" 446.13125 446.13125 Low - - - Free 1 - - 12.5 - 127 "PMR 12" 446.14375 446.14375 Low - - - Free 1 - - 12.5 - 128 "PMR 13" 446.15625 446.15625 Low - - - Free 1 - - 12.5 - 129 "PMR 14" 446.16875 446.16875 Low - - - Free 1 - - 12.5 - 130 "PMR 15" 446.18125 446.18125 Low - - - Free 1 - - 12.5 - 131 "PMR 16" 446.19375 446.19375 Low - - - Free 1 - - 12.5 - # Table of channel zones. # 1) Zone number # 2) Name in quotes. # 3) VFO: Either A or B. # 4) List of channels: numbers and ranges (N-M) separated by comma # Zone Name VFO Channels 1 "KW" A 1,4,5,2,6,11,12,13,15,16,76,77,78,79 1 "KW" B 97,101,102,103,68,69,70,71,72,73,74,75 2 "TH Wildau" A 112,113,114,115 3 "Hessen" A 64,66,67 3 "Hessen" B 102,68,69,70,71,72,73,74,75 4 "Berlin" A 16,18,22,24,28,32,36,79,80,81,82,83,84,85,86 4 "Berlin" B 98,99,102,103,68,69,70,71,72,73,74,75 5 "Potsdam" A 37,38,39,40,41,42,43,44,79,80,87 5 "Potsdam" B 102,103,68,69,70,71,72,73,74,75 6 "Cottbus" A 45,46,47,48,49,50,51,52,88,89,90 6 "Cottbus" B 102,103,68,69,70,71,72,73,74,75 7 "Leipzig" A 53,55,56,57,58,60,61,62,63,91,92,93,94,95,96 7 "Leipzig" B 100,102,103,68,69,70,71,72,73,74,75 8 "Sat" A 106,111,110,109,108,107 8 "Sat" B 105,106,111,110,109,108,107 9 "PMR" A 116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131 9 "PMR" B 116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131 # Table of scan lists. # 1) Scan list number. # 2) Name in quotes. # 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, Sel or ranges (N-M) separated by comma # Scanlist Name PCh1 PCh2 TxCh Channels 1 "KW" - - - 4,5,76,77,97,102,68,112 2 "BerBra" - - - 5,10,16,18,22,24,32,36,28,40,44,52 # Table of GPS systems. # 1) GPS system ID # 2) Name in quotes. # 3) Destination contact ID. # 4) Update period: period in ms # 5) Revert channel ID or '-'. # GPS Name Dest Period Revert 1 "BM ARPS" 21 300 - # Table of APRS systems (there is usually only one). # 1) APRS system ID # 2) Name in quotes. # 3) Transmit channel ID. # 4) Transmit period in seconds. # 5) Your (source) call and SSID as CALLSIGN-SSID. # 6) Destination call and SSID as CALLSIGN-SSID. # 7) Path string or '-'. # 7) Icon name. # 8) Message, optional message in quotes. # APRS Name Channel Period Source Destination Path Icon Message 2 "APRS APAT81" 104 300 DM3MAT-7 APAT81-0 "WIDE1-1WIDE2-1" "Jogger" "Y07, QRG 144.675" # Table of contacts. # 1) Contact number. # 2) Name in quotes. # 3) Call type: Group, Private, All or DTMF # 4) Call ID: 1...16777215 or string with DTMF number # 5) Call receive tone: -, + # Contact Name Type ID RxTone 1 "WW" Group 91 - 2 "EU" Group 92 - 3 "DL" Group 262 - 4 "DL MM" Group 263 - 5 "Regional" Group 8 - 6 "Local" Group 9 - 7 "Bln/Brb" Group 2621 - 8 "Sa/Th" Group 2629 - 9 "SA/MV" Group 2620 - 10 "Hessen" Group 2626 - 11 "HamRadio Village" Group 3177826 - 12 "All Call" All 16777215 - 13 "Simplex TG99" Group 99 - 14 "Cl Brandenburg" Group 26209 - 15 "Cl Berlin" Group 26212 - 16 "Refl Brandenburg" Private 4044 - 17 "Refl Berlin" Private 4016 - 18 "BM SMS" Private 262993 - 19 "BM DAPNET" Private 262994 - 20 "BM Parrot" Private 262997 - 21 "BM APRS" Private 262999 - 22 "DG2RON Ronny" Private 2621246 + 23 "DM9KS Silvio" Private 2621045 + 24 "DM3MAT Hannes" Private 2621370 + 25 "DL2BQF Thomas" Private 2621437 + 26 "DL7JOM Olaf" Private 2621004 + 27 "DG1RPH Peter" Private 2621528 + 28 "DO1RPH Richard" Private 2634160 - # Table of group lists. # 1) Group list number. # 2) Name in quotes. # 3) List of contacts: numbers and ranges (N-M) separated by comma # Grouplist Name Contacts 1 "DL" 1,2,3 2 "Berlin/Brand" 6,5,7 3 "Sa/Th" 5,6,8 4 "SA/MV" 5,6,9 5 "Hessen" 5,6,10 6 "Simplex" 6,13,12 7 "Ham Radio Village" 11 # Table of roaming zones. # 1) Roaming zone number # 2) Name in quotes. # 3) List of digital channels: numbers and ranges (N-M) separated by comma # Roaming Name Channels 1 "Berlin/Brand" 3,8,14,18,20,24,26,30,34,38,42,50 ================================================ FILE: examples/kw.yaml ================================================ --- version: 0.14.1 settings: introLine1: qDMR introLine2: DM3MAT micLevel: 6 speech: false power: High squelch: 1 vox: off defaultID: id1 gnss: fixedPositionEnabled: false fixedPosition: "" systems: - GPS units: Metric dmr: privateCallMatch: true groupCallMatch: true privateCallHangTime: 5 s groupCallHangTime: 3 s sendTalkerAlias: false talkerAliasEncoding: Iso8 preamble: 100 ms radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: WW, ring: false, type: GroupCall, number: 91} - dmr: {id: cont2, name: EU, ring: false, type: GroupCall, number: 92} - dmr: {id: cont3, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont4, name: DL MM, ring: false, type: GroupCall, number: 263} - dmr: {id: cont5, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont6, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont7, name: Bln/Brb, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont8, name: HamRadio Village, ring: false, type: GroupCall, number: 98638} - dmr: {id: cont9, name: Chaoswelle, ring: false, type: GroupCall, number: 26223} - dmr: {id: cont10, name: All Call, ring: false, type: AllCall, number: 16777215} - dmr: {id: cont11, name: Simplex TG99, ring: false, type: GroupCall, number: 99} - dmr: {id: cont12, name: Cl Brandenburg, ring: false, type: GroupCall, number: 26209} - dmr: {id: cont13, name: Cl Berlin, ring: false, type: GroupCall, number: 26212} - dmr: {id: cont14, name: Refl Brandenburg, ring: false, type: PrivateCall, number: 4044} - dmr: {id: cont15, name: Refl Berlin, ring: false, type: PrivateCall, number: 4016} - dmr: {id: cont16, name: BM SMS, ring: false, type: PrivateCall, number: 262993} - dmr: {id: cont17, name: BM DAPNET, ring: false, type: PrivateCall, number: 262994} - dmr: {id: cont18, name: BM Parrot, ring: false, type: PrivateCall, number: 262997} - dmr: {id: cont19, name: BM APRS, ring: false, type: PrivateCall, number: 262999} groupLists: - {id: grp1, name: DL, contacts: [cont1, cont2, cont3]} - {id: grp2, name: Berlin/Brand, contacts: [cont6, cont5, cont7, cont8]} - {id: grp3, name: Simplex, contacts: [cont6, cont11]} - {id: grp4, name: Ham Radio Village, contacts: [cont8, cont9]} channels: - dmr: id: ch1 name: DB0LDS TS1 rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp1 contact: cont6 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch2 name: TG8 DB0LDS TS2 rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont5 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch3 name: TG9 DB0LDS TS2 rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont6 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch4 name: BB DB0LDS TS2 rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont7 aprs: aprs1 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch5 name: HRV DB0LDS TS1 rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp4 contact: cont8 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch6 name: ChW DB0LDS TS1 rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp4 contact: cont9 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch7 name: BB DB0VIP TS2 rxFrequency: 431.75 MHz txFrequency: 439.35 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont7 aprs: aprs1 extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: ! "" - dmr: id: ch8 name: DM0TZN TS1 rxFrequency: 438.825 MHz txFrequency: 431.225 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp1 contact: cont1 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch9 name: TG8 DM0TZN TS2 rxFrequency: 438.825 MHz txFrequency: 431.225 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont5 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch10 name: TG9 DM0TZN TS2 rxFrequency: 438.825 MHz txFrequency: 431.225 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont6 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch11 name: BB DM0TZN TS2 rxFrequency: 438.825 MHz txFrequency: 431.225 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont7 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch12 name: DB0LOS TS1 rxFrequency: 438.475 MHz txFrequency: 430.875 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp1 contact: cont6 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch13 name: R/TG9 DB0LOS TS2 rxFrequency: 438.475 MHz txFrequency: 430.875 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont6 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch14 name: R/TG9 DM0TT TS1 rxFrequency: 439.0875 MHz txFrequency: 431.4875 MHz timeout: ! "" rxOnly: false vox: ! "" admit: ColorCode colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp2 contact: cont6 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch15 name: DMR S0 rxFrequency: 433.45 MHz txFrequency: 433.45 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Free colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp3 contact: cont10 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch16 name: DMR S1 rxFrequency: 433.612 MHz txFrequency: 433.612 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Free colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp3 contact: cont10 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch17 name: DMR S2 rxFrequency: 433.625 MHz txFrequency: 433.625 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Free colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp3 contact: cont10 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch18 name: DMR S3 rxFrequency: 433.638 MHz txFrequency: 433.638 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Free colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp3 contact: cont10 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch19 name: DMR S4 rxFrequency: 433.65 MHz txFrequency: 433.65 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Free colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp3 contact: cont10 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch20 name: DMR S5 rxFrequency: 433.663 MHz txFrequency: 433.663 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Free colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp3 contact: cont10 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch21 name: DMR S6 rxFrequency: 433.675 MHz txFrequency: 433.675 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Free colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp3 contact: cont10 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - dmr: id: ch22 name: DMR S7 rxFrequency: 433.688 MHz txFrequency: 433.688 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Free colorCode: 1 timeSlot: TS1 radioId: ! "" groupList: grp3 contact: cont10 roaming: ! "" extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: High - fm: id: ch23 name: DB0LDS rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ctcss: 67.0 Hz txTone: ctcss: 67.0 Hz bandwidth: Narrow extended: talkaround: false reverseBurst: false power: High - fm: id: ch24 name: DB0RAG rxFrequency: 439.3 MHz txFrequency: 431.7 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: High - fm: id: ch25 name: DB0LUD rxFrequency: 438.575 MHz txFrequency: 430.975 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ctcss: 67.0 Hz txTone: ctcss: 67.0 Hz bandwidth: Narrow extended: talkaround: false reverseBurst: false power: High - fm: id: ch26 name: DB0SP-2 rxFrequency: 145.6 MHz txFrequency: 145 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: High - fm: id: ch27 name: DB0SP-70 rxFrequency: 439.425 MHz txFrequency: 431.825 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: High - fm: id: ch28 name: S20 rxFrequency: 145.3 MHz txFrequency: 145.3 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: High - fm: id: ch29 name: 2m Mobil rxFrequency: 145.5 MHz txFrequency: 145.5 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: High - fm: id: ch30 name: 70cm Mobil rxFrequency: 433.5 MHz txFrequency: 433.5 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: High - fm: id: ch31 name: APRS rxFrequency: 144.8 MHz txFrequency: 144.8 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: High - fm: id: ch32 name: U0 rxFrequency: 430.2 MHz txFrequency: 430.2 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: Low - fm: id: ch33 name: U1 rxFrequency: 430.225 MHz txFrequency: 430.225 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: Low - fm: id: ch34 name: U2 rxFrequency: 430.25 MHz txFrequency: 430.25 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: Low - fm: id: ch35 name: U3 rxFrequency: 430.275 MHz txFrequency: 430.275 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: Low zones: - id: zone1 name: KW A: [ch1, ch3, ch4, ch7, ch5, ch6, ch12, ch13, ch14, ch23, ch24, ch25, ch26] B: [ch28, ch29, ch30, ch15, ch16, ch17, ch18, ch19, ch20, ch21, ch22] - id: zone2 name: TH Wildau A: [ch32, ch33, ch34, ch35] B: [] scanLists: - id: scan1 name: KW channels: [ch3, ch4, ch23, ch24, ch29, ch15, ch32] - id: scan2 name: BerBra channels: [ch4, ch11] - id: scan3 name: PMR channels: [] positioning: - dmr: id: aprs1 name: BM ARPS contact: cont19 revert: ! "" period: 5 min - aprs: id: aprs2 name: APRS APAT81 revert: ch31 icon: Jogger message: Y07, QRG 144.675 period: 5 min destination: APAT81-0 source: DM3MAT-7 path: [WIDE1-1, WIDE2-1] roamingChannels: - id: rch1 name: R DB0LDS rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz colorCode: 1 - id: rch2 name: R DM0TZN rxFrequency: 438.825 MHz txFrequency: 431.225 MHz colorCode: 1 roamingZones: - id: roam1 name: Berlin/Brand channels: - rch1 - rch2 commercial: encryptionKeys: [] sms: format: DMR templates: [] ... ================================================ FILE: examples/minimal.conf ================================================ # # Configuration generated Sa. März 27 11:44:12 2021 by qdrm, version 0.6.2 # see https://dm3mat.darc.de/qdmr for details. # # Unique DMR ID and name (quoted) of this radio. ID: 2621370 Name: "DM3MAT" # Text displayed when the radio powers up (quoted). IntroLine1: "" IntroLine2: "" # Microphone amplification, value 1..10: MICLevel: 2 # Speech-synthesis ('On' or 'Off'): Speech: Off # Table of digital channels. # 1) Channel number. # 2) Name in quotes. E.g., "NAME" # 3) Receive frequency in MHz # 4) Transmit frequency or +/- offset in MHz # 5) Transmit power: Max, High, Mid, Low or Min # 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 # 14) GPS System: - or index in GPS table. # 15) Roaming zone: -, + or index in roaming-zone table. # Digital Name Receive Transmit Power Scan TOT RO Admit CC TS RxGL TxC GPS Roam 1 "L9 DB0LDS" 439.56250 -7.60000 High - - - - 1 2 1 1 - - # Local 2 "BB DB0LDS" 439.56250 -7.60000 High - - - - 1 2 1 3 - - # BB 3 "DL DB0LDS" 439.56250 -7.60000 High - - - - 1 1 2 4 - - # DL # Table of analog channels. # 1) Channel number. # 2) Name in quotes. # 3) Receive frequency in MHz # 4) Transmit frequency or +/- offset in MHz # 5) Transmit power: Max, High, Mid, Low or Min # 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: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 # 11) CTCSS/DCS for receive: frequency (e.g, 67.0), DCS number (e.g., n023 or i023) or '-' to disable # 12) CTCSS/DCS for transmit: frequency (e.g, 67.0), DCS number (e.g., n023 or i023) or '-' to disable # 13) Bandwidth in kHz: 12.5, 25 # 14) APRS system: - or index # Analog Name Receive Transmit Power Scan TOT RO Admit Squelch RxTone TxTone Width APRS # Table of channel zones. # 1) Zone number # 2) Name in quotes. # 3) VFO: Either A or B. # 4) List of channels: numbers and ranges (N-M) separated by comma # Zone Name VFO Channels 1 "Zu Hause" A 1,2 1 "Zu Hause" B 3 # Table of scan lists. # 1) Scan list number. # 2) Name in quotes. # 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, Sel or ranges (N-M) separated by comma # Scanlist Name PCh1 PCh2 TxCh Channels # Table of GPS systems. # 1) GPS system ID # 2) Name in quotes. # 3) Destination contact ID. # 4) Update period: period in ms # 5) Revert channel ID or '-'. # GPS Name Dest Period Revert # Table of APRS systems (there is usually only one). # 1) APRS system ID # 2) Name in quotes. # 3) Transmit channel ID. # 4) Transmit period in seconds. # 5) Your (source) call and SSID as CALLSIGN-SSID. # 6) Destination call and SSID as CALLSIGN-SSID. # 7) Path string or '-'. # 7) Icon name. # 8) Message, optional message in quotes. # APRS Name Channel Period Source Destination Path Icon Message # Table of contacts. # 1) Contact number. # 2) Name in quotes. # 3) Call type: Group, Private, All or DTMF # 4) Call ID: 1...16777215 or string with DTMF number # 5) Call receive tone: -, + # Contact Name Type ID RxTone 1 "Local" Group 9 - 2 "Regional" Group 8 - 3 "BB" Group 2621 - 4 "DL" Group 262 - # Table of group lists. # 1) Group list number. # 2) Name in quotes. # 3) List of contacts: numbers and ranges (N-M) separated by comma # Grouplist Name Contacts 1 "Local" 1,2,3 2 "DL" 4 # Table of roaming zones. # 1) Roaming zone number # 2) Name in quotes. # 3) List of digital channels: numbers and ranges (N-M) separated by comma # Roaming Name Channels ================================================ FILE: examples/minimal.yaml ================================================ --- version: 0.9.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} groupLists: - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} - {id: grp2, name: DL, contacts: [cont4]} - {id: grp3, name: HamRadioVillage, contacts: [cont6]} channels: - digital: id: ch1 name: L9 DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 - digital: id: ch2 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont3 aprs: aprs1 power: High timeout: 0 vox: 0 - digital: id: ch3 name: DL DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp2 contact: cont4 power: High timeout: 0 vox: 0 - digital: id: ch4 name: HRV DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp3 contact: cont6 power: High timeout: 0 vox: 0 zones: - id: zone1 name: Zu Hause A: [ch1, ch2, ch4] B: [ch3] positioning: - dmr: id: aprs1 name: GPS System period: 300 contact: cont5 ... ================================================ FILE: examples/pmr.yaml ================================================ --- version: 0.14.1 settings: introLine1: qDMR introLine2: DM3MAT micLevel: 6 speech: false power: High squelch: 1 vox: off gnss: fixedPositionEnabled: false fixedPosition: "" systems: - GPS units: Metric dmr: privateCallMatch: true groupCallMatch: true privateCallHangTime: 5 s groupCallHangTime: 3 s sendTalkerAlias: false talkerAliasEncoding: Iso8 preamble: 100 ms radioIDs: [] contacts: [] groupLists: [] channels: - fm: id: ch1 name: PMR 1 rxFrequency: 446.00625 MHz txFrequency: 446.00625 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch2 name: PMR 2 rxFrequency: 446.01875 MHz txFrequency: 446.01875 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch3 name: PMR 3 rxFrequency: 446.03125 MHz txFrequency: 446.03125 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch4 name: PMR 4 rxFrequency: 446.04375 MHz txFrequency: 446.04375 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch5 name: PMR 5 rxFrequency: 446.05625 MHz txFrequency: 446.05625 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch6 name: PMR 6 rxFrequency: 446.06875 MHz txFrequency: 446.06875 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch7 name: PMR 7 rxFrequency: 446.08125 MHz txFrequency: 446.08125 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch8 name: PMR 8 rxFrequency: 446.09375 MHz txFrequency: 446.09375 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch9 name: PMR 9 rxFrequency: 446.10625 MHz txFrequency: 446.10625 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch10 name: PMR 10 rxFrequency: 446.11875 MHz txFrequency: 446.11875 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch11 name: PMR 11 rxFrequency: 446.13125 MHz txFrequency: 446.13125 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch12 name: PMR 12 rxFrequency: 446.14375 MHz txFrequency: 446.14375 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch13 name: PMR 13 rxFrequency: 446.15625 MHz txFrequency: 446.15625 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch14 name: PMR 14 rxFrequency: 446.16875 MHz txFrequency: 446.16875 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch15 name: PMR 15 rxFrequency: 446.18125 MHz txFrequency: 446.18125 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch16 name: PMR 16 rxFrequency: 446.19375 MHz txFrequency: 446.19375 MHz timeout: ! "" rxOnly: false scanListRef: scan1 vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" zones: - id: zone1 name: PMR A: [ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8, ch9, ch10, ch11, ch12, ch13, ch14, ch15, ch16] B: [ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8, ch9, ch10, ch11, ch12, ch13, ch14, ch15, ch16] scanLists: - id: scan1 name: PMR channels: [ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8, ch9, ch10, ch11, ch12, ch13, ch14, ch15, ch16] commercial: encryptionKeys: [] sms: format: DMR templates: [] ... ================================================ FILE: examples/potsdam.yaml ================================================ --- version: 0.14.0 settings: introLine1: qDMR introLine2: DM3MAT micLevel: 5 speech: false power: High squelch: 2 vox: off defaultID: id1 gnss: fixedPositionEnabled: false fixedPosition: "" systems: - GPS units: Metric dmr: privateCallMatch: true groupCallMatch: true privateCallHangTime: 5 s groupCallHangTime: 3 s sendTalkerAlias: false talkerAliasEncoding: Iso8 preamble: 100 ms radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont2, name: Emcom EU, ring: false, type: GroupCall, number: 9112} - dmr: {id: cont3, name: Berlin/Brandenburg, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont5, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont6, name: BM SMS, ring: false, type: PrivateCall, number: 262993} - dmr: {id: cont7, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont8, name: TAC1, ring: false, type: GroupCall, number: 26200} - dmr: {id: cont9, name: TAC 2, ring: false, type: GroupCall, number: 26299} - dmr: {id: cont10, name: TAC 3, ring: false, type: GroupCall, number: 26233} - dmr: {id: cont11, name: TAC 4, ring: false, type: GroupCall, number: 26266} groupLists: - {id: grp1, name: DL, contacts: [cont1, cont2, cont8, cont9, cont10, cont11]} - {id: grp2, name: BB, contacts: [cont3, cont4, cont5]} channels: - fm: id: ch1 name: DB0PDM rxFrequency: 145.675 MHz txFrequency: 145.075 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: ! "" - dmr: id: ch2 name: DL DB0PDM TS1 rxFrequency: 438.4 MHz txFrequency: 430.8 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Always colorCode: 1 timeSlot: TS1 radioId: id1 groupList: grp1 contact: cont1 aprs: aprs1 extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: ! "" - dmr: id: ch3 name: BB DB0PDM TS2 rxFrequency: 438.4 MHz txFrequency: 430.8 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Always colorCode: 1 timeSlot: TS2 radioId: id1 groupList: grp2 contact: cont3 aprs: aprs1 extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: ! "" - dmr: id: ch4 name: L9 DB0PDM TS2 rxFrequency: 438.4 MHz txFrequency: 430.8 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Always colorCode: 1 timeSlot: TS2 radioId: id1 groupList: grp2 contact: cont4 extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: ! "" - dmr: id: ch5 name: BB DB0AVH TS2 rxFrequency: 438.45 MHz txFrequency: 430.85 MHz timeout: ! "" rxOnly: false vox: ! "" admit: Always colorCode: 1 timeSlot: TS2 radioId: ! "" groupList: grp2 contact: cont3 extended: talkaround: false privateCallConfirm: false sms: true smsConfirm: false dataConfirm: true dcdm: false loneWorker: false power: ! "" - fm: id: ch6 name: DB0FES rxFrequency: 438.225 MHz txFrequency: 430.625 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ctcss: 67.0 Hz txTone: ctcss: 67.0 Hz bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch7 name: DB0SP VHF rxFrequency: 145.6 MHz txFrequency: 145 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ctcss: 67.0 Hz txTone: ctcss: 67.0 Hz bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch8 name: DB0SP UHF rxFrequency: 439.425 MHz txFrequency: 431.825 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ctcss: 67.0 Hz txTone: ctcss: 67.0 Hz bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch9 name: DB0LUD rxFrequency: 438.575 MHz txFrequency: 430.975 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ctcss: 67.0 Hz txTone: ctcss: 67.0 Hz bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch10 name: DB0BRB rxFrequency: 439.4875 MHz txFrequency: 431.8875 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch11 name: 2m Mobil rxFrequency: 145.5 MHz txFrequency: 145.5 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch12 name: 70cm Mobil rxFrequency: 433.5 MHz txFrequency: 433.5 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow aprs: aprs2 extended: talkaround: false reverseBurst: false power: ! "" - fm: id: ch13 name: APRS rxFrequency: 144.8 MHz txFrequency: 144.8 MHz timeout: ! "" rxOnly: false vox: ! "" squelch: ! "" admit: Free rxTone: ~ txTone: ~ bandwidth: Narrow extended: talkaround: false reverseBurst: false power: ! "" zones: - id: zone1 name: Potsdam A: [ch1, ch2, ch3, ch4, ch6, ch7, ch8, ch9, ch10, ch11, ch12] B: [] positioning: - dmr: id: aprs1 name: BM APRS contact: cont7 revert: ! "" period: 5 min - aprs: id: aprs2 name: FM APRS revert: ch13 icon: Jogger message: Y07 destination: APAT81-0 source: DM3MAT-7 path: [WIDE1-1, WIDE2-1] commercial: encryptionKeys: [] sms: format: DMR templates: [] ... ================================================ FILE: examples/sat.yaml ================================================ --- version: 0.12.0 settings: introLine1: FM Sat introLine2: DM3MAT micLevel: 6 speech: false power: Max squelch: 0 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: [] groupLists: [] channels: - analog: id: ch1 name: ISS APRS rxOnly: false admit: Free bandwidth: Narrow rxFrequency: 145.825 MHz txFrequency: 145.825 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch2 name: ISS AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.81 MHz txFrequency: 145.9875 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch3 name: ISS UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.805 MHz txFrequency: 145.99 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch4 name: ISS APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.8 MHz txFrequency: 145.99 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch5 name: ISS DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.795 MHz txFrequency: 145.99 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch6 name: ISS LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.79 MHz txFrequency: 145.9925 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch7 name: CAS-3H AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.21 MHz txFrequency: 144.3525 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch8 name: CAS-3H UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.205 MHz txFrequency: 144.35 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch9 name: CAS-3H APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.2 MHz txFrequency: 144.35 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch10 name: CAS-3H DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.195 MHz txFrequency: 144.35 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch11 name: CAS-3H LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.19 MHz txFrequency: 144.3475 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch12 name: CAS-5A AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.61 MHz txFrequency: 145.9225 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch13 name: CAS-5A UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.605 MHz txFrequency: 145.925 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch14 name: CAS-5A APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.6 MHz txFrequency: 145.925 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch15 name: CAS-5A DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.595 MHz txFrequency: 145.925 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch16 name: CAS-5A LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.59 MHz txFrequency: 145.9275 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch17 name: CAS-7A AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.7 MHz txFrequency: 145.8975 MHz power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - analog: id: ch18 name: CAS-7A UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.695 MHz txFrequency: 145.9 MHz power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - analog: id: ch19 name: CAS-7A APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.69 MHz txFrequency: 145.9 MHz power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - analog: id: ch20 name: CAS-7A DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.685 MHz txFrequency: 145.9 MHz power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - analog: id: ch21 name: CAS-7A LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 435.68 MHz txFrequency: 145.9025 MHz power: ! "" timeout: ! "" vox: ! "" squelch: ! "" - analog: id: ch22 name: PO-101 AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.9025 MHz txFrequency: 437.59 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 141.300003} squelch: 0 - analog: id: ch23 name: PO-101 UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.9 MHz txFrequency: 437.495 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 141.300003} squelch: 0 - analog: id: ch24 name: PO-101 APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.9 MHz txFrequency: 437.5 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 141.300003} squelch: 0 - analog: id: ch25 name: PO-101 DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.9 MHz txFrequency: 437.505 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 141.300003} squelch: 0 - analog: id: ch26 name: PO-101 LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.8975 MHz txFrequency: 437.51 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 141.300003} squelch: 0 - analog: id: ch27 name: SO-50 EN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.79 MHz txFrequency: 145.85 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 74.4000015} squelch: 0 - analog: id: ch28 name: SO-50 AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.8 MHz txFrequency: 145.8525 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch29 name: SO-50 UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.795 MHz txFrequency: 145.85 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch30 name: SO-50 APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.79 MHz txFrequency: 145.85 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch31 name: SO-50 DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.785 MHz txFrequency: 145.85 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch32 name: SO-50 LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.77 MHz txFrequency: 145.8475 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch33 name: AO-27 AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.805 MHz txFrequency: 145.8475 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch34 name: AO-27 UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.8 MHz txFrequency: 145.85 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch35 name: AO-27 APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.795 MHz txFrequency: 145.85 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch36 name: AO-27 DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.79 MHz txFrequency: 145.85 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch37 name: AO-27 LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.785 MHz txFrequency: 145.8525 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch38 name: AO-92 AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.8825 MHz txFrequency: 435.34 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch39 name: AO-92 UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.88 MHz txFrequency: 435.345 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch40 name: AO-92 APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.88 MHz txFrequency: 435.35 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch41 name: AO-92 DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.88 MHz txFrequency: 435.355 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch42 name: AO-92 LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.8775 MHz txFrequency: 435.36 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch43 name: AO-91 AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.9625 MHz txFrequency: 435.24 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch44 name: AO-91 UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.96 MHz txFrequency: 435.245 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch45 name: AO-91 APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.96 MHz txFrequency: 435.25 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch46 name: AO-91 DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.96 MHz txFrequency: 435.255 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch47 name: AO-91 LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 145.9575 MHz txFrequency: 435.26 MHz power: ! "" timeout: ! "" vox: ! "" txTone: {ctcss: 67} squelch: 0 - analog: id: ch48 name: TEVEL AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.41 MHz txFrequency: 145.9675 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch49 name: TEVEL UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.405 MHz txFrequency: 145.97 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch50 name: TEVEL APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.4 MHz txFrequency: 145.97 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch51 name: TEVEL DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.395 MHz txFrequency: 145.97 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch52 name: TEVEL LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 436.39 MHz txFrequency: 145.9725 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch53 name: UVSQ AOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.03 MHz txFrequency: 145.9025 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch54 name: UVSQ UP rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.025 MHz txFrequency: 145.905 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch55 name: UVSQ APO rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.02 MHz txFrequency: 145.905 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch56 name: UVSQ DWN rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.015 MHz txFrequency: 145.905 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 - analog: id: ch57 name: UVSQ LOS rxOnly: false admit: Always bandwidth: Narrow rxFrequency: 437.01 MHz txFrequency: 145.9075 MHz power: ! "" timeout: ! "" vox: ! "" squelch: 0 zones: - id: zone1 name: AO-27 A: [ch33, ch34, ch35, ch36, ch37] B: [] - id: zone2 name: AO-91 FOX-1B A: [ch43, ch44, ch45, ch46, ch47] B: [] - id: zone3 name: AO-92 FOX-1D A: [ch38, ch39, ch40, ch41, ch42] B: [] - id: zone4 name: CAS-3H Lilac-2 A: [ch7, ch8, ch9, ch10, ch11] B: [] - id: zone5 name: CAS-5A FO-118 A: [ch12, ch13, ch14, ch15, ch16] B: [] - id: zone6 name: CAS-7A A: [ch17, ch18, ch19, ch20, ch21] B: [] - id: zone7 name: ISS FM Rep A: [ch2, ch3, ch4, ch5, ch6] B: [] - id: zone8 name: PO-101 Diwata-2 A: [ch22, ch23, ch24, ch25, ch26] B: [] - id: zone9 name: SO-50 SaudiSat-1C A: [ch27, ch28, ch29, ch30, ch31, ch32] B: [] - id: zone10 name: TEVEL 1-8 A: [ch48, ch49, ch50, ch51, ch52] B: [] - id: zone11 name: UVSQ A: [ch53, ch54, ch55, ch56, ch57] B: [] commercial: encryptionKeys: [] ... ================================================ FILE: i18n/Makefile ================================================ update: /usr/lib/qt6/bin/lupdate ../src/*.cc ../src/*.hh ../src/*.ui ../shared/ui/*.ui -noobsolete -ts *.ts ================================================ FILE: i18n/de.ts ================================================ AMChannelDialog Squelch Rauschsperre APRSSelect [None] [Kein] APRSSystemDialog Create APRS system APRS System erzeugen Edit APRS system APRS System bearbeiten [Selected] [Ausgewählt] AboutDialog About qdmr Über qdmr Supported Radios Unterstützte Geräte Application Unsaved changes to codeplug. Nicht gespeicherte Änderungen am Codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Es gibt nicht gespeicherte Änderungen am Codeplug. Diese gehen verloren, wenn Sie fortfahren. Open codeplug Codeplug öffnen Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Codeplug Dateien (*.yaml);;Codeplug Dateien, altes Format (*.conf *.csv *.txt);;Alle Dateien (*) Cannot open file Kann Datei nicht öffnen Cannot read codeplug from file '%1': %2 Kann Codeplug nicht aus Datei '%1' lesen: %2 Cannot read codeplug. Kann Codeplug nicht lesen. Save codeplug Codeplug speichern Codeplug Files (*.yaml *.yml) Codeplug Dateien (*.yaml *.yml) Please use new YAML format. Nutze bitte das neue YAML-Format. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Das Speichern der Config im alten tabellenbasierten Format wurde mit Version 0.9.0 deaktiviert. Das Einlesen dieser Dateien funktioniert nach wie vor. Cannot save codeplug to file '%1': %2 Kann Codeplug nicht in Datei '%1' speichern: %2 Cannot save codeplug Kann Codeplug nicht speichern Cannot save codeplug to file '%1'. Kann Codeplug nicht in Datei '%1' speichern. Export codeplug Codeplug exportieren CHIRP CSV Files (*.csv) CHIRP CSV Dateien (*.csv) Cannot export codeplug Kann Codeplug nicht exportieren Cannot export codeplug to file '%1': %2 Kann Codeplug nich in die Datei '%1' exportieren: %2 Import codeplug Codeplug importieren CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) CHIRP CSV Dateien (*.csv);;YAML Dateien (*.yaml *.yml) Cannot import codeplug Kann Codeplug nicht importieren Cannot import codeplug from '%1': %2 Kann Codeplug nicht aus Datei '%1' importieren: %2 Do not know, how to handle file '%1'. Kann Datei '%1' nicht verabreiten. No matching devices found. Kein passendes Gerät gefunden. Cannot connect to radio Verbindung zum Radio nicht möglich Cannot connect to radio: %1 Kann keine Verbindung zum Gerät herstellen: %1 Radio found Gerät gefunden Found device '%1'. Gerät '%1' gefunden. Verification success Verifizierung erfolgreich The codeplug was successfully verified with the radio '%1' Der aktuelle Codeplug passt zum Gerät '%1'. Read ... Lese … Read error Lesefehler Read complete Gelesen Upload ... Schreibe … Cannot write call-sign DB. Kann Rufzeichendatenbank nicht schreiben. The detected radio '%1' does not support a call-sign DB. Das Gerät '%1' unterstützt keine Rufzeichendatenbank. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. Das Gerät '%1' unterstützt eine Rufzeichendatenbank. Diese Feature wurde jedoch noch nicht implementiert. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. qdmr kuratiert die Rufzeichendatenbank anhand Ihrer DMR ID. Es wurde keine Standard ID definiert. Write call-sign DB ... Schreibe Rufzeichendatenbank … Cannot write satellite config. Kann Satelliteneinstellungen nicht schreiben. The detected radio '%1' does not support satellite tracking. Das erkannte Funkgerät '%1' unterstützt kein Statellitentracking. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. Das erkannte Funkgerät bietet zwar Satellitentracking, jedoch ist das Feature noch nicht implementiert. Write satellite config ... Satellitenkonfiguration schreiben … Write error Schreibfehler Write complete Geschrieben %1 (alias for %2 %3) %1 (Alias für %2 %3) No radio found Kein Gerät erkannt No matching device was found. Kein bekanntes Gerät erkannt. BandwidthSelect Narrow (12.5 kHz) Schmal (12.5 kHz) Wide (25 kHz) Breit (25 kHz) ChannelDialog Edit Channel Kanal bearbeiten <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Hinweis:</span> qdmr bietet Autovervollständigung für Kanäle an, wenn Sie anfangen, das Rufzeichen eines Repeaters einzugeben. Nach drei eingegebenen Zeichen wird eine Anfrage an repeaterbook.com geschickt, um die Informationen der passenden Repeater abzurufen. Diese Anfragen können einen kurzen Moment dauern. Die erhaltenen Ergebnisse werden lokal zwischengespeichert.</p><p>Eine drop-down Liste wird erscheinen, die es Ihnen erlaubt, einen Repeater auszuwählen. Wenn Sie einen Repeater ausgewählt haben, werden die RX &amp; TX Frequenzen sowie die CTCSS Subtoneinstellungen ausgefüllt, insofern vorhanden.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">ausblenden</span></a></p></body></html> Basic Basis Name Name Rx Frequency RX Frequenz Tx Frequency TX Frequenz Tx Offset TX Ablage Power Leistung Max Maximum High Hoch Mid Mittel Low Niedrig Min Minimum Default Standard Tx Timeout TX Timeout Off Aus VOX Level VOX Empfindlichkeit Rx Only Nur RX Scan List Scanliste Extensions Erweiterungen No offset Keine Ablage (simplex) Positive offset Positive Ablage Negative offset Negative Ablage [None] [Kein] ChannelListView Select a single channel first Wählen sie zunächst einen Kanal aus. To clone a channel, please select a single channel to clone. Um einen Kanal zu klonen, wählen Sie zunächst einen Kanal aus. Cannot delete channel Kann Kanal nicht löschen Cannot delete channel: You have to select a channel first. Kann Kanal nicht löschen: Wählen Sie zunächst einen Kanal aus. Delete channel? Kanal löschen? Delete channel %1? Kanal '%1' löschen? Delete %1 channels? %1 Kanäle löschen? Alt+A Alt+A Add Channel ... Kanal hinzufügen … Clone Channel Kanal klonen Alt+C Alt+C Delete Channel Kanal löschen Alt+- Alt+- Add FM Channel FM Kanal hinzufügen Adds a new FM channel Fügt einen neuen FM Kanal hinzu Add DMR Channel DMR Kanal hinzufügen Adds a new DMR channel. Fügt einen neuen DMR Kanal hinzu. Add AM Channel AM Kanal hinzufügen Adds a new AM channel. Fügt einen neuen AM Kanal hinzu. ChannelListWrapper FM FM DMR DMR AM AM [Default] [Standard] Max Maximum High Hoch Mid Mittel Low Niedrig Min Minimum Off Aus On Ein Always Immer Free Frei Color Farbcode Tone Subton [None] [Kein] Open Offen Wide Breit Narrow Schmal Type Typ Name Name Rx Frequency RX Frequenz Tx Frequency TX Frequenz Power Leistung Timeout Timeout Rx Only Nur RX Admit Sendevoraussetzung Scanlist Scanliste Zones Zonen CC CC TS TS RX Group List Empfangsgruppe TX Contact TX Kontakt DMR ID DMR ID GPS/APRS GPS/APRS Roaming Roaming Squelch Rauschsperre Rx Tone RX Subton Tx Tone TX Subton Bandwidth Bandbreite Extensions Erweiterungen ChannelRefListWrapper Channel Kanal ChannelSelectionDialog Select a channel: Wählen Sie einen Kanal aus: ConfigMergeDialog Merging codeplugs ... Codeplugs vereinigen … <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Konfliktlösungsstrategien:</span></p><p>Wenn einige importierte Objekte (Kanäle, Kontakte, …) schon existieren, wähle aus, wie diese Konflikte aufgelöst werden für einzelne Objekte und Listen.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Objekte sind atomische Elemente wie Radio IDs, Kanäle, Kontakte und Roamingkamäle. Items Objekte Ignore Ignorieren Override Überschreiben Duplicate Duplizieren Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Listen sind alle Elemente, die andere Objekte entalten wie Empfangsgruppen, Scanlisten und Roamingzonen. Sets Listen Merge Vereinigen Ignores any duplicate item. Doppelungen ignorieren. Replaces any duplicate item with the imported one. Ersetzt jedes doppelte Objekt mit dem importieten Objekt. Imports any duplicate item with a modified name. Importiert jedes doppelte Objekt mit einem veränderten Namen. Ignores any duplicate set. Ignoriert jede Doppelung. Replaces any duplicate set with the imported one. Ersetzt jede doppelte Liste mit der importierten Liste. Imports any duplicate set with a modified name. Importiert doppelte Listen unter veränderten Namen. Merges duplicate sets. Vereinigt doppelte Listen. ConfigObjectListView Cannot move items. Kann Elemente nicht verschieben. Cannot move items: You have to select at least one item first. Kann Elemente nicht verschieben: Wählen Sie zunächst mind. ein Element aus. Move selected item(s) to the top. Verschiebe die ausgewählten Elemente an den Anfang. Move selected item(s) ten positions up. Verschiebe die ausgewählten Elemente zehn Positionen nach oben. Move selected item(s) one position up. Verschiebe die ausgewählten Elemente um eine Position nach oben. Move selected item(s) one position down. Verschiebe die ausgewählten Elemente um eine Position nach unten. Move selected item(s) ten positions down. Verschiebe die ausgewählten Elemente um zehn Position nach unten. Move selected item(s) to the bottom. Verschiebe die ausgewählten Elemente ans Ende. ConfigObjectTableView Cannot move items. Kann Elemente nicht verschieben Cannot move items: You have to select at least one item first. Kann Elemente nicht verschieben: Wählen Sie zunächst mind. ein Element aus. Cannot move items as long as there is some filter or sorting applied. Kann Element nicht verschieben, solange Filter oder Sortierungen angewendet werden. Move selected item(s) to the top. Verschiebe die ausgewählten Elemente an den Anfang. Move selected item(s) ten positions up. Verschiebe die ausgewählten Elemente zehn Positionen nach oben. Move selected item(s) one position up. Verschiebe die ausgewählten Elemente um eine Position nach oben. Move selected item(s) one position down. Verschiebe die ausgewählten Elemente um eine Position nach unten. Move selected item(s) ten positions down. Verschiebe die ausgewählten Elemente um zehn Position nach unten. Move selected item(s) to the bottom. Verschiebe die ausgewählten Elemente ans Ende. Toggle Filter and Sorting Filter und Sortierung ein- & ausschalten Close Sort and Filter Filter und Sortierungen verstecken ConfigObjectTypeSelectionDialog An instance of %1. Eine Instanz von '%1'. <p>%1<p><p style="margin-left:10px;">%2</p> <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Erweiterungsobjekt erzeugen Select the class of object to create Wählen Sie die Klasse des Objektes aus, das Sie erzeugen wollen. ContactListView Cannot delete contact Kann Kontakt nicht löschen Cannot delete contact: You have to select a contact first. Kann Kontakt nicht löschen: Wählen Sie zunächst einen Kontakt aus. Delete contact? Kontakt löschen? Delete contact %1? Kontakt '%1' löschen? Delete contacts? Kontakte löschen? Delete %1 contacts? Sollen %1 Kontakte gelöscht werden? Adds a contact to the list. Fügt einen Kontakt zu Liste hinzu. Alt++ Alt++ Add M17 Contact M17 Kanal hinzufügen Adds an M17 contact to the list. Füg einen M17 Kanal zur Liste hinzu. Add DTMF Contact DTMF-Kontakt hinzufügen Adds an DTMF (analog) contact to the list. Fügt einen DTMF (analog) Kontakt zur Liste hinzu. Add DMR Contact DMR Kontakt hinzufügen Adds an DMR contact to the list. Fügt einen DMR Kontakt zur Liste hinzu. Delete contact button Dieser Button löscht den ausgewählten Kontakt. Add Contact Kontakt hinzufügen Delete Contact Kontakt löschen Alt+- Alt+- ContactListWrapper DTMF DTMF On An Off Aus Private Call Direktruf Group Call Gruppenruf All Call Rundruf [None] [Kein] M17 M17 [Broadcast] [Rundruf] Type Typ Name Name Number Nummer RX Tone RX Subton Extensions Erweiterungen DMRAdmitSelect Always Immer Channel Free Kanal frei Other Color-code Abweichender Farbcode DMRChannelDialog Radio Id Radio Id Tx Admit TX Voraussetzung Color-code Farbcode Time-slot Zeitschlitz Group list Empfangsgruppe Tx Contact TX Kontakt APRS APRS Roaming zone Roamingzone DMRContactDialog Create DMR Contact DMR Kontakt erstellen Edit DMR Contact DMR Kontakt bearbeiten Private Call Direktruf Group Call Gruppenruf All Call Rundruf Basic Basis Type Typ Name Name Number Nummer Ring Klingelton Extensions Erweiterungen DMRContactSelect [None] [Kein] DMRIDDialog Basic Basis Name Name DMR ID DMR ID Extensions Erweiterungen DMRIdSelect [Default] [Standard] DTMFContactDialog Create DTMF Contact DTMF Contakt erstellen Edit DMR Contact DTMF Kontakt bearbeiten Basic Basis Name Name Number Nummer Ring Klingelton Extensions Erweiterungen DeviceSelectionDialog Select a device Wählen Sie ein Gerät aus <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> Entweder wurde mehr als ein Gerät gefunden oder das gefundene Gerät wird als unsicher betrachtet. Wählen Sie dennoch ein Gerät aus. ErrorMessageView Error: Unknown. Fehler: Unbekannt. Error: %1 Fehler: %1 Traceback: Fehlerverfolgung: ExtensionView Cannot create extension. Kann Erweiterung nicht erzeugen. Cannot create extension, consider reporting a bug. Kann Erweiterung nicht erzeugen. Bitte melden Sie diesen Fehler. Cannot create list element. Kann Listenelement nicht anlegen. Cannot create list element, consider reporting a bug. Kann Listenelement nicht anlegen. Bitte berichten Sie diesen Fehler. Create Erzeugen Remove Entfernen FMAPRSSelect [None] [Kein] FMAPRSSystem [None] [Kein] Police station Polizeiwache Digipeater Digipeater Phone Telefon DX cluster DX Cluster HF gateway HF-Gateway Plane small kleines Flugzeug Mobile Satellite station mobile Satellitenstation Wheel Chair Rollstuhl Snowmobile Scheemobil Red cross Rotes Kreuz Boy scout Pfadfinder Home zu Hause X X Red dot roter Punkt Circle 0 Kreis 0 Circle 1 Kreis 1 Circle 2 Kreis 2 Circle 3 Kreis 3 Circle 4 Kreis 4 Circle 5 Kreis 5 Circle 6 Kreis 6 Circle 7 Kreis 7 Circle 8 Kreis 8 Circle 9 Kreis 9 Fire Feuer Campground Campingplatz Motorcycle Motorrad Rail engine Lok Car Auto File server Dateiserver HC Future Hurrikan Vorhersage Aid station Erste Hilfe BBS Mailbox Canoe Kanu Eyeball Augapfel Tractor Traktor Grid Square Grid Square Hotel Hotel TCP/IP TCP/IP School Schule Logon Logon MacOS MacOS NTS station NTS-Station Balloon Ballon Police car Polizeiauto TBD TBD RV RV Shuttle Shuttle SSTV SSTV Bus Bus ATV ATV Weather service Wetterdienst Helo Helo Yacht Yacht MS Windows MS Windows Jogger Jogger Triangle Dreieck PBBS PBBS Plane large großes Flugzeug Weather station Wetterstation Dish antenna Parabolantenne Ambulance Krankenwagen Bike Fahrrad ICP ICP Fire station Feuerwache Horse Pferd Fire truck Löschzug Glider Gleiter Hospital Krankenhaus IOTA IOTA Jeep Jeep Truck small kleiner Truck Laptop Laptop Mic-E Mic-E Node Knoten EOC EOC Rover Rover Grid Grid Antenna Antenne Power boat Motorboot Truck stop Rastplatz Truck large großer Truck Van Van Water Wasser XAPRS XAPRS Yagi Yagi Shelter Schutzhütte FMAdmitSelect Always Immer Channel Free Kanal frei Other Tone Abweichender Subton FMChannelDialog Squelch Rauschsperre Tx Admit TX Voraussetzung Rx Tone RX Subton Tx Tone TX Subton Bandwidth Bandbreite APRS APRS FlagEditDialog Select Flags Optionen GPSSystemDialog Create DMR APRS System DMR APRS System anlegen Edit DMR APRS System DMR APRS System bearbeiten [Selected] [Ausgewählt] Edit GPS System GPS System bearbeiten Basic Basis Name Name Destination Ziel Update period Aktualisierungsperiode Revert Channel Sendekanal Extensions Erweiterungen GeneralSettingsView Boot Settings Boot-Einstellungen Intro Line 1 Begrüßungszeile 1 First greeting line (if supported by the radio). Erste Begrüßungszeile (insofern das Gerät dies unterstützt). Intro line 1 Begrüßungszeile 1 Intro Line 2 Begrüßungszeile 2 Second greeting line (if supported by the radio). Zweite Begrüßungszeile (insofern das Gerät dies unterstützt). Intro line 2 Begrüßungszeile 2 Audio Settings Audioeinstellungen MIC Amp. MIC Verstärkung Speech Synthesis Sprachsynthese Channel Default Values Kanal-Standardwerte Power Leistung Max Maximum High Hoch Mid Mittel Low Niedrig Min Minimum Squelch Rauschsperre Open Offen Transmit Timeout TX Timeout Off Aus VOX Level VOX Empfindlichkeit Extensions Erweiterungen GroupListWrapper Contact Kontakt GroupListsView Cannot delete RX group list Kann Empfangsgruppe nicht löschen Cannot delete RX group lists: You have to select a group list first. Kann Empfangsgruppe nicht löschen: Wählen Sie zunächst eine Gruppe aus. Delete RX group list? Empfangsgruppe löschen? Delete RX group list %1? Empfangsgruppe '%1' löschen? Delete %1 RX group lists? %1 Empfangsgruppen löschen? Add RX Group Empfangsgruppe hinzufügen Alt++ Alt++ Delete RX Group Empfangsgruppe löschen Alt+- Alt+- GroupListsWrapper RX Group Lists Empfangsgruppen M17ChannelDialog Channel mode Kanalmodus Access number Zugriffsnummer Tx contact TX Kontakt Send position Position senden M17ChannelModeSelect Voice Sprache Data Daten Voice + Data Sprache & Daten M17ContactDialog Edit M17 Contact M17 Kontakt bearbeiten Basic Basis Name Name The name of the contact. Der Name des Kontakts. Call Rufzeichen The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Das Rufzeichen des Kontakts. Darf nicht länger als 9 Zeichen sein und nur aus A-Z, 0-9, ., / und - bestehen. Ring Klingelton Broadcast Rundruf Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Setz diesen Kontakt als Rundrufkontakt, das gesetzte Rufzeichen wird dann ignoriert. Extensions Erweiterungen Create M17 Contact Einen M17 Kontakt hinzufügen M17ContactSelect [None] [Kein] MainWindow File Datei Device Gerät Help Hilfe Databases Datenbanken Toolbar Werkzeugleiste New Neu Creates a new Codeplug. Erzeugt einen neuen Codeplug Ctrl+N Strg+N Open ... Öffnen … <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> Importiert ein Codeplug von "conf" Dateien. Ctrl+O Strg+O Save ... Speichern … <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> Speichert den Codeplug in einer "conf" Datei. Ctrl+S Strg+S Quit Beenden Quits the application. Beendet qdmr. Ctrl+Q Strg+Q Detect Detektieren Detect connected radios. Detektiert angeschlossene Geräte. Verify Verifizieren <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Verifiziert den aktuellen Codeplug für angeschlossene Geräte. Ctrl+R Strg+R Read Lesen Reads a codeplug from connected radios. Liest den Codeplug vom angeschlossenen Gerät. Write Schreiben Writes the codeplug to the connected radio. Schreibt den aktuellen Codeplug auf das angeschlossene Gerät. About qdmr Über qdmr Read the handbook. Lesen Sie das Handbuch. F1 F1 Settings Einstellungen Shows settings dialog Zeigt den Einstellungsdialog an. Write Callsign DB Rufzeichendatenbank schreiben Writes call-sign DB to radio. Schreibt die Rufzeichendatenbank auf das Gerät. Refresh Callsign DB Rufzeichendatenbank aktualisieren Refreshes the downloaded callsign DB Aktualisiert die lokale Rufzeichendatenbank im Hintergrund aus den Onlinequellen. Refresh Talkgroup DB Sprechgruppendatenbank aktualisieren Refreshes the downloaded talkgroup DB Aktualisiert die Sprechgruppendatenbank im Hintergrund aus den Onlinequellen. Export to CHIRP ... Für CHIRP exportieren … Exports all FM channels to CHIRP CSV. Exportiert alle FM Kanäle als CHIRP CSV. Import ... Importieren … Imports and merges a codeplug into the current one. Importiert einen Codeplug und führt diesen mit dem aktuellen Codeplug zusammen. Refresh Orbital Elements Bahnelement aktualisieren Refreshes the orbital elements. Aktualisiert die Bahnelemente. Edit Satellites ... Satelliten bearbeiten … Opens an editor to edit your satellite database. Öffnet einen Editor, um die Satellitendatenbank zu bearbeiten. Write satellites Satelliten schreiben Writes the orbital elements and transponder information onto the connected device. Schreibt die Bahnelemente und Transponderinformationen auf das angeschlossene Gerät. Cannot update callsign DB: %1 Kann Rufzeichendatenbank nicht aktualisieren: %1 Callsign database updated & loaded. Rufzeichendatenbank aktualisiert & geladen. Cannot update talkgroup DB: %1 Kann Sprechgruppendatenbank nicht aktualisieren: %1 Talkgroup database updated & loaded. Sprechgruppendatenbank aktualisiert & geladen. Cannot update orbital elements: %1 Kann Orbitalelemente nicht aktualisieren: %1 Orbital elements updated & loaded. Orbitalelemente aktualisiert & geladen. Radio IDs Radio IDs Contacts Kontakte Group Lists Empfangsgruppen Channels Kanäle Zones Zonen Scan Lists Scanlisten GPS/APRS GPS/APRS Roaming Channels Roamingkanäle Roaming Zones Roamingzonen Extensions Erweiterungen Unsaved changes to codeplug. Nicht gespeicherte Änderungen am Codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Es gibt nicht gespeicherte Änderungen am Codeplug. Diese gehen verloren, wenn Sie fortfahren. MultiChannelSelectionDialog [Selected] [Auswahl] Select a channel: Wählen Sie einen Kanal: MultiGroupCallSelectionDialog Show private calls Direktrufe anzeigen Select a group call: Wählen Sie einen Gruppenruf aus: MultiRoamingChannelSelectionDialog Select roaming channels Roamingkanäle auswählen PositioningSystemListView Cannot delete GPS system Kann GPS System nicht löschen Cannot delete GPS system: You have to select a GPS system first. Kann GPS System nicht löschen: Wählen Sie zunächst ein GPS System aus. Delete positioning system? GPS System löschen? Delete positioning system %1? GPS System '%1' löschen? Delete %1 positioning systems? %1 GPS Systeme löschen? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Hinweis:</span> qdmr ist eine geräteunabhängige CPS. Jedoch unterstützen nicht alle Geräte GPS/APRS. Daher werden diese Einstellungen ignoriert, wenn der Codeplug auf Geräte geschrieben wird, die kein GPS/APRS unterstützen.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Verstecken</span></a></p></body></html> Add GPS System GPS System hinzufügen Alt+G Alt+G Add APRS System APRS System hinzufügen Alt+A Alt+A Delete Position System GPS/APRS System löschen Alt+- Alt+- PositioningSystemListWrapper DMR DMR APRS APRS [None] [Keine] [Selected] [Auswahl] Type Typ Name Name Destination Ziel Period Periode Channel Kanal Message Nachricht Extensions Erweiterungen PropertyDelegate None Kein Off Aus False Falsch True Wahr [None] [Leer] PropertyWrapper new element neues Element Property Eigenschaft Value Wert Description Beschreibung true wahr false falsch None Kein Off Aus [None] [Leer] Instance of %1 Instanz von '%1' List of %1 instances Liste von Instanzen von '%1' QObject [None] [Kein] RXGroupListDialog Create Group List Empfangsgruppe erzeugen Edit Group List Empfangsgruppe bearbeiten Cannot remove group call Kann Empfangsgruppe nicht entfernen Cannot remove group call: You have to select at least one group call first. Kann Empfangsgruppe nicht löschen: Wählen Sie zunächst eine Gruppe aus. Basic Basis Name Name Add Contact Kontakt hinzufügen Alt++ Alt++ Remove Contact Kontakt entfernen Alt+- Alt+- Extensions Erweiterungen RadioIDListView Cannot delete radio IDs Kann Radio ID nicht löschen Cannot delete radio IDs: You have to select a radio ID first. Kann Radio ID nicht löschen: Wählen Sie zunächst eine ID aus. Delete radio ID? Radio ID löschen? Delete radio ID %1? Radio ID '%1' löschen? Delete scan lists? Scanliste löschen? Delete %1 scan lists? %1 Scanlisten löschen? Default Radio ID Standard Radio ID Add Radio ID Radio ID hinzufügen Delete Radio ID Radio ID löschen RadioIdListWrapper [None] [Keine] Type Typ Name Name Number Nummer Extensions Erweiterungen RadioSelectionDialog Cannot auto-detect radio Kann Gerät nicht automatisch detektieren Select a specific radio Wählen Sie Ihr Gerät aus ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Kann Releaseinformationen nicht von https://github.com/hmatuschek/qdmr laden %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Kann Releaseinformationen nicht von https://github.com/hmatuschek/qdmr laden Informationen sind kein JSON Objekt! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. Kann Releaseinformationen nicht von https://github.com/hmatuschek/qdmr laden Release enthält keine Informationen. qDMR was updated to version %1 Eine neue Version %1 von qdmr wurde freigegeben. RoamingChannelDialog Time Slot Zeitschlitz Color Code Farbcode Selected Auswahl Name Name RX Frequency [MHz] RX Frequenz [MHz] TX Frequency [MHz] TX Frequenz [MHz] TS 1 TS 1 TS 2 TS 2 Edit roaming channel Roamingkanal bearbeiten Create roaming channel Roamingkanal anlegen RoamingChannelListView Add Roaming Channel Roamingkanal hinzufügen Delete Roaming Channel Roamingkanal löschen Cannot delete roaming channel Kann Roamingkanal nicht löschen Cannot delete roaming channel: You have to select a channel first. Kann Roamingkanal nicht löschen: Sie müssen zunächst mindestens einen Kanal auswählen. Delete roaming channel? Roamingkanal löschen? Delete roaming channel %1? Roamingkanal %1 löschen? Delete %1 roaming channel? %1 Roamingkanäle löschen? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Hinweis:</span> QDMR ist eine Geräte unabhängige CPS. Wie dem auch sei, nicht alle Geräte unterstützen das Roaming. Diese Einstellungen werden für jene Geräte ignoriert. </p><p><span style=" font-weight:600;">Tip:</span> Sie müssen die Roamingkanäle nicht explizit anlegen. Sie können diese zusammen mit der Roamingzone automatisch erzeugen lassen. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> RoamingChannelListWrapper [Selected] [Auswahl] [None] [Keine] Name Name RX Frequency RX Frequenz CC CC Zones Zonen Extensions Erweiterungen TX Frequency TX Frequenz TS TS RoamingChannelRefListWrapper Roaming Channel Roaming Kanal RoamingListWrapper %1 (containing %2 channels) %1 (mit %2 Kanälen) Roaming zone Roamingzone RoamingZoneDialog Create Roaming Zone Roamingzone erzeugen Set Roaming Zone Roamingzone bearbeiten Basic Basis Name: Name: Alt++ Alt++ Remove Channel Kanal entfernen Alt+- Alt+- Extension Erweiterung Add Roaming Channel Roamingkanal hinzufügen Add DMR Channel DMR Kanal hinzufügen Cannot remove channels. Kann Kanäle nicht entfernen. Cannot remove channels. Select at least one channel first. Kann Kanäle nicht entfernen. Wählen sie zunächst mindestens einen Kanal aus. RoamingZoneListView Generate roaming zone Roamingzone erzeugen Create a roaming zone by collecting all channels with these group calls. Eine Roamingzone aus allen Kanälen mit diesen Gruppenrufen erzeugen. Cannot delete roaming zone Kann Roamingzone nicht löschen Cannot delete roaming zone: You have to select a zone first. Kann Roamingzone nicht löschen: Wählen Sie zunächst eine Zone aus. Delete roaming zone? Roamingzone löschen? Delete roaming zone %1? Roamingzone '%1' löschen? Delete roaming zones? Roamingzonen löschen? Delete %1 roaming zones? %1 Roamingzonen löschen? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr ist eine geräteunabhängige CPS. Jedoch unterstützen nicht alle Geräte das Roaming, Diese Einstellungen werden ignoriert, wenn Sie den Codeplug auf solche Geräte schreiben. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Ausblenden</span></a></p></body></html> Add Roaming Zone Roamingzone hinzufügen Alt++ Alt++ Generate Roaming Zone Roamingzone erzeugen Delete Roaming Zone Roamingzone löschen Alt+- Alt+- RoamingZoneSelect [None] [Kein] [Default] [Standard] SatelliteDatabaseDialog Edit satellite database Satellitendatenbank bearbeiten Add Hinzufügen Delete Löschen SatelliteSelectionDialog Select a satellite Wähle einen Satelliten aus SatelliteTransponderDialog Edit Satellite Transponder Satellitentransponder bearbeiten FM Voice Transponder FM Sprachtransponder Uplink Frequency Uplinkfrequenz Uplink Tone Uplinksubton Downlink Frequency Downlinkfrequenz Downlink Tone Downlinksubton APRS Transponder APRS-Transponder Beacon Funkbake Beacon Frequency Bakenfrequenz ScanListDialog Edit Scan List Scanliste bearbeiten Basic Basis Name Name Primary Channel (50%) Primärkanal (50%) Secondary Channel (25%) Sekundärer Kanal (25%) Transmit Channel Sendekanal Add Channel Kanal hinzufügen Alt++ Alt++ Remove Channel Kanal entfernen Alt+- Alt+- Extensions Erweiterungen Create Scan List Scanliste erstellen [None] [Kein] [Selected] [Auswahl] [Last] [Letzter] ScanListsView Cannot delete scanlist Kann Scanliste nicht löschen Cannot delete scanlist: You have to select a scanlist first. Kann Scanliste nicht löschen: Wählen Sie zunächst eine Scanliste aus. Delete scan list? Scanliste löschen? Delete scan list %1? Scanliste '%1' löschen? Delete scan lists? Scanlisten löschen? Delete %1 scan lists? %1 Scanlisten löschen? Add Scan List Scanliste hinzufügen Alt++ Alt++ Delete Scan List Scanliste löschen Alt+- Alt+- ScanListsWrapper Scan-List Scanliste SearchPopup Ctrl+F Ctrl+F %1/%2 %1/%2 SelectiveCallBox None Kein CTCSS CTCSS DCS DCS Hz Hz Inverted Invertiert SettingsDialog Warning! Warnung! Settings Einstellungen Location Position System location Systemposition Locator Locator Repeater Info Sources Repeaterinformationsquellen enable aktivieren Radio Programming Geräteprogrammierung Update codeplug Codeplug updaten <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> <html><head/><body><p>Aktualisiert den Codeplug auf dem Gerät. Wenn nicht ausgewählt, wird der Codeplug auf dem Gerät überschrieben, mit mehr oder weniger guten Standardeinstellungen.</p><p><br/></p><p>Wenn ausgewählt, wird qdmr den Codeplug auf dem Gerät zunächst herunterladen, mit den Einstellungen aus diesem Codeplug aktualisieren und diesen aktualisierten Codeplug zurück auf das Gerät schreiben (empfohlen).</p></body></html> Auto-enable GPS GPS automatisch einschalten When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. Wenn ein GPS/APRS System für irgendeinen Kanal gesetzt wurde, wird das GPS Modul (insofern vorhanden) automatisch eingeschaltet. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Wenn eine Roamingzone definiert und mit einem Kanal assoziiert ist, wird automatisches Roaming aktiviert. Auto-enable roaming Roaming automatisch aktivieren Data Sources Datenquellen World Weltweit North America Nordamerika Programming Programmierung Radio Interfaces Schnittstellen disable auto-detect Autoerkennung deaktivieren Ignore verification warnings Verifikationswarnungen ignorieren <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> <html><head/><body><p>Da die Schnittstelle zum Gerät nach der Verifikation offen gehalten wird, kann es zu Timeouts zwischen der Verifikation und dem eigentlichen Schreiben des Codeplugs kommen. In der Folge, kann der Schreibvorgang fehlschlagen. Um diese Zeitverzögerung zu verhindern, können Warnungen ignoriert werden und der Schreibvorgang startet sofort. Verifikationsfehler, werden weiterhin den Schreibvorgang verhindern.</p></body></html> Ignore frequency limits Frequenzbereiche ignorieren Do not set this option unless you know what you are doing. Bitte aktivieren Sie diese Option nicht, es sei denn, Sie wissen sehr genau, was Sie tun. Senden außerhalb der spezifizierten Frequenzbereiche kann das Gerät permanent beschädigen. Update Device Clock Geräteuhr stellen Call-Sign DB Rufzeichendatenbank Limit number of DB entries Die Anzahl der Einträge beschränken When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Ist diese Option ausgewählt, wird die Anzahl der Einträge in die Rufzeichendatenbank limitiert. Wenn nicht, wird immer die maximale Anzahl an möglichen Einträgen auf das Gerät geschrieben (abhängig vom Gerät). Number of DB entries Anzahl der Einträge der Rufzeichendatenbank Specifies the number of DB entries (if enabled above). Legt die Anzahl der Einträge in der Rufzeichendatenbank fest, die auf das Gerät geschrieben werden (wenn oben ausgewählt). Select using my DMR ID Auswahl anhand meiner DMR ID If enabled, the entries are selected using the users DMR ID. Wenn ausgewählt, werden die Einträge der Rufzeichendatenbank anhand der eigenen Standard DMR ID ausgewählt. Select using prefixes Auswahl anhand der Prefixe If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. Wenn ausgewählt, spezifizieren diese Komma-getrennten DMR ID prefixe jene Einträge der Rufzeichendatenbank, die auf das Gerät geschrieben werden. SquelchEdit Open Offen Uses the global squelch setting if enabled. Wenn ausgewählt, benutzt die globale Rauschsperreneinstellung. Default Standard TimeslotSelect Slot 1 Zeitschlitz 1 Slot 2 Zeitschlitz 2 TransponderFrequencyEditor None Kein VerifyDialog Verify Codeplug Codeplug verifizieren The codeplug cannot be uploaded, unless all critical issues (red) are resolved. Der Codeplug kann nicht geschrieben werden, solange nicht all kritischen Probleme (rot) aufgelöst wurden. ZoneDialog Edit Zone Zone bearbeiten Basic Basis <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p align="justify"><span style=" font-weight:600;">Hinweis:</span> Zonen fassen Kanäle zusammen, die normalerweise für eine bestimmte Region gelten. D.h., alle Kanäle, für eine Sprechgruppe einer bestimmten Region. </p><p align="justify">qdmr verwaltet diese Kanäle in zwei Listen. Eine für jeden VFO des Gerätes (wenn es denn zwei hat). Viele Geräte erlauben es jedoch, eine Zone für jeden VFO festzulegen. In diesen Fällen wird qdrm die Zone in zwei Zonen (A &amp; B) aufteilen und beide separat auf das Gerät schreiben.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Ausblenden</span></a></p></body></html> Name Name Channels A Kanäle A add hinzufügen remove entfernen Channels B Kanäle B Extension Erweiterungen Create Zone Zone anlegen Select at least one channel first. Wählen sie zunächst mindestens einen Kanal aus. Cannot remove channel Kann Kanal nicht entfernen ZoneListView Cannot delete zone Kann Zone nicht löschen Cannot delete zone: You have to select a zone first. Kann Zone nicht löschen: Wählen Sie zunächst eine Zone aus. Delete zone? Zone löschen? Delete zone %1? Zone '%1' löschen? Delete zones? Zonen löschen? Delete %1 zones? %1 Zonen löschen? Add Zone Zone hinzufügen Alt++ Alt++ Delete Zone Zone löschen Alt+- Alt+- ZoneListWrapper Zone Zone aprssystemdialog Edit APRS System APRS System bearbeiten Basic Basis Name Name Channel Kanal Source Quelle Destination Ziel Path Pfad Icon Icon Update period [s] Periode [s] Message Nachricht Extensions Erweiterungen main Codeplug file to load. Zu ladende Codeplugdatei. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. Legt das Log-level der Standardausgabe fest. Muss `debug`, `info`, `warning`, `error` oder `fatal` sein. ================================================ FILE: i18n/en_US.ts ================================================ AMChannelDialog Squelch Squelch APRSSelect [None] [None] APRSSystemDialog Create APRS system Create APRS system Edit APRS system Edit APRS system [Selected] [Selected] AboutDialog About qdmr About qdmr Supported Radios Supported Radios Application Unsaved changes to codeplug. Unsaved changes to codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Open codeplug Open codeplug Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Cannot open file Cannot open file Cannot read codeplug from file '%1': %2 Cannot read codeplug from file '%1': %2 Cannot read codeplug. Cannot read codeplug. Save codeplug Save codeplug Codeplug Files (*.yaml *.yml) Codeplug Files (*.yaml *.yml) Please use new YAML format. Please use new YAML format. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Cannot save codeplug to file '%1': %2 Cannot save codeplug to file '%1': %2 Cannot save codeplug Cannot save codeplug Cannot save codeplug to file '%1'. Cannot save codeplug to file '%1'. Export codeplug Export codeplug CHIRP CSV Files (*.csv) CHIRP CSV Files (*.csv) Cannot export codeplug Cannot export codeplug Cannot export codeplug to file '%1': %2 Cannot export codeplug to file '%1': %2 Import codeplug Import codeplug CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) Cannot import codeplug Cannot import codeplug Cannot import codeplug from '%1': %2 Cannot import codeplug from '%1': %2 Do not know, how to handle file '%1'. Do not know, how to handle file '%1'. No matching devices found. No matching devices found. Cannot connect to radio Cannot connect to radio Cannot connect to radio: %1 Cannot connect to radio: %1 Radio found Radio found Found device '%1'. Found device '%1'. No radio found No radio found Verification success Verification success The codeplug was successfully verified with the radio '%1' The codeplug was successfully verified with the radio '%1' Read ... Read … Read error Read error Read complete Read complete Upload ... Upload … Cannot write call-sign DB. Cannot write call-sign DB. The detected radio '%1' does not support a call-sign DB. The detected radio '%1' does not support a call-sign DB. No matching device was found. No matching device was found. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. Write call-sign DB ... Write call-sign DB … Cannot write satellite config. Cannot write satellite config. The detected radio '%1' does not support satellite tracking. The detected radio '%1' does not support satellite tracking. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. Write satellite config ... Write satellite config … Write error Write error Write complete Write complete %1 (alias for %2 %3) %1 (alias for %2 %3) BandwidthSelect Narrow (12.5 kHz) Narrow (12.5 kHz) Wide (25 kHz) Wide (25 kHz) ChannelDialog Edit Channel Edit Channel <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> Basic Basic Name Name Rx Frequency RX Frequency Tx Frequency TX Frequency Tx Offset Tx Offset Power Power Max Max High High Mid Mid Low Low Min Min Default Default Tx Timeout TX Timeout Off Off VOX Level VOX Level Rx Only RX Only Scan List Scan List Extensions Extensions No offset No offset Positive offset Positive offset Negative offset Negative offset [None] [None] ChannelListView Select a single channel first Select a single channel first To clone a channel, please select a single channel to clone. To clone a channel, please select a single channel to clone. Cannot delete channel Cannot delete channel Cannot delete channel: You have to select a channel first. Cannot delete channel: You have to select a channel first. Delete channel? Delete channel? Delete channel %1? Delete channel '%1'? Delete %1 channels? Delete %1 channels? Alt+A Alt+A Add Channel ... Add Channel … Clone Channel Clone Channel Alt+C Alt+C Delete Channel Delete Channel Alt+- Alt+- Add FM Channel Add FM Channel Adds a new FM channel Adds a new FM channel Add DMR Channel Add DMR Channel Adds a new DMR channel. Adds a new DMR channel. Add AM Channel Add AM Channel Adds a new AM channel. Adds a new AM channel. ChannelListWrapper FM FM DMR DMR AM AM [Default] [Default] Max Max High High Mid Mid Low Low Min Min Off Off On On Always Always Free Free Color Color Tone Tone [None] [None] Open Open Wide Wide Narrow Narrow Type Type Name Name Rx Frequency RX Frequency Tx Frequency TX Frequency Power Power Timeout Timeout Rx Only RX Only Admit Admit Scanlist Scanlist CC CC TS TS RX Group List RX Group List TX Contact TX Contact DMR ID DMR ID GPS/APRS GPS/APRS Roaming Roaming Squelch Squelch Rx Tone RX Tone Tx Tone TX Tone Extensions Extensions Zones Zones Bandwidth Bandwidth ChannelRefListWrapper Channel Channel ChannelSelectionDialog Select a channel: Select a channel: ConfigMergeDialog Merging codeplugs ... Merging codeplugs … <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, …) already exist, select how these conflicts are resolved for items and sets.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items Items Ignore Ignore Override Override Duplicate Duplicate Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets Sets Merge Merge Ignores any duplicate item. Ignores any duplicate item. Replaces any duplicate item with the imported one. Replaces any duplicate item with the imported one. Imports any duplicate item with a modified name. Imports any duplicate item with a modified name. Ignores any duplicate set. Ignores any duplicate set. Replaces any duplicate set with the imported one. Replaces any duplicate set with the imported one. Imports any duplicate set with a modified name. Imports any duplicate set with a modified name. Merges duplicate sets. Merges duplicate sets. ConfigObjectListView Cannot move items. Cannot move items. Cannot move items: You have to select at least one item first. Cannot move items: You have to select at least one item first. Move selected item(s) to the top. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. Move selected item(s) to the bottom. ConfigObjectTableView Cannot move items. Cannot move items. Cannot move items: You have to select at least one item first. Cannot move items: You have to select at least one item first. Cannot move items as long as there is some filter or sorting applied. Cannot move items as long as there is some filter or sorting applied. Move selected item(s) to the top. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. Move selected item(s) to the bottom. Toggle Filter and Sorting Toggle Filter and Sorting Close Sort and Filter Close Sort and Filter ConfigObjectTypeSelectionDialog An instance of %1. An instance of '%1'. <p>%1<p><p style="margin-left:10px;">%2</p> <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Create extension object Select the class of object to create Select the class of object to create ContactListView Cannot delete contact Cannot delete contact Cannot delete contact: You have to select a contact first. Cannot delete contact: You have to select a contact first. Delete contact? Delete contact? Delete contact %1? Delete contact '%1'? Delete contacts? Delete contacts? Delete %1 contacts? Delete %1 contacts? Adds a contact to the list. Adds a contact to the list. Alt++ Alt++ Add M17 Contact Add M17 Contact Adds an M17 contact to the list. Adds an M17 contact to the list. Add DTMF Contact Add DTMF Contact Adds an DTMF (analog) contact to the list. Adds a DTMF (analog) contact to the list. Add DMR Contact Add DMR Contact Adds an DMR contact to the list. Adds a DMR contact to the list. Delete contact button Delete contact button Add Contact Add Contact Delete Contact Delete Contact Alt+- Alt+- ContactListWrapper DTMF DTMF On On Off Off Private Call Private Call Group Call Group Call All Call All Call [None] [None] M17 M17 [Broadcast] [Broadcast] Type Type Name Name Number Number RX Tone RX Tone Extensions Extensions DMRAdmitSelect Always Always Channel Free Channel Free Other Color-code Other Color-code DMRChannelDialog Radio Id Radio Id Tx Admit TX Admit Color-code Color-code Time-slot Time-slot Group list Group list Tx Contact TX Contact APRS APRS Roaming zone Roaming zone DMRContactDialog Create DMR Contact Create DMR Contact Edit DMR Contact Edit DMR Contact Private Call Private Call Group Call Group Call All Call All Call Basic Basic Type Type Name Name Number Number Ring Ring Extensions Extensions DMRContactSelect [None] [None] DMRIDDialog Basic Basic Name Name DMR ID DMR ID Extensions Extensions DMRIdSelect [Default] [Default] DTMFContactDialog Create DTMF Contact Create DTMF Contact Edit DMR Contact Edit DMR Contact Basic Basic Name Name Number Number Ring Ring Extensions Extensions DeviceSelectionDialog Select a device Select a device <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> ErrorMessageView Error: Unknown. Error: Unknown. Error: %1 Error: %1 Traceback: Traceback: ExtensionView Cannot create extension. Cannot create extension. Cannot create extension, consider reporting a bug. Cannot create extension, consider reporting a bug. Cannot create list element. Cannot create list element. Cannot create list element, consider reporting a bug. Cannot create list element, consider reporting a bug. Create Create Remove Remove FMAPRSSelect [None] [None] FMAPRSSystem [None] [None] Police station Police station Digipeater Digipeater Phone Phone DX cluster DX cluster HF gateway HF gateway Plane small Plane small Mobile Satellite station Mobile satellite station Wheel Chair Wheel chair Snowmobile Snowmobile Red cross Red cross Boy scout Boy scout Home Home X X Red dot Red dot Circle 0 Circle 0 Circle 1 Circle 1 Circle 2 Circle 2 Circle 3 Circle 3 Circle 4 Circle 4 Circle 5 Circle 5 Circle 6 Circle 6 Circle 7 Circle 7 Circle 8 Circle 8 Circle 9 Circle 9 Fire Fire Campground Campground Motorcycle Motorcycle Rail engine Rail engine Car Car File server File server HC Future HC Future Aid station Aid station BBS BBS Canoe Canoe Eyeball Eyeball Tractor Tractor Grid Square Grid Square Hotel Hotel TCP/IP TCP/IP School School Logon Logon MacOS MacOS NTS station NTS station Balloon Balloon Police car Police car TBD TBD RV RV Shuttle Shuttle SSTV SSTV Bus Bus ATV ATV Weather service Weather service Helo Helicopter Yacht Yacht MS Windows MS Windows Jogger Jogger Triangle Triangle PBBS PBBS Plane large Plane large Weather station Weather station Dish antenna Dish antenna Ambulance Ambulance Bike Bike ICP ICP Fire station Fire station Horse Horse Fire truck Fire truck Glider Glider Hospital Hospital IOTA IOTA Jeep Jeep Truck small Truck small Laptop Laptop Mic-E Mic-E Node Node EOC EOC Rover Rover Grid Grid Antenna Antenna Power boat Power boat Truck stop Truck stop Truck large Truck large Van Van Water Water XAPRS XAPRS Yagi Yagi Shelter Shelter FMAdmitSelect Always Always Channel Free Channel Free Other Tone Other Tone FMChannelDialog Squelch Squelch Tx Admit TX Admit Rx Tone RX Tone Tx Tone TX Tone Bandwidth Bandwidth APRS APRS FlagEditDialog Select Flags Select Flags GPSSystemDialog Create DMR APRS System Create DMR APRS System Edit DMR APRS System Edit DMR APRS System [Selected] [Selected] Edit GPS System Edit GPS System Basic Basic Name Name Destination Destination Update period Update period Revert Channel Revert Channel Extensions Extensions GeneralSettingsView Boot Settings Boot Settings Intro Line 1 Intro Line 1 First greeting line (if supported by the radio). First greeting line (if supported by the radio). Intro line 1 Intro line 1 Intro Line 2 Intro Line 2 Second greeting line (if supported by the radio). Second greeting line (if supported by the radio). Intro line 2 Intro line 2 Audio Settings Audio Settings MIC Amp. MIC Amp. Speech Synthesis Speech Synthesis Channel Default Values Channel Default Values Power Power Max Max High High Mid Mid Low Low Min Min Squelch Squelch Open Open Transmit Timeout Transmit Timeout Off Off VOX Level VOX Level Extensions Extensions GroupListWrapper Contact Contact GroupListsView Cannot delete RX group list Cannot delete group list Cannot delete RX group lists: You have to select a group list first. Cannot delete group lists: You have to select a group list first. Delete RX group list? Delete group list? Delete RX group list %1? Delete group list '%1'? Delete %1 RX group lists? Delete %1 group lists? Add RX Group Add Group List Alt++ Alt++ Delete RX Group Delete Group List Alt+- Alt+- GroupListsWrapper RX Group Lists Group Lists M17ChannelDialog Channel mode Channel mode Access number Access number Tx contact Tx contact Send position Send position M17ChannelModeSelect Voice Voice Data Data Voice + Data Voice + Data M17ContactDialog Edit M17 Contact Edit M17 Contact Basic Basic Name Name The name of the contact. The name of the contact. Call Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. The call-sign of the contact. Must not be longer than 9 chars, A-Z, 0-9, ., /, -. Ring Ring Broadcast Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Extensions Create M17 Contact Create M17 Contact M17ContactSelect [None] [None] MainWindow File File Device Device Help Help Databases Databases Toolbar New New Creates a new Codeplug. Creates a new Codeplug. Ctrl+N Ctrl+N Open ... Open … <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> Ctrl+O Ctrl+O Save ... Save … <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> Ctrl+S Ctrl+S Quit Quit Quits the application. Quits the application. Ctrl+Q Ctrl+Q Detect Detect Detect connected radios. Detect connected radios. Verify Verify <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Verifies the current codeplug with connected radios. Ctrl+R Ctrl+R Read Read Reads a codeplug from connected radios. Reads a codeplug from connected radios. Write Write Writes the codeplug to the connected radio. Writes the codeplug to the connected radio. About qdmr About qdmr Read the handbook. Read the handbook. F1 F1 Settings Settings Shows settings dialog Shows settings dialog. Write Callsign DB Write call-sign DB Writes call-sign DB to radio. Writes call-sign DB to radio. Refresh Callsign DB Refresh call-sign DB Refreshes the downloaded callsign DB Refreshes the downloaded call-sign DB Refresh Talkgroup DB Refresh talk-group DB Refreshes the downloaded talkgroup DB Refreshes the downloaded talk-group DB Export to CHIRP ... Export to CHIRP … Exports all FM channels to CHIRP CSV. Exports all FM channels to CHIRP CSV. Import ... Import … Imports and merges a codeplug into the current one. Imports and merges a codeplug into the current one. Refresh Orbital Elements Refresh Orbital Elements Refreshes the orbital elements. Refreshes the orbital elements. Edit Satellites ... Edit Satellites … Opens an editor to edit your satellite database. Opens an editor to edit your satellite database. Write satellites Write satellites Writes the orbital elements and transponder information onto the connected device. Writes the orbital elements and transponder information onto the connected device. Cannot update callsign DB: %1 Cannot update call-sign DB: %1 Callsign database updated & loaded. Call-sign database updated & loaded. Cannot update talkgroup DB: %1 Cannot update talk-group DB: %1 Talkgroup database updated & loaded. Talk-group database updated & loaded. Cannot update orbital elements: %1 Cannot update orbital elements: %1 Orbital elements updated & loaded. Orbital elements updated & loaded. Radio IDs Radio IDs Contacts Contacts Group Lists Group Lists Channels Channels Zones Zones Scan Lists Scan Lists GPS/APRS GPS/APRS Roaming Channels Roaming Channels Roaming Zones Roaming Zones Extensions Extensions Unsaved changes to codeplug. Unsaved changes to codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. There are unsaved changes to the current codeplug. These changes are lost if you proceed. MultiChannelSelectionDialog [Selected] [Selected] Select a channel: Select a channel: MultiGroupCallSelectionDialog Show private calls Show private calls Select a group call: Select a group call: MultiRoamingChannelSelectionDialog Select roaming channels Select roaming channels PositioningSystemListView Cannot delete GPS system Cannot delete GPS system Cannot delete GPS system: You have to select a GPS system first. Cannot delete GPS system: You have to select a GPS system first. Delete positioning system? Delete positioning system? Delete positioning system %1? Delete positioning system '%1'? Delete %1 positioning systems? Delete %1 positioning systems? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add GPS System Add GPS System Alt+G Alt+G Add APRS System Add APRS System Alt+A Alt+A Delete Position System Delete Positioning System Alt+- Alt+- PositioningSystemListWrapper DMR DMR APRS APRS [None] [None] [Selected] [Selected] Type Type Name Name Destination Destination Period Period Channel Channel Message Message Extensions Extensions PropertyDelegate None None Off Off False False True True [None] [None] PropertyWrapper new element new element Property Property Value Value Description Description true true false false None None Off Off [None] [None] Instance of %1 Instance of '%1' List of %1 instances List of %1 instances QObject [None] [None] RXGroupListDialog Create Group List Create group list Edit Group List Edit group list Cannot remove group call Cannot remove group call Cannot remove group call: You have to select at least one group call first. Cannot remove group call: You have to select at least one group call first. Basic Basic Name Name Add Contact Add Contact Alt++ Alt++ Remove Contact Remove Contact Alt+- Alt+- Extensions Extensions RadioIDListView Cannot delete radio IDs Cannot delete radio IDs Cannot delete radio IDs: You have to select a radio ID first. Cannot delete radio IDs: You have to select a radio ID first. Delete radio ID? Delete radio ID? Delete radio ID %1? Delete radio ID '%1'? Delete scan lists? Delete scan lists? Delete %1 scan lists? Delete %1 scan lists? Default Radio ID Default Radio ID Add Radio ID Add Radio ID Delete Radio ID Delete Radio ID RadioIdListWrapper [None] [None] Type Type Name Name Number Number Extensions Extensions RadioSelectionDialog Cannot auto-detect radio Cannot auto-detect radio Select a specific radio Select a specific radio ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. qDMR was updated to version %1 qdmr was updated to version %1 RoamingChannelDialog Name Name RX Frequency [MHz] RX Frequency [MHz] TX Frequency [MHz] TX Frequency [MHz] Time Slot Time Slot Color Code Color Code Selected Selected Edit roaming channel Edit roaming channel Create roaming channel Create roaming channel TS 1 TS 1 TS 2 TS 2 RoamingChannelListView <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Channel Add Roaming Channel Delete Roaming Channel Delete Roaming Channel Cannot delete roaming channel Cannot delete roaming channel Cannot delete roaming channel: You have to select a channel first. Cannot delete roaming channel: You have to select a channel first. Delete roaming channel? Delete roaming channel? Delete roaming channel %1? Delete roaming channel %1? Delete %1 roaming channel? Delete %1 roaming channel? RoamingChannelListWrapper [Selected] [Selected] [None] [None] Name Name RX Frequency RX Frequency TX Frequency TX Frequency TS TS Zones Zones Extensions Extensions CC CC RoamingChannelRefListWrapper Roaming Channel Roaming Channel RoamingListWrapper %1 (containing %2 channels) %1 (containing %2 channels) Roaming zone Roaming zone RoamingZoneDialog Create Roaming Zone Create roaming zone Set Roaming Zone Edit roaming zone Cannot remove channels. Cannot remove channels. Cannot remove channels. Select at least one channel first. Cannot remove channels. Select at least one channel first. Basic Basic Name: Name: Add Roaming Channel Add Roaming Channel Add DMR Channel Add DMR Channel Alt++ Alt++ Remove Channel Remove Channel Alt+- Alt+- Extension Extension RoamingZoneListView Generate roaming zone Generate roaming zone Create a roaming zone by collecting all channels with these group calls. Create a roaming zone by collecting all channels with these group calls. Cannot delete roaming zone Cannot delete roaming zone Cannot delete roaming zone: You have to select a zone first. Cannot delete roaming zone: You have to select a zone first. Delete roaming zone? Delete roaming zone? Delete roaming zone %1? Delete roaming zone '%1'? Delete roaming zones? Delete roaming zones? Delete %1 roaming zones? Delete %1 roaming zones? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Zone Add Roaming Zone Alt++ Alt++ Generate Roaming Zone Generate Roaming Zone Delete Roaming Zone Delete Roaming Zone Alt+- Alt+- RoamingZoneSelect [None] [None] [Default] [Default] SatelliteDatabaseDialog Edit satellite database Edit satellite database Add Add Delete Delete SatelliteSelectionDialog Select a satellite Select a satellite SatelliteTransponderDialog Edit Satellite Transponder Edit Satellite Transponder FM Voice Transponder FM Voice Transponder Uplink Frequency Uplink Frequency Uplink Tone Uplink Tone Downlink Frequency Downlink Frequency Downlink Tone Downlink Tone APRS Transponder APRS Transponder Beacon Beacon Beacon Frequency Beacon Frequency ScanListDialog Edit Scan List Edit scan list Basic Basic Name Name Primary Channel (50%) Primary Channel (50%) Secondary Channel (25%) Secondary Channel (25%) Transmit Channel Transmit Channel Add Channel Add Channel Alt++ Alt++ Remove Channel Remove Channel Alt+- Alt+- Extensions Extensions Create Scan List Create scan list [None] [None] [Selected] [Selected] [Last] [Last] ScanListsView Cannot delete scanlist Cannot delete scanlist Cannot delete scanlist: You have to select a scanlist first. Cannot delete scanlist: You have to select a scanlist first. Delete scan list? Delete scan list? Delete scan list %1? Delete scan list '%1'? Delete scan lists? Delete scan lists? Delete %1 scan lists? Delete %1 scan lists? Add Scan List Add Scan List Alt++ Alt++ Delete Scan List Delete Scan List Alt+- Alt+- ScanListsWrapper Scan-List Scanlist SearchPopup Ctrl+F Ctrl+F %1/%2 %1/%2 SelectiveCallBox None None CTCSS CTCSS DCS DCS Hz Hz Inverted Inverted SettingsDialog Warning! Warning! Settings Settings Location Location System location System location Locator Locator Repeater Info Sources Repeater Info Sources enable enable Radio Programming Radio Programming Update codeplug Update codeplug <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> Auto-enable GPS Auto-enable GPS When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Auto-enable roaming Auto-enable roaming Data Sources Data Sources World World North America North America Programming Programming Radio Interfaces Radio Interfaces disable auto-detect disable auto-detect Ignore verification warnings Ignore verification warnings <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> Ignore frequency limits Ignore frequency limits Do not set this option unless you know what you are doing. Do not set this option unless you know what you are doing. Update Device Clock Update Device Clock Call-Sign DB Call-Sign DB Limit number of DB entries Limit number of DB entries When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Number of DB entries Number of DB entries Specifies the number of DB entries (if enabled above). Specifies the number of DB entries (if enabled above). Select using my DMR ID Select using my DMR ID If enabled, the entries are selected using the users DMR ID. If enabled, the entries are selected using the users DMR ID. Select using prefixes Select using prefixes If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. SquelchEdit Open Open Uses the global squelch setting if enabled. Uses the global squelch setting if enabled. Default Default TimeslotSelect Slot 1 Slot 1 Slot 2 Slot 2 TransponderFrequencyEditor None None VerifyDialog Verify Codeplug Verify Codeplug The codeplug cannot be uploaded, unless all critical issues (red) are resolved. The codeplug cannot be uploaded, unless all critical issues (red) are resolved. ZoneDialog Edit Zone Edit zone Basic Basic <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Name Name Channels A Channels A add add remove remove Channels B Channels B Extension Extension Create Zone Create zone Cannot remove channel Cannot remove channel Select at least one channel first. Select at least one channel first. ZoneListView Cannot delete zone Cannot delete zone Cannot delete zone: You have to select a zone first. Cannot delete zone: You have to select a zone first. Delete zone? Delete zone? Delete zone %1? Delete zone '%1'? Delete zones? Delete zones? Delete %1 zones? Delete %1 zones? Add Zone Add Zone Alt++ Alt++ Delete Zone Delete Zone Alt+- Alt+- ZoneListWrapper Zone Zone aprssystemdialog Edit APRS System Edit APRS System Basic Basic Name Name Channel Channel Source Source Destination Destination Path Path Icon Icon Update period [s] Update period [s] Message Message Extensions Extensions main Codeplug file to load. Codeplug file to load. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. ================================================ FILE: i18n/fr.ts ================================================ AMChannelDialog Squelch Squelch APRSSelect [None] [Aucun] APRSSystemDialog Create APRS system Créer un système APRS Edit APRS system Modifier le système APRS [Selected] [Sélectionné] AboutDialog About qdmr À propos de qdrm Supported Radios Radios prises en charge Application Unsaved changes to codeplug. Modifications au codeplug non enregistrées. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Il y a des modifications non enregsitrées dans le codeplug. Ces modifications seront perdues si vous continuez. Open codeplug Ouvrir un codeplug Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Fichiers Codeplug (*.yaml);;Fichiers Codeplug Files, ancien format (*.conf *.csv *.txt);;Tous les fichiers (*) Cannot open file Impossible d'ouvrir le fichier Cannot read codeplug from file '%1': %2 Impossible de lire le codeplug depuis le fichier '%1': %2 Cannot read codeplug. Impossible de lire le codeplug. Save codeplug Enregistrer le codeplug Codeplug Files (*.yaml *.yml) Fichiers Codeplug (*.yaml *.yml) Please use new YAML format. Merci d'utiliser le nouveau format YAML. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Enregistrer dans l'ancien format de paramétrage tabulaire est désactivé depuis la version 0.9.0. Lire ces fichiers reste possible. Cannot save codeplug to file '%1': %2 Impossible d'enregistrer le codeplug dans le fichier '%1': %2 Cannot save codeplug Impossible d'enregistrer le codeplug Cannot save codeplug to file '%1'. Impossible d'enregistrer le codeplug dans le fichier '%1'. Export codeplug Exporter le codeplug CHIRP CSV Files (*.csv) Fichiers CSV CHIRP (*.csv) Cannot export codeplug Impossible d'exporter le codeplug Cannot export codeplug to file '%1': %2 Impossible d'exporter le codeplug vers le fichier '%1': %2 Import codeplug Importer un codeplug CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) Fichiers CSV CHIRP (*.csv);;Fichiers YAML (*.yaml *.yml) Cannot import codeplug Impossible d'importer le codeplug Cannot import codeplug from '%1': %2 Impossible d'importer le codeplug depuis '%1': %2 Do not know, how to handle file '%1'. Gestion du fichier '%1' inconnue. No matching devices found. Aucune radio correspondante trouvé. Cannot connect to radio Échec de connexion à la radio Cannot connect to radio: %1 Échec de connexion à la radio: %1 Radio found Radio accessible Found device '%1'. Radio trouvée '%1'. No radio found Aucune radio accessible No matching device was found. Aucune radio correspondante trouvée. Verification success Succès de la validation The codeplug was successfully verified with the radio '%1' Le codeplug a été validé avec succès pour la radio '%1' Read ... Lecture … Read error Erreur de lecture Read complete Lecture terminée Upload ... Téléversement … Cannot write call-sign DB. Impossible d'écrire la base d'indicatifs. The detected radio '%1' does not support a call-sign DB. La radio '%1' détectée n'accepte pas une base d'indicatifs. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. La radio '%1' détectée ne gère pas de base d'indicatifs. Cependant, cette fonctionnalité n'est pas encore disponible. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. QDMR sélectionne les indicatifs à écrire sur la base du DMR ID par défaut de la radio. Aucun ID par défaut de configuré. Write call-sign DB ... Écriture de la base des indicatifs … Cannot write satellite config. Échec de l’écriture du paramétrage des satellites. The detected radio '%1' does not support satellite tracking. La radio '%1' détectée ne prend pas en charge le suivi des satellites. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. La radio '%1' détectée ne prend pas en charge le suivi des satellites. Cependant, cette fonctionnalité n'est pas encore mise en œuvre. Write satellite config ... Écriture du paramétrage des satellites … Write error Erreur d'écriture Write complete Écriture terminée %1 (alias for %2 %3) %1 (raccourci pour %2 %3) BandwidthSelect Narrow (12.5 kHz) Étroite (12.5 kHz) Wide (25 kHz) Large (25 kHz) ChannelDialog Edit Channel <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note :</span> qdmr propose la complétion automatique des canaux. Commencer à saisir l'indicatif d'un relai, à partir de 3 caractères saisis, une requête est envoyée vers repeaterbook.com pour récupérer les répéteurs correspondants. Ces requêtes pouvant prendre du temps, les résultats sont stockés localement dans un cache.</p><p>Une liste déroulante apparaît permettant la sélection du relai. Une fois le relai sélectionné, les fréquences RX/TX et les codes couleurs sont complétés.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Masquer</span></a></p></body></html> Basic Name Nom Rx Frequency Fréquence RX Tx Frequency Fréquence TX Tx Offset Décalage TX Power Puissance Max Max High Haute Mid Moyenne Low Basse Min Min Default Défaut Tx Timeout Anti-bavard Off VOX Level Niveau VOX Rx Only RX Uniquement Scan List Liste de recherche Extensions Extensions No offset Positive offset Décalage positif Negative offset Décalage négatif [None] [Aucun] ChannelListView Select a single channel first Commencer par sélectionner un canal To clone a channel, please select a single channel to clone. Pour dupliquer un canal, merci de sélectionner un unique canal à dupliquer. Cannot delete channel Impossible de supprimer le canal Cannot delete channel: You have to select a channel first. Impossible de supprimer un canal: Vous devez d'abord sélectionner le canal. Delete channel? Supprimer le canal? Delete channel %1? Supprimer le canal '%1'? Delete %1 channels? Supprimer %1 canaux? Alt+A Alt+A Add Channel ... Clone Channel Dupliquer le canal Alt+C Alt+C Delete Channel Supprimer le canal Alt+- Alt+- Add FM Channel Adds a new FM channel Add DMR Channel Ajouter un canal DMR Adds a new DMR channel. Add AM Channel Adds a new AM channel. ChannelListWrapper FM FM DMR DMR AM [Default] [Défaut] Max Max High Haute Mid Moyenne Low Basse Min Min Off Non On Oui Always Toujours Free Libre Color Couleur Tone Tonalité [None] [Aucun] Open Ouvert Wide Large Narrow Étroite Type Type Name Nom Rx Frequency Fréquence RX Tx Frequency Fréquence TX Power Puissance Timeout Anti-bavard Rx Only RX Uniquement Admit Accepter Scanlist Liste de recherche Zones Zones CC CC TS TS RX Group List Liste Groupes RX TX Contact Contact TX DMR ID ID DMR GPS/APRS GPS/APRS Roaming Roaming Squelch Squelch Rx Tone Tonalité RX Tx Tone Tonalité TX Bandwidth Largeur de bande Extensions Extensions ChannelRefListWrapper Channel Canal ChannelSelectionDialog Select a channel: Sélectionner un canal: ConfigMergeDialog Merging codeplugs ... Fusion de codeplugs ... <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Stratégies de résolution de conflits:</span></p><p>Si des objets importés (canaux, contacts, ...) existent déjà, sélectionner comment ces conflits sont à résoudre pour les éléments et les ensembles.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Les éléments sont tous des objets unitaires comme IDs de radio, canaux, contacts et canaux de roaming. Items Éléments Ignore Ignorer Override Écraser Duplicate Dupliquer Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Les ensembles sont tous des objets contenant d'autres éléments comme les listes de groupes, zones, listes de recherche et zones de roaming. Sets Ensembles Merge Fusionner Ignores any duplicate item. Ignorer les éléments en doublon. Replaces any duplicate item with the imported one. Remplacer les éléments en doublons par ceux importés. Imports any duplicate item with a modified name. importer tout élément en doublon avec un nom modifié. Ignores any duplicate set. Ignorer tout ensemble en doublon. Replaces any duplicate set with the imported one. Remplacer tout ensemble en doublon par celui importé. Imports any duplicate set with a modified name. Importer tout ensemble en doublon avec un nom modifier. Merges duplicate sets. Fusionner les ensembles en doublon. ConfigObjectListView Cannot move items. Impossible de déplacer les éléments. Cannot move items: You have to select at least one item first. Impossible de déplacer les éléments: sélectionner au moins un élément. Move selected item(s) to the top. Déplacer les éléments sélectionnés tout en haut. Move selected item(s) ten positions up. Déplacer les éléments sélectionnés 10 positions vers le haut. Move selected item(s) one position up. Déplacer les éléments sélectionnés une position vers le haut. Move selected item(s) one position down. Déplacer les éléments sélectionnés une position vers le bas. Move selected item(s) ten positions down. Déplacer les éléments sélectionnés dix positions vers le bas. Move selected item(s) to the bottom. Déplacer les éléments sélectionnés tout en bas. ConfigObjectTableView Cannot move items. Impossible de déplacer les éléments. Cannot move items: You have to select at least one item first. Impossible de déplacer les éléments: sélectionner au moins un élément. Cannot move items as long as there is some filter or sorting applied. Move selected item(s) to the top. Déplacer les éléments sélectionnés tout en haut. Move selected item(s) ten positions up. Déplacer les éléments sélectionnés dix positions vers le haut. Move selected item(s) one position up. Déplacer les éléments sélectionnés une position vers le haut. Move selected item(s) one position down. Déplacer les éléments sélectionnés une position vers le bas. Move selected item(s) ten positions down. Déplacer les éléments sélectionnés dix positions vers le bas. Move selected item(s) to the bottom. Déplacer les éléments sélectionnés tout en bas. Toggle Filter and Sorting Close Sort and Filter ConfigObjectTypeSelectionDialog An instance of %1. Une instance de '%1'. <p>%1<p><p style="margin-left:10px;">%2</p> <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Créer un objet d'extension Select the class of object to create Sélectionner la classe de l'objet à créer ContactListView Cannot delete contact Impossible de supprimer le contact Cannot delete contact: You have to select a contact first. Impossible de supprimer un contact: commencer par sélectionner un contact. Delete contact? Supprimer le contact? Delete contact %1? Supprimer le contact '%1'? Delete contacts? Supprimer les contacts? Delete %1 contacts? Supprimer %1 contacts? Adds a contact to the list. Ajouter un contact à la liste. Alt++ Alt++ Add M17 Contact Adds an M17 contact to the list. Add DTMF Contact Ajouter un contact DTMF Adds an DTMF (analog) contact to the list. Add DMR Contact Adds an DMR contact to the list. Delete contact button Bouton de suppression de contact Add Contact Ajouter un contact Delete Contact Supprimer le contact Alt+- Alt+- ContactListWrapper DTMF DTMF On Actif Off Inactif Private Call Appel privé Group Call Appel de groupe All Call Tout appel [None] [Aucun] M17 [Broadcast] Type Type Name Nom Number Nombre RX Tone Tonalité RX Extensions Extensions DMRAdmitSelect Always Toujours Channel Free Canal libre Other Color-code DMRChannelDialog Radio Id Tx Admit Color-code Time-slot Group list Tx Contact Contact TX APRS APRS Roaming zone Zone de roaming DMRContactDialog Create DMR Contact Créer un contact DMR Edit DMR Contact Éditer contact DMR Private Call Appel privé Group Call Appel de groupe All Call Appel à tous Basic Basique Type Type Name Nom Number Nombre Ring Sonnerie Extensions Extensions DMRContactSelect [None] [Aucun] DMRIDDialog Basic Basiques Name Nom DMR ID ID DMR Extensions Extensions DMRIdSelect [Default] [Défaut] DTMFContactDialog Create DTMF Contact Créer un contact DTMF Edit DMR Contact Éditer un contact DMR Basic Basiques Name Nom Number Nombre Ring Sonnerie Extensions Extensions DeviceSelectionDialog Select a device Sélectionner une radio <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> <html><head/><body><p>Il y a plusieurs radios détectées ou la radio identifiée n'est pas considéré comme sûr d'accès. Dans tous les cas, sélectionner la radio à utiliser.</p></body></html> ErrorMessageView Error: Unknown. Erreur : Inconnue. Error: %1 Erreur : %1 Traceback: Trace d'erreur : ExtensionView Cannot create extension. Impossible de créer l'extension. Cannot create extension, consider reporting a bug. Impossible de créer l'extension, merci de signaler ce bug. Cannot create list element. Impossible de créer un élément de liste. Cannot create list element, consider reporting a bug. Impossible de créer un élément de liste, merci de signaler ce bug. Create Créer Remove Supprimer FMAPRSSelect [None] [Aucun] FMAPRSSystem [None] [Aucun] Police station Poste de police Digipeater Digipeater Phone Téléphone DX cluster Cluster DX HF gateway Passerelle HF Plane small Avion (petit) Mobile Satellite station Station satellite mobile Wheel Chair Fauteuil roulant Snowmobile Motoneige Red cross Croix rouge Boy scout Scout Home Maison X X Red dot Point rouge Circle 0 Cercle 0 Circle 1 Cercle 1 Circle 2 Cercle 2 Circle 3 Cercle 3 Circle 4 Cercle 4 Circle 5 Cercle 5 Circle 6 Cercle 6 Circle 7 Cercle 7 Circle 8 Cercle 8 Circle 9 Cercle 9 Fire Feu Campground Camping Motorcycle Moto Rail engine Locomotive Car Voiture File server Serveur de fichier HC Future Futur HC Aid station Poste de secours BBS BBS Canoe Canoë Eyeball Œil Tractor Tracteur Grid Square Grid Square Hotel Hôtel TCP/IP TCP/IP School École Logon Connexion MacOS MacOS NTS station Station NTS Balloon Ballon Police car Voiture de police TBD À définir RV RV Shuttle Navette SSTV SSTV Bus Bus ATV ATV Weather service Service météo Helo HELO Yacht Voilier MS Windows MS Windows Jogger Joggeur Triangle Triangle PBBS PBBS Plane large Avion (grand) Weather station Sation météo Dish antenna Antenne sattelite Ambulance Ambulance Bike Vélo ICP ICP Fire station Poste incendie Horse Cheval Fire truck Camion de pompiers Glider Planeur Hospital Hôpital IOTA IOTA Jeep Jeep Truck small Camion (petit) Laptop Ordinateur portable Mic-E Mic-e Node Nœud EOC EOC Rover Ballade Grid Grille Antenna Antenne Power boat Bateau Truck stop Aire de repos Truck large Camion (gros) Van Van Water Eau XAPRS XAPRS Yagi Yagi Shelter Abri FMAdmitSelect Always Toujours Channel Free Canal libre Other Tone FMChannelDialog Squelch Squelch Tx Admit Rx Tone Tonalité RX Tx Tone Tonalité TX Bandwidth Largeur de bande APRS APRS FlagEditDialog Select Flags GPSSystemDialog Create DMR APRS System Edit DMR APRS System [Selected] [Sélectionné] Edit GPS System Édition système GPS Basic Basiques Name Nom Destination Destination Update period Mettre à jour la période Revert Channel Inverser le canal Extensions Extensions GeneralSettingsView Boot Settings Paramètres de démarrage Intro Line 1 Intro ligne 1 First greeting line (if supported by the radio). Première ligne d'accueil (si supporté par la radio). Intro line 1 Intro ligne 1 Intro Line 2 Intro ligne 2 Second greeting line (if supported by the radio). Seconde ligne d'accueil (si supporté par la radio). Intro line 2 Intro ligne 2 Audio Settings Paramètres audio MIC Amp. Gain micro Speech Synthesis Synthèse vocale Channel Default Values Valeurs par défaut des canaux Power Puissance Max Max High Haute Mid Moyenne Low Basse Min Min Squelch Squelch Open Ouvrir Transmit Timeout Anti-bavard Off Désactivé VOX Level Niveau VOX Extensions Extensions GroupListWrapper Contact Contact GroupListsView Cannot delete RX group list Impossible de supprimer la liste de groupes Cannot delete RX group lists: You have to select a group list first. Impossible de supprimer des listes de groupes : Sélectionner d'abord une liste de groupes. Delete RX group list? Supprimer la liste de groupes ? Delete RX group list %1? Supprimer la liste de groupes '%1'  ? Delete %1 RX group lists? Supprimer %1 listes de groupes ? Add RX Group Ajouter un Groupe RX Alt++ Alt++ Delete RX Group Supprimer Groupe RX Alt+- Alt+- GroupListsWrapper RX Group Lists Listes de Groupes M17ChannelDialog Channel mode Access number Tx contact Send position M17ChannelModeSelect Voice Data Voice + Data M17ContactDialog Edit M17 Contact Basic Name Nom The name of the contact. Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Ring Sonnerie Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Extensions Create M17 Contact M17ContactSelect [None] [Aucun] MainWindow File Fichier Device Radio Help Aide Databases Bases Toolbar New Nouveau Creates a new Codeplug. Créer un nouveau Codeplug. Ctrl+N Ctrl+N Open ... Ouvrir … <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> <html><head/><body><p>Importer un nouveau codeplug depuis un fichier &quot;conf&quot;.</p></body></html> Ctrl+O Ctrl+O Save ... Enregistrer … <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> <html><head/><body><p>Enregistrer le codeplug dans un fichier &quot;conf&quot;.</p></body></html> Ctrl+S Ctrl+S Quit Quitter Quits the application. Quitte l'application. Ctrl+Q Ctrl+Q Detect Déctecter Detect connected radios. Détecter les radios connectées. Verify Vérifier <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Vérifie le codeplug avec les radios connectées. Ctrl+R Ctrl+R Read Lire Reads a codeplug from connected radios. Lire un codeplug depuis les radios connectée. Write Écrire Writes the codeplug to the connected radio. Écrire le codeplug dans la radio connectée. About qdmr À propos de qdrm Read the handbook. Lire le manuel. F1 F1 Settings Paramètres Shows settings dialog Afficher l'écran de paramétrage. Write Callsign DB Écrire la base d'indicatifs Writes call-sign DB to radio. Écrire la base d'indicatifs dans la radio. Refresh Callsign DB Mettre à jour la base d'indicatifs Refreshes the downloaded callsign DB Met à jour la base locale d'indicatifs Refresh Talkgroup DB Mettre à jour la base des groupes Refreshes the downloaded talkgroup DB Mise à jour de la base locale des groupes Export to CHIRP ... Export vers CHIRP ... Exports all FM channels to CHIRP CSV. Import ... Import ... Imports and merges a codeplug into the current one. Importe et fusionne un codeplug dans le codeplug courant. Refresh Orbital Elements Mettre à jour les éléments orbitaux Refreshes the orbital elements. Mise à jour des éléments orbitaux. Edit Satellites ... Modifier les satellites ... Opens an editor to edit your satellite database. Ouvre un éditeur pour modifier la base de satellites. Write satellites Sauvegarder les satellites Writes the orbital elements and transponder information onto the connected device. Sauvegarder les éléments orbitaux et les informations de transpondeurs dans la radio connectée. Cannot update callsign DB: %1 Impossible de mettre à jour la base des indicatifs : %1 Callsign database updated & loaded. Base des indicatifs mise à jour et chargée. Cannot update talkgroup DB: %1 Impossible de mettre à jour la base des groupes : %1 Talkgroup database updated & loaded. Base des talk-groups mise à jour et chargée. Cannot update orbital elements: %1 Impossible de mettre à jour les éléments orbitaux : %1 Orbital elements updated & loaded. Éléments orbitaux mis à jour et chargés. Radio IDs IDs de la radio Contacts Contacts Group Lists Listes de groupes Channels Canaux Zones Zones Scan Lists Listes de recherche GPS/APRS GPS/APRS Roaming Channels Canaux de roaming Roaming Zones Zones de roaming Extensions Extensions Unsaved changes to codeplug. Modifications au codeplug non sauvegardées. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Modifications au codeplug non sauvegardées. Ces modifications seront perdues si vous continuez. MultiChannelSelectionDialog [Selected] [Sélectionné] Select a channel: Sélectionner un canal : MultiGroupCallSelectionDialog Show private calls Afficher les appels privés Select a group call: Sélectionner un appel de groupe : MultiRoamingChannelSelectionDialog Select roaming channels Sélectionner des canaux de roaming PositioningSystemListView Cannot delete GPS system Impossible de supprimer un système GPS Cannot delete GPS system: You have to select a GPS system first. Impossible de supprimer un système GPS : commencer par sélectionner un système GPS. Delete positioning system? Supprimer le système de localisation ? Delete positioning system %1? Supprimer le système de localisation '%1' ? Delete %1 positioning systems? Supprimer %1 systèmes de localisation ? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR est un CPS indépendant des modèles de radios. Cependant, toutes les radios ne disposent pas du GPS ou de l'APRS. Cette partie du paramétrage peut être ignoré lors de l'écriture du code-plug dans la radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Masquer</span></a></p></body></html> Add GPS System Ajouter un système GPS Alt+G Alt+G Add APRS System Ajouter un système APRS Alt+A Alt+A Delete Position System Supprimer un système de localisation Alt+- Alt+- PositioningSystemListWrapper DMR DMR APRS APRS [None] [Aucun] [Selected] [Sélectionné] Type Type Name Nom Destination Destination Period Channel Canal Message Message Extensions Extensions PropertyDelegate None Aucun Off False Faux True Vrai [None] [Aucun] PropertyWrapper new element Nouvel élément Property Propriété Value Valeur Description Description true vrai false faux None Aucun Off [None] [Aucun] Instance of %1 Instance de '%1' List of %1 instances Liste de %1 instances QObject [None] [Aucun] RXGroupListDialog Create Group List Créer une liste de groupes Edit Group List Modifier une liste de groupes Cannot remove group call Impossible de supprimer l'appel de groupe Cannot remove group call: You have to select at least one group call first. Impossible de supprimer l'appel de groupe : commencer par sélectionner au moins un appel de groupe. Basic Basiques Name Nom Add Contact Ajouter un contact Alt++ Alt++ Remove Contact Supprimer un contact Alt+- Alt+- Extensions Extensions RadioIDListView Cannot delete radio IDs Impossible de supprimer les IDs radio Cannot delete radio IDs: You have to select a radio ID first. Impossible de supprimer les IDs radio : commencer par sélectionner un ID radio. Delete radio ID? Supprimer l'ID radio ? Delete radio ID %1? Supprimer l'ID radio '%1' ? Delete scan lists? Supprimer des listes de recherche ? Delete %1 scan lists? Supprimer %1 listes de recherche ? Default Radio ID ID radio par défaut Add Radio ID Ajouter un ID radio Delete Radio ID Supprimer un ID radio RadioIdListWrapper [None] [Aucun] Type Type Name Nom Number Nombre Extensions Extensions RadioSelectionDialog Cannot auto-detect radio Impossible de détecter automatiquement la radio Select a specific radio Sélectionner la radio ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Impossible de télécharger les notes de version depuis https://github.com/hmatuschek/qdmr %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Impossible de lire les notes de version depuis https ://github.com/hmatuschek/qdmr Les détails de la version ne sont pas au format JSON ! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. Impossible de lire les notes de version depuis https ://github.com/hmatuschek/qdmr La version ne contient pas de notes. qDMR was updated to version %1 qdmr est mis à jour vers la version %1 RoamingChannelDialog Name Nom RX Frequency [MHz] Fréquence RX [MHz] TX Frequency [MHz] Fréquence TX [MHz] Time Slot Time Slot Color Code Code Couleur Selected Sélectionné Edit roaming channel Édition canal de roaming Create roaming channel Créer un canal de roaming TS 1 TS 1 TS 2 TS 2 RoamingChannelListView <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR est un CPS indépendant des modèles de radios. Cependant, toutes les radios ne prennent pas en charge le Roaming. Cette partie du paramétrage sera peut-être ignorée lors de l'écriture du code-plug dans la radio. </p><p><span style=" font-weight:600;">Conseil:</span> Vous n'avez pas besoin d'ajouter des canaux de Roaming explicitement, créez simplement des zones de Roaming et ajoutez leurs des canaux. Ces canaux seront alors automatiquement ajoutés à cette liste.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Masquer</span></a></p></body></html> Add Roaming Channel Ajouter un canal de roaming Delete Roaming Channel Supprimer un canal de roaming Cannot delete roaming channel Impossible de supprimer le canal de roaming Cannot delete roaming channel: You have to select a channel first. Impossible de supprimer le canal de roaming : commencer par sélectionner un canal. Delete roaming channel? Supprimer le canal de roaming ? Delete roaming channel %1? Supprimer le canal de roaming %1 ? Delete %1 roaming channel? Supprimer %1 canaux de roaming ? RoamingChannelListWrapper [Selected] [Sélectionné] [None] [Aucun] Name Nom RX Frequency Fréquence RX TX Frequency Fréquence TX TS TS Zones Zones Extensions Extensions CC CC RoamingChannelRefListWrapper Roaming Channel Canal de roaming RoamingListWrapper %1 (containing %2 channels) %1 (contenant %2 canaux) Roaming zone Zone de roaming RoamingZoneDialog Create Roaming Zone Créer une zone de roaming Set Roaming Zone Modifier une zone de roaming Cannot remove channels. Impossible de supprimer des canaux. Cannot remove channels. Select at least one channel first. Impossible de supprimer des canaux. Commencer par sélectionner un canal. Basic Basiques Name: Nom : Add Roaming Channel Ajouter un canal de roaming Add DMR Channel Ajouter un canal DMR Alt++ Alt++ Remove Channel Supprimer un canal Alt+- Alt+- Extension Extension RoamingZoneListView Generate roaming zone Générer une zone de roaming Create a roaming zone by collecting all channels with these group calls. Créer une zone de roaming en collectant tous les canaux avec ces appels de groupe. Cannot delete roaming zone Impossible de supprimer une zone de roaming Cannot delete roaming zone: You have to select a zone first. Impossible de supprimer une zone de roaming : commencer par sélectionner une zone. Delete roaming zone? Supprimer la zone de roaming ? Delete roaming zone %1? Supprimer la zone de roaming %1 ? Delete roaming zones? Supprimer les zones de roaming ? Delete %1 roaming zones? Supprimer %1 zones de roaming ? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR est un CPS indépendant des modèles de radios. Cependant, toutes les radios ne prennent pas en charge le Roaming. Cette partie du paramétrage sera peut-être ignorée lors de l'écriture du code-plug dans la radio. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Masquer</span></a></p></body></html> Add Roaming Zone Ajouter une zone de roaming Alt++ Alt++ Generate Roaming Zone Générer une zone de roaming Delete Roaming Zone Supprimer une zone de roaming Alt+- Alt+- RoamingZoneSelect [None] [Aucun] [Default] [Défaut] SatelliteDatabaseDialog Edit satellite database Modifier la base de satellites Add Ajouter Delete Supprimer SatelliteSelectionDialog Select a satellite Sélectionner un satellite SatelliteTransponderDialog Edit Satellite Transponder FM Voice Transponder Uplink Frequency Uplink Tone Downlink Frequency Downlink Tone APRS Transponder Beacon Beacon Frequency ScanListDialog Edit Scan List Modifier une liste de recherche Basic Basiques Name Nom Primary Channel (50%) Canal principal (50%) Secondary Channel (25%) Canal secondaire (25%) Transmit Channel Canal d'appel Add Channel Ajouter un canal Alt++ Alt++ Remove Channel Supprimer le canal Alt+- Alt+- Extensions Extensions Create Scan List Créer une liste de recherche [None] [Aucun] [Selected] [Sélectionné] [Last] [Dernier] ScanListsView Cannot delete scanlist Impossible de supprimer la liste de recherche Cannot delete scanlist: You have to select a scanlist first. Impossible de supprimer la liste de recherche : commencer par sélectionner une liste. Delete scan list? Supprimer la liste de recherche ? Delete scan list %1? Supprimer la liste de recherche '%1' ? Delete scan lists? Supprimer les listes de recherche ? Delete %1 scan lists? Supprimer %1 listes de recherche ? Add Scan List Ajouter une liste de recherche Alt++ Alt++ Delete Scan List Supprimer la liste de recherche Alt+- Alt+- ScanListsWrapper Scan-List Liste de recherche SearchPopup Ctrl+F Ctrl+F %1/%2 %1/%2 SelectiveCallBox None Aucun CTCSS CTCSS DCS DCS Hz Hz Inverted Inversé SettingsDialog Warning! Attention ! Settings Paramètres Location Localisation System location Système de localisation Locator Locator Repeater Info Sources Sources d'informations des relais enable activer Radio Programming Programmation de la radio Update codeplug Mise à jour du codeplug <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> <html><head/><body><p>Mettre à jour le codeplug de la radio. Si aucune sélection de paramètres, le codeplug de la radio est écrasé avec potentiellement des valeurs par défaut incomplètes. </p><p><br/></p><p>Si une sélection de paramètres, QDMR télécharge le codeplug actuel de la radio et met à jour uniquement les paramètres sélectionnés. Les paramètres restant de la radio ne sont pas modifiés (recommandé).</p></body></html> Auto-enable GPS GPS auto-activable When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. Lorsqu'un système GPS ou APRS est défini et utilisé pour tout les canaux, le module GPS est activé automatiquement. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Lorsqu'une zone de roaming est définie et utilisée pour tout les canaux, le roaming automatique est activé. Auto-enable roaming Activer automatiquement le roaming Data Sources Sources de données World Monde North America Amérique du nord Programming Programmation Radio Interfaces Interfaces radios disable auto-detect désactiver la détection automatique Ignore verification warnings Ignorer les alertes de vérification <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> <html><head/><body><p>Comme l'interface de communication avec la radio reste ouverte après la phase de vérification, le délai peut être dépassé et l'écriture du codeplug échouer lorsque le dialogue de vérification s'ouvre. Pour éviter ce comportement, les messages d'information (warnings) de la vérification peuvent être ignorés, supprimant le délai entre la vérification et l'écriture. Les erreurs de vérification continuent à bloquer une écriture de codeplug incorrect.</p></body></html> Ignore frequency limits Ignorer les limites de fréquences Do not set this option unless you know what you are doing. Ne pas activer cette option à moins de savoir ce que vous faites. Update Device Clock Mettre à jour l'heure de la radio Call-Sign DB Base des identifiants Limit number of DB entries Limiter le nombre d'entrées dans la base When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Lorsque activé, le nombre d'entrées de la base sera limité. Autrement, le maximum d'entrées sera généré (dépendant de la radio). Number of DB entries Nombre d'entrées dans la base Specifies the number of DB entries (if enabled above). Spécifier le nombre d'entrée de la base (si activé au dessus). Select using my DMR ID Sélectionner en utilisant mon ID DMR If enabled, the entries are selected using the users DMR ID. Si activé, les entrées sont sélectionnées en utilisant les IDs DMR des utilisateurs. Select using prefixes Sélectionner en utilisation les préfixes If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. Si activé, ces préfixes d'IDs DMR séparés par des virgules sont utilisés pour sélectionner les entrées dans la base des indicatifs. SquelchEdit Open Uses the global squelch setting if enabled. Default Défaut TimeslotSelect Slot 1 Slot 2 TransponderFrequencyEditor None Aucun VerifyDialog Verify Codeplug Vérifier le codeplug The codeplug cannot be uploaded, unless all critical issues (red) are resolved. Le codeplug ne peut être envoyé vers la radio tant que les problèmes critiques (rouge) ne sont pas résolus. ZoneDialog Edit Zone Édition de zone Basic Basiques <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Les zones sont des collections de canaux habituellement valides pour une région particulière.</p><p align="justify">QDMR gère les zones en permettant l'utilisation de deux listes indépendantes, une par VFO si la radio dispose de deux VFO. Cependant, certaines radios ne permettent pas d'assigner des zones différentes à chaque VFO. Dans ce cas, QDMR découpe la zone en deux (A &amp; B) en les programmant individuellement dans la radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Masquer</span></a></p></body></html> Name Nom Channels A Canaux A add ajouter remove retirer Channels B Canaux B Extension Extension Create Zone Créer zone Cannot remove channel Impossible de retirer le canal Select at least one channel first. Sélectionner au moins un canal. ZoneListView Cannot delete zone Impossible de supprimer la zone Cannot delete zone: You have to select a zone first. Impossible de supprimer la zone : commencer par sélectionner une zone. Delete zone? Supprimer la zone ? Delete zone %1? Supprimer la zone '%1' ? Delete zones? Supprimer les zones ? Delete %1 zones? Supprimer %1 zones ? Add Zone Ajouter la zone Alt++ Alt++ Delete Zone Supprimer une zone Alt+- Alt+- ZoneListWrapper Zone Zone aprssystemdialog Edit APRS System Édition système APRS Basic Basiques Name Nom Channel Canal Source Source Destination Destination Path Chemin Icon Icône Update period [s] Périodicité de mise à jour [s] Message Message Extensions Extensions main Codeplug file to load. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. ================================================ FILE: i18n/it.ts ================================================ AMChannelDialog Squelch Squelch APRSSelect [None] [Nessuno/a] APRSSystemDialog Create APRS system Crea sistema APRS Edit APRS system Modifica sistema APRS [Selected] [Selezionato] AboutDialog About qdmr Informazioni su qdmr Supported Radios Radio supportate Application Unsaved changes to codeplug. Modifiche non salvate al codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Ci sono modifiche non salvate al codeplug corrente. Procedendo, queste modifiche andranno perse. Open codeplug Apri codeplug Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) File codeplug (*.yaml);;File codeplug, vecchio formato (*.conf *.csv *.txt);;Tutti i file (*) Cannot open file Impossibile aprire il file Cannot read codeplug from file '%1': %2 Impossibile leggere il codeplug dal file '%1': %2 Cannot read codeplug. Impossibile leggere il codeplug. Save codeplug Salva codeplug Codeplug Files (*.yaml *.yml) File codeplug (*.yaml *.yml) Please use new YAML format. Utilizzare il nuovo formato YAML. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Il salvataggio nel vecchio formato di configurazione basato su tabelle è stato disabilitato dalla versione 0.9.0. La lettura di questi file funziona ancora. Cannot save codeplug to file '%1': %2 Impossibile salvare il codeplug nel file '%1': %2 Cannot save codeplug Impossibile salvare il codeplug Cannot save codeplug to file '%1'. Impossibile salvare il codeplug nel file '%1'. Export codeplug Esporta codeplug CHIRP CSV Files (*.csv) File CSV di CHIRP (*.csv) Cannot export codeplug Impossibile esportare il codeplug Cannot export codeplug to file '%1': %2 Impossibile esportare il codeplug nel file '%1': %2 Import codeplug Importa codeplug CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) File CSV di CHIRP (*.csv);; File YAML (*.yaml *.yml) Cannot import codeplug Impossibile importare il codeplug Cannot import codeplug from '%1': %2 Impossibile importare il codeplug da '%1': %2 Do not know, how to handle file '%1'. Impossibile gestire il file '%1'. No matching devices found. Nessun dispositivo corrispondente trovato. Cannot connect to radio Impossibile connettersi alla radio Cannot connect to radio: %1 Impossibile connettersi alla radio: %1 Radio found Radio rilevata Found device '%1'. Rilevato dispositivo '%1'. No radio found Nessuna radio rilevata No matching device was found. Non è stato trovato nessun dispositivo corrispondente. Verification success Verifica riuscita The codeplug was successfully verified with the radio '%1' Il codeplug è stato verificato con la radio '%1 Read ... Lettura… Read error Errore di lettura Read complete Lettura completata Upload ... Caricamento… Cannot write call-sign DB. Impossibile scrivere il DB dei nominativi. The detected radio '%1' does not support a call-sign DB. La radio rilevata '%1' non supporta un DB dei nominativi. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. La radio rilevata '%1' supporta un DB dei nominativi. Tuttavia, questa funzione non è ancora stata implementata. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. QDMR seleziona i nominativi da scrivere in base all'ID DMR predefinito della radio. Non è stato impostato alcun ID predefinito. Write call-sign DB ... Scrittura del DB dei nominativi… Cannot write satellite config. Impossibile scrivere la configurazione del satellite. The detected radio '%1' does not support satellite tracking. La radio rilevata '%1' non supporta il tracciamento satellitare. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. La radio rilevata '%1' non supporta il tracciamento satellitare. Questa funzione, tuttavia, non è ancora implementata. Write satellite config ... Salvataggio della configurazione del satellite… Write error Errore di scrittura Write complete Scrittura completata %1 (alias for %2 %3) %1 (alias di %2 %3) BandwidthSelect Narrow (12.5 kHz) stretta (12.5 kHz) Wide (25 kHz) larga (25 kHz) ChannelDialog Edit Channel <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Nota:</span> qdmr fornisce un completamento automatico per i canali. In pratica, inizia a digitare il nominativo di un ripetitore. Dopo aver inserito tre caratteri, viene inviata una richiesta a repeaterbook.com per recuperare i ripetitori corrispondenti. Queste operazioni possono richiedere qualche istante. I risultati vengono memorizzati localmente in una cache.</p><p>Apparirà un menu a discesa che permette di selezionare un ripetitore. Una volta selezionato un ripetitore, le frequenze RX/TX e i toni CTCSS vengono compilati automaticamente (se applicabili).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">nascondi</span></a></p></body></html> Basic Base Name Nome Rx Frequency Frequenza RX Tx Frequency Frequenza TX Tx Offset Power Potenza Max Massima High Alta Mid Media Low Bassa Min Minima Default Predefinito/a Tx Timeout Timeout TX Off Disattivato VOX Level Livello VOX Rx Only Solo RX Scan List Lista di scansione Extensions Estensioni No offset Nessun offset Positive offset Offset positivo Negative offset Offset negativo [None] [Nessuno/a] ChannelListView Select a single channel first Selezionare prima un singolo canale To clone a channel, please select a single channel to clone. Per clonare un canale, selezionare un canale singolo da clonare. Cannot delete channel Impossibile eliminare il canale Cannot delete channel: You have to select a channel first. Impossibile eliminare il canale: devi prima selezionarne uno. Delete channel? Eliminare il canale? Delete channel %1? Eliminare il canale '%1'? Delete %1 channels? Eliminare %1 canali? Alt+A Alt+A Add Channel ... Aggiungi canale… Clone Channel Clona canale Alt+C Alt+C Delete Channel Elimina canale Alt+- Alt+- Add FM Channel Aggiungi canale FM Adds a new FM channel Aggiunge un nuovo canale FM Add DMR Channel Aggiungi canale DMR Adds a new DMR channel. Aggiunge un nuovo canale DMR. Add AM Channel Aggiungi canale AM Adds a new AM channel. Aggiunge un nuovo canale AM. ChannelListWrapper FM FM DMR DMR AM AM [Default] [Predefinito/a] Max Massima High Alta Mid Media Low Bassa Min Minima Off Disattivato On Attivato Always Sempre Free Libero Color Colore Tone Tono [None] [Nessuno/a] Open Aperto Wide Larga Narrow Stretta Type Tipo Name Nome Rx Frequency Frequenza RX Tx Frequency Frequenza TX Power Potenza Timeout Timeout Rx Only Solo RX Admit Consenti Scanlist Lista di scansione Zones Zone CC CC TS TS RX Group List Lista gruppi RX TX Contact Contatto TX DMR ID ID DMR GPS/APRS GPS/APRS Roaming Roaming Squelch Squelch Rx Tone Tono RX Tx Tone Tono TX Bandwidth Larghezza di banda Extensions Estensioni ChannelRefListWrapper Channel Canale ChannelSelectionDialog Select a channel: Seleziona un canale: ConfigMergeDialog Merging codeplugs ... Unione dei codeplug… <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Strategie di risoluzione dei conflitti:</span></p><p>se alcuni degli oggetti importati (canali, contatti, …) esistono già, seleziona come risolvere questi conflitti per singoli elementi e insiemi.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Gli elementi sono tutti oggetti atomici come ID radio, canali, contatti e canali roaming. Items Elementi Ignore Ignora Override Sostituisci Duplicate Duplica Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Gli insiemi sono tutti oggetti che contengono altri elementi, come liste gruppi, zone, liste di scansione e zone roaming. Sets Insiemi Merge Unisci Ignores any duplicate item. Ignora qualsiasi elemento duplicato. Replaces any duplicate item with the imported one. Sostituisci qualsiasi elemento duplicato con quello importato. Imports any duplicate item with a modified name. Importa qualsiasi elemento duplicato con un nome modificato. Ignores any duplicate set. Ignora qualsiasi insieme duplicato. Replaces any duplicate set with the imported one. Sostituisci qualsiasi insieme duplicato con quello importato. Imports any duplicate set with a modified name. Importa qualsiasi insieme duplicato con un nome modificato. Merges duplicate sets. Unisci gli insiemi duplicati. ConfigObjectListView Cannot move items. Impossibile spostare gli elementi. Cannot move items: You have to select at least one item first. Impossibile spostare gli elementi: devi prima selezionarne almeno uno. Move selected item(s) to the top. Sposta gli elementi selezionati in cima. Move selected item(s) ten positions up. Sposta gli elementi selezionati di dieci posizioni verso l’alto. Move selected item(s) one position up. Sposta gli elementi selezionati di una posizione verso l’alto. Move selected item(s) one position down. Sposta gli elementi selezionati di una posizione verso il basso. Move selected item(s) ten positions down. Sposta gli elementi selezionati di dieci posizioni verso il basso. Move selected item(s) to the bottom. Sposta gli elementi selezionati in fondo. ConfigObjectTableView Cannot move items. Impossibile spostare gli elementi. Cannot move items: You have to select at least one item first. Impossibile spostare gli elementi: devi prima selezionarne almeno uno. Cannot move items as long as there is some filter or sorting applied. Impossibile spostare gli elementi finché è applicato un filtro o un ordinamento. Move selected item(s) to the top. Sposta gli elementi selezionati in cima. Move selected item(s) ten positions up. Sposta gli elementi selezionati di dieci posizioni verso l’alto. Move selected item(s) one position up. Sposta gli elementi selezionati di una posizione verso l’alto. Move selected item(s) one position down. Sposta gli elementi selezionati di una posizione verso il basso. Move selected item(s) ten positions down. Sposta gli elementi selezionati di dieci posizioni verso il basso. Move selected item(s) to the bottom. Sposta gli elementi selezionati in fondo. Toggle Filter and Sorting Attiva/Disattiva filtro e ordinamento Close Sort and Filter Chiudi ordinamento e filtro ConfigObjectTypeSelectionDialog An instance of %1. Un’istanza di '%1'. <p>%1<p><p style="margin-left:10px;">%2</p> <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Crea oggetto di estensione Select the class of object to create Selezionare la classe dell'oggetto da creare ContactListView Cannot delete contact Impossibile eliminare il contatto Cannot delete contact: You have to select a contact first. Impossibile eliminare il contatto: devi prima selezionarne uno. Delete contact? Eliminare il contatto? Delete contact %1? Eliminare il contatto '%1'? Delete contacts? Eliminare i contatti? Delete %1 contacts? Eliminare %1 contatti? Adds a contact to the list. Aggiunge un contatto alla lista. Alt++ Alt++ Add M17 Contact Adds an M17 contact to the list. Add DTMF Contact Aggiungi contatto DTMF Adds an DTMF (analog) contact to the list. Add DMR Contact Adds an DMR contact to the list. Delete contact button Pulsante elimina contatto Add Contact Aggiungi contatto Delete Contact Elimina contatto Alt+- Alt+- ContactListWrapper DTMF DTMF On Attivato Off Disattivato Private Call Chiamata privata Group Call Chiamata di gruppo All Call Chiamata generale [None] [Nessuno/a] M17 [Broadcast] Type Tipo Name Nome Number Numero RX Tone Tono RX Extensions Estensioni DMRAdmitSelect Always Sempre Channel Free Canale libero Other Color-code DMRChannelDialog Radio Id Tx Admit Consenti TX Color-code Time-slot Group list Tx Contact Contatto TX APRS APRS Roaming zone Zona roaming DMRContactDialog Create DMR Contact Crea contatto DMR Edit DMR Contact Modifica contatto DMR Private Call Chiamata privata Group Call Chiamata di gruppo All Call Chiamata generale Basic Base Type Tipo Name Nome Number Numero Ring Suoneria Extensions Estensioni DMRContactSelect [None] [Nessuno/a] DMRIDDialog Basic Base Name Nome DMR ID ID DMR Extensions Estensioni DMRIdSelect [Default] [Predefinito/a] DTMFContactDialog Create DTMF Contact Crea contatto DTMF Edit DMR Contact Modifica contatto DMR Basic Base Name Nome Number Numero Ring Suoneria Extensions Estensioni DeviceSelectionDialog Select a device Seleziona un dispositivo <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> <html><head/><body><p>È stato rilevato più di un dispositivo oppure quello trovato non è considerato sicuro da usare. In ogni caso, seleziona il dispositivo da utilizzare.</p></body></html> ErrorMessageView Error: Unknown. Errore: sconosciuto. Error: %1 Errore: %1 Traceback: Traccia dello stack: ExtensionView Cannot create extension. Impossibile creare l'estensione. Cannot create extension, consider reporting a bug. Impossibile creare l’estensione, considera di segnalare un bug. Cannot create list element. Impossibile creare l'elemento della lista. Cannot create list element, consider reporting a bug. Impossibile creare l’elemento della lista, considera di segnalare un bug. Create Crea Remove Rimuovere FMAPRSSelect [None] [Nessuno/a] FMAPRSSystem [None] [Nessuno/a] Police station Stazione di polizia Digipeater Digipeater Phone Telefono DX cluster Cluster DX HF gateway Gateway HF Plane small Aereo piccolo Mobile Satellite station Stazione satellitare mobile Wheel Chair Sedia a rotelle Snowmobile Motoslitta Red cross Croce rossa Boy scout Boy scout Home Casa X X Red dot Punto rosso Circle 0 Cerchio 0 Circle 1 Cerchio 1 Circle 2 Cerchio 2 Circle 3 Cerchio 3 Circle 4 Cerchio 4 Circle 5 Cerchio 5 Circle 6 Cerchio 6 Circle 7 Cerchio 7 Circle 8 Cerchio 8 Circle 9 Cerchio 9 Fire Fuoco Campground Campeggio Motorcycle Motocicletta Rail engine Locomotiva Car Automobile File server File server HC Future Futuro HC Aid station Pronto soccorso BBS BBS Canoe Canoa Eyeball Occhio Tractor Trattore Grid Square Grid square Hotel Hotel TCP/IP TCP/IP School Scuola Logon Accesso MacOS MacOS NTS station Stazione NTS Balloon Mongolfiera Police car Auto della polizia TBD TBD RV RV Shuttle Navetta SSTV SSTV Bus Autobus ATV ATV Weather service Servizio meteo Helo Elicottero Yacht Yacht MS Windows MS Windows Jogger Corridore Triangle Triangolo PBBS PBBS Plane large Aereo grande Weather station Stazione meteo Dish antenna Antenna parabolica Ambulance Ambulanza Bike Bicicletta ICP ICP Fire station Caserma dei vigili del fuoco Horse Cavallo Fire truck Automezzo dei vigili del fuoco Glider Aliante Hospital Ospedale IOTA IOTA Jeep Jeep Truck small Camion piccolo Laptop Computer portatile Mic-E Mic-E Node Nodo EOC EOC Rover Rover Grid Griglia Antenna Antenna Power boat Motoscafo Truck stop Area di sosta per camion Truck large Camion grande Van Furgone Water Acqua XAPRS XAPRS Yagi Yagi Shelter Rifugio FMAdmitSelect Always Sempre Channel Free Canale libero Other Tone FMChannelDialog Squelch Squelch Tx Admit Consenti TX Rx Tone Tono RX Tx Tone Tono TX Bandwidth Larghezza di banda APRS APRS FlagEditDialog Select Flags Seleziona flag GPSSystemDialog Create DMR APRS System Crea sistema DMR APRS Edit DMR APRS System Modifica sistema DMR APRS [Selected] [Selezionato] Edit GPS System Modifica sistema GPS Basic Base Name Nome Destination Destinazione Update period Intervallo di aggiornamento Revert Channel Ripristina canale Extensions Estensioni GeneralSettingsView Boot Settings Impostazioni di avvio Intro Line 1 Riga introduttiva 1 First greeting line (if supported by the radio). Prima riga di benvenuto (se supportata dalla radio). Intro line 1 Riga introduttiva 1 Intro Line 2 Riga introduttiva 2 Second greeting line (if supported by the radio). Seconda riga di benvenuto (se supportata dalla radio). Intro line 2 Riga introduttiva 2 Audio Settings Impostazioni audio MIC Amp. Amp. MIC. Speech Synthesis Sintesi vocale Channel Default Values Valori predefiniti dei canali Power Potenza Max Massima High Alta Mid Media Low Bassa Min Minima Squelch Squelch Open Aperto Transmit Timeout Timeout di trasmissione Off Disattivato VOX Level Livello VOX Extensions Estensioni GroupListWrapper Contact Contatto GroupListsView Cannot delete RX group list Impossibile eliminare la lista gruppi Cannot delete RX group lists: You have to select a group list first. Impossibile eliminare le liste gruppi: devi prima selezionarne una. Delete RX group list? Eliminare la lista gruppi? Delete RX group list %1? Eliminare la lista gruppi '%1'? Delete %1 RX group lists? Eliminare %1 liste gruppi? Add RX Group Aggiungi lista gruppi Alt++ Alt++ Delete RX Group Elimina lista gruppi Alt+- Alt+- GroupListsWrapper RX Group Lists Liste gruppi M17ChannelDialog Channel mode Access number Tx contact Send position M17ChannelModeSelect Voice Data Voice + Data M17ContactDialog Edit M17 Contact Basic Base Name Nome The name of the contact. Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Ring Suoneria Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Estensioni Create M17 Contact M17ContactSelect [None] [Nessuno/a] MainWindow File File Device Dispositivo Help Aiuto Databases Database Toolbar Barra degli strumenti New Nuovo Creates a new Codeplug. Crea un nuovo codeplug. Ctrl+N Ctrl+N Open ... Apri… <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> <html><head/><body><p>Importa un codeplug dai file &quot;conf&quot;.</p></body></html> Ctrl+O Ctrl+O Save ... Salva… <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> <html><head/><body><p>Salva il codeplug in un file &quot;conf&quot;.</p></body></html> Ctrl+S Ctrl+S Quit Esci Quits the application. Esci dall'applicazione. Ctrl+Q Ctrl+Q Detect Rileva Detect connected radios. Rileva le radio collegate. Verify Verifica <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> <html><head/><body><p>Verifica il codeplug corrente con le radio collegate.</p></body></html> Ctrl+R Ctrl+R Read Leggi Reads a codeplug from connected radios. Legge il codeplug dalle radio collegate. Write Scrivi Writes the codeplug to the connected radio. Scrive il codeplug sulla radio collegata. About qdmr Informazioni su qdmr Read the handbook. Leggi il manuale. F1 F1 Settings Impostazioni Shows settings dialog Mostra la finestra delle impostazioni Write Callsign DB Scrivi il DB dei nominativi Writes call-sign DB to radio. Scrive il DB dei nominativi sulla radio. Refresh Callsign DB Aggiorna il DB dei nominativi Refreshes the downloaded callsign DB Aggiorna il DB dei nominativi scaricati Refresh Talkgroup DB Aggiorna il DB dei talk-group Refreshes the downloaded talkgroup DB Aggiorna il DB dei talk-group scaricati Export to CHIRP ... Esporta in CHIRP… Exports all FM channels to CHIRP CSV. Esporta tutti i canali FM in un file CSV per CHIRP. Import ... Importa… Imports and merges a codeplug into the current one. Importa e unisce un codeplug in quello corrente. Refresh Orbital Elements Aggiorna gli elementi orbitali Refreshes the orbital elements. Aggiorna gli elementi orbitali. Edit Satellites ... Modifica i satelliti… Opens an editor to edit your satellite database. Apre un editor per modificare il database dei satelliti. Write satellites Salva i satelliti Writes the orbital elements and transponder information onto the connected device. Scrive gli elementi orbitali e le informazioni del transponder sul dispositivo connesso. Cannot update callsign DB: %1 Impossibile aggiornare il database dei nominativi: %1 Callsign database updated & loaded. Database dei nominativi aggiornato e caricato. Cannot update talkgroup DB: %1 Impossibile aggiornare il database dei talk-group: %1 Talkgroup database updated & loaded. Database dei talk-group aggiornato e caricato. Cannot update orbital elements: %1 Impossibile aggiornare gli elementi orbitali: %1 Orbital elements updated & loaded. Elementi orbitali aggiornati e caricati. Radio IDs ID Radio Contacts Contatti Group Lists Liste gruppi Channels Canali Zones Zone Scan Lists Liste di scansione GPS/APRS GPS/APRS Roaming Channels Canali roaming Roaming Zones Zone roaming Extensions Estensioni Unsaved changes to codeplug. Modifiche non salvate al codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Ci sono modifiche non salvate al codeplug corrente. Procedendo queste modifiche andranno perse. MultiChannelSelectionDialog [Selected] [Selezionato/a] Select a channel: Seleziona un canale: MultiGroupCallSelectionDialog Show private calls Mostra chiamate private Select a group call: Seleziona una chiamata di gruppo: MultiRoamingChannelSelectionDialog Select roaming channels Seleziona canali roaming PositioningSystemListView Cannot delete GPS system Impossibile eliminare sistema GPS Cannot delete GPS system: You have to select a GPS system first. Impossibile eliminare il sistema GPS: devi prima selezionarne uno. Delete positioning system? Eliminare sistema di posizionamento? Delete positioning system %1? Eliminare sistema di posizionamento '%1'? Delete %1 positioning systems? Eliminare %1 sistemi di posizionamento? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Nota:</span> QDMR è un CPS indipendente dal dispositivo. Tuttavia, non tutte le radio supportano GPS o APRS. Pertanto, queste impostazioni potrebbero essere ignorate durante la programmazione del codeplug sul dispositivo.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Nascondi</span></a></p></body></html> Add GPS System Aggiungi sistema GPS Alt+G Alt+G Add APRS System Aggiungi sistema APRS Alt+A Alt+A Delete Position System Elimina sistema di posizionamento Alt+- Alt+- PositioningSystemListWrapper DMR DMR APRS APRS [None] [Nessuno/a] [Selected] [Selezionato] Type Tipo Name Nome Destination Destinazione Period Intervallo Channel Canale Message Messaggio Extensions Estensioni PropertyDelegate None Nessuno/a Off Disattivato False Falso True Vero [None] [Nessuno/a] PropertyWrapper new element nuovo elemento Property Proprietà Value Valore Description Descrizione true vero false falso None Nessuno/a Off Disattivato [None] [Nessuno/a] Instance of %1 Istanza di '%1' List of %1 instances Elenco di %1 istanze QObject [None] [Nessuno/a] RXGroupListDialog Create Group List Crea lista gruppi Edit Group List Modifica lista gruppi Cannot remove group call Impossibile rimuovere la chiamata di gruppo Cannot remove group call: You have to select at least one group call first. Impossibile rimuovere la chiamata di gruppo: devi prima selezionarne almeno una. Basic Base Name Nome Add Contact Aggiungi contatto Alt++ Alt++ Remove Contact Rimuovi contatto Alt+- Alt+- Extensions Estensioni RadioIDListView Cannot delete radio IDs Impossibile eliminare gli ID radio Cannot delete radio IDs: You have to select a radio ID first. Impossibile eliminare gli ID radio: devi prima selezionarne uno. Delete radio ID? Eliminare l’ID radio? Delete radio ID %1? Eliminare l’ID radio '%1'? Delete scan lists? Eliminare le liste di scansione? Delete %1 scan lists? Eliminare %1 liste di scansione? Default Radio ID ID radio predefinito Add Radio ID Aggiungi ID radio Delete Radio ID Elimina ID radio RadioIdListWrapper [None] [Nessuno/a] Type Tipo Name Nome Number Numero Extensions Estensioni RadioSelectionDialog Cannot auto-detect radio Impossibile rilevare automaticamente la radio Select a specific radio Seleziona una radio specifica ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Impossibile scaricare le note di rilascio da https://github.com/hmatuschek/qdmr %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Impossibile leggere le note di rilascio da https://github.com/hmatuschek/qdmr Il rilascio non è un oggetto JSON! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. Impossibile leggere le note di rilascio da https://github.com/hmatuschek/qdmr La release non contiene note di rilascio. qDMR was updated to version %1 qdmr è stato aggiornato alla versione %1 RoamingChannelDialog Name Nome RX Frequency [MHz] Frequenza RX [MHz] TX Frequency [MHz] Frequenza TX [MHz] Time Slot Time slot Color Code Color code Selected Selezionato Edit roaming channel Modifica canale roaming Create roaming channel Crea canale roaming TS 1 TS 1 TS 2 TS 2 RoamingChannelListView <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Nota:</span> QDMR è un CPS indipendente dal dispositivo. Tuttavia, non tutte le radio supportano il roaming. Pertanto, queste impostazioni potrebbero essere ignorate durante la programmazione del codeplug sul dispositivo. </p><p><span style=" font-weight:600;">Suggerimento:</span> non è necessario aggiungere esplicitamente un canale roaming, basta creare le zone roaming e aggiungere i canali lì. Questi canali verranno poi aggiunti automaticamente a questo elenco.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Nascondi</span></a></p></body></html> Add Roaming Channel Aggiungi canale roaming Delete Roaming Channel Elimina canale roaming Cannot delete roaming channel Impossibile eliminare il canale roaming Cannot delete roaming channel: You have to select a channel first. Impossibile eliminare il canale roaming: devi prima selezionarne uno. Delete roaming channel? Eliminare il canale roaming? Delete roaming channel %1? Eliminare il canale roaming %1? Delete %1 roaming channel? Eliminare %1 canali roaming? RoamingChannelListWrapper [Selected] [Selezionato] [None] [Nessuno/a] Name Nome RX Frequency Frequenza RX TX Frequency Frequenza TX TS TS Zones Zone Extensions Estensioni CC CC RoamingChannelRefListWrapper Roaming Channel Canale roaming RoamingListWrapper %1 (containing %2 channels) %1 (contenente %2 canali) Roaming zone Zona roaming RoamingZoneDialog Create Roaming Zone Crea zona roaming Set Roaming Zone Modifica zona roaming Cannot remove channels. Impossibile rimuovere i canali. Cannot remove channels. Select at least one channel first. Impossibile rimuovere i canali. Seleziona prima almeno un canale. Basic Base Name: Nome: Add Roaming Channel Aggiungi canale roaming Add DMR Channel Aggiungi canale DMR Alt++ Alt++ Remove Channel Rimuovi canale Alt+- Alt+- Extension Estensione RoamingZoneListView Generate roaming zone Genera zona roaming Create a roaming zone by collecting all channels with these group calls. Crea una zona roaming raccogliendo tutti i canali con queste chiamate di gruppo. Cannot delete roaming zone Impossibile eliminare la zona roaming Cannot delete roaming zone: You have to select a zone first. Impossibile eliminare la zona roaming: devi prima selezionarne una. Delete roaming zone? Eliminare la zona roaming? Delete roaming zone %1? Eliminare la zona roaming '%1'? Delete roaming zones? Eliminare le zone roaming? Delete %1 roaming zones? Eliminare %1 zone roaming? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Nota:</span> QDMR è un CPS indipendente dal dispositivo. Tuttavia, non tutte le radio supportano il roaming. Pertanto, queste impostazioni potrebbero essere ignorate durante la programmazione del codeplug sul dispositivo. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Nascondi</span></a></p></body></html> Add Roaming Zone Aggiungi zona roaming Alt++ Alt++ Generate Roaming Zone Genera zona roaming Delete Roaming Zone Elimina zona roaming Alt+- Alt+- RoamingZoneSelect [None] [Nessuno/a] [Default] [Predefinito/a] SatelliteDatabaseDialog Edit satellite database Modifica il database dei satelliti Add Aggiungi Delete Elimina SatelliteSelectionDialog Select a satellite Seleziona un satellite SatelliteTransponderDialog Edit Satellite Transponder Modifica transponder satellitare FM Voice Transponder Transponder voce FM Uplink Frequency Frequenza di uplink Uplink Tone Tono di uplink Downlink Frequency Frequenza di downlink Downlink Tone Tono di downlink APRS Transponder Transponder APRS Beacon Beacon Beacon Frequency Frequenza del beacon ScanListDialog Edit Scan List Modifica lista di scansione Basic Base Name Nome Primary Channel (50%) Canale primario (50%) Secondary Channel (25%) Canale secondario (25%) Transmit Channel Canale di trasmissione Add Channel Aggiungi canale Alt++ Alt++ Remove Channel Rimuovi canale Alt+- Alt+- Extensions Estensioni Create Scan List Crea lista di scansione [None] [Nessuno/a] [Selected] [Selezionato] [Last] [Ultimo] ScanListsView Cannot delete scanlist Impossibile eliminare la lista di scansione Cannot delete scanlist: You have to select a scanlist first. Impossibile eliminare la lista di scansione: devi prima selezionarne una. Delete scan list? Eliminare la lista di scansione? Delete scan list %1? Eliminare la lista di scansione '%1'? Delete scan lists? Eliminare le liste di scansione? Delete %1 scan lists? Eliminare %1 liste di scansione? Add Scan List Aggiungi lista di scansione Alt++ Alt++ Delete Scan List Elimina lista di scansione Alt+- Alt+- ScanListsWrapper Scan-List Lista di scansione SearchPopup Ctrl+F Ctrl+F %1/%2 %1/%2 SelectiveCallBox None Nessuno/a CTCSS CTCSS DCS DCS Hz Hz Inverted Invertito SettingsDialog Warning! Attenzione! Settings Impostazioni Location Posizione System location Posizione del sistema Locator Localizzatore Repeater Info Sources Fonti informazioni ripetitore enable abilita Radio Programming Programmazione radio Update codeplug Aggiorna codeplug <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> <html><head/><body><p>Aggiorna il codeplug sulla radio. Se non selezionato, il codeplug sulla radio verrà sovrascritto con valori predefiniti eventualmente incompleti.</p><p><br/></p><p>Se selezionato, QDMR scarica il codeplug dalla radio e aggiorna solo le impostazioni specificate. Le restanti impostazioni della radio non vengono modificate (consigliato).</p></body></html> Auto-enable GPS Abilita automaticamente il GPS When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. Quando un sistema GPS o APRS è definito e utilizzato per un canale, il modulo GPS viene abilitato automaticamente. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Quando una zona roaming è definita e utilizzata da un canale, il roaming automatico viene abilitato. Auto-enable roaming Abilita automaticamente il roaming Data Sources Fonti dati World Mondo North America America del Nord Programming Programmazione Radio Interfaces Interfacce radio disable auto-detect disabilita il rilevamento automatico Ignore verification warnings Ignora gli avvisi di verifica <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> <html><head/><body><p>Poiché l’interfaccia di comunicazione con la radio rimane aperta dopo la verifica, potrebbero verificarsi time-out e il caricamento del codeplug potrebbe fallire quando compare la finestra di dialogo di verifica. Per evitare questo, gli avvisi di verifica possono essere ignorati, eliminando il gap temporale tra verifica e caricamento. Gli errori di verifica continuano comunque a impedire il caricamento.</p></body></html> Ignore frequency limits Ignora i limiti di frequenza Do not set this option unless you know what you are doing. Non impostare questa opzione, a meno che tu non sappia cosa stai facendo. Update Device Clock Aggiorna l’orologio del dispositivo Call-Sign DB Database dei nominativi Limit number of DB entries Limita il numero di voci del database When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Se abilitato, il numero di voci del database sarà limitato. Altrimenti, verrà generato il numero massimo di voci (dipendente dal dispositivo). Number of DB entries Numero di voci del database Specifies the number of DB entries (if enabled above). Specifica il numero di voci del database (se abilitato sopra). Select using my DMR ID Seleziona usando il mio ID DMR If enabled, the entries are selected using the users DMR ID. Se abilitato, le voci vengono selezionate usando l’ID DMR dell’utente. Select using prefixes Seleziona usando i prefissi If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. Se abilitati, questi prefissi DMR separati da virgola vengono utilizzati per selezionare le voci del database dei nominativi. SquelchEdit Open Aperto Uses the global squelch setting if enabled. Default Predefinito/a TimeslotSelect Slot 1 Slot 2 TransponderFrequencyEditor None Nessuno/a VerifyDialog Verify Codeplug Verifica codeplug The codeplug cannot be uploaded, unless all critical issues (red) are resolved. Il codeplug non può essere caricato, fin quando tutti i problemi critici (rossi) non saranno risolti. ZoneDialog Edit Zone Modifica zona Basic Base <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p align="justify"><span style=" font-weight:600;">Nota:</span> le zone sono raccolte di canali generalmente valide per una specifica regione. Cioè, una raccolta di canali per ripetitori all’interno di una certa area.</p><p align="justify">QDMR gestisce le zone permettendo due elenchi di canali indipendenti per ciascun VFO della radio (se ne ha due). Tuttavia, molte radio permettono di assegnare le zone a ciascun VFO individualmente. In questi casi, QDMR dividerà la zona in due (A e B) e le programmerà singolarmente sulla radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Nascondi</span></a></p></body></html> Name Nome Channels A Canali A add aggiungi remove elimina Channels B Canali B Extension Estensione Create Zone Crea zona Cannot remove channel Impossibile eliminare il canale Select at least one channel first. Seleziona prima almeno un canale. ZoneListView Cannot delete zone Impossibile eliminare la zona Cannot delete zone: You have to select a zone first. Impossibile eliminare la zona: devi prima selezionarne una. Delete zone? Eliminare la zona? Delete zone %1? Eliminare la zona '%1'? Delete zones? Eliminare le zone? Delete %1 zones? Eliminare %1 zone? Add Zone Aggiungi zona Alt++ Alt++ Delete Zone Elimina zona Alt+- Alt+- ZoneListWrapper Zone Zona aprssystemdialog Edit APRS System Modifica sistema APRS Basic Base Name Nome Channel Canale Source Fonte Destination Destinazione Path Percorso Icon Icona Update period [s] Intervallo di aggiornamento [s] Message Messaggio Extensions Estensioni main Codeplug file to load. File codeplug da caricare. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. Specifica il livello di log dell'applicazione da inviare a stdout. Deve essere uno tra `debug`, `info`, `warning`, `error` o `fatal`. ================================================ FILE: i18n/nl.ts ================================================ AMChannelDialog Squelch Squelch APRSSelect [None] [Geen] APRSSystemDialog Create APRS system Edit APRS system [Selected] AboutDialog About qdmr Supported Radios Ondersteunde Radios Application Unsaved changes to codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Open codeplug Open codeplug Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Cannot open file Kan bestand niet openen Cannot read codeplug from file '%1': %2 Cannot read codeplug. Kan codeplug niet lezen. Save codeplug Codeplug opslaan Codeplug Files (*.yaml *.yml) Codeplug Bestanden (*.yaml *.yml) Please use new YAML format. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Cannot save codeplug to file '%1': %2 Cannot save codeplug Kan codeplug niet opslaan Cannot save codeplug to file '%1'. Export codeplug CHIRP CSV Files (*.csv) CHIRP CSV Bestanden (*.csv) Cannot export codeplug Cannot export codeplug to file '%1': %2 Import codeplug CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) Cannot import codeplug Cannot import codeplug from '%1': %2 Do not know, how to handle file '%1'. No matching devices found. Cannot connect to radio Kan niet verbinden met radio Cannot connect to radio: %1 Radio found Radio gevonden Found device '%1'. No radio found Geen radio gevonden No matching device was found. Verification success The codeplug was successfully verified with the radio '%1' Read ... Lees … Read error Leesfout Read complete Upload ... Upload … Cannot write call-sign DB. The detected radio '%1' does not support a call-sign DB. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. Write call-sign DB ... Cannot write satellite config. The detected radio '%1' does not support satellite tracking. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. Write satellite config ... Write error Schrijffout Write complete %1 (alias for %2 %3) BandwidthSelect Narrow (12.5 kHz) Smal (12.5 kHz) Wide (25 kHz) Breed (25 kHz) ChannelDialog Edit Channel <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> Basic Name Naam Rx Frequency RX Frequentie Tx Frequency TX Frequentie Tx Offset Power Vermogen Max Max High Hoog Mid Middel Low Laag Min Min Default Tx Timeout TX Timeout Off Uit VOX Level Rx Only Scan List Scan Lijst Extensions Extensies No offset Positive offset Negative offset [None] [Geen] ChannelListView Select a single channel first To clone a channel, please select a single channel to clone. Cannot delete channel Cannot delete channel: You have to select a channel first. Delete channel? Kanaal verwijderen? Delete channel %1? Delete %1 channels? %1 kanalen verwijderen? Alt+A Alt+A Add Channel ... Clone Channel Kanaal Klonen Alt+C Alt+C Delete Channel Kanaal Verwijderen Alt+- Alt+- Add FM Channel Adds a new FM channel Add DMR Channel Adds a new DMR channel. Add AM Channel Adds a new AM channel. ChannelListWrapper FM FM DMR DMR AM [Default] Max Max High Hoog Mid Middel Low Laag Min Min Off Uit On Aan Always Altijd Free Vrij Color Kleur Tone Toon [None] [Geen] Open Open Wide Breed Narrow Smal Type Type Name Naam Rx Frequency RX Frequentie Tx Frequency TX Frequentie Power Vermogen Timeout Timeout Rx Only Admit Scanlist Scanlijst Zones Zones CC TS RX Group List TX Contact TX Contact DMR ID DMR ID GPS/APRS GPS/APRS Roaming Roaming Squelch Squelch Rx Tone RX Toon Tx Tone TX Toon Bandwidth Bandbreedte Extensions Extensies ChannelRefListWrapper Channel Kanaal ChannelSelectionDialog Select a channel: ConfigMergeDialog Merging codeplugs ... <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items Ignore Override Duplicate Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets Merge Ignores any duplicate item. Replaces any duplicate item with the imported one. Imports any duplicate item with a modified name. Ignores any duplicate set. Replaces any duplicate set with the imported one. Imports any duplicate set with a modified name. Merges duplicate sets. ConfigObjectListView Cannot move items. Cannot move items: You have to select at least one item first. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. ConfigObjectTableView Cannot move items. Cannot move items: You have to select at least one item first. Cannot move items as long as there is some filter or sorting applied. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. Toggle Filter and Sorting Close Sort and Filter ConfigObjectTypeSelectionDialog An instance of %1. <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Select the class of object to create ContactListView Cannot delete contact Cannot delete contact: You have to select a contact first. Delete contact? Delete contact %1? Delete contacts? Delete %1 contacts? Adds a contact to the list. Alt++ Alt++ Add M17 Contact Adds an M17 contact to the list. Add DTMF Contact Adds an DTMF (analog) contact to the list. Add DMR Contact Adds an DMR contact to the list. Delete contact button Add Contact Delete Contact Alt+- Alt+- ContactListWrapper DTMF DTMF On Aan Off Uit Private Call Group Call All Call [None] [Geen] M17 [Broadcast] Type Type Name Naam Number Nummer RX Tone RX Toon Extensions Extensies DMRAdmitSelect Always Altijd Channel Free Kanaal Vrij Other Color-code DMRChannelDialog Radio Id Tx Admit Color-code Time-slot Group list Tx Contact APRS APRS Roaming zone DMRContactDialog Create DMR Contact Edit DMR Contact Private Call Group Call All Call Basic Type Type Name Naam Number Nummer Ring Extensions Extensies DMRContactSelect [None] [Geen] DMRIDDialog Basic Name Naam DMR ID DMR ID Extensions Extensies DMRIdSelect [Default] DTMFContactDialog Create DTMF Contact Edit DMR Contact Basic Name Naam Number Nummer Ring Extensions Extensies DeviceSelectionDialog Select a device <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> ErrorMessageView Error: Unknown. Error: %1 Fout: %1 Traceback: Traceback: ExtensionView Cannot create extension. Cannot create extension, consider reporting a bug. Cannot create list element. Cannot create list element, consider reporting a bug. Create Remove FMAPRSSelect [None] [Geen] FMAPRSSystem [None] [Geen] Police station Digipeater Digipeater Phone Telefoon DX cluster DX cluster HF gateway Plane small Vliegtuig klein Mobile Satellite station Mobiel satelliet station Wheel Chair Rolstoel Snowmobile Sneeuwscooter Red cross Rode kruis Boy scout Home Thuis X X Red dot Circle 0 Cirkel 0 Circle 1 Cirkel 1 Circle 2 Cirkel 2 Circle 3 Cirkel 3 Circle 4 Cirkel 4 Circle 5 Cirkel 5 Circle 6 Cirkel 6 Circle 7 Cirkel 7 Circle 8 Cirkel 8 Circle 9 Cirkel 9 Fire Vuur Campground Kampeerterrein Motorcycle Motorfiets Rail engine Car Auto File server HC Future Aid station BBS Canoe Kano Eyeball Tractor Trekker Grid Square Hotel Hotel TCP/IP TCP/IP School School Logon MacOS MacOS NTS station Balloon Ballon Police car Politie auto TBD RV Shuttle SSTV SSTV Bus Bus ATV ATV Weather service Helo Yacht Jacht MS Windows MS Windows Jogger Jogger Triangle Driehoek PBBS Plane large Vliegtuig groot Weather station Weerstation Dish antenna Schotelantenne Ambulance Ambulance Bike Fiets ICP Fire station Brandweerkazerne Horse Paard Fire truck Brandweerauto Glider Hospital Ziekenhuis IOTA IOTA Jeep Jeep Truck small Vrachtwagen klein Laptop Laptop Mic-E Mic-E Node EOC Rover Grid Antenna Antenne Power boat Truck stop Truck large Van Water Water XAPRS XAPRS Yagi Yagi Shelter FMAdmitSelect Always Altijd Channel Free Kanaal Vrij Other Tone FMChannelDialog Squelch Squelch Tx Admit Rx Tone RX Toon Tx Tone TX Toon Bandwidth Bandbreedte APRS APRS FlagEditDialog Select Flags GPSSystemDialog Create DMR APRS System Edit DMR APRS System [Selected] Edit GPS System Basic Name Naam Destination Update period Revert Channel Extensions Extensies GeneralSettingsView Boot Settings Intro Line 1 First greeting line (if supported by the radio). Intro line 1 Intro Line 2 Second greeting line (if supported by the radio). Intro line 2 Audio Settings MIC Amp. Speech Synthesis Channel Default Values Power Vermogen Max Max High Hoog Mid Middel Low Laag Min Min Squelch Squelch Open Open Transmit Timeout Off Uit VOX Level Extensions Extensies GroupListWrapper Contact Contact GroupListsView Cannot delete RX group list Cannot delete RX group lists: You have to select a group list first. Delete RX group list? Delete RX group list %1? Delete %1 RX group lists? Add RX Group Alt++ Alt++ Delete RX Group Alt+- Alt+- GroupListsWrapper RX Group Lists M17ChannelDialog Channel mode Access number Tx contact Send position M17ChannelModeSelect Voice Data Voice + Data M17ContactDialog Edit M17 Contact Basic Name Naam The name of the contact. Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Ring Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Extensies Create M17 Contact M17ContactSelect [None] [Geen] MainWindow File Bestand Device Help Databases Toolbar New Nieuw Creates a new Codeplug. Ctrl+N Ctrl+N Open ... <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> Ctrl+O Ctrl+O Save ... Opslaan … <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> Ctrl+S Ctrl+S Quit Quits the application. Ctrl+Q Ctrl+Q Detect Detecteren Detect connected radios. Verify Verifiëren <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Ctrl+R Ctrl+R Read Reads a codeplug from connected radios. Write Writes the codeplug to the connected radio. About qdmr Read the handbook. F1 F1 Settings Instellingen Shows settings dialog Write Callsign DB Writes call-sign DB to radio. Refresh Callsign DB Refreshes the downloaded callsign DB Refresh Talkgroup DB Refreshes the downloaded talkgroup DB Export to CHIRP ... Exports all FM channels to CHIRP CSV. Import ... Imports and merges a codeplug into the current one. Refresh Orbital Elements Refreshes the orbital elements. Edit Satellites ... Opens an editor to edit your satellite database. Write satellites Writes the orbital elements and transponder information onto the connected device. Cannot update callsign DB: %1 Callsign database updated & loaded. Cannot update talkgroup DB: %1 Talkgroup database updated & loaded. Cannot update orbital elements: %1 Orbital elements updated & loaded. Radio IDs Radio IDs Contacts Contacten Group Lists Groepenlijst Channels Kanalen Zones Zones Scan Lists Scan Lijsten GPS/APRS GPS/APRS Roaming Channels Roaming Kanalen Roaming Zones Roaming Zones Extensions Extensies Unsaved changes to codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. MultiChannelSelectionDialog [Selected] Select a channel: MultiGroupCallSelectionDialog Show private calls Select a group call: MultiRoamingChannelSelectionDialog Select roaming channels PositioningSystemListView Cannot delete GPS system Cannot delete GPS system: You have to select a GPS system first. Delete positioning system? Delete positioning system %1? Delete %1 positioning systems? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add GPS System Alt+G Add APRS System Alt+A Alt+A Delete Position System Alt+- Alt+- PositioningSystemListWrapper DMR DMR APRS APRS [None] [Geen] [Selected] Type Type Name Naam Destination Period Channel Kanaal Message Bericht Extensions Extensies PropertyDelegate None Off Uit False True [None] [Geen] PropertyWrapper new element Property Value Waarde Description Beschrijving true false None Off Uit [None] [Geen] Instance of %1 List of %1 instances QObject [None] [Geen] RXGroupListDialog Create Group List Edit Group List Cannot remove group call Cannot remove group call: You have to select at least one group call first. Basic Name Naam Add Contact Alt++ Alt++ Remove Contact Alt+- Alt+- Extensions Extensies RadioIDListView Cannot delete radio IDs Cannot delete radio IDs: You have to select a radio ID first. Delete radio ID? Delete radio ID %1? Delete scan lists? Delete %1 scan lists? Default Radio ID Add Radio ID Delete Radio ID RadioIdListWrapper [None] [Geen] Type Type Name Naam Number Nummer Extensions Extensies RadioSelectionDialog Cannot auto-detect radio Select a specific radio ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. qDMR was updated to version %1 RoamingChannelDialog Name Naam RX Frequency [MHz] TX Frequency [MHz] Time Slot Color Code Selected Edit roaming channel Create roaming channel TS 1 TS 1 TS 2 TS 2 RoamingChannelListView <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Channel Delete Roaming Channel Cannot delete roaming channel Cannot delete roaming channel: You have to select a channel first. Delete roaming channel? Delete roaming channel %1? Delete %1 roaming channel? RoamingChannelListWrapper [Selected] [None] [Geen] Name Naam RX Frequency TX Frequency CC TS Zones Zones Extensions Extensies RoamingChannelRefListWrapper Roaming Channel RoamingListWrapper %1 (containing %2 channels) Roaming zone RoamingZoneDialog Create Roaming Zone Set Roaming Zone Cannot remove channels. Cannot remove channels. Select at least one channel first. Basic Name: Add Roaming Channel Add DMR Channel Alt++ Alt++ Remove Channel Alt+- Alt+- Extension RoamingZoneListView Generate roaming zone Create a roaming zone by collecting all channels with these group calls. Cannot delete roaming zone Cannot delete roaming zone: You have to select a zone first. Delete roaming zone? Delete roaming zone %1? Delete roaming zones? Delete %1 roaming zones? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Zone Alt++ Alt++ Generate Roaming Zone Delete Roaming Zone Alt+- Alt+- RoamingZoneSelect [None] [Geen] [Default] SatelliteDatabaseDialog Edit satellite database Add Delete SatelliteSelectionDialog Select a satellite SatelliteTransponderDialog Edit Satellite Transponder FM Voice Transponder Uplink Frequency Uplink Tone Downlink Frequency Downlink Tone APRS Transponder Beacon Beacon Frequency ScanListDialog Edit Scan List Basic Name Naam Primary Channel (50%) Secondary Channel (25%) Transmit Channel Add Channel Alt++ Alt++ Remove Channel Alt+- Alt+- Extensions Extensies Create Scan List [None] [Geen] [Selected] [Last] ScanListsView Cannot delete scanlist Cannot delete scanlist: You have to select a scanlist first. Delete scan list? Delete scan list %1? Delete scan lists? Delete %1 scan lists? Add Scan List Alt++ Alt++ Delete Scan List Alt+- Alt+- ScanListsWrapper Scan-List SearchPopup Ctrl+F Ctrl+F %1/%2 SelectiveCallBox None CTCSS DCS Hz Inverted SettingsDialog Warning! Waarschuwing! Settings Instellingen Location Locatie System location Systeem locatie Locator Locator Repeater Info Sources enable Radio Programming Update codeplug Update codeplug <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> Auto-enable GPS When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Auto-enable roaming Data Sources World Wereld North America Programming Radio Interfaces disable auto-detect Ignore verification warnings <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> Ignore frequency limits Negeer frequentie limieten Do not set this option unless you know what you are doing. Update Device Clock Call-Sign DB Limit number of DB entries When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Number of DB entries Specifies the number of DB entries (if enabled above). Select using my DMR ID If enabled, the entries are selected using the users DMR ID. Select using prefixes If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. SquelchEdit Open Open Uses the global squelch setting if enabled. Default TimeslotSelect Slot 1 Slot 2 TransponderFrequencyEditor None VerifyDialog Verify Codeplug The codeplug cannot be uploaded, unless all critical issues (red) are resolved. ZoneDialog Edit Zone Basic <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Name Naam Channels A add remove Channels B Extension Create Zone Cannot remove channel Select at least one channel first. ZoneListView Cannot delete zone Cannot delete zone: You have to select a zone first. Delete zone? Delete zone %1? Delete zones? Delete %1 zones? Add Zone Alt++ Alt++ Delete Zone Alt+- Alt+- ZoneListWrapper Zone Zone aprssystemdialog Edit APRS System Basic Name Naam Channel Kanaal Source Destination Path Icon Update period [s] Updateperiode [s] Message Bericht Extensions Extensies main Codeplug file to load. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. ================================================ FILE: i18n/pl.ts ================================================ AMChannelDialog Squelch APRSSelect [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. APRSSystemDialog Create APRS system Utwórz system APRS Edit APRS system Edytuj system APRS [Selected] AboutDialog About qdmr O qdrm Supported Radios Wspierane radia Application Unsaved changes to codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Open codeplug Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Cannot open file Cannot read codeplug from file '%1': %2 Cannot read codeplug. Save codeplug Codeplug Files (*.yaml *.yml) Please use new YAML format. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Cannot save codeplug to file '%1': %2 Cannot save codeplug Cannot save codeplug to file '%1'. Export codeplug CHIRP CSV Files (*.csv) Cannot export codeplug Cannot export codeplug to file '%1': %2 Import codeplug CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) Cannot import codeplug Cannot import codeplug from '%1': %2 Do not know, how to handle file '%1'. No matching devices found. Cannot connect to radio Cannot connect to radio: %1 Radio found Found device '%1'. No radio found No matching device was found. Verification success The codeplug was successfully verified with the radio '%1' Read ... Read error Read complete Upload ... Cannot write call-sign DB. The detected radio '%1' does not support a call-sign DB. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. Write call-sign DB ... Cannot write satellite config. The detected radio '%1' does not support satellite tracking. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. Write satellite config ... Write error Write complete %1 (alias for %2 %3) BandwidthSelect Narrow (12.5 kHz) Wide (25 kHz) ChannelDialog Edit Channel <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> Basic Podstawowe Name Nazwa Rx Frequency Częstotliwość RX Tx Frequency Częstotliwość TX Tx Offset Power Moc Max Maks High Wysoki Mid Średni Low Niski Min Minimum Default Domyślny Tx Timeout Limit czasu TX Off Wyłącz VOX Level Poziom VOX Rx Only Tylko RX Scan List Skanuj listę Extensions Rozszerzenia No offset Positive offset Negative offset [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. ChannelListView Select a single channel first To clone a channel, please select a single channel to clone. Cannot delete channel Cannot delete channel: You have to select a channel first. Delete channel? Delete channel %1? Delete %1 channels? Alt+A Add Channel ... Clone Channel Alt+C Delete Channel Usuń kanał Alt+- Add FM Channel Adds a new FM channel Add DMR Channel Adds a new DMR channel. Add AM Channel Adds a new AM channel. ChannelListWrapper FM DMR AM [Default] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. Max Maks High Wysoki Mid Średni Low Niski Min Minimum Off Wyłącz On Włącz Always Zawsze Free Wolny Color Kolor Tone Ton [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. Open Otwórz Wide Szeroki Narrow Wąski Type Typ Name Nazwa Rx Frequency Częstotliwość RX Tx Frequency Częstotliwość TX Power Moc Timeout Koniec czasu połączenia Rx Only Tylko RX Admit Zezwól Scanlist Lista skanowania Zones Strefy CC TS RX Group List Lista grupy TX Contact Kontakt TX DMR ID Identyfikator DMR GPS/APRS GPS/APRS Roaming Squelch Rx Tone Ton RX Tx Tone Ton TX Bandwidth Szerokość pasma Extensions Rozszerzenia ChannelRefListWrapper Channel Kanał ChannelSelectionDialog Select a channel: Wybierz kanał: ConfigMergeDialog Merging codeplugs ... <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items Ignore Override Duplicate Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets Merge Ignores any duplicate item. Replaces any duplicate item with the imported one. Imports any duplicate item with a modified name. Ignores any duplicate set. Replaces any duplicate set with the imported one. Imports any duplicate set with a modified name. Merges duplicate sets. ConfigObjectListView Cannot move items. Nie można przenieść elementów. Cannot move items: You have to select at least one item first. Nie można przenieść elementów: Musisz wybrać conajmniej jeden z nich. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. ConfigObjectTableView Cannot move items. Nie można przenieść elementów Cannot move items: You have to select at least one item first. Nie można przenieść elementów: Musisz wybrać conajmniej jeden z nich. Cannot move items as long as there is some filter or sorting applied. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. Toggle Filter and Sorting Close Sort and Filter ConfigObjectTypeSelectionDialog An instance of %1. <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Select the class of object to create ContactListView Cannot delete contact Nie można usunąć kontaktu Cannot delete contact: You have to select a contact first. Delete contact? Delete contact %1? Delete contacts? Delete %1 contacts? Adds a contact to the list. Alt++ Add M17 Contact Adds an M17 contact to the list. Add DTMF Contact Adds an DTMF (analog) contact to the list. Add DMR Contact Adds an DMR contact to the list. Delete contact button Add Contact Delete Contact Alt+- ContactListWrapper DTMF On Włącz Off Wyłącz Private Call Group Call All Call [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. M17 [Broadcast] Type Typ Name Nazwa Number Numer RX Tone Extensions Rozszerzenia DMRAdmitSelect Always Zawsze Channel Free Wolny kanał Other Color-code DMRChannelDialog Radio Id Tx Admit Color-code Time-slot Group list Tx Contact APRS APRS Roaming zone DMRContactDialog Create DMR Contact Edit DMR Contact Edytuj kontakt DMR Private Call Group Call All Call Basic Podstawowe Type Typ Name Nazwa Number Numer Ring Extensions Rozszerzenia DMRContactSelect [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. DMRIDDialog Basic Podstawowe Name Nazwa DMR ID Identyfikator DMR Extensions Rozszerzenia DMRIdSelect [Default] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. DTMFContactDialog Create DTMF Contact Utwórz kontakt DTMF Edit DMR Contact Edytuj kontakt DMR Basic Podstawowe Name Nazwa Number Numer Ring Extensions Rozszerzenia DeviceSelectionDialog Select a device <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> ErrorMessageView Error: Unknown. Error: %1 Traceback: ExtensionView Cannot create extension. Cannot create extension, consider reporting a bug. Cannot create list element. Cannot create list element, consider reporting a bug. Create Remove FMAPRSSelect [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. FMAPRSSystem [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. Police station Digipeater Phone DX cluster HF gateway Plane small Mobile Satellite station Wheel Chair Snowmobile Red cross Boy scout Home X Red dot Circle 0 Circle 1 Circle 2 Circle 3 Circle 4 Circle 5 Circle 6 Circle 7 Circle 8 Circle 9 Fire Campground Motorcycle Rail engine Car File server HC Future Aid station BBS Canoe Eyeball Tractor Grid Square Hotel TCP/IP School Logon MacOS NTS station Balloon Police car TBD RV Shuttle SSTV Bus ATV Weather service Helo Yacht MS Windows Jogger Triangle PBBS Plane large Weather station Dish antenna Ambulance Bike ICP Fire station Horse Fire truck Glider Hospital IOTA Jeep Truck small Laptop Mic-E Node EOC Rover Grid Antenna Power boat Truck stop Truck large Van Water XAPRS Yagi Shelter FMAdmitSelect Always Zawsze Channel Free Wolny kanał Other Tone FMChannelDialog Squelch Tx Admit Rx Tone Ton RX Tx Tone Ton TX Bandwidth Szerokość pasma APRS APRS FlagEditDialog Select Flags GPSSystemDialog Create DMR APRS System Edit DMR APRS System [Selected] Edit GPS System Basic Podstawowe Name Nazwa Destination Update period Revert Channel Extensions Rozszerzenia GeneralSettingsView Boot Settings Intro Line 1 First greeting line (if supported by the radio). Intro line 1 Intro Line 2 Second greeting line (if supported by the radio). Intro line 2 Audio Settings MIC Amp. Speech Synthesis Channel Default Values Power Moc Max Maks High Wysoki Mid Średni Low Niski Min Minimum Squelch Open Otwórz Transmit Timeout Off Wyłącz VOX Level Poziom VOX Extensions Rozszerzenia GroupListWrapper Contact GroupListsView Cannot delete RX group list Cannot delete RX group lists: You have to select a group list first. Delete RX group list? Delete RX group list %1? Delete %1 RX group lists? Add RX Group Alt++ Delete RX Group Alt+- GroupListsWrapper RX Group Lists M17ChannelDialog Channel mode Access number Tx contact Send position M17ChannelModeSelect Voice Data Voice + Data M17ContactDialog Edit M17 Contact Basic Podstawowe Name Nazwa The name of the contact. Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Ring Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Rozszerzenia Create M17 Contact M17ContactSelect [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. MainWindow File Device Help Databases Toolbar New Creates a new Codeplug. Ctrl+N Open ... <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> Ctrl+O Save ... <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> Ctrl+S Quit Quits the application. Ctrl+Q Detect Detect connected radios. Verify <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Ctrl+R Read Reads a codeplug from connected radios. Write Writes the codeplug to the connected radio. About qdmr O qdrm Read the handbook. F1 Settings Ustawienia Shows settings dialog Write Callsign DB Writes call-sign DB to radio. Refresh Callsign DB Refreshes the downloaded callsign DB Refresh Talkgroup DB Refreshes the downloaded talkgroup DB Export to CHIRP ... Exports all FM channels to CHIRP CSV. Import ... Imports and merges a codeplug into the current one. Refresh Orbital Elements Refreshes the orbital elements. Edit Satellites ... Opens an editor to edit your satellite database. Write satellites Writes the orbital elements and transponder information onto the connected device. Cannot update callsign DB: %1 Callsign database updated & loaded. Cannot update talkgroup DB: %1 Talkgroup database updated & loaded. Cannot update orbital elements: %1 Orbital elements updated & loaded. Radio IDs Identyfikatory radia Contacts Kontakty Group Lists Channels Kanały Zones Strefy Scan Lists Skanuj listy GPS/APRS GPS/APRS Roaming Channels Roaming Zones Extensions Rozszerzenia Unsaved changes to codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. MultiChannelSelectionDialog [Selected] Select a channel: Wybierz kanał: MultiGroupCallSelectionDialog Show private calls Select a group call: MultiRoamingChannelSelectionDialog Select roaming channels PositioningSystemListView Cannot delete GPS system Cannot delete GPS system: You have to select a GPS system first. Delete positioning system? Delete positioning system %1? Delete %1 positioning systems? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add GPS System Alt+G Add APRS System Alt+A Delete Position System Alt+- PositioningSystemListWrapper DMR APRS APRS [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. [Selected] Type Typ Name Nazwa Destination Period Channel Kanał Message Extensions Rozszerzenia PropertyDelegate None Off Wyłącz False True [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. PropertyWrapper new element Property Value Description true false None Off Wyłącz [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. Instance of %1 List of %1 instances QObject [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. RXGroupListDialog Create Group List Edit Group List Cannot remove group call Cannot remove group call: You have to select at least one group call first. Basic Podstawowe Name Nazwa Add Contact Alt++ Remove Contact Alt+- Extensions Rozszerzenia RadioIDListView Cannot delete radio IDs Cannot delete radio IDs: You have to select a radio ID first. Delete radio ID? Delete radio ID %1? Delete scan lists? Delete %1 scan lists? Default Radio ID Add Radio ID Delete Radio ID RadioIdListWrapper [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. Type Typ Name Nazwa Number Numer Extensions Rozszerzenia RadioSelectionDialog Cannot auto-detect radio Select a specific radio ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. qDMR was updated to version %1 RoamingChannelDialog Name Nazwa RX Frequency [MHz] TX Frequency [MHz] Time Slot Color Code Selected Edit roaming channel Create roaming channel TS 1 TS 2 RoamingChannelListView <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Channel Delete Roaming Channel Cannot delete roaming channel Cannot delete roaming channel: You have to select a channel first. Delete roaming channel? Delete roaming channel %1? Delete %1 roaming channel? RoamingChannelListWrapper [Selected] [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. Name Nazwa RX Frequency TX Frequency TS Zones Strefy Extensions Rozszerzenia CC RoamingChannelRefListWrapper Roaming Channel RoamingListWrapper %1 (containing %2 channels) Roaming zone RoamingZoneDialog Create Roaming Zone Set Roaming Zone Cannot remove channels. Cannot remove channels. Select at least one channel first. Basic Podstawowe Name: Add Roaming Channel Add DMR Channel Alt++ Remove Channel Alt+- Extension RoamingZoneListView Generate roaming zone Create a roaming zone by collecting all channels with these group calls. Cannot delete roaming zone Cannot delete roaming zone: You have to select a zone first. Delete roaming zone? Delete roaming zone %1? Delete roaming zones? Delete %1 roaming zones? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Zone Alt++ Generate Roaming Zone Delete Roaming Zone Alt+- RoamingZoneSelect [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. [Default] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. SatelliteDatabaseDialog Edit satellite database Add Delete SatelliteSelectionDialog Select a satellite SatelliteTransponderDialog Edit Satellite Transponder FM Voice Transponder Uplink Frequency Uplink Tone Downlink Frequency Downlink Tone APRS Transponder Beacon Beacon Frequency ScanListDialog Edit Scan List Basic Podstawowe Name Nazwa Primary Channel (50%) Secondary Channel (25%) Transmit Channel Add Channel Alt++ Remove Channel Alt+- Extensions Rozszerzenia Create Scan List [None] Znaczenie mocno zależy od kontekstu. Sprawdź kontekst źródłowy. [Selected] [Last] ScanListsView Cannot delete scanlist Cannot delete scanlist: You have to select a scanlist first. Delete scan list? Delete scan list %1? Delete scan lists? Delete %1 scan lists? Add Scan List Alt++ Delete Scan List Alt+- ScanListsWrapper Scan-List SearchPopup Ctrl+F %1/%2 SelectiveCallBox None CTCSS DCS Hz Inverted SettingsDialog Warning! Settings Ustawienia Location System location Locator Repeater Info Sources enable Radio Programming Update codeplug <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> Auto-enable GPS When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Auto-enable roaming Data Sources World North America Programming Radio Interfaces disable auto-detect Ignore verification warnings <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> Ignore frequency limits Do not set this option unless you know what you are doing. Update Device Clock Call-Sign DB Limit number of DB entries When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Number of DB entries Specifies the number of DB entries (if enabled above). Select using my DMR ID If enabled, the entries are selected using the users DMR ID. Select using prefixes If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. SquelchEdit Open Otwórz Uses the global squelch setting if enabled. Default Domyślny TimeslotSelect Slot 1 Slot 2 TransponderFrequencyEditor None VerifyDialog Verify Codeplug The codeplug cannot be uploaded, unless all critical issues (red) are resolved. ZoneDialog Edit Zone Basic Podstawowe <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Name Nazwa Channels A add remove Channels B Extension Create Zone Cannot remove channel Select at least one channel first. ZoneListView Cannot delete zone Cannot delete zone: You have to select a zone first. Delete zone? Delete zone %1? Delete zones? Delete %1 zones? Add Zone Alt++ Delete Zone Alt+- ZoneListWrapper Zone aprssystemdialog Edit APRS System Basic Podstawowe Name Nazwa Channel Kanał Source Destination Path Icon Update period [s] Message Extensions Rozszerzenia main Codeplug file to load. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. ================================================ FILE: i18n/pt_BR.ts ================================================ AMChannelDialog Squelch APRSSelect [None] APRSSystemDialog Create APRS system Edit APRS system [Selected] AboutDialog About qdmr Supported Radios Application Unsaved changes to codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Open codeplug Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Cannot open file Cannot read codeplug from file '%1': %2 Cannot read codeplug. Save codeplug Codeplug Files (*.yaml *.yml) Please use new YAML format. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Cannot save codeplug to file '%1': %2 Cannot save codeplug Cannot save codeplug to file '%1'. Export codeplug CHIRP CSV Files (*.csv) Cannot export codeplug Cannot export codeplug to file '%1': %2 Import codeplug CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) Cannot import codeplug Cannot import codeplug from '%1': %2 Do not know, how to handle file '%1'. No matching devices found. Cannot connect to radio Cannot connect to radio: %1 Radio found Found device '%1'. No radio found No matching device was found. Verification success The codeplug was successfully verified with the radio '%1' Read ... Read error Read complete Upload ... Cannot write call-sign DB. The detected radio '%1' does not support a call-sign DB. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. Write call-sign DB ... Cannot write satellite config. The detected radio '%1' does not support satellite tracking. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. Write satellite config ... Write error Write complete %1 (alias for %2 %3) BandwidthSelect Narrow (12.5 kHz) Wide (25 kHz) ChannelDialog Edit Channel <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> Basic Name Rx Frequency Tx Frequency Tx Offset Power Max High Mid Low Min Default Tx Timeout Off VOX Level Rx Only Scan List Extensions No offset Positive offset Negative offset [None] ChannelListView Select a single channel first To clone a channel, please select a single channel to clone. Cannot delete channel Cannot delete channel: You have to select a channel first. Delete channel? Delete channel %1? Delete %1 channels? Alt+A Add Channel ... Clone Channel Alt+C Delete Channel Alt+- Add FM Channel Adds a new FM channel Add DMR Channel Adds a new DMR channel. Add AM Channel Adds a new AM channel. ChannelListWrapper FM DMR AM [Default] Max High Mid Low Min Off On Always Free Color Tone [None] Open Wide Narrow Type Name Rx Frequency Tx Frequency Power Timeout Rx Only Admit Scanlist Zones CC TS RX Group List TX Contact DMR ID GPS/APRS Roaming Squelch Rx Tone Tx Tone Bandwidth Extensions ChannelRefListWrapper Channel ChannelSelectionDialog Select a channel: ConfigMergeDialog Merging codeplugs ... <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items Ignore Override Duplicate Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets Merge Ignores any duplicate item. Replaces any duplicate item with the imported one. Imports any duplicate item with a modified name. Ignores any duplicate set. Replaces any duplicate set with the imported one. Imports any duplicate set with a modified name. Merges duplicate sets. ConfigObjectListView Cannot move items. Cannot move items: You have to select at least one item first. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. ConfigObjectTableView Cannot move items. Cannot move items: You have to select at least one item first. Cannot move items as long as there is some filter or sorting applied. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. Toggle Filter and Sorting Close Sort and Filter ConfigObjectTypeSelectionDialog An instance of %1. <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Select the class of object to create ContactListView Cannot delete contact Cannot delete contact: You have to select a contact first. Delete contact? Delete contact %1? Delete contacts? Delete %1 contacts? Adds a contact to the list. Alt++ Add M17 Contact Adds an M17 contact to the list. Add DTMF Contact Adds an DTMF (analog) contact to the list. Add DMR Contact Adds an DMR contact to the list. Delete contact button Add Contact Delete Contact Alt+- ContactListWrapper DTMF On Off Private Call Group Call All Call [None] M17 [Broadcast] Type Name Number RX Tone Extensions DMRAdmitSelect Always Channel Free Other Color-code DMRChannelDialog Radio Id Tx Admit Color-code Time-slot Group list Tx Contact APRS Roaming zone DMRContactDialog Create DMR Contact Edit DMR Contact Private Call Group Call All Call Basic Type Name Number Ring Extensions DMRContactSelect [None] DMRIDDialog Basic Name DMR ID Extensions DMRIdSelect [Default] DTMFContactDialog Create DTMF Contact Edit DMR Contact Basic Name Number Ring Extensions DeviceSelectionDialog Select a device <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> ErrorMessageView Error: Unknown. Error: %1 Traceback: ExtensionView Cannot create extension. Cannot create extension, consider reporting a bug. Cannot create list element. Cannot create list element, consider reporting a bug. Create Remove FMAPRSSelect [None] FMAPRSSystem [None] Police station Digipeater Phone DX cluster HF gateway Plane small Mobile Satellite station Wheel Chair Snowmobile Red cross Boy scout Home X Red dot Circle 0 Circle 1 Circle 2 Circle 3 Circle 4 Circle 5 Circle 6 Circle 7 Circle 8 Circle 9 Fire Campground Motorcycle Rail engine Car File server HC Future Aid station BBS Canoe Eyeball Tractor Grid Square Hotel TCP/IP School Logon MacOS NTS station Balloon Police car TBD RV Shuttle SSTV Bus ATV Weather service Helo Yacht MS Windows Jogger Triangle PBBS Plane large Weather station Dish antenna Ambulance Bike ICP Fire station Horse Fire truck Glider Hospital IOTA Jeep Truck small Laptop Mic-E Node EOC Rover Grid Antenna Power boat Truck stop Truck large Van Water XAPRS Yagi Shelter FMAdmitSelect Always Channel Free Other Tone FMChannelDialog Squelch Tx Admit Rx Tone Tx Tone Bandwidth APRS FlagEditDialog Select Flags GPSSystemDialog Create DMR APRS System Edit DMR APRS System [Selected] Edit GPS System Basic Name Destination Update period Revert Channel Extensions GeneralSettingsView Boot Settings Intro Line 1 First greeting line (if supported by the radio). Intro line 1 Intro Line 2 Second greeting line (if supported by the radio). Intro line 2 Audio Settings MIC Amp. Speech Synthesis Channel Default Values Power Max High Mid Low Min Squelch Open Transmit Timeout Off VOX Level Extensions GroupListWrapper Contact GroupListsView Cannot delete RX group list Cannot delete RX group lists: You have to select a group list first. Delete RX group list? Delete RX group list %1? Delete %1 RX group lists? Add RX Group Alt++ Delete RX Group Alt+- GroupListsWrapper RX Group Lists M17ChannelDialog Channel mode Access number Tx contact Send position M17ChannelModeSelect Voice Data Voice + Data M17ContactDialog Edit M17 Contact Basic Name The name of the contact. Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Ring Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Create M17 Contact M17ContactSelect [None] MainWindow File Device Help Databases Toolbar New Creates a new Codeplug. Ctrl+N Open ... <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> Ctrl+O Save ... <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> Ctrl+S Quit Quits the application. Ctrl+Q Detect Detect connected radios. Verify <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Ctrl+R Read Reads a codeplug from connected radios. Write Writes the codeplug to the connected radio. About qdmr Read the handbook. F1 Settings Shows settings dialog Write Callsign DB Writes call-sign DB to radio. Refresh Callsign DB Refreshes the downloaded callsign DB Refresh Talkgroup DB Refreshes the downloaded talkgroup DB Export to CHIRP ... Exports all FM channels to CHIRP CSV. Import ... Imports and merges a codeplug into the current one. Refresh Orbital Elements Refreshes the orbital elements. Edit Satellites ... Opens an editor to edit your satellite database. Write satellites Writes the orbital elements and transponder information onto the connected device. Cannot update callsign DB: %1 Callsign database updated & loaded. Cannot update talkgroup DB: %1 Talkgroup database updated & loaded. Cannot update orbital elements: %1 Orbital elements updated & loaded. Radio IDs Contacts Group Lists Channels Zones Scan Lists GPS/APRS Roaming Channels Roaming Zones Extensions Unsaved changes to codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. MultiChannelSelectionDialog [Selected] Select a channel: MultiGroupCallSelectionDialog Show private calls Select a group call: MultiRoamingChannelSelectionDialog Select roaming channels PositioningSystemListView Cannot delete GPS system Cannot delete GPS system: You have to select a GPS system first. Delete positioning system? Delete positioning system %1? Delete %1 positioning systems? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add GPS System Alt+G Add APRS System Alt+A Delete Position System Alt+- PositioningSystemListWrapper DMR APRS [None] [Selected] Type Name Destination Period Channel Message Extensions PropertyDelegate None Off False True [None] PropertyWrapper new element Property Value Description true false None Off [None] Instance of %1 List of %1 instances QObject [None] RXGroupListDialog Create Group List Edit Group List Cannot remove group call Cannot remove group call: You have to select at least one group call first. Basic Name Add Contact Alt++ Remove Contact Alt+- Extensions RadioIDListView Cannot delete radio IDs Cannot delete radio IDs: You have to select a radio ID first. Delete radio ID? Delete radio ID %1? Delete scan lists? Delete %1 scan lists? Default Radio ID Add Radio ID Delete Radio ID RadioIdListWrapper [None] Type Name Number Extensions RadioSelectionDialog Cannot auto-detect radio Select a specific radio ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. qDMR was updated to version %1 RoamingChannelDialog Name RX Frequency [MHz] TX Frequency [MHz] Time Slot Color Code Selected Edit roaming channel Create roaming channel TS 1 TS 2 RoamingChannelListView <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Channel Delete Roaming Channel Cannot delete roaming channel Cannot delete roaming channel: You have to select a channel first. Delete roaming channel? Delete roaming channel %1? Delete %1 roaming channel? RoamingChannelListWrapper [Selected] [None] Name RX Frequency TX Frequency TS Zones Extensions CC RoamingChannelRefListWrapper Roaming Channel RoamingListWrapper %1 (containing %2 channels) Roaming zone RoamingZoneDialog Create Roaming Zone Set Roaming Zone Cannot remove channels. Cannot remove channels. Select at least one channel first. Basic Name: Add Roaming Channel Add DMR Channel Alt++ Remove Channel Alt+- Extension RoamingZoneListView Generate roaming zone Create a roaming zone by collecting all channels with these group calls. Cannot delete roaming zone Cannot delete roaming zone: You have to select a zone first. Delete roaming zone? Delete roaming zone %1? Delete roaming zones? Delete %1 roaming zones? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Zone Alt++ Generate Roaming Zone Delete Roaming Zone Alt+- RoamingZoneSelect [None] [Default] SatelliteDatabaseDialog Edit satellite database Add Delete SatelliteSelectionDialog Select a satellite SatelliteTransponderDialog Edit Satellite Transponder FM Voice Transponder Uplink Frequency Uplink Tone Downlink Frequency Downlink Tone APRS Transponder Beacon Beacon Frequency ScanListDialog Edit Scan List Basic Name Primary Channel (50%) Secondary Channel (25%) Transmit Channel Add Channel Alt++ Remove Channel Alt+- Extensions Create Scan List [None] [Selected] [Last] ScanListsView Cannot delete scanlist Cannot delete scanlist: You have to select a scanlist first. Delete scan list? Delete scan list %1? Delete scan lists? Delete %1 scan lists? Add Scan List Alt++ Delete Scan List Alt+- ScanListsWrapper Scan-List SearchPopup Ctrl+F %1/%2 SelectiveCallBox None CTCSS DCS Hz Inverted SettingsDialog Warning! Settings Location System location Locator Repeater Info Sources enable Radio Programming Update codeplug <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> Auto-enable GPS When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Auto-enable roaming Data Sources World North America Programming Radio Interfaces disable auto-detect Ignore verification warnings <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> Ignore frequency limits Do not set this option unless you know what you are doing. Update Device Clock Call-Sign DB Limit number of DB entries When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Number of DB entries Specifies the number of DB entries (if enabled above). Select using my DMR ID If enabled, the entries are selected using the users DMR ID. Select using prefixes If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. SquelchEdit Open Uses the global squelch setting if enabled. Default TimeslotSelect Slot 1 Slot 2 TransponderFrequencyEditor None VerifyDialog Verify Codeplug The codeplug cannot be uploaded, unless all critical issues (red) are resolved. ZoneDialog Edit Zone Basic <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Name Channels A add remove Channels B Extension Create Zone Cannot remove channel Select at least one channel first. ZoneListView Cannot delete zone Cannot delete zone: You have to select a zone first. Delete zone? Delete zone %1? Delete zones? Delete %1 zones? Add Zone Alt++ Delete Zone Alt+- ZoneListWrapper Zone aprssystemdialog Edit APRS System Basic Name Channel Source Destination Path Icon Update period [s] Message Extensions main Codeplug file to load. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. ================================================ FILE: i18n/ru.ts ================================================ AMChannelDialog Squelch Шумоподавитель APRSSelect [None] [None] APRSSystemDialog Create APRS system Создать систему APRS Edit APRS system Изменить систему APRS [Selected] [Selected] AboutDialog About qdmr О программе qdmr Supported Radios Поддерживаемые радиостанции Application Unsaved changes to codeplug. Несохранённые изменения кодплага. There are unsaved changes to the current codeplug. These changes are lost if you proceed. В текущем кодплаге есть несохранённые изменения. Они будут потеряны, если продолжить. Open codeplug Открыть кодплаг Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Файлы кодплага (*.yaml);;Файлы кодплага, старый формат (*.conf *.csv *.txt);;Все файлы (*) Cannot open file Не удалось открыть файл Cannot read codeplug from file '%1': %2 Не удалось прочитать кодплаг из файла «%1»: %2 Cannot read codeplug. Не удалось прочитать кодплаг. Save codeplug Сохранить кодплаг Codeplug Files (*.yaml *.yml) Файлы кодплага (*.yaml *.yml) Please use new YAML format. Используйте новый формат YAML. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Сохранение в старом табличном формате conf отключено с версии 0.9.0. Чтение таких файлов по-прежнему поддерживается. Cannot save codeplug to file '%1': %2 Не удалось сохранить кодплаг в файл «%1»: %2 Cannot save codeplug Не удалось сохранить кодплаг Cannot save codeplug to file '%1'. Не удалось сохранить кодплаг в файл «%1». Export codeplug Экспорт кодплага CHIRP CSV Files (*.csv) Файлы CHIRP CSV (*.csv) Cannot export codeplug Не удалось экспортировать кодплаг Cannot export codeplug to file '%1': %2 Не удалось экспортировать кодплаг в файл «%1»: %2 Import codeplug Импорт кодплага CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) Файлы CHIRP CSV (*.csv);;Файлы YAML (*.yaml *.yml) Cannot import codeplug Не удалось импортировать кодплаг Cannot import codeplug from '%1': %2 Не удалось импортировать кодплаг из «%1»: %2 Do not know, how to handle file '%1'. Неизвестно, как обработать файл «%1». No matching devices found. Подходящих устройств не найдено. Cannot connect to radio Не удалось подключиться к радиостанции Cannot connect to radio: %1 Не удалось подключиться к радиостанции: %1 Radio found Радиостанция найдена Found device '%1'. Найдено устройство «%1». No radio found Радиостанция не найдена Verification success Проверка пройдена The codeplug was successfully verified with the radio '%1' Кодплаг совпадает с данными в радиостанции «%1» Read ... Считать… Read error Ошибка чтения Read complete Чтение завершено Upload ... Запись… Cannot write call-sign DB. Не удалось записать базу позывных. The detected radio '%1' does not support a call-sign DB. Радиостанция «%1» не поддерживает базу позывных. No matching device was found. Подходящее устройство не найдено. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. Радиостанция «%1» поддерживает базу позывных, но эта функция пока не реализована. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. QDMR подбирает позывные для записи по DMR ID радиостанции. DMR ID по умолчанию не задан. Write call-sign DB ... Запись базы позывных… Cannot write satellite config. Не удалось записать конфигурацию спутников. The detected radio '%1' does not support satellite tracking. Радиостанция «%1» не поддерживает слежение за спутниками. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. Радиостанция «%1» поддерживает слежение за спутниками, но эта функция пока не реализована. Write satellite config ... Запись конфигурации спутников… Write error Ошибка записи Write complete Запись завершена %1 (alias for %2 %3) %1 (псевдоним для %2 %3) BandwidthSelect Narrow (12.5 kHz) Narrow (12.5 kHz) Wide (25 kHz) Wide (25 kHz) ChannelDialog Edit Channel Изменить канал <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> Basic Основное Name Имя Rx Frequency Частота приёма Tx Frequency Частота передачи Tx Offset Tx Offset Power Мощность Max Макс. High Высокая Mid Средняя Low Низкая Min Мин. Default По умолчанию Tx Timeout Tx Timeout Off Выкл. VOX Level VOX Level Rx Only Rx Only Scan List Список сканирования Extensions Расширения No offset No offset Positive offset Positive offset Negative offset Negative offset [None] [None] ChannelListView Select a single channel first Сначала выберите один канал To clone a channel, please select a single channel to clone. Чтобы клонировать канал, выберите один канал для клонирования. Cannot delete channel Не удалось удалить канал Cannot delete channel: You have to select a channel first. Не удалось удалить канал: сначала выберите канал. Delete channel? Удалить канал? Delete channel %1? Удалить канал «%1»? Delete %1 channels? Удалить %1 каналов? Alt+A Alt+A Add Channel ... Добавить канал… Clone Channel Клонировать канал Alt+C Alt+C Delete Channel Удалить канал Alt+- Alt+- Add FM Channel Добавить FM-канал Adds a new FM channel Добавляет новый FM-канал Add DMR Channel Добавить DMR-канал Adds a new DMR channel. Добавляет новый DMR-канал. Add AM Channel Добавить AM-канал Adds a new AM channel. Добавляет новый AM-канал. ChannelListWrapper FM FM DMR DMR AM AM [Default] [Default] Max Макс. High Высокая Mid Средняя Low Низкая Min Мин. Off Выкл. On Вкл. Always Всегда Free Свободно Color Код цвета Tone Тон [None] [None] Open Открыть Wide Широкая Narrow Узкая Type Тип Name Имя Rx Frequency Частота приёма Tx Frequency Частота передачи Power Мощность Timeout Таймаут Rx Only Rx Only Admit Условие передачи Scanlist Scanlist CC CC TS TS RX Group List RX Group List TX Contact TX Contact DMR ID DMR ID GPS/APRS GPS/APRS Roaming Roaming Squelch Шумоподавитель Rx Tone Rx Tone Tx Tone Tx Tone Extensions Расширения Zones Зоны Bandwidth Полоса ChannelRefListWrapper Channel Канал ChannelSelectionDialog Select a channel: Выберите канал: ConfigMergeDialog Merging codeplugs ... Merging codeplugs … <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items Items Ignore Ignore Override Override Duplicate Duplicate Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets Sets Merge Merge Ignores any duplicate item. Ignores any duplicate item. Replaces any duplicate item with the imported one. Replaces any duplicate item with the imported one. Imports any duplicate item with a modified name. Imports any duplicate item with a modified name. Ignores any duplicate set. Ignores any duplicate set. Replaces any duplicate set with the imported one. Replaces any duplicate set with the imported one. Imports any duplicate set with a modified name. Imports any duplicate set with a modified name. Merges duplicate sets. Merges duplicate sets. ConfigObjectListView Cannot move items. Cannot move items. Cannot move items: You have to select at least one item first. Cannot move items: You have to select at least one item first. Move selected item(s) to the top. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. Move selected item(s) to the bottom. ConfigObjectTableView Cannot move items. Cannot move items. Cannot move items: You have to select at least one item first. Cannot move items: You have to select at least one item first. Cannot move items as long as there is some filter or sorting applied. Cannot move items as long as there is some filter or sorting applied. Move selected item(s) to the top. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. Move selected item(s) to the bottom. Toggle Filter and Sorting Toggle Filter and Sorting Close Sort and Filter Close Sort and Filter ConfigObjectTypeSelectionDialog An instance of %1. An instance of %1. <p>%1<p><p style="margin-left:10px;">%2</p> <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Create extension object Select the class of object to create Select the class of object to create ContactListView Cannot delete contact Не удалось удалить контакт Cannot delete contact: You have to select a contact first. Не удалось удалить контакт: сначала выберите контакт. Delete contact? Удалить контакт? Delete contact %1? Удалить контакт «%1»? Delete contacts? Удалить контакты? Delete %1 contacts? Удалить %1 контактов? Adds a contact to the list. Adds a contact to the list. Alt++ Alt++ Add DTMF Contact Добавить DTMF-контакт Delete contact button Delete contact button Delete Contact Удалить контакт Alt+- Alt+- Add M17 Contact Добавить контакт M17 Adds an M17 contact to the list. Adds an M17 contact to the list. Adds an DTMF (analog) contact to the list. Adds an DTMF (analog) contact to the list. Add DMR Contact Добавить DMR-контакт Adds an DMR contact to the list. Adds an DMR contact to the list. Add Contact Добавить контакт ContactListWrapper DTMF DTMF On Вкл. Off Выкл. Private Call Индивидуальный вызов Group Call Групповой вызов All Call Общий вызов [None] [None] Type Тип Name Имя Number Number RX Tone RX Tone Extensions Расширения M17 M17 [Broadcast] [Broadcast] DMRAdmitSelect Always Всегда Channel Free Канал свободен Other Color-code Чужой цветовой код DMRChannelDialog Radio Id Radio Id Tx Admit Tx Admit Color-code Color-code Time-slot Time-slot Group list Group list Tx Contact Tx Contact APRS APRS Roaming zone Roaming zone DMRContactDialog Create DMR Contact Создать DMR-контакт Edit DMR Contact Изменить DMR-контакт Private Call Индивидуальный вызов Group Call Групповой вызов All Call Общий вызов Basic Основное Type Тип Name Имя Number Number Ring Ring Extensions Расширения DMRContactSelect [None] [None] DMRIDDialog Basic Основное Name Имя DMR ID DMR ID Extensions Расширения DMRIdSelect [Default] [Default] DTMFContactDialog Create DTMF Contact Создать DTMF-контакт Edit DMR Contact Изменить DMR-контакт Basic Основное Name Имя Number Number Ring Ring Extensions Расширения DeviceSelectionDialog Select a device Select a device <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> ErrorMessageView Error: Unknown. Error: Unknown. Error: %1 Error: %1 Traceback: Traceback: ExtensionView Cannot create extension. Cannot create extension. Cannot create extension, consider reporting a bug. Cannot create extension, consider reporting a bug. Cannot create list element. Cannot create list element. Cannot create list element, consider reporting a bug. Cannot create list element, consider reporting a bug. Create Создать Remove Удалить FMAPRSSelect [None] [None] FMAPRSSystem [None] [None] Police station Police station Digipeater Digipeater Phone Phone DX cluster DX cluster HF gateway HF gateway Plane small Plane small Mobile Satellite station Mobile Satellite station Wheel Chair Wheel Chair Snowmobile Snowmobile Red cross Red cross Boy scout Boy scout Home Home X X Red dot Red dot Circle 0 Circle 0 Circle 1 Circle 1 Circle 2 Circle 2 Circle 3 Circle 3 Circle 4 Circle 4 Circle 5 Circle 5 Circle 6 Circle 6 Circle 7 Circle 7 Circle 8 Circle 8 Circle 9 Circle 9 Fire Fire Campground Campground Motorcycle Motorcycle Rail engine Rail engine Car Car File server File server HC Future HC Future Aid station Aid station BBS BBS Canoe Canoe Eyeball Eyeball Tractor Tractor Grid Square Grid Square Hotel Hotel TCP/IP TCP/IP School School Logon Logon MacOS MacOS NTS station NTS station Balloon Balloon Police car Police car TBD TBD RV RV Shuttle Shuttle SSTV SSTV Bus Bus ATV ATV Weather service Weather service Helo Helo Yacht Yacht MS Windows MS Windows Jogger Jogger Triangle Triangle PBBS PBBS Plane large Plane large Weather station Weather station Dish antenna Dish antenna Ambulance Ambulance Bike Bike ICP ICP Fire station Fire station Horse Horse Fire truck Fire truck Glider Glider Hospital Hospital IOTA IOTA Jeep Jeep Truck small Truck small Laptop Laptop Mic-E Mic-E Node Node EOC EOC Rover Rover Grid Grid Antenna Antenna Power boat Power boat Truck stop Truck stop Truck large Truck large Van Van Water Water XAPRS XAPRS Yagi Yagi Shelter Shelter FMAdmitSelect Always Всегда Channel Free Канал свободен Other Tone Чужой тон FMChannelDialog Squelch Шумоподавитель Tx Admit Tx Admit Rx Tone Rx Tone Tx Tone Tx Tone Bandwidth Полоса APRS APRS FlagEditDialog Select Flags Select Flags GPSSystemDialog Create DMR APRS System Создать DMR-систему APRS Edit DMR APRS System Изменить DMR-систему APRS [Selected] [Selected] Edit GPS System Edit GPS System Basic Основное Name Имя Destination Destination Update period Update period Revert Channel Revert Channel Extensions Расширения GeneralSettingsView Boot Settings Параметры загрузки Intro Line 1 Intro Line 1 First greeting line (if supported by the radio). First greeting line (if supported by the radio). Intro line 1 Intro line 1 Intro Line 2 Intro Line 2 Second greeting line (if supported by the radio). Second greeting line (if supported by the radio). Intro line 2 Intro line 2 Audio Settings Аудио MIC Amp. Усиление МК Speech Synthesis Синтез речи Channel Default Values Значения канала по умолчанию Power Мощность Max Макс. High Высокая Mid Средняя Low Низкая Min Мин. Squelch Шумоподавитель Open Открыть Transmit Timeout Таймаут передачи Off Выкл. VOX Level VOX Level Extensions Расширения GroupListWrapper Contact Контакт GroupListsView Cannot delete RX group list Не удалось удалить групповой список Cannot delete RX group lists: You have to select a group list first. Не удалось удалить групповые списки: сначала выберите список. Delete RX group list? Удалить групповой список? Delete RX group list %1? Удалить групповой список «%1»? Delete %1 RX group lists? Удалить %1 групповых списков? Add RX Group Добавить групповой список Alt++ Alt++ Delete RX Group Удалить групповой список Alt+- Alt+- GroupListsWrapper RX Group Lists Групповые списки M17ChannelDialog Channel mode Channel mode Access number Access number Tx contact Tx contact Send position Send position M17ChannelModeSelect Voice Речь Data Данные Voice + Data Voice + Data M17ContactDialog Edit M17 Contact Изменить контакт M17 Basic Основное Name Имя The name of the contact. The name of the contact. Call Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Ring Ring Broadcast Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Расширения Create M17 Contact Создать контакт M17 M17ContactSelect [None] [None] MainWindow File Файл Device Устройство Help Справка Databases Базы данных Toolbar Панель инструментов New Создать Creates a new Codeplug. Создаёт новый кодплаг. Ctrl+N Ctrl+N Open ... Открыть… <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> Ctrl+O Ctrl+O Save ... Сохранить… <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> Ctrl+S Ctrl+S Quit Выход Quits the application. Завершает работу приложения. Ctrl+Q Ctrl+Q Detect Найти устройство Detect connected radios. Найти подключённые радиостанции. Verify Проверить <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Ctrl+R Ctrl+R Read Считать Reads a codeplug from connected radios. Считывает кодплаг из подключённой радиостанции. Write Записать Writes the codeplug to the connected radio. Записывает кодплаг в подключённую радиостанцию. About qdmr О программе qdmr Read the handbook. Открыть руководство. F1 F1 Settings Настройки Shows settings dialog Показать окно настроек Write Callsign DB Записать базу позывных Writes call-sign DB to radio. Записывает базу позывных в радиостанцию. Refresh Callsign DB Обновить базу позывных Refreshes the downloaded callsign DB Обновляет загруженную базу позывных Refresh Talkgroup DB Обновить БД токгрупп Refreshes the downloaded talkgroup DB Обновляет загруженную базу токгрупп Export to CHIRP ... Экспорт в CHIRP… Exports all FM channels to CHIRP CSV. Экспортирует все FM-каналы в CSV для CHIRP. Import ... Импорт… Imports and merges a codeplug into the current one. Импортирует и объединяет кодплаг с текущим. Refresh Orbital Elements Обновить орбитальные элементы Refreshes the orbital elements. Обновляет орбитальные элементы. Edit Satellites ... Редактировать спутники… Opens an editor to edit your satellite database. Открывает редактор базы спутников. Write satellites Записать спутники Writes the orbital elements and transponder information onto the connected device. Записывает орбитальные элементы и данные транспондера на подключённое устройство. Cannot update callsign DB: %1 Не удалось обновить базу позывных: %1 Callsign database updated & loaded. База позывных обновлена и загружена. Cannot update talkgroup DB: %1 Не удалось обновить БД токгрупп: %1 Talkgroup database updated & loaded. База токгрупп обновлена и загружена. Cannot update orbital elements: %1 Не удалось обновить орбитальные элементы: %1 Orbital elements updated & loaded. Орбитальные элементы обновлены и загружены. Radio IDs DMR ID Contacts Контакты Group Lists Групповые списки Channels Каналы Zones Зоны Scan Lists Списки сканирования GPS/APRS GPS/APRS Roaming Channels Каналы роуминга Roaming Zones Зоны роуминга Extensions Расширения Unsaved changes to codeplug. Несохранённые изменения кодплага. There are unsaved changes to the current codeplug. These changes are lost if you proceed. В текущем кодплаге есть несохранённые изменения. Они будут потеряны, если продолжить. MultiChannelSelectionDialog [Selected] [Selected] Select a channel: Выберите канал: MultiGroupCallSelectionDialog Show private calls Show private calls Select a group call: Select a group call: MultiRoamingChannelSelectionDialog Select roaming channels Select roaming channels PositioningSystemListView Cannot delete GPS system Не удалось удалить систему GPS Cannot delete GPS system: You have to select a GPS system first. Не удалось удалить систему GPS: сначала выберите систему. Delete positioning system? Удалить систему позиционирования? Delete positioning system %1? Удалить систему позиционирования «%1»? Delete %1 positioning systems? Удалить %1 систем позиционирования? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add GPS System Добавить систему GPS Alt+G Alt+G Add APRS System Добавить систему APRS Alt+A Alt+A Delete Position System Delete Position System Alt+- Alt+- PositioningSystemListWrapper DMR DMR APRS APRS [None] [None] [Selected] [Selected] Type Тип Name Имя Destination Destination Period Period Channel Канал Message Message Extensions Расширения PropertyDelegate None Нет Off Выкл. False Нет True Да [None] [None] PropertyWrapper new element new element Property Property Value Значение Description Описание true true false false None Нет Off Выкл. [None] [None] Instance of %1 Instance of %1 List of %1 instances List of %1 instances QObject [None] [None] RXGroupListDialog Create Group List Создать групповой список Edit Group List Изменить групповой список Cannot remove group call Cannot remove group call Cannot remove group call: You have to select at least one group call first. Cannot remove group call: You have to select at least one group call first. Basic Основное Name Имя Add Contact Добавить контакт Alt++ Alt++ Remove Contact Remove Contact Alt+- Alt+- Extensions Расширения RadioIDListView Cannot delete radio IDs Не удалось удалить DMR ID Cannot delete radio IDs: You have to select a radio ID first. Не удалось удалить DMR ID: сначала выберите запись. Delete radio ID? Удалить DMR ID? Delete radio ID %1? Удалить DMR ID «%1»? Delete scan lists? Удалить выбранные DMR ID? Delete %1 scan lists? Удалить %1 записей DMR ID? Default Radio ID Основной DMR ID Add Radio ID Добавить DMR ID Delete Radio ID Удалить DMR ID RadioIdListWrapper [None] [None] Type Тип Name Имя Number Number Extensions Расширения RadioSelectionDialog Cannot auto-detect radio Cannot auto-detect radio Select a specific radio Select a specific radio ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. qDMR was updated to version %1 qDMR was updated to version %1 RoamingChannelDialog Name Имя RX Frequency [MHz] RX Frequency [MHz] TX Frequency [MHz] TX Frequency [MHz] Time Slot Time Slot Color Code Color Code Selected Selected Edit roaming channel Edit roaming channel Create roaming channel Создать канал роуминга TS 1 TS 1 TS 2 TS 2 RoamingChannelListView <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Channel Добавить канал роуминга Delete Roaming Channel Удалить канал роуминга Cannot delete roaming channel Не удалось удалить канал роуминга Cannot delete roaming channel: You have to select a channel first. Не удалось удалить канал роуминга: сначала выберите канал. Delete roaming channel? Удалить канал роуминга? Delete roaming channel %1? Удалить канал роуминга «%1»? Delete %1 roaming channel? Удалить %1 канал(ов) роуминга? RoamingChannelListWrapper [Selected] [Selected] [None] [None] Name Имя RX Frequency Частота приёма TX Frequency Частота передачи TS TS Zones Зоны Extensions Расширения CC CC RoamingChannelRefListWrapper Roaming Channel Roaming Channel RoamingListWrapper %1 (containing %2 channels) %1 (containing %2 channels) Roaming zone Roaming zone RoamingZoneDialog Create Roaming Zone Создать зону роуминга Set Roaming Zone Set Roaming Zone Cannot remove channels. Cannot remove channels. Cannot remove channels. Select at least one channel first. Cannot remove channels. Select at least one channel first. Basic Основное Name: Name: Add Roaming Channel Добавить канал роуминга Add DMR Channel Добавить DMR-канал Alt++ Alt++ Remove Channel Remove Channel Alt+- Alt+- Extension Extension RoamingZoneListView Generate roaming zone Generate roaming zone Create a roaming zone by collecting all channels with these group calls. Create a roaming zone by collecting all channels with these group calls. Cannot delete roaming zone Не удалось удалить зону роуминга Cannot delete roaming zone: You have to select a zone first. Не удалось удалить зону роуминга: сначала выберите зону. Delete roaming zone? Удалить зону роуминга? Delete roaming zone %1? Удалить зону роуминга «%1»? Delete roaming zones? Удалить зоны роуминга? Delete %1 roaming zones? Delete %1 roaming zones? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Add Roaming Zone Добавить зону роуминга Alt++ Alt++ Generate Roaming Zone Generate Roaming Zone Delete Roaming Zone Удалить зону роуминга Alt+- Alt+- RoamingZoneSelect [None] [None] [Default] [Default] SatelliteDatabaseDialog Edit satellite database Edit satellite database Add Добавить Delete Удалить SatelliteSelectionDialog Select a satellite Select a satellite SatelliteTransponderDialog Edit Satellite Transponder Edit Satellite Transponder FM Voice Transponder FM Voice Transponder Uplink Frequency Uplink Frequency Uplink Tone Uplink Tone Downlink Frequency Downlink Frequency Downlink Tone Downlink Tone APRS Transponder APRS Transponder Beacon Beacon Beacon Frequency Beacon Frequency ScanListDialog Edit Scan List Изменить список сканирования Basic Основное Name Имя Primary Channel (50%) Primary Channel (50%) Secondary Channel (25%) Secondary Channel (25%) Transmit Channel Transmit Channel Add Channel Добавить канал Alt++ Alt++ Remove Channel Remove Channel Alt+- Alt+- Extensions Расширения Create Scan List Создать список сканирования [None] [None] [Selected] [Selected] [Last] [Last] ScanListsView Cannot delete scanlist Не удалось удалить список сканирования Cannot delete scanlist: You have to select a scanlist first. Не удалось удалить список сканирования: сначала выберите список. Delete scan list? Удалить список сканирования? Delete scan list %1? Удалить список сканирования «%1»? Delete scan lists? Удалить списки сканирования? Delete %1 scan lists? Удалить %1 списков сканирования? Add Scan List Добавить список сканирования Alt++ Alt++ Delete Scan List Удалить список сканирования Alt+- Alt+- ScanListsWrapper Scan-List Scan-List SearchPopup Ctrl+F Ctrl+F %1/%2 %1/%2 SelectiveCallBox None Нет CTCSS CTCSS DCS DCS Hz Hz Inverted Inverted SettingsDialog Warning! Внимание! Settings Настройки Location Местоположение System location Системное местоположение Locator Локатор Repeater Info Sources Источники данных о ретрансляторах enable включить Radio Programming Программирование радиостанции Update codeplug Обновлять кодплаг <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> Auto-enable GPS Включать GPS автоматически When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Auto-enable roaming Включать роуминг автоматически Data Sources Источники данных World Мир North America Северная Америка Programming Программирование Radio Interfaces Интерфейсы радиостанции disable auto-detect отключить автоопределение Ignore verification warnings Игнорировать предупреждения проверки <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> Ignore frequency limits Игнорировать ограничения по частоте Do not set this option unless you know what you are doing. Do not set this option unless you know what you are doing. Update Device Clock Обновлять часы устройства Call-Sign DB База позывных Limit number of DB entries Limit number of DB entries When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Number of DB entries Number of DB entries Specifies the number of DB entries (if enabled above). Specifies the number of DB entries (if enabled above). Select using my DMR ID Select using my DMR ID If enabled, the entries are selected using the users DMR ID. If enabled, the entries are selected using the users DMR ID. Select using prefixes Select using prefixes If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. SquelchEdit Open Открыть Uses the global squelch setting if enabled. Uses the global squelch setting if enabled. Default По умолчанию TimeslotSelect Slot 1 Слот 1 Slot 2 Слот 2 TransponderFrequencyEditor None Нет VerifyDialog Verify Codeplug Проверить кодплаг The codeplug cannot be uploaded, unless all critical issues (red) are resolved. Запись кодплага невозможна, пока не устранены все критические проблемы (красные). ZoneDialog Edit Zone Изменить зону Basic Основное <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Name Имя Channels A Каналы A add add remove remove Channels B Каналы B Extension Extension Create Zone Создать зону Cannot remove channel Cannot remove channel Select at least one channel first. Select at least one channel first. ZoneListView Cannot delete zone Не удалось удалить зону Cannot delete zone: You have to select a zone first. Не удалось удалить зону: сначала выберите зону. Delete zone? Удалить зону? Delete zone %1? Удалить зону «%1»? Delete zones? Delete zones? Delete %1 zones? Удалить %1 зон? Add Zone Добавить зону Alt++ Alt++ Delete Zone Удалить зону Alt+- Alt+- ZoneListWrapper Zone Зона aprssystemdialog Edit APRS System Изменить систему APRS Basic Основное Name Имя Channel Канал Source Source Destination Destination Path Path Icon Icon Update period [s] Update period [s] Message Message Extensions Расширения main Codeplug file to load. Файл кодплага для загрузки. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. Уровень журнала в stdout: `debug`, `info`, `warning`, `error` или `fatal`. ================================================ FILE: i18n/sv.ts ================================================ AMChannelDialog Squelch Squelch APRSSelect [None] [Ingen] APRSSystemDialog Create APRS system Skapa APRS-system Edit APRS system Redigera APRS-system [Selected] [Vald] AboutDialog About qdmr Om qdmr Supported Radios Radio som stöds Application Unsaved changes to codeplug. Ej sparade ändringar i codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Det finns osparade ändringar av den aktuella kodpluggen. Dessa ändringar går förlorade om du fortsätter. Open codeplug Öppna kodpluggen Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*) Codeplug-filer (*.yaml);;Codeplug-filer, gammalt format (*.conf *.csv *.txt);;Alla filer (*) Cannot open file Kan inte öppna filen Cannot read codeplug from file '%1': %2 Kan inte läsa codeplug från filen '%1': %2 Cannot read codeplug. Kan inte läsa kodpluggen. Save codeplug Spara kodpluggen Codeplug Files (*.yaml *.yml) Codeplug-filer (*.yaml *.yml) Please use new YAML format. Använd det nya YAML-formatet. Saving in the old table-based conf format was disabled with 0.9.0. Reading these files still works. Spara i det gamla tabellbaserade conf-formatet inaktiverades med 0.9.0. Att läsa dessa filer fungerar fortfarande. Cannot save codeplug to file '%1': %2 Det går inte att spara codeplug till filen '%1': %2 Cannot save codeplug Det går inte att spara codepluggen Cannot save codeplug to file '%1'. Det går inte att spara codeplug till filen '%1'. Export codeplug CHIRP CSV Files (*.csv) Cannot export codeplug Cannot export codeplug to file '%1': %2 Import codeplug CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml) Cannot import codeplug Cannot import codeplug from '%1': %2 Do not know, how to handle file '%1'. No matching devices found. Inga matchande enheter hittades. Cannot connect to radio Kan inte ansluta till radio Cannot connect to radio: %1 Kan inte ansluta till radio: %1 Radio found Radio hittad Found device '%1'. Hittade enheten '%1'. No radio found Ingen radio hittades No matching device was found. Ingen matchande enhet hittades. Verification success Verifieringen lyckades The codeplug was successfully verified with the radio '%1' Kodpluggen har verifierats med radion '%1'. Read ... Läs… Read error Läsfel Read complete Läs komplett Upload ... Skriv… Cannot write call-sign DB. Kan inte skriva anropssignal DB. The detected radio '%1' does not support a call-sign DB. Den upptäckta radion '%1' stöder inte en anropssignal-DB. The detected radio '%1' does support a call-sign DB. This feature, however, is not implemented yet. Den upptäckta radion '%1' stöder en anropssignal-DB. Denna funktion är dock inte implementerad ännu. QDMR selects the call-signs to be written based on the default DMR ID of the radio. No default ID set. QDMR väljer de anropssignaler som ska skrivas baserat på radions standard DMR-ID. Inget standard-ID inställt. Write call-sign DB ... Skriv anropssignal DB … Cannot write satellite config. The detected radio '%1' does not support satellite tracking. The detected radio '%1' does support satellite tracking. This feature, however, is not implemented yet. Write satellite config ... Write error Skrivfel Write complete Skriv komplett %1 (alias for %2 %3) %1 (alias för %2 %3) BandwidthSelect Narrow (12.5 kHz) Smal (12,5 kHz) Wide (25 kHz) Bred (25 kHz) ChannelDialog Edit Channel <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Obs!</span> qdmr tillhandahåller viss autokomplettering för kanaler. Det vill säga, börja skriva anropssignalen för en repeater. Efter att tre tecken har angetts skickas en begäran till repeaterbook.com för att hämta matchande repeatrar. Dessa förfrågningar kan ta lite tid. Resultaten lagras lokalt i en cache.</p><p>En rullgardinslista visas där du kan välja en repeater. När en repeater har valts fylls RX/TX-frekvenserna och CTCSS-tonerna i (om tillämpligt).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> Basic Name Namn Rx Frequency RX-frekvens Tx Frequency TX-frekvens Tx Offset Power Effekt Max max. High Hög Mid Mitten Low Lågt Min Minimalt Default Standard Tx Timeout Off VOX Level VOX-nivå Rx Only Bara RX Scan List Scan List Extensions Extensions No offset Positive offset Negative offset [None] [Ingen] ChannelListView Select a single channel first Välj en enskild kanal först To clone a channel, please select a single channel to clone. För att klona en kanal, välj en enskild kanal att klona. Cannot delete channel Kan inte ta bort kanal Cannot delete channel: You have to select a channel first. Kan inte ta bort kanal: Du måste först välja en kanal. Delete channel? Ta bort kanal? Delete channel %1? Ta bort kanalen '%1'? Delete %1 channels? Ta bort %1 kanaler? Alt+A Alt+A Add Channel ... Clone Channel Klona kanal Alt+C Alt+C Delete Channel Ta bort kanal Alt+- Alt+- Add FM Channel Adds a new FM channel Add DMR Channel Lägg till DMR-kanal Adds a new DMR channel. Add AM Channel Adds a new AM channel. ChannelListWrapper FM FM DMR DMR AM [Default] [Standard] Max max. High Hög Mid Mitten Low Lågt Min Minimalt Off Av On Aktiverat Always Alltid Free Fri Color Färg Tone Ton [None] [Ingen] Open Öppet Wide bred Narrow smal Type typ Name Namn Rx Frequency RX-frekvens Tx Frequency TX-frekvens Power Effekt Timeout timeouttid Rx Only Bara RX Admit Tillåta Scanlist skanningslistan Zones Zoner CC CC TS TS RX Group List Grupplistan TX Contact TX kontakt DMR ID DMR ID GPS/APRS GPS/APRS Roaming Roaming Squelch Squelch Rx Tone RX Ton Tx Tone TX Ton Bandwidth Bandbredd Extensions Extensions ChannelRefListWrapper Channel Kanal ChannelSelectionDialog Select a channel: Välj kanal: ConfigMergeDialog Merging codeplugs ... <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items Ignore Override Duplicate Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets Merge Ignores any duplicate item. Replaces any duplicate item with the imported one. Imports any duplicate item with a modified name. Ignores any duplicate set. Replaces any duplicate set with the imported one. Imports any duplicate set with a modified name. Merges duplicate sets. ConfigObjectListView Cannot move items. Kan inte flytta objekt. Cannot move items: You have to select at least one item first. Kan inte flytta objekt: Du måste välja minst ett objekt först. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. ConfigObjectTableView Cannot move items. Kan inte flytta objekt Cannot move items: You have to select at least one item first. Kan inte flytta objekt: Du måste välja minst ett objekt först. Cannot move items as long as there is some filter or sorting applied. Move selected item(s) to the top. Move selected item(s) ten positions up. Move selected item(s) one position up. Move selected item(s) one position down. Move selected item(s) ten positions down. Move selected item(s) to the bottom. Toggle Filter and Sorting Close Sort and Filter ConfigObjectTypeSelectionDialog An instance of %1. En förekomst av '%1'. <p>%1<p><p style="margin-left:10px;">%2</p> <p>%1<p><p style="margin-left:10px;">%2</p> Create extension object Skapa extension object Select the class of object to create Välj objektklassen som ska skapas ContactListView Cannot delete contact Kan inte ta bort kontakt Cannot delete contact: You have to select a contact first. Kan inte ta bort kontakt: Du måste först välja en kontakt. Delete contact? Ta bort kontakt? Delete contact %1? Ta bort kontakten '%1'? Delete contacts? Ta bort kontakter? Delete %1 contacts? Ta bort %1 kontakter? Adds a contact to the list. Lägger till en kontakt i listan. Alt++ Alt++ Add M17 Contact Adds an M17 contact to the list. Add DTMF Contact Lägg till DTMF-kontakt Adds an DTMF (analog) contact to the list. Add DMR Contact Adds an DMR contact to the list. Delete contact button Ta bort kontakt-knappen Add Contact Lägg till kontakt Delete Contact Ta bort kontakt Alt+- Alt+- ContactListWrapper DTMF DTMF On Påslagen Off Avstängd Private Call Privat samtal Group Call Gruppsamtal All Call All Call [None] [Ingen] M17 [Broadcast] Type Typ Name Namn Number Nummer RX Tone RX-ton Extensions Extensions DMRAdmitSelect Always Alltid Channel Free Kanal Tillgänglig Other Color-code DMRAdmitSelect Always Alltid Channel Free Kanal Tillgänglig Other Color-code DMRChannelDialog Radio Id Tx Admit TX Admit Color-code Time-slot Group list Tx Contact TX kontakt APRS APRS Roaming zone Roamingzon DMRContactDialog Create DMR Contact Skapa DMR-kontakt Edit DMR Contact Redigera DMR-kontakt Private Call Privat samtal Group Call Gruppsamtal All Call All Call Basic Grund inställningar Type Typ Name Namn Number Nummer Ring Ringa Extensions Extensions DMRContactSelect [None] [Ingen] DMRIDDialog Basic Grund inställningar Name Namn DMR ID DMR ID Extensions Extensions DMRIdSelect [Default] [Standard] DTMFContactDialog Create DTMF Contact Skapa DTMF-kontakt Edit DMR Contact Redigera DMR-kontakt Basic Grund inställningar Name Namn Number Nummer Ring Ringa Extensions Extensions DeviceSelectionDialog Select a device Välj en enhet <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> <html><head/><body><p>Antingen har mer än en enhet upptäckts eller så anses den hittade inte vara säkert för åtkomst. Hur som helst, välj vilken enhet du vill använda.</p></body></html> ErrorMessageView Error: Unknown. Fel: Okänt. Error: %1 Fel: %1 Traceback: Traceback: ExtensionView Cannot create extension. Det går inte att skapa tillägg. Cannot create extension, consider reporting a bug. Kan inte skapa tillägg, överväg att rapportera ett fel. Cannot create list element. Kan inte skapa listelement. Cannot create list element, consider reporting a bug. Kan inte skapa listelement, överväg att rapportera ett fel. Create Skapa Remove Ta bort FMAPRSSelect [None] [Ingen] FMAPRSSystem [None] [Ingen] Police station Polisstation Digipeater Digipeater Phone Telefon DX cluster DX-kluster HF gateway HF gateway Plane small Litet flygplan Mobile Satellite station Mobil satellitstation Wheel Chair Rullstol Snowmobile Snöskoter Red cross Röda Korset Boy scout Pojk Scout Home Hem X X Red dot Röd prick Circle 0 Cirkel 0 Circle 1 Cirkel 1 Circle 2 Cirkel 2 Circle 3 Cirkel 3 Circle 4 Cirkel 4 Circle 5 Cirkel 5 Circle 6 Cirkel 6 Circle 7 Cirkel 7 Circle 8 Cirkel 8 Circle 9 Cirkel 9 Fire Brand Campground Camping Motorcycle Motorcykel Rail engine Spårmotor Car Bil File server Fil server HC Future HC Future Aid station Biståndsstation BBS BBS Canoe Kanot Eyeball Ögonglob Tractor Traktor Grid Square Grid Square Hotel Hotell TCP/IP TCP/IP School Skola Logon Logga in MacOS MacOS NTS station NTS station Balloon Ballong Police car Polis bil TBD TBD RV RV Shuttle Shuttle SSTV SSTV Bus Buss ATV ATV Weather service Vädertjänst Helo Yacht Yacht MS Windows MS Windows Jogger Joggare Triangle Triangel PBBS Plane large Weather station Väderstation Dish antenna Diskantenn Ambulance Ambulans Bike Cykel ICP Fire station Brandstation Horse Häst Fire truck Brandbil Glider Segelflygplan Hospital Sjukhus IOTA IOTA Jeep Jeep Truck small Lilla lastbil Laptop Bärbar dator Mic-E Node Nod EOC Rover Rover Grid Rutnät Antenna Antenn Power boat Motorbåt Truck stop Lastbilsstopp Truck large Stor lastbil Van Skåpbil Water Vatten XAPRS Yagi Yagi Shelter Skydd FMAdmitSelect Always Alltid Channel Free Kanal Tillgänglig Other Tone FMChannelDialog Squelch Squelch Tx Admit TX Admit Rx Tone RX Ton Tx Tone TX Ton Bandwidth Bandbredd APRS APRS FlagEditDialog Select Flags GPSSystemDialog Create DMR APRS System Edit DMR APRS System [Selected] [Vald] Edit GPS System Redigera GPS-system Basic Grundläggande Name Namn Destination Destination Update period Revert Channel Återställ kanal Extensions Extensions GeneralSettingsView Boot Settings Startinställningar Intro Line 1 Intro rad 1 First greeting line (if supported by the radio). Första hälsningsraden (om det stöds av radion). Intro line 1 Intro rad 1 Intro Line 2 Intro rad 2 Second greeting line (if supported by the radio). Andra hälsningsraden (om den stöds av radion). Intro line 2 Intro rad 2 Audio Settings Ljudinställningar MIC Amp. MIC-förstärkare Speech Synthesis Talsyntes Channel Default Values Kanalens standardvärden Power Effekt Max max. High Hög Mid Mitten Low Lågt Min Minimalt Squelch Squelch Open Öppet Transmit Timeout Timeout för sändning Off Av VOX Level VOX-nivå Extensions Extensions GroupListWrapper Contact Kontakt GroupListsView Cannot delete RX group list Kan inte radera grupplistan Cannot delete RX group lists: You have to select a group list first. Kan inte ta bort grupplistor: Du måste först välja en grupplista. Delete RX group list? Ta bort grupplistan? Delete RX group list %1? Ta bort grupplistan '%1'? Delete %1 RX group lists? Ta bort %1 grupplistor? Add RX Group Lägg till grupplista Alt++ Alt++ Delete RX Group Ta bort grupplista Alt+- Alt+- GroupListsWrapper RX Group Lists Grupplistor M17ChannelDialog Channel mode Access number Tx contact Send position M17ChannelModeSelect Voice Data Voice + Data M17ContactDialog Edit M17 Contact Basic Name Namn The name of the contact. Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Ring Ringa Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Extensions Create M17 Contact M17ContactSelect [None] [Ingen] MainWindow File Fil Device Enhet Help Hjälp Databases Databaser Toolbar New Ny Creates a new Codeplug. Skapar en ny Codeplug. Ctrl+N Ctrl+N Open ... Öppna … <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> <html><head/><body><p>Importerar en codeplug från &quot;conf&quot; filer.</p></body></html> Ctrl+O Ctrl+O Save ... Spara … <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> <html><head/><body><p>Sparar codepluggen i en &quot;conf&quot; fil.</p></body></html> Ctrl+S Ctrl+S Quit Sluta Quits the application. Avsluter applikationen. Ctrl+Q Ctrl+Q Detect Upptäck Detect connected radios. Upptäck anslutna radioapparater. Verify Verifiera <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Verifierar den aktuella kodpluggen med anslutna radioapparater. Ctrl+R Ctrl+R Read Läsa Reads a codeplug from connected radios. Läser en kodplugg från anslutna radioapparater. Write Skriva Writes the codeplug to the connected radio. Skriver kodpluggen till den anslutna radion. About qdmr Om qdmr Read the handbook. Läs handboken. F1 F1 Settings Inställningar Shows settings dialog Visar inställningsdialog. Write Callsign DB Skriv anropssignal DB Writes call-sign DB to radio. Skriver anropssignal DB till radio. Refresh Callsign DB Uppdatera anropssignal DB Refreshes the downloaded callsign DB Uppdaterar den nedladdade anropssignal-DB Refresh Talkgroup DB Uppdatera samtalsgrupp DB Refreshes the downloaded talkgroup DB Uppdaterar den nedladdade samtalsgruppens DB Export to CHIRP ... Exports all FM channels to CHIRP CSV. Import ... Imports and merges a codeplug into the current one. Refresh Orbital Elements Refreshes the orbital elements. Edit Satellites ... Opens an editor to edit your satellite database. Write satellites Writes the orbital elements and transponder information onto the connected device. Cannot update callsign DB: %1 Callsign database updated & loaded. Cannot update talkgroup DB: %1 Talkgroup database updated & loaded. Cannot update orbital elements: %1 Orbital elements updated & loaded. Radio IDs Radio ID-nummrar Contacts Kontakter Group Lists Grupplistor Channels Kanaler Zones Zoner Scan Lists Scan Lists GPS/APRS GPS/APRS Roaming Channels Roaming-kanaler Roaming Zones Roamingzoner Extensions Extensions Unsaved changes to codeplug. Ej sparade ändringar i codeplug. There are unsaved changes to the current codeplug. These changes are lost if you proceed. Det finns osparade ändringar av den aktuella kodpluggen. Dessa ändringar går förlorade om du fortsätter. MultiChannelSelectionDialog [Selected] [Vald] Select a channel: Välj kanal: MultiGroupCallSelectionDialog Show private calls Visa privata samtal Select a group call: Välj ett gruppsamtal: MultiRoamingChannelSelectionDialog Select roaming channels Välj roamingkanaler PositioningSystemListView Cannot delete GPS system Kan inte radera GPS-systemet Cannot delete GPS system: You have to select a GPS system first. Kan inte ta bort GPS-system: Du måste först välja ett GPS-system. Delete positioning system? Ta bort positioneringssystemet? Delete positioning system %1? Ta bort positioneringssystemet '%1'? Delete %1 positioning systems? Ta bort %1 positioneringssystem? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Obs!</span> QDMR är en enhetsoberoende CPS. Det är dock inte alla radioapparater som stöder GPS eller APRS. Därför kan dessa inställningar ignoreras när kodpluggen programmeras till enheten. </p><p align="right"><a href="#hide"> <span style=" text-decoration: underline; color:#0000ff;">Göm</span></a></p></body></html> Add GPS System Lägg till GPS-system Alt+G Alt+G Add APRS System Lägg till APRS-system Alt+A Alt+A Delete Position System Ta bort positioneringssystem Alt+- Alt+- PositioningSystemListWrapper DMR DMR APRS APRS [None] [Ingen] [Selected] [Vald] Type Typ Name Namn Destination Destination Period Channel Kanal Message Meddelande Extensions Extensions PropertyDelegate None Off False Falsk True Sann [None] [Ingen] PropertyWrapper new element nytt element Property Egenskap Value Värde Description Beskrivning true sann false falsk None Off [None] [Ingen] Instance of %1 Förekomst av '%1' List of %1 instances Lista över %1 instanser QObject [None] [Ingen] RXGroupListDialog Create Group List Skapa grupplista Edit Group List Redigera grupplista Cannot remove group call Kan inte ta bort gruppsamtal Cannot remove group call: You have to select at least one group call first. Kan inte ta bort gruppsamtal: Du måste först välja minst ett gruppsamtal. Basic Grundläggande Name Namn Add Contact Lägg till kontakt Alt++ Alt++ Remove Contact Ta bort kontakt Alt+- Alt+- Extensions Extensions RadioIDListView Cannot delete radio IDs Kan inte radera radio-ID:n Cannot delete radio IDs: You have to select a radio ID first. Kan inte radera radio-ID:n: Du måste först välja ett radio-ID. Delete radio ID? Ta bort radio-ID? Delete radio ID %1? Ta bort radio-ID '%1'? Delete scan lists? Ta bort skanningslistor? Delete %1 scan lists? Ta bort %1 skanningslistor? Default Radio ID Standard radio-ID Add Radio ID Lägg till radio-ID Delete Radio ID Ta bort radio-ID RadioIdListWrapper [None] [Ingen] Type Typ Name Namn Number Nummer Extensions Extensions RadioSelectionDialog Cannot auto-detect radio Kan inte automatiskt upptäcka radio Select a specific radio Välj en specifik radio ReleaseNotes Cannot download release notes from https://github.com/hmatuschek/qdmr %1 Det går inte att ladda ned release notes från https://github.com/hmatuschek/qdmr %1 Cannot read release notes from https://github.com/hmatuschek/qdmr Release is not a JSON object! Kan inte läsa release notes från https://github.com/hmatuschek/qdmr Release är inte ett JSON-objekt! Cannot read release notes from https://github.com/hmatuschek/qdmr Release does not contain a release note. Kan inte läsa release notes från https://github.com/hmatuschek/qdmr Releasen innehåller ingen release note. qDMR was updated to version %1 qdmr uppdaterades till version %1 RoamingChannelDialog Name Namn RX Frequency [MHz] RX-frekvens [MHz] TX Frequency [MHz] Sändningsfrekvens [MHz] Time Slot Tidslucka Color Code Färgkod Selected Vald Edit roaming channel Redigera roamingkanal Create roaming channel Skapa roamingkanal TS 1 TS 1 TS 2 TS 2 RoamingChannelListView <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Obs!</span> QDMR är en enhetsoberoende CPS. Det är dock inte alla radioapparater som stöder roaming. Därför kan dessa inställningar ignoreras när kodkontakten programmeras till enheten. </p><p><span style=" font-weight:600;">Tips:</span> Du behöver inte lägga till en roamingkanal uttryckligen, skapa bara roamingzoner och lägg till kanaler där. Dessa kanaler läggs sedan till i den här listan.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Göm</span></a></p></body></html> Add Roaming Channel Lägg till roamingkanal Delete Roaming Channel Ta bort roamingkanal Cannot delete roaming channel Det går inte att ta bort roamingkanal Cannot delete roaming channel: You have to select a channel first. Kan inte ta bort roamingkanal: Du måste först välja en kanal. Delete roaming channel? Delete roaming channel %1? Delete %1 roaming channel? RoamingChannelListWrapper [Selected] [Vald] [None] [Ingen] Name Namn RX Frequency RX-frekvens TX Frequency Sändningsfrekvens TS TS Zones Zoner Extensions Extensions CC CC RoamingChannelRefListWrapper Roaming Channel Roaming kanal RoamingListWrapper %1 (containing %2 channels) %1 (innehåller %2 kanaler) Roaming zone Roamingzon RoamingZoneDialog Create Roaming Zone Skapa roamingzon Set Roaming Zone Redigera roamingzon Cannot remove channels. Kan inte ta bort kanaler. Cannot remove channels. Select at least one channel first. Kan inte ta bort kanaler. Välj minst en kanal först. Basic Grundläggande Name: Namn: Add Roaming Channel Lägg till roamingkanal Add DMR Channel Lägg till DMR-kanal Alt++ Alt++ Remove Channel Ta bort kanal Alt+- Alt+- Extension Förlängning RoamingZoneListView Generate roaming zone Skapa roamingzon Create a roaming zone by collecting all channels with these group calls. Skapa en roamingzon genom att samla alla kanaler med dessa gruppsamtal. Cannot delete roaming zone Det går inte att ta bort roamingzon Cannot delete roaming zone: You have to select a zone first. Det går inte att ta bort roamingzon: Du måste först välja en zon. Delete roaming zone? Ta bort roamingzon? Delete roaming zone %1? Ta bort roamingzon '%1'? Delete roaming zones? Ta bort roamingzoner? Delete %1 roaming zones? Ta bort %1 roamingzoner? <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p><span style=" font-weight:600;">Obs!</span> QDMR är en enhetsoberoende CPS. Det är dock inte alla radioapparater som stöder roaming. Därför kan dessa inställningar ignoreras när kodkontakten programmeras till enheten. </p> <p align="right"><a href="#hide"> <span style=" text-decoration: underline; color:#0000ff;">Göm</span></a></p></body></html> Add Roaming Zone Lägg till roamingzon Alt++ Alt++ Generate Roaming Zone Skapa roamingzon Delete Roaming Zone Ta bort roamingzon Alt+- Alt+- RoamingZoneSelect [None] [Ingen] [Default] [Standard] SatelliteDatabaseDialog Edit satellite database Add Delete SatelliteSelectionDialog Select a satellite SatelliteTransponderDialog Edit Satellite Transponder FM Voice Transponder Uplink Frequency Uplink Tone Downlink Frequency Downlink Tone APRS Transponder Beacon Beacon Frequency ScanListDialog Edit Scan List Redigera skanningslista Basic Grundläggande Name Namn Primary Channel (50%) Primär kanal (50 %) Secondary Channel (25%) Sekundär kanal (25 %) Transmit Channel Add Channel Lägg till kanal Alt++ Alt++ Remove Channel Ta bort kanal Alt+- Alt+- Extensions Extensions Create Scan List Skapa skanningslista [None] [Ingen] [Selected] [Vald] [Last] [Sista] ScanListsView Cannot delete scanlist Kan inte radera skanningslistan Cannot delete scanlist: You have to select a scanlist first. Kan inte ta bort skanningslista: Du måste först välja en skanningslista. Delete scan list? Ta bort skanningslistan? Delete scan list %1? Ta bort skanningslistan '%1'? Delete scan lists? Ta bort skanningslistor? Delete %1 scan lists? Ta bort %1 skanningslistor? Add Scan List Lägg till skanningslista Alt++ Alt++ Delete Scan List Ta bort skanningslista Alt+- Alt+- ScanListsWrapper Scan-List Scanlista SearchPopup Ctrl+F Ctrl+F %1/%2 %1/%2 SelectiveCallBox None CTCSS DCS Hz Inverted SettingsDialog Warning! Varning! Settings Inställningar Location System location Locator Repeater Info Sources enable Radio Programming Update codeplug Uppdatera codeplug <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> <html><head/><body><p>Uppdatera kodpluggen på radion. Om det inte väljs, åsidosätts kodpluggen på radion med möjligen ofullständiga standardvärden.</p> <p><br/></p><p>Om det väljs, laddar QDMR ner kodpluggen från radion och uppdaterar endast dessa inställningar specificerad. De återstående inställningarna i radion berörs inte (rekommenderas).</p></body></html> Auto-enable GPS Autoaktivera GPS When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. När ett GPS- eller APRS-system definieras och används för valfri kanal, aktiveras GPS-modulen automatiskt. When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. När en roamingzon definieras och används av en kanal, aktiveras automatisk roaming. Auto-enable roaming Aktivera roaming automatiskt Data Sources World North America Programming Radio Interfaces disable auto-detect Ignore verification warnings Ignorera verifieringsvarningar <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> <html><head/><body><p>Eftersom kommunikationsgränssnittet till radion hålls öppet efter verifiering kan time-outs inträffa och kodplugguppladdningen kan misslyckas när verifieringsdialogrutan dyker upp. För att förhindra detta kan verifieringsvarningar ignoreras, vilket eliminerar tidsskillnaden mellan verifiering och uppladdning. Verifieringsfel förhindrar fortfarande uppladdningen.</p></body></html> Ignore frequency limits Ignorera frekvensgränser Do not set this option unless you know what you are doing. Ställ inte in det här alternativet om du inte vet vad du gör. Update Device Clock Call-Sign DB Anropssignal DB Limit number of DB entries Begränsa antalet DB-poster When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). När det är aktiverat kommer antalet DB-poster att vara begränsat. Annars genereras det maximala antalet poster (enhetsberoende). Number of DB entries Antal DB-poster Specifies the number of DB entries (if enabled above). Anger antalet DB-poster (om aktiverat ovan). Select using my DMR ID Välj med mitt DMR-ID If enabled, the entries are selected using the users DMR ID. Om det är aktiverat väljs posterna med användarens DMR-ID. Select using prefixes Välj med prefix If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. Om aktiverat används dessa kommaseparerade DMR ID-prefix för att välja anropssignal DB-posterna. SquelchEdit Open Öppet Uses the global squelch setting if enabled. Default Standard TimeslotSelect Slot 1 Slot 2 TransponderFrequencyEditor None VerifyDialog Verify Codeplug Verifiera Codeplug The codeplug cannot be uploaded, unless all critical issues (red) are resolved. ZoneDialog Edit Zone Redigera zon Basic Grundläggande <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> <html><head/><body><p align="justify"><span style=" font-weight:600;">Obs! </span> Zoner är samlingar av kanaler som vanligtvis är giltiga för en specifik region. Dvs en samling kanaler för repeatrar inom en viss region. </p> <p align="justify">QDMR hanterar zoner genom att tillåta två oberoende kanallistor för varje VFO i radion (om den har två). Många radioapparater tillåter dock en att tilldela zoner till varje VFO individuellt. I dessa fall kommer QDMR att dela upp zonen i två (A &amp; B) och programmera in dem individuellt i radion.</p><p align="right"> <a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Göm </span></a></p></body></html> Name Namn Channels A Kanaler A add Lägg till remove ta bort Channels B Kanaler B Extension Förlängning Create Zone Skapa zon Cannot remove channel Kan inte ta bort kanalen Select at least one channel first. Välj minst en kanal först. ZoneListView Cannot delete zone Kan inte ta bort zon Cannot delete zone: You have to select a zone first. Kan inte ta bort zon: Du måste först välja en zon. Delete zone? Ta bort zon? Delete zone %1? Ta bort zon '%1'? Delete zones? Ta bort zoner? Delete %1 zones? Ta bort %1 zoner? Add Zone Lägg till zon Alt++ Alt++ Delete Zone Ta bort zon Alt+- Alt+- ZoneListWrapper Zone Zon aprssystemdialog Edit APRS System Redigera APRS-system Basic Grundläggande Name Namn Channel Kanal Source Källa Destination Destination Path Väg Icon Ikon Update period [s] Uppdateringsperiod [s] Message Meddelande Extensions Extensions main Codeplug file to load. Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`. ================================================ FILE: lib/CMakeLists.txt ================================================ IF (APPLE) SET(hid_SOURCES hid_macos.cc) SET(hid_HEADERS hid_macos.hh) ELSE (APPLE) SET(hid_SOURCES hid_libusb.cc) SET(hid_HEADERS hid_libusb.hh) ENDIF(APPLE) configure_file(config.h.in ${CMAKE_CURRENT_BINARY_DIR}/config.h) qt_add_library(libdmrconf SHARED ${hid_SOURCES} ${CMAKE_CURRENT_BINARY_DIR}/config.h utils.cc crc32.cc addressmap.cc radiointerface.cc errorstack.cc frequency.cc interval.cc ranges.cc dummyfilereader.cc chirpformat.cc signaling.cc radio.cc dfu_libusb.cc usbserial.cc radioinfo.cc usbdevice.cc radiolimits.cc csvreader.cc dfufile.cc userdatabase.cc logger.cc level.cc melody.cc melody_stream.cc visitor.cc configlabelingvisitor.cc configcopyvisitor.cc intermediaterepresentation.cc configmergevisitor.cc configobject.cc configreference.cc config.cc radiosettings.cc contact.cc rxgrouplist.cc channel.cc zone.cc scanlist.cc gpssystem.cc codeplug.cc roamingzone.cc roamingchannel.cc callsigndb.cc talkgroupdatabase.cc radioid.cc transferflags.cc encryptionextension.cc commercial_extension.cc smsextension.cc gnsssettings.cc dmrsettings.cc channel_extension.cc bootsettings.cc audiosettings.cc tonesettings.cc packetstream.cc satellitedatabase.cc orbitalelementsdatabase.cc transponderdatabase.cc satelliteconfig.cc tyt_radio.cc tyt_interface.cc tyt_codeplug.cc tyt_callsigndb.cc tyt_extensions.cc md2017.cc md2017_codeplug.cc md2017_callsigndb.cc md2017_filereader.cc md2017_limits.cc md390.cc md390_codeplug.cc md390_filereader.cc md390_limits.cc uv390.cc uv390_codeplug.cc uv390_callsigndb.cc uv390_filereader.cc uv390_limits.cc dm1701.cc dm1701_codeplug.cc dm1701_callsigndb.cc dm1701_filereader.cc dm1701_limits.cc radioddity_radio.cc radioddity_codeplug.cc radioddity_extensions.cc radioddity_interface.cc rd5r.cc rd5r_codeplug.cc rd5r_filereader.cc rd5r_limits.cc gd77.cc gd77_codeplug.cc gd77_callsigndb.cc gd77_filereader.cc gd77_limits.cc opengd77_interface.cc opengd77base.cc opengd77base_codeplug.cc opengd77base_callsigndb.cc opengd77base_satelliteconfig.cc opengd77_extension.cc opengd77_limits.cc opengd77.cc opengd77_codeplug.cc opengd77_callsigndb.cc opengd77_satelliteconfig.cc openuv380.cc openuv380_codeplug.cc openuv380_callsigndb.cc openuv380_satelliteconfig.cc openrtx.cc openrtx_link.cc openrtx_interface.cc openrtx_codeplug.cc c7000device.cc gd73.cc gd73_interface.cc gd73_codeplug.cc gd73_filereader.cc gd73_limits.cc anytone_interface.cc anytone_radio.cc anytone_codeplug.cc anytone_extension.cc anytone_limits.cc anytone_satelliteconfig.cc anytone_settingsextension.cc d868uv.cc d868uv_codeplug.cc d868uv_callsigndb.cc d868uv_limits.cc d878uv.cc d878uv_codeplug.cc d878uv_limits.cc d578uv.cc d578uv_codeplug.cc d578uv_limits.cc d878uv2.cc d878uv2_codeplug.cc d878uv2_limits.cc d878uv2_callsigndb.cc d168uv.cc d168uv_codeplug.cc d168uv_limits.cc d168uv_satelliteconfig.cc dmr6x2uv.cc dmr6x2uv_codeplug.cc dmr6x2uv_limits.cc dmr6x2uv2.cc dmr6x2uv2_codeplug.cc dmr6x2uv2_limits.cc auctus_a6_interface.cc dr1801uv.cc dr1801uv_interface.cc dr1801uv_codeplug.cc dr1801uv_filereader.cc dr1801uv_limits.cc dm32uv.cc dm32uv_interface.cc dm32uv_limits.cc dm32uv_codeplug.cc dm32uv_callsigndb.cc) target_sources(libdmrconf PUBLIC FILE_SET HEADERS BASE_DIRS ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR} FILES ${CMAKE_CURRENT_BINARY_DIR}/config.h libdmrconf.hh utils.hh ${hid_HEADERS} crc32.hh addressmap.hh radiointerface.hh errorstack.hh frequency.hh interval.hh ranges.hh dummyfilereader.hh chirpformat.hh signaling.hh radio.hh level.hh dfu_libusb.hh usbserial.hh radioinfo.hh usbdevice.hh radiolimits.hh csvreader.hh dfufile.hh userdatabase.hh logger.hh melody.hh melody_stream.hh visitor.hh configlabelingvisitor.hh configcopyvisitor.hh intermediaterepresentation.hh configmergevisitor.hh configobject.hh configreference.hh config.hh radiosettings.hh contact.hh rxgrouplist.hh channel.hh zone.hh scanlist.hh gpssystem.hh codeplug.hh roamingzone.hh roamingchannel.hh callsigndb.hh talkgroupdatabase.hh radioid.hh transferflags.hh encryptionextension.hh commercial_extension.hh smsextension.hh gnsssettings.hh dmrsettings.hh channel_extension.hh bootsettings.hh audiosettings.hh tonesettings.hh packetstream.hh satellitedatabase.hh orbitalelementsdatabase.hh transponderdatabase.hh satelliteconfig.hh tyt_radio.hh tyt_interface.hh tyt_codeplug.hh tyt_callsigndb.hh tyt_extensions.hh md2017.hh md2017_codeplug.hh md2017_callsigndb.hh md2017_filereader.hh md2017_limits.hh md390.hh md390_codeplug.hh md390_filereader.hh md390_limits.hh uv390.hh uv390_codeplug.hh uv390_callsigndb.hh uv390_filereader.hh uv390_limits.hh dm1701.hh dm1701_codeplug.hh dm1701_callsigndb.hh dm1701_filereader.hh dm1701_limits.hh radioddity_radio.hh radioddity_codeplug.hh radioddity_extensions.hh radioddity_interface.hh rd5r.hh rd5r_codeplug.hh rd5r_filereader.hh rd5r_limits.hh gd77.hh gd77_codeplug.hh gd77_callsigndb.hh gd77_filereader.hh gd77_limits.hh opengd77_interface.hh opengd77base.hh opengd77base_codeplug.hh opengd77base_callsigndb.hh opengd77base_satelliteconfig.hh opengd77_extension.hh opengd77_limits.hh opengd77.hh opengd77_codeplug.hh opengd77_callsigndb.hh opengd77_satelliteconfig.hh openuv380.hh openuv380_codeplug.hh openuv380_callsigndb.hh openuv380_satelliteconfig.hh openrtx.hh openrtx_link.hh openrtx_interface.hh openrtx_codeplug.hh c7000device.hh gd73.hh gd73_interface.hh gd73_codeplug.hh gd73_filereader.hh gd73_limits.hh anytone_interface.hh anytone_radio.hh anytone_codeplug.hh anytone_extension.hh anytone_limits.hh anytone_satelliteconfig.hh anytone_settingsextension.hh d868uv.hh d868uv_codeplug.hh d868uv_callsigndb.hh d868uv_limits.hh d878uv.hh d878uv_codeplug.hh d878uv_limits.hh d578uv.hh d578uv_codeplug.hh d578uv_limits.hh d878uv2.hh d878uv2_codeplug.hh d878uv2_limits.hh d878uv2_callsigndb.hh d168uv.hh d168uv_codeplug.hh d168uv_limits.hh d168uv_satelliteconfig.hh dmr6x2uv.hh dmr6x2uv_codeplug.hh dmr6x2uv_limits.hh dmr6x2uv2.hh dmr6x2uv2_codeplug.hh dmr6x2uv2_limits.hh auctus_a6_interface.hh dr1801uv.hh dr1801uv_interface.hh dr1801uv_codeplug.hh dr1801uv_filereader.hh dr1801uv_limits.hh dm32uv.hh dm32uv_interface.hh dm32uv_limits.hh dm32uv_codeplug.hh dm32uv_callsigndb.hh) set_target_properties(libdmrconf PROPERTIES MACOSX_RPATH TRUE OUTPUT_NAME dmrconf VERSION "${PROJECT_VERSION}" SOVERSION "${PROJECT_VERSION_MAJOR}") target_link_libraries(libdmrconf PRIVATE Qt6::Core Qt6::SerialPort Qt6::Positioning Qt6::Network Qt6::Concurrent Qt6::Multimedia ${LIBUSB_1_LIBRARIES} ${YAMLCPP_LIBRARIES} ${ADDITIONAL_LIBS}) install(TARGETS libdmrconf DESTINATION ${CMAKE_INSTALL_FULL_LIBDIR} FILE_SET HEADERS DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/libdmrconf/) if (UNIX AND NOT APPLE AND ${INSTALL_UDEV_RULES}) install(FILES "${CMAKE_SOURCE_DIR}/dist/99-qdmr.rules" DESTINATION ${INSTALL_UDEV_PATH}) endif(UNIX AND NOT APPLE AND ${INSTALL_UDEV_RULES}) ================================================ FILE: lib/addressmap.cc ================================================ #include "addressmap.hh" #include AddressMap::AddressMap() : _items() { // pass... } AddressMap::AddressMap(const AddressMap &other) : _items(other._items) { // pass... } AddressMap & AddressMap::operator =(const AddressMap &other) { _items = other._items; return *this; } void AddressMap::clear() { _items.clear(); } bool AddressMap::add(uint32_t addr, uint32_t len, int idx) { if (0 > idx) idx = _items.size(); AddrMapItem item(addr, len, idx); std::vector::iterator at = std::lower_bound(_items.begin(), _items.end(), item); if (_items.end() == at) _items.push_back(item); else _items.insert(at, item); return true; } bool AddressMap::rem(uint32_t idx) { std::vector::iterator at = _items.begin(); for (; at!=_items.end(); at++) { if (at->index == idx) break; } if (_items.end() == at) return false; _items.erase(at); return true; } bool AddressMap::contains(uint32_t addr) const { return 0 <= find(addr); } int AddressMap::find(uint32_t addr) const { std::vector::const_iterator at = std::lower_bound(_items.begin(), _items.end(), addr); if (_items.end() == at) return _items.back().contains(addr) ? _items.back().index : -1; if (at->contains(addr)) return at->index; if (_items.begin() == at) return -1; --at; return at->contains(addr) ? at->index : -1; } ================================================ FILE: lib/addressmap.hh ================================================ #ifndef ADDRESSMAP_HH #define ADDRESSMAP_HH #include #include /** This class represents a memory map. * That is, it maintains a vector of memory regions (address and length) that can be searched * efficiently. This should speedup the generation of codeplugs consisting of many small memory * sections. * * @ingroup util */ class AddressMap { public: /** Empty constructor. */ AddressMap(); /** Copy constructor. */ AddressMap(const AddressMap &other); /** Copy assignment. */ AddressMap &operator=(const AddressMap &other); /** Clears the address map. */ void clear(); /** Adds an item to the address map. */ bool add(uint32_t addr, uint32_t len, int idx=-1); /** Removes an item from the address map associated with the given index. */ bool rem(uint32_t idx); /** Returns @c true if the given address is contained in any of the memory regions. */ bool contains(uint32_t addr) const; /** Finds the index of the memory region containing the given address. If no such region is found, * -1 is returned. */ int find(uint32_t addr) const; protected: /** Memory map item. * That is, a collection of address, length and associated index. */ struct AddrMapItem { uint32_t address; ///< The start address of the item. uint32_t length; ///< The size/length of the memory item. uint32_t index; ///< The associated (element) index. /** Constructor. */ inline AddrMapItem(uint32_t addr, uint32_t len, uint32_t idx) : address(addr), length(len), index(idx) { // pass... } /** Comparison operator. */ inline bool operator<(const AddrMapItem &other) const { return address < other.address; } /** Comparison operator. */ inline bool operator<(uint32_t addr) const { return address < addr; } /** Returns @c true if the given address is contained within this memory region. */ inline bool contains(uint32_t addr) const { return (address <= addr) && ((address+length) > addr); } }; protected: /** Holds the vector of memory items, the order of these items is maintained. */ std::vector _items; }; #endif // ADDRESSMAP_HH ================================================ FILE: lib/anytone_codeplug.cc ================================================ #include "anytone_codeplug.hh" #include "utils.hh" #include "logger.hh" #include "anytone_extension.hh" #include "config.hh" #include "intermediaterepresentation.hh" #include #include #define CUSTOM_CTCSS_TONE 0x33 QVector _anytone_bin_dtmf_tab = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','*','#' }; /* ******************************************************************************************** * * Implementation of AnytoneCodeplug::CTCSS * ******************************************************************************************** */ SelectiveCall AnytoneCodeplug::CTCSS::_codeTable[] = { SelectiveCall(62.5), SelectiveCall(67.0), SelectiveCall(69.3), SelectiveCall(71.9), SelectiveCall(74.4), SelectiveCall(77.0), SelectiveCall(79.7), SelectiveCall(82.5), SelectiveCall(85.4), SelectiveCall(88.5), SelectiveCall(91.5), SelectiveCall(94.8), SelectiveCall(97.4), SelectiveCall(100.0), SelectiveCall(103.5), SelectiveCall(107.2), SelectiveCall(110.9), SelectiveCall(114.8), SelectiveCall(118.8), SelectiveCall(123.0), SelectiveCall(127.3), SelectiveCall(131.8), SelectiveCall(136.5), SelectiveCall(141.3), SelectiveCall(146.2), SelectiveCall(151.4), SelectiveCall(156.7), SelectiveCall(159.8), SelectiveCall(162.2), SelectiveCall(165.5), SelectiveCall(167.9), SelectiveCall(171.3), SelectiveCall(173.8), SelectiveCall(177.3), SelectiveCall(179.9), SelectiveCall(183.5), SelectiveCall(186.2), SelectiveCall(189.9), SelectiveCall(192.8), SelectiveCall(196.6), SelectiveCall(199.5), SelectiveCall(203.5), SelectiveCall(206.5), SelectiveCall(210.7), SelectiveCall(218.1), SelectiveCall(225.7), SelectiveCall(229.1), SelectiveCall(233.6), SelectiveCall(241.8), SelectiveCall(250.3), SelectiveCall(254.1), SelectiveCall() }; uint8_t AnytoneCodeplug::CTCSS::encode(const SelectiveCall &code) { for (uint8_t i=0; i<52; i++) { if (code == _codeTable[i]) return i; } return 0; } SelectiveCall AnytoneCodeplug::CTCSS::decode(uint8_t num) { if (num >= 52) return SelectiveCall(); return _codeTable[num]; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::InvertedBytemapElement * ********************************************************************************************* */ AnytoneCodeplug::InvertedBytemapElement::InvertedBytemapElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } void AnytoneCodeplug::InvertedBytemapElement::clear() { memset(_data, 0xff, _size); } bool AnytoneCodeplug::InvertedBytemapElement::isEncoded(unsigned int idx) const { if (idx >= _size) return false; return 0 == _data[idx]; } void AnytoneCodeplug::InvertedBytemapElement::setEncoded(unsigned int idx, bool enable) { if (idx >= _size) return; _data[idx] = enable ? 0x00 : 0xff; } void AnytoneCodeplug::InvertedBytemapElement::enableFirst(unsigned int n) { memset(_data, 0x00, n); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ChannelElement * ********************************************************************************************* */ AnytoneCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : Element(ptr, ChannelElement::size()) { // pass... } AnytoneCodeplug::ChannelElement::~ChannelElement() { // pass... } void AnytoneCodeplug::ChannelElement::clear() { setRXFrequency(0); setTXOffset(0); setMode(Mode::Analog); setPower(Channel::Power::Low); setBandwidth(FMChannel::Bandwidth::Narrow); setRXTone(SelectiveCall()); setTXTone(SelectiveCall()); setBit(0x0008, 5, false); // Unused set to 0 } unsigned AnytoneCodeplug::ChannelElement::rxFrequency() const { return ((unsigned)getBCD8_be(Offset::rxFrequency()))*10; } void AnytoneCodeplug::ChannelElement::setRXFrequency(unsigned hz) { setBCD8_be(Offset::rxFrequency(), hz/10); } unsigned AnytoneCodeplug::ChannelElement::txOffset() const { return ((unsigned)getBCD8_be(Offset::txFrequencyOffset()))*10; } void AnytoneCodeplug::ChannelElement::setTXOffset(unsigned hz) { setBCD8_be(Offset::txFrequencyOffset(), hz/10); } unsigned AnytoneCodeplug::ChannelElement::txFrequency() const { unsigned rx = rxFrequency(), off = txOffset(); if (RepeaterMode::Simplex == repeaterMode()) return rx; else if (RepeaterMode::Positive == repeaterMode()) return rx+off; return rx-off; } void AnytoneCodeplug::ChannelElement::setTXFrequency(unsigned hz) { unsigned rx = rxFrequency(); if (rx == hz) { setTXOffset(0); setRepeaterMode(RepeaterMode::Simplex); } else if (rx < hz) { setTXOffset(hz-rx); setRepeaterMode(RepeaterMode::Positive); } else { setTXOffset(rx-hz); setRepeaterMode(RepeaterMode::Negative); } } AnytoneCodeplug::ChannelElement::Mode AnytoneCodeplug::ChannelElement::mode() const { return (Mode) getUInt2(Offset::channelMode()); } void AnytoneCodeplug::ChannelElement::setMode(Mode mode) { setUInt2(Offset::channelMode(), (unsigned)mode); } Channel::Power AnytoneCodeplug::ChannelElement::power() const { switch ((Power)getUInt2(Offset::power())) { case POWER_LOW: return Channel::Power::Low; case POWER_MIDDLE: return Channel::Power::Mid; case POWER_HIGH: return Channel::Power::High; case POWER_TURBO: return Channel::Power::Max; } return Channel::Power::Low; } void AnytoneCodeplug::ChannelElement::setPower(Channel::Power power) { switch (power) { case Channel::Power::Min: case Channel::Power::Low: setUInt2(Offset::power(), (unsigned)POWER_LOW); break; case Channel::Power::Mid: setUInt2(Offset::power(), (unsigned)POWER_MIDDLE); break; case Channel::Power::High: setUInt2(Offset::power(), (unsigned)POWER_HIGH); break; case Channel::Power::Max: setUInt2(Offset::power(), (unsigned)POWER_TURBO); break; } } FMChannel::Bandwidth AnytoneCodeplug::ChannelElement::bandwidth() const { if (getBit(Offset::bandwidth())) return FMChannel::Bandwidth::Wide; return FMChannel::Bandwidth::Narrow; } void AnytoneCodeplug::ChannelElement::setBandwidth(FMChannel::Bandwidth bw) { switch (bw) { case FMChannel::Bandwidth::Narrow: setBit(Offset::bandwidth(), false); break; case FMChannel::Bandwidth::Wide: setBit(Offset::bandwidth(), true); break; } } AnytoneCodeplug::ChannelElement::RepeaterMode AnytoneCodeplug::ChannelElement::repeaterMode() const { return (RepeaterMode)getUInt2(Offset::repeaterMode()); } void AnytoneCodeplug::ChannelElement::setRepeaterMode(RepeaterMode mode) { setUInt2(Offset::repeaterMode(), (unsigned)mode); } AnytoneCodeplug::ChannelElement::SignalingMode AnytoneCodeplug::ChannelElement::rxSignalingMode() const { return (SignalingMode)getUInt2(Offset::rxSignalingMode()); } void AnytoneCodeplug::ChannelElement::setRXSignalingMode(SignalingMode mode) { setUInt2(Offset::rxSignalingMode(), (unsigned)mode); } SelectiveCall AnytoneCodeplug::ChannelElement::rxTone() const { if (SignalingMode::None == rxSignalingMode()) return SelectiveCall(); else if (SignalingMode::CTCSS == rxSignalingMode()) return rxCTCSS(); else if (SignalingMode::DCS == rxSignalingMode()) return rxDCS(); return SelectiveCall(); } void AnytoneCodeplug::ChannelElement::setRXTone(const SelectiveCall &code) { if (code.isInvalid()) { setRXSignalingMode(SignalingMode::None); } else if (code.isCTCSS()) { setRXSignalingMode(SignalingMode::CTCSS); setRXCTCSS(code); } else if (code.isDCS()) { setRXSignalingMode(SignalingMode::DCS); setRXDCS(code); } } AnytoneCodeplug::ChannelElement::SignalingMode AnytoneCodeplug::ChannelElement::txSignalingMode() const { return (SignalingMode)getUInt2(Offset::txSignalingMode()); } void AnytoneCodeplug::ChannelElement::setTXSignalingMode(SignalingMode mode) { setUInt2(Offset::txSignalingMode(), (unsigned)mode); } SelectiveCall AnytoneCodeplug::ChannelElement::txTone() const { if (SignalingMode::None == txSignalingMode()) return SelectiveCall(); else if (SignalingMode::CTCSS == txSignalingMode()) return txCTCSS(); else if (SignalingMode::DCS == txSignalingMode()) return txDCS(); return SelectiveCall(); } void AnytoneCodeplug::ChannelElement::setTXTone(const SelectiveCall &code) { if (code.isInvalid()) { setTXSignalingMode(SignalingMode::None); } else if (code.isCTCSS()) { setTXSignalingMode(SignalingMode::CTCSS); setTXCTCSS(code); } else if (code.isDCS()) { setTXSignalingMode(SignalingMode::DCS); setTXDCS(code); } } bool AnytoneCodeplug::ChannelElement::rxTxSwapped() const { return getBit(Offset::swapRXTX()); } void AnytoneCodeplug::ChannelElement::enableSwapRxTx(bool enable) { setBit(Offset::swapRXTX(), enable); } bool AnytoneCodeplug::ChannelElement::rxOnly() const { return getBit(Offset::rxOnly()); } void AnytoneCodeplug::ChannelElement::enableRXOnly(bool enable) { setBit(Offset::rxOnly(), enable); } bool AnytoneCodeplug::ChannelElement::callConfirm() const { return getBit(Offset::callConfirm()); } void AnytoneCodeplug::ChannelElement::enableCallConfirm(bool enable) { setBit(Offset::callConfirm(), enable); } bool AnytoneCodeplug::ChannelElement::talkaround() const { return getBit(Offset::talkaround()); } void AnytoneCodeplug::ChannelElement::enableTalkaround(bool enable) { setBit(Offset::talkaround(), enable); } bool AnytoneCodeplug::ChannelElement::txCTCSSIsCustom() const { return CUSTOM_CTCSS_TONE == getUInt8(Offset::txCTCSS()); } SelectiveCall AnytoneCodeplug::ChannelElement::txCTCSS() const { return CTCSS::decode(getUInt8(Offset::txCTCSS())); } void AnytoneCodeplug::ChannelElement::setTXCTCSS(const SelectiveCall &tone) { setUInt8(Offset::txCTCSS(), CTCSS::encode(tone)); } void AnytoneCodeplug::ChannelElement::enableTXCustomCTCSS() { setUInt8(Offset::txCTCSS(), CUSTOM_CTCSS_TONE); } bool AnytoneCodeplug::ChannelElement::rxCTCSSIsCustom() const { return CUSTOM_CTCSS_TONE == getUInt8(Offset::rxCTCSS()); } SelectiveCall AnytoneCodeplug::ChannelElement::rxCTCSS() const { return CTCSS::decode(getUInt8(Offset::rxCTCSS())); } void AnytoneCodeplug::ChannelElement::setRXCTCSS(const SelectiveCall &tone) { setUInt8(Offset::rxCTCSS(), CTCSS::encode(tone)); } void AnytoneCodeplug::ChannelElement::enableRXCustomCTCSS() { setUInt8(Offset::rxCTCSS(), CUSTOM_CTCSS_TONE); } SelectiveCall AnytoneCodeplug::ChannelElement::txDCS() const { uint16_t code = getUInt16_le(Offset::txDCS()); if (512 > code) return SelectiveCall::fromBinaryDCS(code, false); return SelectiveCall::fromBinaryDCS(code-512, true); } void AnytoneCodeplug::ChannelElement::setTXDCS(const SelectiveCall &code) { if (code.isDCS()) setUInt16_le(Offset::txDCS(), code.binCode() + (code.isInverted() ? 512 : 0)); else setUInt16_le(Offset::txDCS(), 0); } SelectiveCall AnytoneCodeplug::ChannelElement::rxDCS() const { uint16_t code = getUInt16_le(Offset::rxDCS()); if (512 > code) return SelectiveCall::fromBinaryDCS(code, false); return SelectiveCall::fromBinaryDCS(code-512, true); } void AnytoneCodeplug::ChannelElement::setRXDCS(const SelectiveCall &code) { if (code.isDCS()) setUInt16_le(Offset::rxDCS(), code.binCode() + (code.isInverted() ? 512 : 0)); else setUInt16_le(Offset::rxDCS(), 0); } double AnytoneCodeplug::ChannelElement::customCTCSSFrequency() const { return ((double) getUInt16_le(Offset::customCTCSS()))/10; } void AnytoneCodeplug::ChannelElement::setCustomCTCSSFrequency(double hz) { setUInt16_le(Offset::customCTCSS(), hz*10); } unsigned AnytoneCodeplug::ChannelElement::twoToneDecodeIndex() const { return getUInt16_le(Offset::twoFunctionIndex()); } void AnytoneCodeplug::ChannelElement::setTwoToneDecodeIndex(unsigned idx) { setUInt16_le(Offset::twoFunctionIndex(), idx); } unsigned AnytoneCodeplug::ChannelElement::contactIndex() const { return getUInt32_le(Offset::contactIndex()); } void AnytoneCodeplug::ChannelElement::setContactIndex(unsigned idx) { return setUInt32_le(Offset::contactIndex(), idx); } unsigned AnytoneCodeplug::ChannelElement::radioIDIndex() const { return getUInt8(Offset::radioIdIndex()); } void AnytoneCodeplug::ChannelElement::setRadioIDIndex(unsigned idx) { return setUInt8(Offset::radioIdIndex(), idx); } AnytoneFMChannelExtension::SquelchMode AnytoneCodeplug::ChannelElement::squelchMode() const { return (AnytoneFMChannelExtension::SquelchMode)getUInt3(Offset::squelchMode()); } void AnytoneCodeplug::ChannelElement::setSquelchMode(AnytoneFMChannelExtension::SquelchMode mode) { setUInt3(Offset::squelchMode(), (unsigned)mode); } AnytoneCodeplug::ChannelElement::Admit AnytoneCodeplug::ChannelElement::admit() const { return (Admit)getUInt2(Offset::admitCriterion()); } void AnytoneCodeplug::ChannelElement::setAdmit(Admit admit) { setUInt2(Offset::admitCriterion(), (unsigned)admit); } AnytoneCodeplug::ChannelElement::OptSignaling AnytoneCodeplug::ChannelElement::optionalSignaling() const { return (OptSignaling)getUInt2(Offset::optionalSingnaling()); } void AnytoneCodeplug::ChannelElement::setOptionalSignaling(OptSignaling sig) { setUInt2(Offset::optionalSingnaling(), (unsigned)sig); } bool AnytoneCodeplug::ChannelElement::hasScanListIndex() const { return 0xff != scanListIndex(); } unsigned AnytoneCodeplug::ChannelElement::scanListIndex() const { return getUInt8(Offset::scanListIndex()); } void AnytoneCodeplug::ChannelElement::setScanListIndex(unsigned idx) { setUInt8(Offset::scanListIndex(), idx); } void AnytoneCodeplug::ChannelElement::clearScanListIndex() { setScanListIndex(0xff); } bool AnytoneCodeplug::ChannelElement::hasGroupListIndex() const { return 0xff != groupListIndex(); } unsigned AnytoneCodeplug::ChannelElement::groupListIndex() const { return getUInt8(Offset::groupListIndex()); } void AnytoneCodeplug::ChannelElement::setGroupListIndex(unsigned idx) { setUInt8(Offset::groupListIndex(), idx); } void AnytoneCodeplug::ChannelElement::clearGroupListIndex() { setGroupListIndex(0xff); } unsigned AnytoneCodeplug::ChannelElement::twoToneIDIndex() const { return getUInt8(Offset::twoToneIDIndex()); } void AnytoneCodeplug::ChannelElement::setTwoToneIDIndex(unsigned idx) { setUInt8(Offset::twoToneIDIndex(), idx); } unsigned AnytoneCodeplug::ChannelElement::fiveToneIDIndex() const { return getUInt8(Offset::fiveToneIDIndex()); } void AnytoneCodeplug::ChannelElement::setFiveToneIDIndex(unsigned idx) { setUInt8(Offset::fiveToneIDIndex(), idx); } unsigned AnytoneCodeplug::ChannelElement::dtmfIDIndex() const { return getUInt8(Offset::dtmfIDIndex()); } void AnytoneCodeplug::ChannelElement::setDTMFIDIndex(unsigned idx) { setUInt8(Offset::dtmfIDIndex(), idx); } unsigned AnytoneCodeplug::ChannelElement::colorCode() const { return getUInt8(Offset::colorCode()); } void AnytoneCodeplug::ChannelElement::setColorCode(unsigned code) { setUInt8(Offset::colorCode(), code); } DMRChannel::TimeSlot AnytoneCodeplug::ChannelElement::timeSlot() const { if (false == getBit(Offset::timeSlot())) return DMRChannel::TimeSlot::TS1; return DMRChannel::TimeSlot::TS2; } void AnytoneCodeplug::ChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { if (DMRChannel::TimeSlot::TS1 == ts) setBit(Offset::timeSlot(), false); else setBit(Offset::timeSlot(), true); } bool AnytoneCodeplug::ChannelElement::smsConfirm() const { return getBit(Offset::smsConfirm()); } void AnytoneCodeplug::ChannelElement::enableSMSConfirm(bool enable) { setBit(Offset::smsConfirm(), enable); } bool AnytoneCodeplug::ChannelElement::simplexTDMA() const { return getBit(Offset::simplexTDMA()); } void AnytoneCodeplug::ChannelElement::enableSimplexTDMA(bool enable) { setBit(Offset::simplexTDMA(), enable); } bool AnytoneCodeplug::ChannelElement::adaptiveTDMA() const { return getBit(Offset::adaptiveTDMA()); } void AnytoneCodeplug::ChannelElement::enableAdaptiveTDMA(bool enable) { setBit(Offset::adaptiveTDMA(), enable); } bool AnytoneCodeplug::ChannelElement::rxAPRS() const { return getBit(Offset::rxAPRS()); } void AnytoneCodeplug::ChannelElement::enableRXAPRS(bool enable) { setBit(Offset::rxAPRS(), enable); } bool AnytoneCodeplug::ChannelElement::loneWorker() const { return getBit(Offset::loneWorker()); } void AnytoneCodeplug::ChannelElement::enableLoneWorker(bool enable) { setBit(Offset::loneWorker(), enable); } QString AnytoneCodeplug::ChannelElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void AnytoneCodeplug::ChannelElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } Channel * AnytoneCodeplug::ChannelElement::toChannelObj(Context &ctx) const { Q_UNUSED(ctx) Channel *ch; if ((Mode::Analog == mode()) || (Mode::MixedAnalog == mode())) { if (Mode::MixedAnalog == mode()) logWarn() << "Mixed mode channels are not supported (for now). Treat ch '" << name() <<"' as analog channel."; FMChannel *ach = new FMChannel(); switch(this->admit()) { case Admit::Always: ach->setAdmit(FMChannel::Admit::Always); break; case Admit::Busy: ach->setAdmit(FMChannel::Admit::Free); break; case Admit::Tone: ach->setAdmit(FMChannel::Admit::Tone); break; default: ach->setAdmit(FMChannel::Admit::Always); break; } ach->setRXTone(rxTone()); ach->setTXTone(txTone()); ach->setBandwidth(bandwidth()); // no per channel squelch settings ach->setSquelchDefault(); ach->extended()->enableTalkaround(talkaround()); // Create extension AnytoneFMChannelExtension *ext = new AnytoneFMChannelExtension(); ach->setAnytoneChannelExtension(ext); ext->enableRXCustomCTCSS(rxCTCSSIsCustom()); ext->enableTXCustomCTCSS(txCTCSSIsCustom()); ext->setCustomCTCSS(customCTCSSFrequency()); ext->setSquelchMode(squelchMode()); // done ch = ach; } else if ((Mode::Digital == mode()) || (Mode::MixedDigital == mode())) { if (Mode::MixedDigital == mode()) logWarn() << "Mixed mode channels are not supported (for now). Treat ch '" << name() <<"' as digital channel."; DMRChannel *dch = new DMRChannel(); switch (this->admit()) { case Admit::Always: dch->setAdmit(DMRChannel::Admit::Always); break; case Admit::Free: dch->setAdmit(DMRChannel::Admit::Free); break; case Admit::DifferentColorCode:dch->setAdmit(DMRChannel::Admit::ColorCode); break; case Admit::SameColorCode: logWarn() << "Cannot decode admit cirit. 'same CC', use 'different CC' instead."; dch->setAdmit(DMRChannel::Admit::ColorCode); break; } dch->setColorCode(colorCode()); dch->setTimeSlot(timeSlot()); dch->extended()->enableTalkaround(talkaround()); dch->extended()->enablePrivateCallConfirm(callConfirm()); dch->extended()->enableSMSConfirm(smsConfirm()); dch->extended()->enableDCDM(simplexTDMA()); dch->extended()->enableLoneWorker(loneWorker()); // Create extension AnytoneDMRChannelExtension *ext = new AnytoneDMRChannelExtension(); dch->setAnytoneChannelExtension(ext); ext->enableAdaptiveTDMA(adaptiveTDMA()); // Done ch = dch; } else { logError() << "Cannot create channel '" << name() << "': Channel type " << (unsigned)mode() << "not supported."; return nullptr; } ch->setName(name()); ch->setRXFrequency(Frequency::fromHz(rxFrequency())); ch->setTXFrequency(Frequency::fromHz(txFrequency())); ch->setPower(power()); ch->setRXOnly(rxOnly()); // No per channel vox & tot setting ch->setVOXDefault(); ch->setDefaultTimeout(); return ch; } bool AnytoneCodeplug::ChannelElement::linkChannelObj(Channel *c, Context &ctx) const { if (Mode::Digital == mode()) { // If channel is digital DMRChannel *dc = c->as(); if (nullptr == dc) return false; // Check if default contact is set, in fact a valid contact index is mandatory. if (! ctx.has(contactIndex())) { logError() << "Cannot resolve contact index " << contactIndex() << " for channel '" << c->name() << "'."; return false; } dc->setContact(ctx.get(contactIndex())); // Set if RX group list is set if (hasGroupListIndex() && ctx.has(groupListIndex())) dc->setGroupList(ctx.get(groupListIndex())); // Link radio ID DMRRadioID *rid = ctx.get(radioIDIndex()); if (rid == ctx.config()->settings()->defaultIdRef()->as()) dc->setRadioId(DefaultRadioID::get()); else dc->setRadioId(rid); } else if (Mode::Analog == mode()) { // If channel is analog FMChannel *ac = c->as(); if (nullptr == ac) return false; } // For both, analog and digital channels: // If channel has scan list if (hasScanListIndex() && ctx.has(scanListIndex())) c->setScanList(ctx.get(scanListIndex())); return true; } bool AnytoneCodeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx) { // Clear channel element clear(); // set channel name setName(c->name()); // set rx and tx frequencies setRXFrequency(c->rxFrequency().inHz()); setTXFrequency(c->txFrequency().inHz()); // set power if (c->defaultPower()) setPower(ctx.config()->settings()->power()); else setPower(c->power()); // set tx-enable enableRXOnly(c->rxOnly()); if (nullptr == c->scanList()) clearScanListIndex(); else setScanListIndex(ctx.index(c->scanList())); // Dispatch by channel type if (c->is()) { const FMChannel *ac = c->as(); setMode(Mode::Analog); // set admit criterion switch (ac->admit()) { case FMChannel::Admit::Always: setAdmit(Admit::Always); break; case FMChannel::Admit::Free: setAdmit(Admit::Busy); break; case FMChannel::Admit::Tone: setAdmit(Admit::Tone); break; } // squelch mode setRXTone(ac->rxTone()); setTXTone(ac->txTone()); if (ac->rxTone().isValid()) setSquelchMode(AnytoneFMChannelExtension::SquelchMode::SubTone); else setSquelchMode(AnytoneFMChannelExtension::SquelchMode::Carrier); // set bandwidth setBandwidth(ac->bandwidth()); // Apply common settings enableTalkaround(ac->extended()->talkaround()); // Apply FM settings // Handle extension if (AnytoneFMChannelExtension *ext = ac->anytoneChannelExtension()) { setCustomCTCSSFrequency(ext->customCTCSS()); if (ext->rxCustomCTCSS()) enableRXCustomCTCSS(); if (ext->txCustomCTCSS()) enableTXCustomCTCSS(); setSquelchMode(ext->squelchMode()); } } else if (c->is()) { const DMRChannel *dc = c->as(); // pack digital channel config. setMode(Mode::Digital); // set admit criterion switch(dc->admit()) { case DMRChannel::Admit::Always: setAdmit(Admit::Always); break; case DMRChannel::Admit::Free: setAdmit(Admit::Free); break; case DMRChannel::Admit::ColorCode: setAdmit(Admit::DifferentColorCode); break; } // set color code setColorCode(dc->colorCode()); // set time-slot setTimeSlot(dc->timeSlot()); // link transmit contact if (nullptr == dc->contact()) setContactIndex(0); else setContactIndex(ctx.index(dc->contact())); // link RX group list if (nullptr == dc->groupList()) clearGroupListIndex(); else setGroupListIndex(ctx.index(dc->groupList())); // Set radio ID if ((nullptr == dc->radioId()) || (DefaultRadioID::get() == dc->radioId())) { if (nullptr == ctx.config()->settings()->defaultIdRef()->as()) { logWarn() << "No default radio ID set: using index 0."; setRadioIDIndex(0); } else { setRadioIDIndex(ctx.index(ctx.config()->settings()->defaultIdRef()->as())); } } else { setRadioIDIndex(ctx.index(dc->radioId())); } // Apply common settings enableTalkaround(dc->extended()->talkaround()); // Apply DMR settings enableCallConfirm(dc->extended()->privateCallConfirm()); enableSMSConfirm(dc->extended()->smsConfirm()); enableSimplexTDMA(dc->extended()->dcdm()); enableLoneWorker(dc->extended()->loneWorker()); // Handle extension if (AnytoneDMRChannelExtension *ext = dc->anytoneChannelExtension()) { enableAdaptiveTDMA(ext->adaptiveTDMA()); } } return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ChannelBitmapElement * ********************************************************************************************* */ AnytoneCodeplug::ChannelBitmapElement::ChannelBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } AnytoneCodeplug::ChannelBitmapElement::ChannelBitmapElement(uint8_t *ptr) : BitmapElement(ptr, ChannelBitmapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ContactElement * ********************************************************************************************* */ AnytoneCodeplug::ContactElement::ContactElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::ContactElement::ContactElement(uint8_t *ptr) : Element(ptr, ContactElement::size()) { // pass... } AnytoneCodeplug::ContactElement::~ContactElement() { // pass... } void AnytoneCodeplug::ContactElement::clear() { memset(_data, 0, _size); } bool AnytoneCodeplug::ContactElement::isValid() const { return !name().isEmpty(); } DMRContact::Type AnytoneCodeplug::ContactElement::type() const { switch (getUInt8(Offset::type())) { case 0: return DMRContact::PrivateCall; case 1: return DMRContact::GroupCall; case 2: return DMRContact::AllCall; } return DMRContact::PrivateCall; } void AnytoneCodeplug::ContactElement::setType(DMRContact::Type type) { switch (type) { case DMRContact::PrivateCall: setUInt8(Offset::type(), 0); break; case DMRContact::GroupCall: setUInt8(Offset::type(), 1); break; case DMRContact::AllCall: setUInt8(Offset::type(), 2); break; } } QString AnytoneCodeplug::ContactElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void AnytoneCodeplug::ContactElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } unsigned AnytoneCodeplug::ContactElement::number() const { return getBCD8_be(Offset::number()); } void AnytoneCodeplug::ContactElement::setNumber(unsigned number) { setBCD8_be(Offset::number(), number); } AnytoneContactExtension::AlertType AnytoneCodeplug::ContactElement::alertType() const { switch (getUInt8(Offset::alertType())) { case (uint8_t)AnytoneContactExtension::AlertType::None: return AnytoneContactExtension::AlertType::None; case (uint8_t)AnytoneContactExtension::AlertType::Ring: return AnytoneContactExtension::AlertType::Ring; case (uint8_t)AnytoneContactExtension::AlertType::Online: return AnytoneContactExtension::AlertType::Online; default: break; } logWarn() << "Unknown value " << getUInt8(Offset::alertType()) << " for alert type of AnyTone contact element. Maybe the codeplug implementation is" " outdated. Consider reporting it at https://github.com/hmatuschek/qdmr."; return AnytoneContactExtension::AlertType::None; } void AnytoneCodeplug::ContactElement::setAlertType(AnytoneContactExtension::AlertType type) { setUInt8(Offset::alertType(), (unsigned)type); } DMRContact * AnytoneCodeplug::ContactElement::toContactObj(Context &ctx) const { Q_UNUSED(ctx); // Common settings DMRContact *cont = new DMRContact(); cont->setType(type()); cont->setName(name()); cont->setNumber(number()); cont->setRing(AnytoneContactExtension::AlertType::None != alertType()); // Create AnyTone specific extension AnytoneContactExtension *ext = new AnytoneContactExtension(); cont->setAnytoneExtension(ext); ext->setAlertType(alertType()); return cont; } bool AnytoneCodeplug::ContactElement::fromContactObj(const DMRContact *contact, Context &ctx) { Q_UNUSED(ctx) clear(); setType(contact->type()); setName(contact->name()); setNumber(contact->number()); setAlertType(contact->ring() ? AnytoneContactExtension::AlertType::Ring : AnytoneContactExtension::AlertType::None); if (AnytoneContactExtension *ext = contact->anytoneExtension()) { setAlertType(ext->alertType()); } return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ContactBitmapElement * ********************************************************************************************* */ AnytoneCodeplug::ContactBitmapElement::ContactBitmapElement(uint8_t *ptr, size_t size) : InvertedBitmapElement(ptr, size) { // pass... } AnytoneCodeplug::ContactBitmapElement::ContactBitmapElement(uint8_t *ptr) : InvertedBitmapElement(ptr, ContactBitmapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::DTMFContactElement * ********************************************************************************************* */ AnytoneCodeplug::DTMFContactElement::DTMFContactElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::DTMFContactElement::DTMFContactElement(uint8_t *ptr) : Element(ptr, DTMFContactElement::size()) { // pass... } AnytoneCodeplug::DTMFContactElement::~DTMFContactElement() { // pass... } void AnytoneCodeplug::DTMFContactElement::clear() { memset(_data, 0, _size); } QString AnytoneCodeplug::DTMFContactElement::number() const { QString number; int n = getUInt8(Offset::numDigits()); for (int i=0; i>4)&0xf]); else number.append(_anytone_bin_dtmf_tab[(byte>>0)&0xf]); } return number; } void AnytoneCodeplug::DTMFContactElement::setNumber(const QString &number) { if (! validDTMFNumber(number)) return; memset(_data+Offset::digits(), 0, Limit::digitCount()/2); unsigned int n = std::min((unsigned int)number.length(), Limit::digitCount()); setUInt8(Offset::numDigits(), n); for (unsigned int i=0; inumber()); setName(contact->name()); return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::DTMFContactBytemapElement * ********************************************************************************************* */ AnytoneCodeplug::DTMFContactBytemapElement::DTMFContactBytemapElement(uint8_t *ptr, size_t size) : InvertedBytemapElement(ptr, size) { // pass... } AnytoneCodeplug::DTMFContactBytemapElement::DTMFContactBytemapElement(uint8_t *ptr) : InvertedBytemapElement(ptr, DTMFContactBytemapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::GroupListElement * ********************************************************************************************* */ AnytoneCodeplug::GroupListElement::GroupListElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::GroupListElement::GroupListElement(uint8_t *ptr) : Element(ptr, GroupListElement::size()) { // pass... } void AnytoneCodeplug::GroupListElement::clear() { memset(_data, 0x00, _size); // set member indices to 0xffffffff memset(_data, 0xff, Offset::betweenMembers()*Limit::members()); } bool AnytoneCodeplug::GroupListElement::isValid() const { return !name().isEmpty(); } QString AnytoneCodeplug::GroupListElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void AnytoneCodeplug::GroupListElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } bool AnytoneCodeplug::GroupListElement::hasMemberIndex(unsigned n) const { return 0xffffffff != memberIndex(n); } unsigned AnytoneCodeplug::GroupListElement::memberIndex(unsigned n) const { return getUInt32_le(Offset::members() + Offset::betweenMembers()*n); } void AnytoneCodeplug::GroupListElement::setMemberIndex(unsigned n, unsigned idx) { setUInt32_le(Offset::members() + Offset::betweenMembers()*n, idx); } void AnytoneCodeplug::GroupListElement::clearMemberIndex(unsigned n) { setMemberIndex(n, 0xffffffff); } RXGroupList * AnytoneCodeplug::GroupListElement::toGroupListObj() const { return new RXGroupList(name()); } bool AnytoneCodeplug::GroupListElement::linkGroupList(RXGroupList *lst, Context &ctx) const { for (uint8_t i=0; i continue if (! hasMemberIndex(i)) continue; // Missing contact ignore. if (! ctx.has(memberIndex(i))) { logWarn() << "Cannot link contact " << memberIndex(i) << " to group list '" << this->name() << "': Invalid contact index. Ignored."; continue; } lst->addContact(ctx.get(memberIndex(i))); } return true; } bool AnytoneCodeplug::GroupListElement::fromGroupListObj(const RXGroupList *lst, Context &ctx) { clear(); // set name of group list setName(lst->name()); int j=0; // set members for (uint8_t i=0; icount() > j) && (DMRContact::GroupCall != lst->contact(j)->type())) { logWarn() << "Contact '" << lst->contact(j)->name() << "' in group list '" << lst->name() << "' is not a group call. Skip entry."; j++; } if (lst->count() > j) { setMemberIndex(i, ctx.index(lst->contact(j))); j++; } else { clearMemberIndex(i); } } return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::GroupListBitmapElement * ********************************************************************************************* */ AnytoneCodeplug::GroupListBitmapElement::GroupListBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } AnytoneCodeplug::GroupListBitmapElement::GroupListBitmapElement(uint8_t *ptr) : BitmapElement(ptr, GroupListBitmapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ScanListElement * ********************************************************************************************* */ AnytoneCodeplug::ScanListElement::ScanListElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::ScanListElement::ScanListElement(uint8_t *ptr) : Element(ptr, ScanListElement::size()) { // pass... } void AnytoneCodeplug::ScanListElement::clear() { memset(_data, 0, _size); setPriorityChannels(PriChannel::Off); clearPrimaryChannel(); clearSecondaryChannel(); setLookBackTimeA(Interval::fromSeconds(150)); setLookBackTimeB(Interval::fromSeconds(250)); setDropOutDelay(Interval::fromSeconds(290)); setDwellTime(Interval::fromSeconds(290)); setRevertChannel(RevertChannel::Selected); // clear members memset(_data+Offset::members(), 0xff, Offset::betweenMembers()*Limit::members()); } AnytoneCodeplug::ScanListElement::PriChannel AnytoneCodeplug::ScanListElement::priorityChannels() const { return (PriChannel) getUInt8(Offset::priorityChannel()); } void AnytoneCodeplug::ScanListElement::setPriorityChannels(PriChannel sel) { setUInt8(Offset::priorityChannel(), (unsigned)sel); } bool AnytoneCodeplug::ScanListElement::hasPrimary() const { return 0xffff != getUInt16_le(Offset::primaryChannel()); } bool AnytoneCodeplug::ScanListElement::primaryIsSelected() const { return 0 == getUInt16_le(Offset::primaryChannel()); } unsigned AnytoneCodeplug::ScanListElement::primary() const { return ((unsigned)getUInt16_le(Offset::primaryChannel()))-1; } void AnytoneCodeplug::ScanListElement::setPrimary(unsigned idx) { setUInt16_le(Offset::primaryChannel(), idx+1); } void AnytoneCodeplug::ScanListElement::setPrimarySelected() { setUInt16_le(Offset::primaryChannel(), 0); } void AnytoneCodeplug::ScanListElement::clearPrimaryChannel() { setUInt16_le(Offset::primaryChannel(), 0xffff); } bool AnytoneCodeplug::ScanListElement::hasSecondary() const { return 0xffff != getUInt16_le(Offset::secondaryChannel()); } bool AnytoneCodeplug::ScanListElement::secondaryIsSelected() const { return 0 == getUInt16_le(Offset::secondaryChannel()); } unsigned AnytoneCodeplug::ScanListElement::secondary() const { return ((unsigned)getUInt16_le(Offset::secondaryChannel()))-1; } void AnytoneCodeplug::ScanListElement::setSecondary(unsigned idx) { setUInt16_le(Offset::secondaryChannel(), idx+1); } void AnytoneCodeplug::ScanListElement::setSecondarySelected() { setUInt16_le(Offset::secondaryChannel(), 0); } void AnytoneCodeplug::ScanListElement::clearSecondaryChannel() { setUInt16_le(Offset::secondaryChannel(), 0xffff); } Interval AnytoneCodeplug::ScanListElement::lookBackTimeA() const { return Interval::fromSeconds((unsigned)getUInt16_le(Offset::lookBackTimeA()) * 10); } void AnytoneCodeplug::ScanListElement::setLookBackTimeA(const Interval& sec) { setUInt16_le(Offset::lookBackTimeA(), sec.seconds()/10); } Interval AnytoneCodeplug::ScanListElement::lookBackTimeB() const { return Interval::fromSeconds((unsigned)getUInt16_le(Offset::lookBackTimeB())*10); } void AnytoneCodeplug::ScanListElement::setLookBackTimeB(const Interval &sec) { setUInt16_le(Offset::lookBackTimeB(), sec.seconds()/10); } Interval AnytoneCodeplug::ScanListElement::dropOutDelay() const { return Interval::fromSeconds((unsigned)getUInt16_le(Offset::dropOutDelay())*10); } void AnytoneCodeplug::ScanListElement::setDropOutDelay(const Interval &sec) { setUInt16_le(Offset::dropOutDelay(), sec.seconds()/10); } Interval AnytoneCodeplug::ScanListElement::dwellTime() const { return Interval::fromSeconds((unsigned)getUInt16_le(Offset::dwellTime()) * 10); } void AnytoneCodeplug::ScanListElement::setDwellTime(const Interval &sec) { setUInt16_le(Offset::dwellTime(), sec.seconds()/10); } AnytoneCodeplug::ScanListElement::RevertChannel AnytoneCodeplug::ScanListElement::revertChannel() const { return (RevertChannel)getUInt8(Offset::revertChannel()); } void AnytoneCodeplug::ScanListElement::setRevertChannel(RevertChannel type) { setUInt8(Offset::revertChannel(), (unsigned)type); } QString AnytoneCodeplug::ScanListElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void AnytoneCodeplug::ScanListElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } bool AnytoneCodeplug::ScanListElement::hasMemberIndex(unsigned n) const { return 0xffff != memberIndex(n); } unsigned AnytoneCodeplug::ScanListElement::memberIndex(unsigned n) const { return getUInt16_le(Offset::members() + Offset::betweenMembers()*n); } void AnytoneCodeplug::ScanListElement::setMemberIndex(unsigned n, unsigned idx) { setUInt16_le(Offset::members() + Offset::betweenMembers()*n, idx); } void AnytoneCodeplug::ScanListElement::clearMemberIndex(unsigned n) { setMemberIndex(n, 0xffff); } ScanList * AnytoneCodeplug::ScanListElement::toScanListObj() const { return new ScanList(name()); } bool AnytoneCodeplug::ScanListElement::linkScanListObj(ScanList *lst, Context &ctx) const { if (((PriChannel::Both == priorityChannels()) || (PriChannel::Primary==priorityChannels())) && hasPrimary()) { if (primaryIsSelected()) lst->setPrimaryChannel(SelectedChannel::get()); else if (ctx.has(primary())) lst->setPrimaryChannel(ctx.get(primary())); } if (((PriChannel::Both == priorityChannels()) || (PriChannel::Secondary==priorityChannels())) && hasSecondary()) { if (secondaryIsSelected()) lst->setSecondaryChannel(SelectedChannel::get()); else if (ctx.has(secondary())) lst->setSecondaryChannel(ctx.get(secondary())); } for (uint16_t i=0; i(memberIndex(i))) { logError() << "Cannot link scanlist '" << name() << "', channel index " << memberIndex(i) << " unknown."; continue; } lst->addChannel(ctx.get(memberIndex(i))); } return true; } bool AnytoneCodeplug::ScanListElement::fromScanListObj(ScanList *lst, Context &ctx) { clear(); setName(lst->name()); if (lst->primaryChannel() && lst->secondaryChannel()) { setPriorityChannels(PriChannel::Both); } else if (lst->primaryChannel()) { setPriorityChannels(PriChannel::Primary); } else if (lst->secondaryChannel()) { setPriorityChannels(PriChannel::Secondary); } if (lst->primaryChannel()) { if (SelectedChannel::get() == lst->primaryChannel()) setPrimarySelected(); else setPrimary(ctx.index(lst->primaryChannel())); } if (lst->secondaryChannel()) { if (SelectedChannel::get() == lst->secondaryChannel()) setSecondarySelected(); else setSecondary(ctx.index(lst->secondaryChannel())); } for (int i=0; icount()); i++) { if (SelectedChannel::get() == lst->channel(i)) continue; setMemberIndex(i, ctx.index(lst->channel(i))); } return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ScanListBitmapElement * ********************************************************************************************* */ AnytoneCodeplug::ScanListBitmapElement::ScanListBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } AnytoneCodeplug::ScanListBitmapElement::ScanListBitmapElement(uint8_t *ptr) : BitmapElement(ptr, ScanListBitmapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::RadioIDElement * ********************************************************************************************* */ AnytoneCodeplug::RadioIDElement::RadioIDElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::RadioIDElement::RadioIDElement(uint8_t *ptr) : Element(ptr, RadioIDElement::size()) { // pass... } void AnytoneCodeplug::RadioIDElement::clear() { memset(_data, 0x00, _size); } unsigned AnytoneCodeplug::RadioIDElement::number() const { return getBCD8_be(Offset::number()); } void AnytoneCodeplug::RadioIDElement::setNumber(unsigned number) { setBCD8_be(Offset::number(), number); } QString AnytoneCodeplug::RadioIDElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void AnytoneCodeplug::RadioIDElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } DMRRadioID * AnytoneCodeplug::RadioIDElement::toRadioID() const { return new DMRRadioID(name(), number()); } bool AnytoneCodeplug::RadioIDElement::fromRadioID(DMRRadioID *id) { setName(id->name()); setNumber(id->number()); return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::RadioIDBitmapElement * ********************************************************************************************* */ AnytoneCodeplug::RadioIDBitmapElement::RadioIDBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } AnytoneCodeplug::RadioIDBitmapElement::RadioIDBitmapElement(uint8_t *ptr) : BitmapElement(ptr, RadioIDBitmapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::PrimaryRadioIdElement * ********************************************************************************************* */ AnytoneCodeplug::PrimaryRadioIdElement::PrimaryRadioIdElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void AnytoneCodeplug::PrimaryRadioIdElement::clear() { memset(_data, 0x00, size()); } bool AnytoneCodeplug::PrimaryRadioIdElement::isValid() const { return 0 != number() && ! name().isEmpty(); } unsigned int AnytoneCodeplug::PrimaryRadioIdElement::number() const { return getBCD8_be(Offset::id()); } void AnytoneCodeplug::PrimaryRadioIdElement::setNumber(unsigned number) { setBCD8_be(Offset::id(), number); } bool AnytoneCodeplug::PrimaryRadioIdElement::enabled() const { return 0x01 == getUInt8(Offset::enabled()); } void AnytoneCodeplug::PrimaryRadioIdElement::enable(bool enabled) { setUInt8(Offset::enabled(), enabled ? 0x01 : 0x00); } QString AnytoneCodeplug::PrimaryRadioIdElement::name() const { return readASCII(Offset::name(), Limit::name(), 0x00); } void AnytoneCodeplug::PrimaryRadioIdElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::name(), 0x00); } bool AnytoneCodeplug::PrimaryRadioIdElement::encode(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); bool enabled = this->enabled(); if (! flags.updateCodeplug()) enabled = false; clear(); if (nullptr == ctx.config()->settings()->defaultId()) return true; auto id = ctx.config()->settings()->defaultId(); setName(id->name()); setNumber(id->number()); enable(enabled); return true; } bool AnytoneCodeplug::PrimaryRadioIdElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); // pass... return true; } bool AnytoneCodeplug::PrimaryRadioIdElement::link(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); if (! isValid()) return true; // Check if ID is already present as a DMR radio id auto id = ctx.config()->radioIDs()->find(number()); // If not, create one if (nullptr == id) { id = new DMRRadioID(name(), number()); ctx.config()->radioIDs()->add(id, 0); } // ... and set as default. ctx.config()->settings()->setDefaultId(id); return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::GeneralSettingsElement * ********************************************************************************************* */ AnytoneCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } void AnytoneCodeplug::GeneralSettingsElement::clear() { memset(_data, 0, _size); } bool AnytoneCodeplug::GeneralSettingsElement::displayFrequency() const { return 0!=getUInt8(Offset::displayMode()); } void AnytoneCodeplug::GeneralSettingsElement::enableDisplayFrequency(bool enable) { setUInt8(Offset::displayMode(), (enable ? 0x01 : 0x00)); } bool AnytoneCodeplug::GeneralSettingsElement::autoKeyLock() const { return 0!=getUInt8(Offset::autoKeyLock()); } void AnytoneCodeplug::GeneralSettingsElement::enableAutoKeyLock(bool enable) { setUInt8(Offset::autoKeyLock(), (enable ? 0x01 : 0x00)); } Interval AnytoneCodeplug::GeneralSettingsElement::autoShutdownDelay() const { switch ((AutoShutdown) getUInt8(Offset::autoShutDown())) { case AutoShutdown::Off: return Interval::fromMinutes(0); case AutoShutdown::After10min: return Interval::fromMinutes(10); case AutoShutdown::After30min: return Interval::fromMinutes(30); case AutoShutdown::After60min: return Interval::fromMinutes(60); case AutoShutdown::After120min: return Interval::fromMinutes(120); } return Interval(); } void AnytoneCodeplug::GeneralSettingsElement::setAutoShutdownDelay(Interval intv) { if (0 == intv.minutes()) { setUInt8(Offset::autoShutDown(), (unsigned)AutoShutdown::Off); } else if (intv.minutes() <= 10) { setUInt8(Offset::autoShutDown(), (unsigned)AutoShutdown::After10min); } else if (intv.minutes() <= 30) { setUInt8(Offset::autoShutDown(), (unsigned)AutoShutdown::After30min); } else if (intv.minutes() <= 60) { setUInt8(Offset::autoShutDown(), (unsigned)AutoShutdown::After60min); } else { setUInt8(Offset::autoShutDown(), (unsigned)AutoShutdown::After120min); } } BootSettings::BootDisplay AnytoneCodeplug::GeneralSettingsElement::bootDisplay() const { switch ((BootDisplay) getUInt8(Offset::bootDisplay())) { case BootDisplay::Default: return BootSettings::BootDisplay::Logo; case BootDisplay::CustomText: return BootSettings::BootDisplay::Text; case BootDisplay::CustomImage: return BootSettings::BootDisplay::Image; } return BootSettings::BootDisplay::Logo; } void AnytoneCodeplug::GeneralSettingsElement::setBootDisplay(BootSettings::BootDisplay mode) { switch (mode) { case BootSettings::BootDisplay::Logo: setUInt8(Offset::bootDisplay(), (unsigned)BootDisplay::Default); break; case BootSettings::BootDisplay::Text: setUInt8(Offset::bootDisplay(), (unsigned)BootDisplay::CustomText); break; case BootSettings::BootDisplay::Image: setUInt8(Offset::bootDisplay(), (unsigned)BootDisplay::CustomImage); break; } } bool AnytoneCodeplug::GeneralSettingsElement::bootPassword() const { return getUInt8(Offset::bootPassword()); } void AnytoneCodeplug::GeneralSettingsElement::enableBootPassword(bool enable) { setUInt8(Offset::bootPassword(), (enable ? 0x01 : 0x00)); } Level AnytoneCodeplug::GeneralSettingsElement::squelchLevelA() const { return Level::fromValue(getUInt8(Offset::squelchLevelA()), {1,5}); } void AnytoneCodeplug::GeneralSettingsElement::setSquelchLevelA(Level level) { setUInt8(Offset::squelchLevelA(), level.mapTo({1,5})); } Level AnytoneCodeplug::GeneralSettingsElement::squelchLevelB() const { return Level::fromValue(getUInt8(Offset::squelchLevelB()), {1,5}); } void AnytoneCodeplug::GeneralSettingsElement::setSquelchLevelB(Level level) { setUInt8(Offset::squelchLevelB(), level.mapTo({1, 5})); } bool AnytoneCodeplug::GeneralSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // If auto-enable GPS is enabled if (flags.autoEnableGPS()) { // Check if GPS is required -> enable if (ctx.config()->requiresGPS()) { enableGPS(true); // Set time zone based on system time zone. setGPSTimeZone(QTimeZone::systemTimeZone()); enableGetGPSPosition(false); } else { enableGPS(false); } } // Encode boot settings setBootDisplay(ctx.config()->settings()->boot()->bootDisplay()); enableBootPassword(ctx.config()->settings()->boot()->bootPasswordEnabled()); enableDefaultChannel( ctx.config()->settings()->boot()->defaultChannelEnabled() && (! ctx.config()->settings()->boot()->zoneA()->isNull()) && (! ctx.config()->settings()->boot()->zoneB()->isNull())); if (defaultChannel()) { setDefaultZoneIndexA(ctx.index(ctx.config()->settings()->boot()->zoneA()->as())); if (ctx.config()->settings()->boot()->channelA()->isNull() || (! ctx.config()->settings()->boot()->zoneA()->as()->A()->has( ctx.config()->settings()->boot()->channelA()->as()))) setDefaultChannelAToVFO(); else setDefaultChannelAIndex(ctx.config()->settings()->boot()->zoneA()->as()->A()->indexOf( ctx.config()->settings()->boot()->channelA()->as())); setDefaultZoneIndexB(ctx.index(ctx.config()->settings()->boot()->zoneB()->as())); if (ctx.config()->settings()->boot()->channelB()->isNull() || (! ctx.config()->settings()->boot()->zoneB()->as()->A()->has( ctx.config()->settings()->boot()->channelB()->as()))) setDefaultChannelBToVFO(); else setDefaultChannelBIndex(ctx.config()->settings()->boot()->zoneB()->as()->A()->indexOf( ctx.config()->settings()->boot()->channelB()->as())); } // Handle audio settings: setSquelchLevelA(ctx.config()->settings()->audio()->squelch()); setSquelchLevelB(ctx.config()->settings()->audio()->squelch()); // Set microphone gain // For the 868UV, this setting sets both, FM and DMR mic gain. // For all other devices, there is an additional FM mic gain setting. setDMRMicGain(ctx.config()->settings()->audio()->micGain()); setMaxSpeakerVolume(ctx.config()->settings()->audio()->maxSpeakerVolume()); // Handle tone settings: enableKeyTone(ctx.config()->settings()->tone()->keyToneEnabled()); enableSMSAlert(ctx.config()->settings()->tone()->smsToneEnabled()); enableCallAlert(ctx.config()->settings()->tone()->ringtoneEnabled()); enableStartupTone(ctx.config()->settings()->tone()->bootToneEnabled()); enableDMRTalkPermit(ctx.config()->settings()->tone()->talkPermit().testFlag(Channel::Type::DMR)); enableFMTalkPermit(ctx.config()->settings()->tone()->talkPermit().testFlag(Channel::Type::FM)); setCallToneMelody(*ctx.config()->settings()->tone()->callStartMelody()); setIdleToneMelody(*ctx.config()->settings()->tone()->channelIdleMelody()); enableDMRResetTone(ctx.config()->settings()->tone()->callResetEnabled()); setResetToneMelody(*ctx.config()->settings()->tone()->callResetMelody()); enableGPSUnitsImperial(GNSSSettings::Units::Archaic == ctx.config()->settings()->gnss()->units()); // Handle extensions if (AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension()) { setAutoShutdownDelay(ext->powerSaveSettings()->autoShutdown()); setVFOScanType(ext->vfoScanType()); enableVFOModeA(AnytoneSettingsExtension::VFOMode::VFO == ext->modeA()); enableVFOModeB(AnytoneSettingsExtension::VFOMode::VFO == ext->modeB()); if ((AnytoneSettingsExtension::VFOMode::VFO == ext->modeA()) || ext->zoneA()->isNull()) setMemoryZoneA(0); else setMemoryZoneA(ctx.index(ext->zoneA()->as())); if ((AnytoneSettingsExtension::VFOMode::VFO == ext->modeB()) || ext->zoneB()->isNull()) setMemoryZoneB(0); else setMemoryZoneB(ctx.index(ext->zoneB()->as())); enableActiveChannelB(AnytoneSettingsExtension::VFO::B == ext->selectedVFO()); enableSubChannel(ext->subChannelEnabled()); setMinVFOScanFrequencyUHF(ext->minVFOScanFrequencyUHF()); setMaxVFOScanFrequencyUHF(ext->maxVFOScanFrequencyUHF()); setMinVFOScanFrequencyVHF(ext->minVFOScanFrequencyVHF()); setMaxVFOScanFrequencyVHF(ext->maxVFOScanFrequencyVHF()); // Encode key settings setFuncKeyAShort(ext->keySettings()->funcKeyAShort()); setFuncKeyALong(ext->keySettings()->funcKeyALong()); setFuncKeyBShort(ext->keySettings()->funcKeyBShort()); setFuncKeyBLong(ext->keySettings()->funcKeyBLong()); setFuncKeyCShort(ext->keySettings()->funcKeyCShort()); setFuncKeyCLong(ext->keySettings()->funcKeyCLong()); setFuncKey1Short(ext->keySettings()->funcKey1Short()); setFuncKey1Long(ext->keySettings()->funcKey1Long()); setFuncKey2Short(ext->keySettings()->funcKey2Short()); setFuncKey2Long(ext->keySettings()->funcKey2Long()); setLongPressDuration(ext->keySettings()->longPressDuration()); enableAutoKeyLock(ext->keySettings()->autoKeyLockEnabled()); // Encode display settings enableDisplayFrequency(ext->displaySettings()->displayFrequencyEnabled()); setBrightness(ext->displaySettings()->brightness()); enableCallEndPrompt(ext->displaySettings()->callEndPromptEnabled()); setLastCallerDisplayMode(ext->displaySettings()->lastCallerDisplay()); enableDisplayClock(ext->displaySettings()->showClockEnabled()); enableDisplayCall(ext->displaySettings()->showCallEnabled()); setCallDisplayColor(ext->displaySettings()->callColor()); enableVolumeChangePrompt(ext->displaySettings()->volumeChangePromptEnabled()); // Encode audio settings enableRecording(ext->audioSettings()->recordingEnabled()); enableEnhancedAudio(ext->audioSettings()->enhanceAudioEnabled()); // Encode menu settings setMenuExitTime(ext->menuSettings()->duration()); // Encode auto-repeater settings setAutoRepeaterDirectionA(ext->autoRepeaterSettings()->directionA()); setAutoRepeaterDirectionB(ext->autoRepeaterSettings()->directionB()); if (ext->autoRepeaterSettings()->vhfRef()->isNull()) clearAutoRepeaterOffsetFrequencyIndexVHF(); else setAutoRepeaterOffsetFrequenyIndexVHF( ctx.index(ext->autoRepeaterSettings()->vhfRef()->as())); if (ext->autoRepeaterSettings()->uhfRef()->isNull()) clearAutoRepeaterOffsetFrequencyIndexUHF(); else setAutoRepeaterOffsetFrequenyIndexUHF( ctx.index(ext->autoRepeaterSettings()->uhfRef()->as())); setAutoRepeaterMinFrequencyVHF(ext->autoRepeaterSettings()->vhfMin()); setAutoRepeaterMaxFrequencyVHF(ext->autoRepeaterSettings()->vhfMax()); setAutoRepeaterMinFrequencyUHF(ext->autoRepeaterSettings()->uhfMin()); setAutoRepeaterMaxFrequencyUHF(ext->autoRepeaterSettings()->uhfMax()); // Encode GPS Settings setGPSTimeZone(ext->gpsSettings()->timeZone()); // Encode other settings enableKeepLastCaller(ext->keepLastCallerEnabled()); } else if (! flags.updateCodeplug()) { clearAutoRepeaterOffsetFrequencyIndexVHF(); clearAutoRepeaterOffsetFrequencyIndexUHF(); } return true; } bool AnytoneCodeplug::GeneralSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // get microphone gain ctx.config()->settings()->audio()->setMicGain(dmrMicGain()); ctx.config()->settings()->audio()->setMaxSpeakerVolume(this->maxSpeakerVolume()); // D868UV does not support speech synthesis? ctx.config()->settings()->audio()->enableSpeechSynthesis(false); ctx.config()->settings()->audio()->setSquelch(std::max(squelchLevelA(), squelchLevelB())); // Handle tone settings ctx.config()->settings()->tone()->setKeyToneVolume( this->keyToneEnabled() ? Level::fromValue(5) : Level::null()); ctx.config()->settings()->tone()->enableSMSTone(smsAlert()); ctx.config()->settings()->tone()->enableRingtone(callAlert()); ctx.config()->settings()->tone()->enableBootTone(startupTone()); ctx.config()->settings()->tone()->setTalkPermit( (dmrTalkPermit() ? Channel::Type::DMR : Channel::Type::None) | (fmTalkPermit() ? Channel::Type::FM : Channel::Type::None) ); callToneMelody(*ctx.config()->settings()->tone()->callStartMelody()); idleToneMelody(*ctx.config()->settings()->tone()->channelIdleMelody()); ctx.config()->settings()->tone()->enableCallReset(dmrResetTone()); resetToneMelody(*ctx.config()->settings()->tone()->callResetMelody()); // Store boot settings ctx.config()->settings()->boot()->setBootDisplay(bootDisplay()); ctx.config()->settings()->boot()->enableBootPassword(bootPassword()); ctx.config()->settings()->boot()->enableDefaultChannel(this->defaultChannel()); ctx.config()->settings()->gnss()->setUnits( this->gpsUnitsImperial() ? GNSSSettings::Units::Archaic : GNSSSettings::Units::Metric); // Set extension AnytoneSettingsExtension *ext = nullptr; if (ctx.config()->settings()->anytoneExtension()) ext = ctx.config()->settings()->anytoneExtension(); else ctx.config()->settings()->setAnytoneExtension(ext = new AnytoneSettingsExtension()); ext->powerSaveSettings()->setAutoShutdown(autoShutdownDelay()); ext->setVFOScanType(vfoScanType()); ext->setModeA(vfoModeA() ? AnytoneSettingsExtension::VFOMode::VFO : AnytoneSettingsExtension::VFOMode::Memory); ext->setModeB(vfoModeB() ? AnytoneSettingsExtension::VFOMode::VFO : AnytoneSettingsExtension::VFOMode::Memory); if ((! vfoModeA()) && ctx.has(memoryZoneA())) ext->zoneA()->set(ctx.get(memoryZoneA())); if ((! vfoModeB()) && ctx.has(memoryZoneB())) ext->zoneB()->set(ctx.get(memoryZoneB())); ext->setSelectedVFO(activeChannelB() ? AnytoneSettingsExtension::VFO::B : AnytoneSettingsExtension::VFO::A); ext->enableSubChannel(subChannel()); ext->setMinVFOScanFrequencyUHF(this->minVFOScanFrequencyUHF()); ext->setMaxVFOScanFrequencyUHF(this->maxVFOScanFrequencyUHF()); ext->setMinVFOScanFrequencyVHF(this->minVFOScanFrequencyVHF()); ext->setMaxVFOScanFrequencyVHF(this->maxVFOScanFrequencyVHF()); // Store key settings ext->keySettings()->setFuncKeyAShort(funcKeyAShort()); ext->keySettings()->setFuncKeyALong(funcKeyALong()); ext->keySettings()->setFuncKeyBShort(funcKeyBShort()); ext->keySettings()->setFuncKeyBLong(funcKeyBLong()); ext->keySettings()->setFuncKeyCShort(funcKeyCShort()); ext->keySettings()->setFuncKeyCLong(funcKeyCLong()); ext->keySettings()->setFuncKey1Short(funcKey1Short()); ext->keySettings()->setFuncKey1Long(funcKey1Long()); ext->keySettings()->setFuncKey2Short(funcKey2Short()); ext->keySettings()->setFuncKey2Long(funcKey2Long()); ext->keySettings()->setLongPressDuration(longPressDuration()); ext->keySettings()->enableAutoKeyLock(autoKeyLock()); // Store display settings ext->displaySettings()->enableDisplayFrequency(displayFrequency()); ext->displaySettings()->setBrightness(brightness()); ext->displaySettings()->enableVolumeChangePrompt(this->volumeChangePrompt()); ext->displaySettings()->enableCallEndPrompt(this->callEndPrompt()); ext->displaySettings()->setLastCallerDisplay(this->lastCallerDisplayMode()); ext->displaySettings()->enableShowClock(displayClock()); ext->displaySettings()->enableShowCall(displayCall()); ext->displaySettings()->setCallColor(this->callDisplayColor()); // Menu settings ext->menuSettings()->setDuration(this->menuExitTime()); // Store audio settings ext->audioSettings()->enableRecording(recording()); ext->audioSettings()->enableEnhanceAudio(this->enhanceAudio()); // Store auto-repeater settings ext->autoRepeaterSettings()->setDirectionA(this->autoRepeaterDirectionA()); ext->autoRepeaterSettings()->setDirectionB(autoRepeaterDirectionB()); ext->autoRepeaterSettings()->setVHFMin(this->autoRepeaterMinFrequencyVHF()); ext->autoRepeaterSettings()->setVHFMax(this->autoRepeaterMaxFrequencyVHF()); ext->autoRepeaterSettings()->setUHFMin(this->autoRepeaterMinFrequencyUHF()); ext->autoRepeaterSettings()->setUHFMax(this->autoRepeaterMaxFrequencyUHF()); // Store GPS settings ext->gpsSettings()->setTimeZone(gpsTimeZone()); // Other settings ext->enableKeepLastCaller(this->keepLastCaller()); return true; } bool AnytoneCodeplug::GeneralSettingsElement::linkSettings(RadioSettings *settings, Context &ctx, const ErrorStack &err) { // Link boot settings if (this->defaultChannel()) { if (! ctx.has(this->defaultZoneIndexA())) { errMsg(err) << "Cannot link default zone A. Zone index " << this->defaultZoneIndexA() << " not defined."; return false; } Zone *zoneA = ctx.get(this->defaultZoneIndexA()); ctx.config()->settings()->boot()->zoneA()->set(zoneA); if (this->defaultChannelAIsVFO()) { // pass... } else if (this->defaultChannelAIndex() >= (unsigned int)zoneA->A()->count()) { errMsg(err) << "Cannot link default channel A. Index " << this->defaultChannelAIndex() << " not defined."; return false; } else { ctx.config()->settings()->boot()->channelA()->set( zoneA->A()->get(this->defaultChannelAIndex())->as()); } if (! ctx.has(this->defaultZoneIndexB())) { errMsg(err) << "Cannot link default zone B. Zone index " << this->defaultZoneIndexB() << " not defined."; return false; } Zone *zoneB = ctx.get(this->defaultZoneIndexB()); ctx.config()->settings()->boot()->zoneB()->set(zoneB); if (this->defaultChannelBIsVFO()) { // pass... } else if (this->defaultChannelBIndex() >= (unsigned int)zoneB->A()->count()) { errMsg(err) << "Cannot link default channel B. Index " << this->defaultChannelBIndex() << " not defined."; return false; } else { ctx.config()->settings()->boot()->channelB()->set( zoneB->A()->get(this->defaultChannelBIndex())->as()); } } if (! settings->anytoneExtension()) return false; AnytoneSettingsExtension *ext = settings->anytoneExtension(); // Link repeater offsets if (this->hasAutoRepeaterOffsetFrequencyIndexVHF()) { if (! ctx.has(this->autoRepeaterOffsetFrequencyIndexVHF())) { errMsg(err) << "Cannot link auto-repeater offset for VHF, index " << this->autoRepeaterOffsetFrequencyIndexVHF() << " not defined."; return false; } ext->autoRepeaterSettings()->vhfRef()->set( ctx.get(this->autoRepeaterOffsetFrequencyIndexVHF())); } if (this->hasAutoRepeaterOffsetFrequencyIndexUHF()) { if (! ctx.has(this->autoRepeaterOffsetFrequencyIndexUHF())) { errMsg(err) << "Cannot link auto-repeater offset for UHF, index " << this->autoRepeaterOffsetFrequencyIndexUHF() << " not defined."; return false; } ext->autoRepeaterSettings()->uhfRef()->set( ctx.get(this->autoRepeaterOffsetFrequencyIndexUHF())); } // Link auto-repeater if (hasAutoRepeaterOffsetFrequencyIndexVHF()) { if (! ctx.has(this->autoRepeaterOffsetFrequencyIndexVHF())) { errMsg(err) << "Cannot link auto-repeater offset frequency for VHF, index " << this->autoRepeaterOffsetFrequencyIndexVHF() << " not defined."; return false; } ext->autoRepeaterSettings()->vhfRef()->set( ctx.get(this->autoRepeaterOffsetFrequencyIndexVHF())); } if (hasAutoRepeaterOffsetFrequencyIndexUHF()) { if (! ctx.has(this->autoRepeaterOffsetFrequencyIndexUHF())) { errMsg(err) << "Cannot link auto-repeater offset frequency for UHF, index " << this->autoRepeaterOffsetFrequencyIndexUHF() << " not defined."; return false; } ext->autoRepeaterSettings()->uhfRef()->set( ctx.get(this->autoRepeaterOffsetFrequencyIndexUHF())); } return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ExtendedSettingsElement * ********************************************************************************************* */ AnytoneCodeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } bool AnytoneCodeplug::ExtendedSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); if (! flags.updateCodeplug()) this->clear(); if (nullptr == ctx.config()->settings()->anytoneExtension()) { // If there is no extension, done return true; } // Get extension AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); // Encode display settings setChannelBNameColor(ext->displaySettings()->channelBNameColor()); setZoneANameColor(ext->displaySettings()->zoneNameColor()); setZoneBNameColor(ext->displaySettings()->zoneBNameColor()); return true; } bool AnytoneCodeplug::ExtendedSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // Get or add extension if not present AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Store display settings ext->displaySettings()->setChannelBNameColor(channelBNameColor()); ext->displaySettings()->setZoneNameColor(zoneANameColor()); ext->displaySettings()->setZoneBNameColor(zoneBNameColor()); return true; } bool AnytoneCodeplug::ExtendedSettingsElement::linkConfig(Context &ctx, const ErrorStack &err) { // Get or add extension if not present AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { errMsg(err) << "Cannot link config extension: not set."; return false; } return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ZoneChannelListElement * ********************************************************************************************* */ AnytoneCodeplug::ZoneChannelListElement::ZoneChannelListElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pas... } AnytoneCodeplug::ZoneChannelListElement::ZoneChannelListElement(uint8_t *ptr) : Element(ptr, ZoneChannelListElement::size()) { // pass... } void AnytoneCodeplug::ZoneChannelListElement::clear() { memset(_data, 0x00, _size); memset(_data + Offset::channelsA(), 0xff, Offset::betweenChannels()*Limit::zones()); memset(_data + Offset::channelsB(), 0xff, Offset::betweenChannels()*Limit::zones()); } bool AnytoneCodeplug::ZoneChannelListElement::hasChannelA(unsigned n) const { return 0xffff != channelIndexA(n); } unsigned AnytoneCodeplug::ZoneChannelListElement::channelIndexA(unsigned n) const { return getUInt16_le(Offset::channelsA() + Offset::betweenChannels()*n); } void AnytoneCodeplug::ZoneChannelListElement::setChannelIndexA(unsigned n, unsigned idx) { setUInt16_le(Offset::channelsA() + Offset::betweenChannels()*n, idx); } void AnytoneCodeplug::ZoneChannelListElement::clearChannelIndexA(unsigned n) { setChannelIndexA(n, 0xffff); } bool AnytoneCodeplug::ZoneChannelListElement::hasChannelB(unsigned n) const { return 0xffff != channelIndexB(n); } unsigned AnytoneCodeplug::ZoneChannelListElement::channelIndexB(unsigned n) const { return getUInt16_le(Offset::channelsB() + Offset::betweenChannels()*n); } void AnytoneCodeplug::ZoneChannelListElement::setChannelIndexB(unsigned n, unsigned idx) { setUInt16_le(Offset::channelsB() + Offset::betweenChannels()*n, idx); } void AnytoneCodeplug::ZoneChannelListElement::clearChannelIndexB(unsigned n) { setChannelIndexB(n, 0xffff); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ZoneBitmapElement * ********************************************************************************************* */ AnytoneCodeplug::ZoneBitmapElement::ZoneBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } AnytoneCodeplug::ZoneBitmapElement::ZoneBitmapElement(uint8_t *ptr) : BitmapElement(ptr, ZoneBitmapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::BootSettingsElement * ********************************************************************************************* */ AnytoneCodeplug::BootSettingsElement::BootSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::BootSettingsElement::BootSettingsElement(uint8_t *ptr) : Element(ptr, BootSettingsElement::size()) { // pass... } void AnytoneCodeplug::BootSettingsElement::clear() { memset(_data, 0x00, _size); } QString AnytoneCodeplug::BootSettingsElement::introLine1() const { return readASCII(Offset::introLine1(), Limit::introLineLength(), 0x00); } void AnytoneCodeplug::BootSettingsElement::setIntroLine1(const QString &txt) { writeASCII(Offset::introLine1(), txt, Limit::introLineLength(), 0x00); } QString AnytoneCodeplug::BootSettingsElement::introLine2() const { return readASCII(Offset::introLine2(), Limit::introLineLength(), 0x00); } void AnytoneCodeplug::BootSettingsElement::setIntroLine2(const QString &txt) { writeASCII(Offset::introLine2(), txt, Limit::introLineLength(), 0x00); } QString AnytoneCodeplug::BootSettingsElement::password() const { return readASCII(Offset::password(), Limit::passwordLength(), 0x00); } void AnytoneCodeplug::BootSettingsElement::setPassword(const QString &txt) { QRegularExpression pattern("[0-9]{0,8}"); if (pattern.match(txt).hasMatch()) writeASCII(Offset::password(), txt, Limit::passwordLength(), 0x00); } bool AnytoneCodeplug::BootSettingsElement::fromConfig(const Flags &flags, Context &ctx) { Q_UNUSED(flags) setIntroLine1(ctx.config()->settings()->boot()->message1()); setIntroLine2(ctx.config()->settings()->boot()->message2()); if (ctx.config()->settings()->boot()->bootPasswordEnabled()) setPassword(ctx.config()->settings()->boot()->bootPassword()); else setPassword(""); return true; } bool AnytoneCodeplug::BootSettingsElement::updateConfig(Context &ctx) { ctx.config()->settings()->boot()->setMessage1(introLine1()); ctx.config()->settings()->boot()->setMessage2(introLine2()); ctx.config()->settings()->boot()->setBootPassword(password()); ctx.config()->settings()->boot()->enableBootPassword(! password().isEmpty()); return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::DMRAPRSSettingsElement * ********************************************************************************************* */ AnytoneCodeplug::DMRAPRSSettingsElement::DMRAPRSSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::DMRAPRSSettingsElement::DMRAPRSSettingsElement(uint8_t *ptr) : Element(ptr, DMRAPRSSettingsElement::size()) { // pass... } void AnytoneCodeplug::DMRAPRSSettingsElement::clear() { memset(_data, 0x00, _size); } Interval AnytoneCodeplug::DMRAPRSSettingsElement::manualInterval() const { return Interval::fromSeconds(getUInt8(Offset::manualInterval())); } void AnytoneCodeplug::DMRAPRSSettingsElement::setManualInterval(const Interval &sec) { setUInt8(Offset::manualInterval(), sec.seconds()); } bool AnytoneCodeplug::DMRAPRSSettingsElement::automatic() const { return 0!=getUInt8(Offset::automaticInterval()); } Interval AnytoneCodeplug::DMRAPRSSettingsElement::automaticInterval() const { return Interval::fromSeconds(45 + 15*((unsigned)getUInt8(Offset::automaticInterval()))); } void AnytoneCodeplug::DMRAPRSSettingsElement::setAutomaticInterval(const Interval &sec) { unsigned int s = sec.seconds(); if (s < 60) s = 60; setUInt8(Offset::automaticInterval(), (s-45)/15); } void AnytoneCodeplug::DMRAPRSSettingsElement::disableAutomatic() { setUInt8(Offset::automaticInterval(), 0x00); } bool AnytoneCodeplug::DMRAPRSSettingsElement::fixedLocation() const { return getUInt8(Offset::fixedLocation()); } QGeoCoordinate AnytoneCodeplug::DMRAPRSSettingsElement::location() const { double latitude = getUInt8(Offset::latitudeDeg()) + double(getUInt8(Offset::latitudeMin()))/60 + double(getUInt8(Offset::latitudeSec()))/3600; if (getUInt8(Offset::latitudeHemi())) latitude *= -1; double longitude = getUInt8(Offset::longitudeDeg()) + double(getUInt8(Offset::longitudeMin()))/60 + double(getUInt8(Offset::longitudeSec()))/3600; if (getUInt8(Offset::longitudeHemi())) longitude *= -1; return QGeoCoordinate(latitude, longitude); } void AnytoneCodeplug::DMRAPRSSettingsElement::setLocation(const QGeoCoordinate &pos) { double latitude = pos.latitude(); bool south = (0 > latitude); latitude = std::abs(latitude); unsigned lat_deg = int(latitude); latitude -= lat_deg; latitude *= 60; unsigned lat_min = int(latitude); latitude -= lat_min; latitude *= 60; unsigned lat_sec = int(latitude); double longitude = pos.longitude(); bool west = (0 > longitude); longitude = std::abs(longitude); unsigned lon_deg = int(longitude); longitude -= lon_deg; longitude *= 60; unsigned lon_min = int(longitude); longitude -= lon_min; longitude *= 60; unsigned lon_sec = int(longitude); setUInt8(Offset::latitudeDeg(), lat_deg); setUInt8(Offset::latitudeMin(), lat_min); setUInt8(Offset::latitudeSec(), lat_sec); setUInt8(Offset::latitudeHemi(), (south ? 0x01 : 0x00)); setUInt8(Offset::longitudeDeg(), lon_deg); setUInt8(Offset::longitudeMin(), lon_min); setUInt8(Offset::longitudeSec(), lon_sec); setUInt8(Offset::longitudeHemi(), (west ? 0x01 : 0x00)); } void AnytoneCodeplug::DMRAPRSSettingsElement::enableFixedLocation(bool enable) { setUInt8(Offset::fixedLocation(), (enable ? 0x01 : 0x00)); } Channel::Power AnytoneCodeplug::DMRAPRSSettingsElement::power() const { switch (getUInt8(Offset::power())) { case 0x00: return Channel::Power::Low; case 0x01: return Channel::Power::Mid; case 0x02: return Channel::Power::High; case 0x03: return Channel::Power::Max; } return Channel::Power::Low; } void AnytoneCodeplug::DMRAPRSSettingsElement::setPower(Channel::Power power) { switch (power) { case Channel::Power::Min: case Channel::Power::Low: setUInt8(Offset::power(), 0x00); break; case Channel::Power::Mid: setUInt8(Offset::power(), 0x01); break; case Channel::Power::High: setUInt8(Offset::power(), 0x02); break; case Channel::Power::Max: setUInt8(Offset::power(), 0x03); break; } } bool AnytoneCodeplug::DMRAPRSSettingsElement::hasChannel(unsigned n) const { return 0xffff != channelIndex(n); } bool AnytoneCodeplug::DMRAPRSSettingsElement::channelIsVFOA(unsigned n) const { return 0x0fa0 == channelIndex(n); } bool AnytoneCodeplug::DMRAPRSSettingsElement::channelIsVFOB(unsigned n) const { return 0x0fa1 == channelIndex(n); } bool AnytoneCodeplug::DMRAPRSSettingsElement::channelIsSelected(unsigned n) const { return 0x0fa2 == channelIndex(n); } unsigned AnytoneCodeplug::DMRAPRSSettingsElement::channelIndex(unsigned n) const { return getUInt16_le(0x000c + 2*n); } void AnytoneCodeplug::DMRAPRSSettingsElement::setChannelIndex(unsigned n, unsigned idx) { setUInt16_le(0x000c + 2*n, idx); } void AnytoneCodeplug::DMRAPRSSettingsElement::setChannelVFOA(unsigned n) { setChannelIndex(n, 0x0fa0); } void AnytoneCodeplug::DMRAPRSSettingsElement::setChannelVFOB(unsigned n) { setChannelIndex(n, 0x0fa1); } void AnytoneCodeplug::DMRAPRSSettingsElement::setChannelSelected(unsigned n) { setChannelIndex(n, 0x0fa2); } void AnytoneCodeplug::DMRAPRSSettingsElement::clearChannel(unsigned n) { setChannelIndex(n, 0xffff); } unsigned AnytoneCodeplug::DMRAPRSSettingsElement::destination() const { return getBCD8_be(0x001c); } void AnytoneCodeplug::DMRAPRSSettingsElement::setDestination(unsigned id) { setBCD8_be(0x001c, id); } DMRContact::Type AnytoneCodeplug::DMRAPRSSettingsElement::callType() const { switch (getUInt8(0x0020)) { case 0x00: return DMRContact::PrivateCall; case 0x01: return DMRContact::GroupCall; case 0x02: return DMRContact::AllCall; } return DMRContact::PrivateCall; } void AnytoneCodeplug::DMRAPRSSettingsElement::setCallType(DMRContact::Type type) { switch (type) { case DMRContact::PrivateCall: setUInt8(0x0020, 0x00); break; case DMRContact::GroupCall: setUInt8(0x0020, 0x01); break; case DMRContact::AllCall: setUInt8(0x0020, 0x02); break; } } bool AnytoneCodeplug::DMRAPRSSettingsElement::timeSlotOverride() const { return 0 != getUInt8(0x0021); } DMRChannel::TimeSlot AnytoneCodeplug::DMRAPRSSettingsElement::timeslot() const { if (1 == getUInt8(0x0021)) return DMRChannel::TimeSlot::TS1; else if (2 == getUInt8(0x0021)) return DMRChannel::TimeSlot::TS2; return DMRChannel::TimeSlot::TS1; } void AnytoneCodeplug::DMRAPRSSettingsElement::overrideTimeSlot(DMRChannel::TimeSlot ts) { if (DMRChannel::TimeSlot::TS1 == ts) setUInt8(0x0021, 0x01); else setUInt8(0x0021, 0x02); } void AnytoneCodeplug::DMRAPRSSettingsElement::disableTimeSlotOverride() { setUInt8(0x0021, 0x00); } bool AnytoneCodeplug::DMRAPRSSettingsElement::fromConfig(const Flags &flags, Context &ctx) { Q_UNUSED(flags) // Encode fixed location if valid and enabled if (ctx.config()->settings()->gnss()->fixedPosition().isValid()) { setLocation(ctx.config()->settings()->gnss()->fixedPosition()); // Enable if there are no GNSS enabled enableFixedLocation(ctx.config()->settings()->gnss()->fixedPositionEnabled()); } if (1 < ctx.count()) { logDebug() << "D868UV only supports a single independent GPS positioning system."; } else if (0 == ctx.count()) { return true; } DMRAPRSSystem *sys = ctx.get(0); setDestination(sys->contact()->number()); setCallType(sys->contact()->type()); setManualInterval(sys->period()); setAutomaticInterval(sys->period()); disableTimeSlotOverride(); if (! sys->hasRevertChannel()) { setChannelSelected(0); } else if (sys->revertChannelRef()->is()) { setChannelIndex(0, ctx.index(sys->revertChannel())); } else { clearChannel(0); } return true; } bool AnytoneCodeplug::DMRAPRSSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); if (location().isValid()) { ctx.config()->settings()->gnss()->setFixedPosition(location()); ctx.config()->settings()->gnss()->enableFixedPosition(fixedLocation()); } return true; } bool AnytoneCodeplug::DMRAPRSSettingsElement::createGPSSystem(uint8_t i, Context &ctx) { DMRAPRSSystem *sys = new DMRAPRSSystem(QString("GPS sys %1").arg(i+1), nullptr, nullptr, automaticInterval()); ctx.config()->posSystems()->add(sys); ctx.add(sys, i); return true; } bool AnytoneCodeplug::DMRAPRSSettingsElement::linkGPSSystem(uint8_t i, Context &ctx) { DMRContact *cont = nullptr; // Find matching contact, if not found -> create one. for (unsigned int i=0; i(); i++) { if (ctx.get(i)->number() == destination()) { cont = ctx.get(i); break; } } if (nullptr == cont) { cont = new DMRContact(callType(), QString("GPS target"), destination()); ctx.config()->contacts()->add(cont); } ctx.get(i)->setContact(cont); // Check if there is a revert channel set if ((! channelIsSelected(i)) && (ctx.has(channelIndex(i))) && (ctx.get(channelIndex(i)))->is()) { DMRChannel *ch = ctx.get(channelIndex(i))->as(); ctx.get(i)->setRevertChannel(ch); } else { ctx.get(i)->resetRevertChannel(); } return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::DMRAPRSMessageElement * ********************************************************************************************* */ AnytoneCodeplug::DMRAPRSMessageElement::DMRAPRSMessageElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneCodeplug::DMRAPRSMessageElement::DMRAPRSMessageElement(uint8_t *ptr) : Element(ptr, DMRAPRSMessageElement::size()) { // pass... } void AnytoneCodeplug::DMRAPRSMessageElement::clear() { memset(_data, 0x00, _size); } QString AnytoneCodeplug::DMRAPRSMessageElement::message() const { return readASCII(Offset::message(), Limit::length(), 0x00); } void AnytoneCodeplug::DMRAPRSMessageElement::setMessage(const QString &message) { writeASCII(Offset::message(), message, Limit::length(), 0x00); } bool AnytoneCodeplug::DMRAPRSMessageElement::fromConfig(Codeplug::Flags flags, Context &ctx) { Q_UNUSED(flags); Q_UNUSED(ctx) return true; } bool AnytoneCodeplug::DMRAPRSMessageElement::updateConfig(Context &ctx) const { Q_UNUSED(ctx) return true; } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::RepeaterOffsetListElement * ********************************************************************************************* */ AnytoneCodeplug::RepeaterOffsetListElement::RepeaterOffsetListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneCodeplug::RepeaterOffsetListElement::RepeaterOffsetListElement(uint8_t *ptr) : Element(ptr, RepeaterOffsetListElement::size()) { // pass... } void AnytoneCodeplug::RepeaterOffsetListElement::clear() { memset(_data, 0x00, _size); for (unsigned int i=0; i= Limit::numEntries()) return false; return 0 != getUInt32_le(Offset::frequencies() + n * Offset::betweenFrequencies()); } Frequency AnytoneCodeplug::RepeaterOffsetListElement::offset(unsigned int n) const { if (n >= Limit::numEntries()) return Frequency::fromHz(0); return Frequency::fromHz( ((unsigned long long)getUInt32_le(Offset::frequencies() + n * Offset::betweenFrequencies()))*10); } void AnytoneCodeplug::RepeaterOffsetListElement::setOffset(unsigned int n, Frequency freq) { if (n >= Limit::numEntries()) return; setUInt32_le(Offset::frequencies() + n*Offset::betweenFrequencies(), freq.inHz()/10); } void AnytoneCodeplug::RepeaterOffsetListElement::clearOffset(unsigned int n) { if (n >= Limit::numEntries()) return; setUInt32_le(Offset::frequencies() + n*Offset::betweenFrequencies(), 0); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::MessageListElement * ********************************************************************************************* */ AnytoneCodeplug::MessageListElement::MessageListElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::MessageListElement::MessageListElement(uint8_t *ptr) : Element(ptr, MessageListElement::size()) { // pass... } void AnytoneCodeplug::MessageListElement::clear() { memset(_data, 0x00, _size); clearNext(); clearIndex(); } bool AnytoneCodeplug::MessageListElement::hasNext() const { return 0xff != next(); } unsigned AnytoneCodeplug::MessageListElement::next() const { return getUInt8(Offset::next()); } void AnytoneCodeplug::MessageListElement::setNext(unsigned idx) { setUInt8(Offset::next(), idx); } void AnytoneCodeplug::MessageListElement::clearNext() { setNext(0xff); } bool AnytoneCodeplug::MessageListElement::hasIndex() const { return 0xff != index(); } unsigned AnytoneCodeplug::MessageListElement::index() const { return getUInt8(Offset::index()); } void AnytoneCodeplug::MessageListElement::setIndex(unsigned idx) { setUInt8(Offset::index(), idx); } void AnytoneCodeplug::MessageListElement::clearIndex() { setIndex(0xff); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::MessageElement * ********************************************************************************************* */ AnytoneCodeplug::MessageElement::MessageElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::MessageElement::MessageElement(uint8_t *ptr) : Element(ptr, MessageElement::size()) { // pass... } void AnytoneCodeplug::MessageElement::clear() { memset(_data, 0x00, _size); } QString AnytoneCodeplug::MessageElement::message() const { return readASCII(Offset::message(), Limit::messageLength(), 0x00); } void AnytoneCodeplug::MessageElement::setMessage(const QString &msg) { writeASCII(Offset::message(), msg, Limit::messageLength(), 0x00); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::MessageBytemapElement * ********************************************************************************************* */ AnytoneCodeplug::MessageBytemapElement::MessageBytemapElement(uint8_t *ptr, size_t size) : InvertedBytemapElement(ptr, size) { // pass... } AnytoneCodeplug::MessageBytemapElement::MessageBytemapElement(uint8_t *ptr) : InvertedBytemapElement(ptr, MessageBytemapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::AnalogQuickCallElement * ********************************************************************************************* */ AnytoneCodeplug::AnalogQuickCallElement::AnalogQuickCallElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::AnalogQuickCallElement::AnalogQuickCallElement(uint8_t *ptr) : Element(ptr, AnalogQuickCallElement::size()) { // pass... } void AnytoneCodeplug::AnalogQuickCallElement::clear() { memset(_data, 0x00, _size); clearContactIndex(); } AnytoneCodeplug::AnalogQuickCallElement::Type AnytoneCodeplug::AnalogQuickCallElement::type() const { return (Type)getUInt8(Offset::type()); } void AnytoneCodeplug::AnalogQuickCallElement::setType(Type type) { setUInt8(Offset::type(), (unsigned)type); } bool AnytoneCodeplug::AnalogQuickCallElement::hasContactIndex() const { return 0xff != contactIndex(); } unsigned AnytoneCodeplug::AnalogQuickCallElement::contactIndex() const { return getUInt8(Offset::contactIndex()); } void AnytoneCodeplug::AnalogQuickCallElement::setContactIndex(unsigned idx) { setUInt8(Offset::contactIndex(), idx); } void AnytoneCodeplug::AnalogQuickCallElement::clearContactIndex() { setContactIndex(0xff); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::AnalogQuickCallsElement * ********************************************************************************************* */ AnytoneCodeplug::AnalogQuickCallsElement::AnalogQuickCallsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneCodeplug::AnalogQuickCallsElement::AnalogQuickCallsElement(uint8_t *ptr) : Element(ptr, AnalogQuickCallsElement::size()) { // pass... } void AnytoneCodeplug::AnalogQuickCallsElement::clear() { memset(_data, 0x00, _size); for (unsigned int i=0; i= Limit::numMessages()) return; writeASCII(Offset::messages()+n*Offset::betweenMessages(), msg, Limit::messageLength(), 0x00); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::StatusMessageBitmapElement * ********************************************************************************************* */ AnytoneCodeplug::StatusMessageBitmapElement::StatusMessageBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } AnytoneCodeplug::StatusMessageBitmapElement::StatusMessageBitmapElement(uint8_t *ptr) : BitmapElement(ptr, StatusMessageBitmapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::HotKeyElement * ********************************************************************************************* */ AnytoneCodeplug::HotKeyElement::HotKeyElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::HotKeyElement::HotKeyElement(uint8_t *ptr) : Element(ptr, HotKeyElement::size()) { // pass... } void AnytoneCodeplug::HotKeyElement::clear() { memset(_data, 0x00, _size); clearContactIndex(); clearMessageIndex(); } AnytoneCodeplug::HotKeyElement::Type AnytoneCodeplug::HotKeyElement::type() const { return (Type)getUInt8(Offset::type()); } void AnytoneCodeplug::HotKeyElement::setType(Type type) { setUInt8(Offset::type(), (unsigned)type); } AnytoneCodeplug::HotKeyElement::MenuItem AnytoneCodeplug::HotKeyElement::menuItem() const { return (MenuItem) getUInt8(Offset::menuItem()); } void AnytoneCodeplug::HotKeyElement::setMenuItem(MenuItem item) { setUInt8(Offset::menuItem(), (unsigned)item); } AnytoneCodeplug::HotKeyElement::CallType AnytoneCodeplug::HotKeyElement::callType() const { return (CallType)getUInt8(Offset::callType()); } void AnytoneCodeplug::HotKeyElement::setCallType(CallType type) { setUInt8(Offset::callType(), (unsigned)type); } AnytoneCodeplug::HotKeyElement::DigiCallType AnytoneCodeplug::HotKeyElement::digiCallType() const { return (DigiCallType)getUInt8(Offset::digiCallType()); } void AnytoneCodeplug::HotKeyElement::setDigiCallType(DigiCallType type) { setUInt8(Offset::digiCallType(), (unsigned)type); } bool AnytoneCodeplug::HotKeyElement::hasContactIndex() const { return 0xffffffff != contactIndex(); } unsigned AnytoneCodeplug::HotKeyElement::contactIndex() const { return getUInt32_le(Offset::contactIndex()); } void AnytoneCodeplug::HotKeyElement::setContactIndex(unsigned idx) { setUInt32_le(Offset::contactIndex(), idx); } void AnytoneCodeplug::HotKeyElement::clearContactIndex() { setContactIndex(0xffffffff); } bool AnytoneCodeplug::HotKeyElement::hasMessageIndex() const { return 0xff != messageIndex(); } unsigned AnytoneCodeplug::HotKeyElement::messageIndex() const { return getUInt8(Offset::messageIndex()); } void AnytoneCodeplug::HotKeyElement::setMessageIndex(unsigned idx) { setUInt8(Offset::messageIndex(), idx); } void AnytoneCodeplug::HotKeyElement::clearMessageIndex() { setMessageIndex(0xff); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::HotKeySettingsElement * ********************************************************************************************* */ AnytoneCodeplug::HotKeySettingsElement::HotKeySettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneCodeplug::HotKeySettingsElement::HotKeySettingsElement(uint8_t *ptr) : Element(ptr, HotKeySettingsElement::size()) { // pass... } void AnytoneCodeplug::HotKeySettingsElement::clear() { memset(_data, 0x00, _size); for (unsigned int i=0; i m) m = 1; setUInt8(Offset::voiceBroadcastDuration(), m-1); } Interval AnytoneCodeplug::AlarmSettingElement::DigitalAlarm::areaBroadcastDuration() const { return Interval::fromMinutes(getUInt8(Offset::areaBroadcastDuration())+1); } void AnytoneCodeplug::AlarmSettingElement::DigitalAlarm::setAreaBroadcastDuration(const Interval &min) { unsigned int m = min.minutes(); if (1 > m) m = 1; setUInt8(Offset::areaBroadcastDuration(), m-1); } bool AnytoneCodeplug::AlarmSettingElement::DigitalAlarm::vox() const { return getUInt8(Offset::vox()); } void AnytoneCodeplug::AlarmSettingElement::DigitalAlarm::enableVOX(bool enable) { setUInt8(Offset::vox(), (enable ? 0x01 : 0x00)); } bool AnytoneCodeplug::AlarmSettingElement::DigitalAlarm::rxAlarm() const { return getUInt8(Offset::rxAlarm()); } void AnytoneCodeplug::AlarmSettingElement::DigitalAlarm::enableRXAlarm(bool enable) { setUInt8(Offset::rxAlarm(), (enable ? 0x01 : 0x00)); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::AlarmSettingElement * ********************************************************************************************* */ AnytoneCodeplug::AlarmSettingElement::AlarmSettingElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::AlarmSettingElement::AlarmSettingElement(uint8_t *ptr) : Element(ptr, AlarmSettingElement::size()) { // pass... } void AnytoneCodeplug::AlarmSettingElement::clear() { AnalogAlarm(analog()).clear(); DigitalAlarm(digital()).clear(); } uint8_t * AnytoneCodeplug::AlarmSettingElement::analog() const { return _data + Offset::analog(); } uint8_t * AnytoneCodeplug::AlarmSettingElement::digital() const { return _data + Offset::digital(); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::DigitalAlarmExtensionElement * ********************************************************************************************* */ AnytoneCodeplug::DigitalAlarmExtensionElement::DigitalAlarmExtensionElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::DigitalAlarmExtensionElement::DigitalAlarmExtensionElement(uint8_t *ptr) : Element(ptr, DigitalAlarmExtensionElement::size()) { // pass... } void AnytoneCodeplug::DigitalAlarmExtensionElement::clear() { memset(_data, 0x00, _size); } DMRContact::Type AnytoneCodeplug::DigitalAlarmExtensionElement::callType() const { switch (getUInt8(Offset::callType())) { case 0x00: return DMRContact::PrivateCall; case 0x01: return DMRContact::GroupCall; case 0x02: return DMRContact::AllCall; } return DMRContact::PrivateCall; } void AnytoneCodeplug::DigitalAlarmExtensionElement::setCallType(DMRContact::Type type) { switch (type) { case DMRContact::PrivateCall: setUInt8(Offset::callType(), 0x00); break; case DMRContact::GroupCall: setUInt8(Offset::callType(), 0x01); break; case DMRContact::AllCall: setUInt8(Offset::callType(), 0x02); break; } } unsigned AnytoneCodeplug::DigitalAlarmExtensionElement::destination() const { return getBCD8_be(Offset::destination()); } void AnytoneCodeplug::DigitalAlarmExtensionElement::setDestination(unsigned number) { setBCD8_be(Offset::destination(), number); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::FiveToneIDElement * ********************************************************************************************* */ AnytoneCodeplug::FiveToneIDElement::FiveToneIDElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::FiveToneIDElement::FiveToneIDElement(uint8_t *ptr) : Element(ptr, FiveToneIDElement::size()) { // pass... } void AnytoneCodeplug::FiveToneIDElement::clear() { memset(_data, 0x00, _size); } AnytoneCodeplug::FiveToneIDElement::Standard AnytoneCodeplug::FiveToneIDElement::standard() const { return (Standard) getUInt8(Offset::standard()); } void AnytoneCodeplug::FiveToneIDElement::setStandard(Standard std) { setUInt8(Offset::standard(), (unsigned)std); } Interval AnytoneCodeplug::FiveToneIDElement::toneDuration() const { return Interval::fromMilliseconds(getUInt8(Offset::toneDuration())); } void AnytoneCodeplug::FiveToneIDElement::setToneDuration(const Interval &ms) { setUInt8(Offset::toneDuration(), ms.milliseconds()); } QString AnytoneCodeplug::FiveToneIDElement::id() const { unsigned len = getUInt8(Offset::idLength()); QString id; for (unsigned i=0; i>4)&0xf,16)); else id.append(QString::number((b>>0)&0xf,16)); } return id; } void AnytoneCodeplug::FiveToneIDElement::setID(const QString &id) { unsigned len = 0; for (int i=0; i= Limit::numEntries()) return nullptr; return _data + n*FiveToneIDElement::size(); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::FiveToneFunctionElement * ********************************************************************************************* */ AnytoneCodeplug::FiveToneFunctionElement::FiveToneFunctionElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::FiveToneFunctionElement::FiveToneFunctionElement(uint8_t *ptr) : Element(ptr, FiveToneFunctionElement::size()) { // pass... } void AnytoneCodeplug::FiveToneFunctionElement::clear() { memset(_data, 0x00, _size); } AnytoneCodeplug::FiveToneFunctionElement::Function AnytoneCodeplug::FiveToneFunctionElement::function() const { return (Function) getUInt8(Offset::function()); } void AnytoneCodeplug::FiveToneFunctionElement::setFunction(Function function) { setUInt8(Offset::function(), (unsigned)function); } AnytoneCodeplug::FiveToneFunctionElement::Response AnytoneCodeplug::FiveToneFunctionElement::response() const { return (Response) getUInt8(Offset::response()); } void AnytoneCodeplug::FiveToneFunctionElement::setResponse(Response response) { setUInt8(Offset::response(), (unsigned)response); } QString AnytoneCodeplug::FiveToneFunctionElement::id() const { unsigned len = getUInt8(Offset::idLength()); QString id; for (unsigned i=0; i>4)&0xf, 16)); else id.append(QString::number((b>>0)&0xf, 16)); } return id; } void AnytoneCodeplug::FiveToneFunctionElement::setID(const QString &id) { unsigned len = 0; for (int i=0; i= Limit::numFunctions()) return nullptr; return _data + n*FiveToneFunctionElement::size(); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::FiveToneSettingsElement * ********************************************************************************************* */ AnytoneCodeplug::FiveToneSettingsElement::FiveToneSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::FiveToneSettingsElement::FiveToneSettingsElement(uint8_t *ptr) : Element(ptr, FiveToneSettingsElement::size()) { // pass... } void AnytoneCodeplug::FiveToneSettingsElement::clear() { memset(_data, 0x00, _size); } AnytoneCodeplug::FiveToneSettingsElement::Response AnytoneCodeplug::FiveToneSettingsElement::decodingResponse() const { return (Response) getUInt8(Offset::decodingResponse()); } void AnytoneCodeplug::FiveToneSettingsElement::setDecodingResponse(Response response) { setUInt8(Offset::decodingResponse(), (unsigned)response); } AnytoneCodeplug::FiveToneSettingsElement::Standard AnytoneCodeplug::FiveToneSettingsElement::decodingStandard() const { return (Standard) getUInt8(Offset::decodingStandard()); } void AnytoneCodeplug::FiveToneSettingsElement::setDecodingStandard(Standard standard) { setUInt8(Offset::decodingStandard(), (unsigned)standard); } Interval AnytoneCodeplug::FiveToneSettingsElement::decodingToneDuration() const { return Interval::fromMilliseconds(getUInt8(Offset::decodingToneDuration())); } void AnytoneCodeplug::FiveToneSettingsElement::setDecodingToneDuration(const Interval &ms) { setUInt8(Offset::decodingToneDuration(), ms.milliseconds()); } QString AnytoneCodeplug::FiveToneSettingsElement::id() const { unsigned len = getUInt8(Offset::idLength()); QString id; for (unsigned i=0; i>4)&0xf, 16)); else id.append(QString::number((b>>0)&0xf, 16)); } return id; } void AnytoneCodeplug::FiveToneSettingsElement::setID(const QString &id) { unsigned len = 0; for (int i=0; i>4)&0xf, 16)); else id.append(QString::number((b>>0)&0xf, 16)); } return id; } void AnytoneCodeplug::FiveToneSettingsElement::setBOTID(const QString &id) { unsigned len = 0; for (int i=0; i>4)&0xf, 16)); else id.append(QString::number((b>>0)&0xf, 16)); } return id; } void AnytoneCodeplug::FiveToneSettingsElement::setEOTID(const QString &id) { unsigned len = 0; for (int i=0; i= Limit::numEntries()) return false; return 0xff != getUInt8(n*Limit::numberLength()); } QString AnytoneCodeplug::DTMFIDListElement::number(unsigned int n) const { if (n >= Limit::numEntries()) return ""; uint8_t *num = _data + n*Limit::numberLength(); return decode_dtmf_bin(num, Limit::numberLength(), 0xff); } void AnytoneCodeplug::DTMFIDListElement::setNumber(unsigned int n, const QString &number) { if (n >= Limit::numEntries()) return; uint8_t *num = _data + n*Limit::numberLength(); encode_dtmf_bin(number, num, Limit::numberLength(), 0xff); } void AnytoneCodeplug::DTMFIDListElement::clearNumber(unsigned int n) { if (n >= Limit::numEntries()) return; uint8_t *num = _data + n*Limit::numberLength(); memset(num, 0xff, Limit::numberLength()); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::WFMChannelListElement * ********************************************************************************************* */ AnytoneCodeplug::WFMChannelListElement::WFMChannelListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneCodeplug::WFMChannelListElement::WFMChannelListElement(uint8_t *ptr) : Element(ptr, WFMChannelListElement::size()) { // pass... } void AnytoneCodeplug::WFMChannelListElement::clear() { memset(_data, 0x00, _size); } bool AnytoneCodeplug::WFMChannelListElement::hasChannel(unsigned int n) const { if (n >= Limit::numEntries()) return false; return 0 != getBCD8_le(n*Offset::betweenChannels()); } Frequency AnytoneCodeplug::WFMChannelListElement::channel(unsigned int n) const { if (n >= Limit::numEntries()) return Frequency(); return Frequency::fromHz(((unsigned long long)getBCD8_le(n*Offset::betweenChannels()))*100); } void AnytoneCodeplug::WFMChannelListElement::setChannel(unsigned int n, Frequency freq) { if (n >= Limit::numEntries()) return; setBCD8_le(n*Offset::betweenChannels(), freq.inHz()/100); } void AnytoneCodeplug::WFMChannelListElement::clearChannel(unsigned int n) { if (n >= Limit::numEntries()) return; setBCD8_le(n*Offset::betweenChannels(), 0); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::WFMChannelBitmapElement * ********************************************************************************************* */ AnytoneCodeplug::WFMChannelBitmapElement::WFMChannelBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } AnytoneCodeplug::WFMChannelBitmapElement::WFMChannelBitmapElement(uint8_t *ptr) : BitmapElement(ptr, WFMChannelBitmapElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::WFMVFOElement * ********************************************************************************************* */ AnytoneCodeplug::WFMVFOElement::WFMVFOElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneCodeplug::WFMVFOElement::WFMVFOElement(uint8_t *ptr) : Element(ptr, WFMVFOElement::size()) { // pass... } void AnytoneCodeplug::WFMVFOElement::clear() { memset(_data, 0x00, _size); setFrequency(Frequency::fromMHz(88)); } Frequency AnytoneCodeplug::WFMVFOElement::frequency() const { return Frequency::fromHz(((unsigned long long)getBCD8_le(0))*100); } void AnytoneCodeplug::WFMVFOElement::setFrequency(Frequency freq) { setBCD8_le(0, freq.inHz()/100); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::DMREncryptionKeyListElement * ********************************************************************************************* */ AnytoneCodeplug::DMREncryptionKeyListElement::DMREncryptionKeyListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneCodeplug::DMREncryptionKeyListElement::DMREncryptionKeyListElement(uint8_t *ptr) : Element(ptr, DMREncryptionKeyListElement::size()) { // pass... } void AnytoneCodeplug::DMREncryptionKeyListElement::clear() { memset(_data, 0x00, _size); } bool AnytoneCodeplug::DMREncryptionKeyListElement::hasKey(unsigned int n) const { if (n >= Limit::numEntries()) return false; return 0 != getUInt16_be(n*Offset::betweenKeys()); } QByteArray AnytoneCodeplug::DMREncryptionKeyListElement::key(unsigned int n) const { if (n >= Limit::numEntries()) return QByteArray::fromHex("0000"); return QByteArray((char *)_data + n*Offset::betweenKeys(), 2); } void AnytoneCodeplug::DMREncryptionKeyListElement::setKey(unsigned int n, const BasicEncryptionKey &key) { if (n >= Limit::numEntries()) return; memcpy(_data + n*Offset::betweenKeys(), key.key().data(), 2); } void AnytoneCodeplug::DMREncryptionKeyListElement::clearKey(unsigned int n) { if (n >= Limit::numEntries()) return; setUInt16_be(n*Offset::betweenKeys(), 0000); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::EnhancedEncryptionKeyListElement * ********************************************************************************************* */ AnytoneCodeplug::EnhancedEncryptionKeyListElement::EnhancedEncryptionKeyListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneCodeplug::EnhancedEncryptionKeyListElement::EnhancedEncryptionKeyListElement(uint8_t *ptr) : Element(ptr, EnhancedEncryptionKeyListElement::size()) { // pass... } void AnytoneCodeplug::EnhancedEncryptionKeyListElement::clear() { memset(_data, 0x00, _size); for (unsigned int i=0; i= Limit::numEntries()) return QByteArray(); return QByteArray::fromRawData((const char *)_data + Offset::keys() + n*Offset::betweenKeys(), 2); } void AnytoneCodeplug::EnhancedEncryptionKeyListElement::setKey(unsigned int n, const QByteArray &key) { if ((n >= Limit::numEntries()) || (2 != key.size())) return; memcpy(_data + Offset::keys() + n*Offset::betweenKeys(), key.constData(), 2); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug::ContactMapElement * ********************************************************************************************* */ AnytoneCodeplug::ContactMapElement::ContactMapElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } AnytoneCodeplug::ContactMapElement::ContactMapElement(uint8_t *ptr) : Element(ptr, ContactMapElement::size()) { // pass... } void AnytoneCodeplug::ContactMapElement::clear() { memset(_data, 0xff, _size); } bool AnytoneCodeplug::ContactMapElement::isValid() const { return (0xffffffff!=getUInt32_le(Offset::id())) && (0xffffffff!=getUInt32_le(Offset::index())); } bool AnytoneCodeplug::ContactMapElement::isGroup() const { uint32_t tmp = getUInt32_le(Offset::id()); return tmp & 0x01; } unsigned AnytoneCodeplug::ContactMapElement::id() const { uint32_t tmp = getUInt32_le(Offset::id()); tmp = tmp >> 1; return decode_dmr_id_bcd_le((uint8_t *)&tmp); } void AnytoneCodeplug::ContactMapElement::setID(unsigned id, bool group) { uint32_t tmp; encode_dmr_id_bcd_le((uint8_t *)&tmp, id); tmp = ( (tmp << 1) | (group ? 1 : 0) ); setUInt32_le(Offset::id(), tmp); } unsigned AnytoneCodeplug::ContactMapElement::index() const { return getUInt32_le(Offset::index()); } void AnytoneCodeplug::ContactMapElement::setIndex(unsigned idx) { setUInt32_le(Offset::index(), idx); } /* ********************************************************************************************* * * Implementation of AnytoneCodeplug * ********************************************************************************************* */ AnytoneCodeplug::AnytoneCodeplug(const QString &label, QObject *parent) : Codeplug(parent), _label(label) { // pass... } AnytoneCodeplug::~AnytoneCodeplug() { // pass... } void AnytoneCodeplug::clear() { while (this->numImages()) remImage(0); addImage(_label); // Allocate bitmaps this->allocateBitmaps(); } bool AnytoneCodeplug::index(Config *config, Context &ctx, const ErrorStack &err) const { Q_UNUSED(err) // AM channels are indexed separately. ctx.addTable(&AMChannel::staticMetaObject); // Map radio IDs for (int i=0; iradioIDs()->count(); i++) { if (config->radioIDs()->get(i)->is()) ctx.add(config->radioIDs()->get(i)->as(), i); } // Map digital and DTMF contacts for (int i=0, d=0, a=0; icontacts()->count(); i++) { if (config->contacts()->contact(i)->is()) { ctx.add(config->contacts()->contact(i)->as(), d); d++; } else if (config->contacts()->contact(i)->is()) { ctx.add(config->contacts()->contact(i)->as(), a); a++; } else { logInfo() << "Cannot index contact '" << config->contacts()->contact(i)->name() << "'. Contact type '" << config->contacts()->contact(i)->metaObject()->className() << "' not supported by or implemented for AnyTone devices."; } } // Map rx group lists for (int i=0; irxGroupLists()->count(); i++) ctx.add(config->rxGroupLists()->list(i), i); // Map channels for (int i=0, common=0, am=0; ichannelList()->count(); i++) { if (config->channelList()->channel(i)->is() || config->channelList()->channel(i)->is()) { ctx.add(config->channelList()->channel(i), common++); } else if (config->channelList()->channel(i)->is()) { ctx.add(config->channelList()->channel(i), am++); } } // Map zones for (int i=0; izones()->count(); i++) ctx.add(config->zones()->zone(i), i); // Map scan lists for (int i=0; iscanlists()->count(); i++) ctx.add(config->scanlists()->scanlist(i), i); // Map DMR APRS systems for (int i=0,a=0,d=0; iposSystems()->count(); i++) { if (config->posSystems()->system(i)->is()) { ctx.add(config->posSystems()->system(i)->as(), d); d++; } else if (config->posSystems()->system(i)->is()) { auto *aprs = config->posSystems()->system(i)->as(); ctx.add(aprs, a); a++; // Index FM APRS frequencies (referenced in channel extensions). if (auto *ext = aprs->anytoneExtension()) { for (int j=0; jfrequencies()->count(); j++) { // Index 0 = default, so index is 1-based ctx.add(ext->frequencies()->get(j)->as(), j+1); } } } } // Map roaming for (int i=0; iroamingZones()->count(); i++) ctx.add(config->roamingZones()->zone(i), i); for (int i=0; iroamingChannels()->count(); i++) ctx.add(config->roamingChannels()->channel(i), i); // Map auto-repeater offsets if (config->settings()->anytoneExtension()) { auto *autoRep = config->settings()->anytoneExtension()->autoRepeaterSettings(); for (int i=0; ioffsets()->count(); i++) ctx.add(autoRep->offsets()->get(i)->as(), i); } // Map SMS templates for (int i=0; ismsExtension()->smsTemplates()->count(); i++) ctx.add(config->smsExtension()->smsTemplates()->get(i)->as(), i); if (config->commercialExtension()) { // Map all encryption keys for (int i=0, basic=0, enhanced=0, aes=0; icommercialExtension()->encryptionKeys()->count(); i++) { auto key = config->commercialExtension()->encryptionKeys()->key(i); if (key->is()) ctx.add(key->as(), basic++); else if (key->is()) ctx.add(key->as(), enhanced++); else if (key->is()) ctx.add(key->as(), aes++); } } return true; } bool AnytoneCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { if (! this->createElements(ctx, err)) { errMsg(err) << "Cannot decode AnyTone codeplug: Creation of config objects failed."; return false; } if (! this->linkElements(ctx, err)) { errMsg(err) << "Cannot decode AnyTone codeplug: Linking of config objects failed."; return false; } return true; } Config * AnytoneCodeplug::preprocess(Config *config, const ErrorStack &err) const { Config *intermediate = Codeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot pre-process codeplug for anytone device."; return nullptr; } // Remove all AM & M17 channels ObjectFilterVisitor amFilter{AMChannel::staticMetaObject, M17Channel::staticMetaObject}; if (! amFilter.process(intermediate, err)) { errMsg(err) << "Remove AM & M17 channels."; delete intermediate; return nullptr; } ZoneSplitVisitor splitter; if (! splitter.process(intermediate, err)) { errMsg(err) << "Split multi-VFO zones."; delete intermediate; return nullptr; } return intermediate; } bool AnytoneCodeplug::postprocess(Config *config, const ErrorStack &err) const { if (! Codeplug::postprocess(config, err)) { errMsg(err) << "Cannot post-process codeplug for anytone device."; return false; } ZoneMergeVisitor merger; if (! merger.process(config, err)) { errMsg(err) << "Cannot post-process codeplug for anytone device."; return false; } return true; } bool AnytoneCodeplug::encode(Config *config, const Flags &flags, const ErrorStack &err) { Context ctx(config); // Register table for auto-repeater offsets ctx.addTable(&AnytoneAutoRepeaterOffset::staticMetaObject); // Register table for FM APRS frequencies ctx.addTable(&AnytoneAPRSFrequency::staticMetaObject); if (! index(config, ctx, err)) { errMsg(err) << "Cannot encode anytone codeplug."; return false; } // If codeplug is generated from scratch -> clear and reallocate if (! flags.updateCodeplug()) { // Clear codeplug this->clear(); // Then allocate elements this->allocateUpdated(); } // First set bitmaps this->setBitmaps(ctx); // Allocate all memory elements representing the common config this->allocateForEncoding(); // Then encode everything. return this->encodeElements(flags, ctx, err); } bool AnytoneCodeplug::decode(Config *config, const ErrorStack &err) { // Maps code-plug indices to objects Context ctx(config); // Register table for auto-repeater offsets ctx.addTable(&AnytoneAutoRepeaterOffset::staticMetaObject); // Register table for FM APRS frequencies ctx.addTable(&AnytoneAPRSFrequency::staticMetaObject); return this->decodeElements(ctx, err); } ================================================ FILE: lib/anytone_codeplug.hh ================================================ #ifndef ANYTONECODEPLUG_HH #define ANYTONECODEPLUG_HH #include "anytone_settingsextension.hh" #include "bootsettings.hh" #include "channel.hh" #include "codeplug.hh" #include "contact.hh" #include class RadioSettings; /** Base class interface for all Anytone radio codeplugs. * * This class extends the generic @c CodePlug to provide an interface to the multi-step up and * download of the binary codeplug. In contrast to the majority of radios, the Anytone codeplugs * are heavily segmented and only valid sections are read from a written to the device. * * @ingroup anytone */ class AnytoneCodeplug : public Codeplug { Q_OBJECT public: /** Implements encoding of CTCSS tones. */ struct CTCSS { public: /** Encodes Signaling::Code CTCSS tones. */ static uint8_t encode(const SelectiveCall &tone); /** Decodes to Signaling::Code CTCSS tones. */ static SelectiveCall decode(uint8_t code); protected: /** Translation table. */ static SelectiveCall _codeTable[52]; }; /** Represents the base class for inverted bytemaps in all AnyTone codeplugs. * This is obviously a result of a lazy firmware developer. There is already some code in the * firmware to handle bitmaps. The developer, however, copied some BS code from elsewhere. It is * inverted, because erased flash memory is usually initialized with 0xff. However, the AnyTone * memory gets erased to 0x00. So the inversion is not necessary. Someone really took pride in * his/her work and consequently, I need to implement some BS elements more. */ class InvertedBytemapElement: public Element { protected: /** Hidden constructor. */ InvertedBytemapElement(uint8_t *ptr, size_t size); public: /** Clears the bitmap, disables all channels. */ void clear(); /** Returns @c true if the given index is valid. */ virtual bool isEncoded(unsigned int idx) const ; /** Enables/disables the specified index. */ virtual void setEncoded(unsigned int idx, bool enable); /** Enables the first n elements. */ virtual void enableFirst(unsigned int n); }; /** Represents the base class for channel encodings in all AnyTone codeplugs. * * Memory layout of encoded channel (0x40 bytes): * @verbinclude anytone_channel.txt */ class ChannelElement: public Element { public: /** Defines all possible channel modes, see @c channelMode. */ enum class Mode { Analog = 0, ///< Analog channel. Digital = 1, ///< Digital (DMR) channel. MixedAnalog = 2, ///< Mixed, analog channel with digital RX. MixedDigital = 3 ///< Mixed, digital channel with analog RX. }; /** Defines all possible power settings.*/ enum Power { POWER_LOW = 0, ///< Low power, usually 1W. POWER_MIDDLE = 1, ///< Medium power, usually 2.5W. POWER_HIGH = 2, ///< High power, usually 5W. POWER_TURBO = 3 ///< Higher power, usually 7W on VHF and 6W on UHF. }; /** Defines all possible repeater modes. */ enum class RepeaterMode { Simplex = 0, ///< Simplex mode, that is TX frequency = RX frequency. @c tx_offset is ignored. Positive = 1, ///< Repeater mode with positive @c tx_offset. Negative = 2 ///< Repeater mode with negative @c tx_offset. }; /** Possible analog signaling modes. */ enum class SignalingMode { None = 0, ///< None. CTCSS = 1, ///< Use CTCSS tones DCS = 2 ///< Use DCS codes. }; /** Defines possible admit criteria. */ enum class Admit { Always = 0, ///< For both channel types. Free = 1, ///< For digital channels. DifferentColorCode = 2, ///< For digital channels. SameColorCode = 3, ///< For digital channels. Tone = 1, ///< For analog channels Busy = 2 ///< For analog channels. }; /** Defines all possible optional signalling settings. */ enum class OptSignaling { Off = 0, ///< None. DTMF = 1, ///< Use DTMF. TwoTone = 2, ///< Use 2-tone. FiveTone = 3 ///< Use 5-tone. }; protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ChannelElement(uint8_t *ptr); /** Destructor. */ virtual ~ChannelElement(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0040; } /** Resets the channel. */ void clear(); /** Returns the RX frequency in Hz. */ virtual unsigned rxFrequency() const; /** Sets the RX frequency in Hz. */ virtual void setRXFrequency(unsigned hz); /** Returns the TX frequency offset in Hz. * This method returns an unsigned value, the sign of the offset frequency is stored in * @c repeaterMode(). */ virtual unsigned txOffset() const; /** Sets the TX frequency offset in Hz. * This method accepts unsigned values, the sign of the offset frequency is stored in * @c repeaterMode(). */ virtual void setTXOffset(unsigned hz); /** Returns the TX frequency in Hz. */ virtual unsigned txFrequency() const; /** Sets the TX frequency indirectly. That is, relative to the RX frequency which must be set * first. This method also updates the @c repeaterMode. */ virtual void setTXFrequency(unsigned hz); /** Returns the channel mode (analog, digtital, etc). */ virtual Mode mode() const; /** Sets the channel mode. */ virtual void setMode(Mode mode); /** Returns the channel power. */ virtual Channel::Power power() const; /** Sets the channel power. */ virtual void setPower(Channel::Power power); /** Returns the band width of the channel. */ virtual FMChannel::Bandwidth bandwidth() const; /** Sets the band width of the channel. */ virtual void setBandwidth(FMChannel::Bandwidth bw); /** Returns the transmit offset direction. */ virtual RepeaterMode repeaterMode() const; /** Sets the transmit offset direction. */ virtual void setRepeaterMode(RepeaterMode mode); /** Returns the RX signaling mode */ virtual SignalingMode rxSignalingMode() const; /** Sets the RX signaling mode */ virtual void setRXSignalingMode(SignalingMode mode); /** Simplified access to RX signaling (tone). */ virtual SelectiveCall rxTone() const; /** Sets the RX signaling (tone). */ virtual void setRXTone(const SelectiveCall &code); /** Returns the TX signaling mode */ virtual SignalingMode txSignalingMode() const; /** Sets the TX signaling mode */ virtual void setTXSignalingMode(SignalingMode mode); /** Simplified access to TX signaling (tone). */ virtual SelectiveCall txTone() const; /** Sets the RX signaling (tone). */ virtual void setTXTone(const SelectiveCall &code); /** Returns @c true if RX and TX frequencies are swapped. */ virtual bool rxTxSwapped() const; /** Swaps RX and TX frequencies. */ virtual void enableSwapRxTx(bool enable); /** Returns @c true if the RX only is enabled. */ virtual bool rxOnly() const; /** Enables/disables RX only. */ virtual void enableRXOnly(bool enable); /** Returns @c true if the call confirm is enabled. */ virtual bool callConfirm() const; /** Enables/disables call confirm. */ virtual void enableCallConfirm(bool enable); /** Returns @c true if the talkaround is enabled. */ virtual bool talkaround() const; /** Enables/disables talkaround. */ virtual void enableTalkaround(bool enable); /** Returns @c true if the TX CTCSS tone frequency is custom (non standard). */ virtual bool txCTCSSIsCustom() const; /** Returns the TX CTCSS tone. */ virtual SelectiveCall txCTCSS() const; /** Sets the TX CTCSS tone. */ virtual void setTXCTCSS(const SelectiveCall &tone); /** Enables TX custom CTCSS frequency. */ virtual void enableTXCustomCTCSS(); /** Returns @c true if the RX CTCSS tone frequency is custom (non standard). */ virtual bool rxCTCSSIsCustom() const; /** Returns the RX CTCSS tone. */ virtual SelectiveCall rxCTCSS() const; /** Sets the RX CTCSS tone. */ virtual void setRXCTCSS(const SelectiveCall &tone); /** Enables RX custom CTCSS frequency. */ virtual void enableRXCustomCTCSS(); /** Returns the TX DCS code. */ virtual SelectiveCall txDCS() const; /** Sets the TX DCS code. */ virtual void setTXDCS(const SelectiveCall &code); /** Returns the RX DCS code. */ virtual SelectiveCall rxDCS() const; /** Sets the RX DCS code. */ virtual void setRXDCS(const SelectiveCall &code); /** Returns the custom CTCSS frequency in Hz. */ virtual double customCTCSSFrequency() const; /** Sets the custom CTCSS frequency in Hz. */ virtual void setCustomCTCSSFrequency(double hz); /** Returns the 2-tone decode index (0-based). */ virtual unsigned twoToneDecodeIndex() const; /** Sets the 2-tone decode index (0-based). */ virtual void setTwoToneDecodeIndex(unsigned idx); /** Returns the transmit contact index (0-based). */ virtual unsigned contactIndex() const; /** Sets the transmit contact index (0-based). */ virtual void setContactIndex(unsigned idx); /** Returns the radio ID index (0-based). */ virtual unsigned radioIDIndex() const; /** Sets the radio ID index (0-based). */ virtual void setRadioIDIndex(unsigned idx); /** Returns @c true if the sequelch is silent and @c false if open. */ virtual AnytoneFMChannelExtension::SquelchMode squelchMode() const; /** Enables/disables silent squelch. */ virtual void setSquelchMode(AnytoneFMChannelExtension::SquelchMode mode); /** Returns the admit criterion. */ virtual Admit admit() const; /** Sets the admit criterion. */ virtual void setAdmit(Admit admit); /** Returns the optional signalling type. */ virtual OptSignaling optionalSignaling() const; /** Sets the optional signaling type. */ virtual void setOptionalSignaling(OptSignaling sig); /** Returns @c true, if a scan list index is set. */ virtual bool hasScanListIndex() const; /** Returns the scan list index (0-based). */ virtual unsigned scanListIndex() const; /** Sets the scan list index (0-based). */ virtual void setScanListIndex(unsigned idx); /** Clears the scan list index. */ virtual void clearScanListIndex(); /** Returns @c true, if a group list index is set. */ virtual bool hasGroupListIndex() const; /** Returns the scan list index (0-based). */ virtual unsigned groupListIndex() const; /** Sets the group list index (0-based). */ virtual void setGroupListIndex(unsigned idx); /** Clears the group list index. */ virtual void clearGroupListIndex(); /** Returns the two-tone ID index (0-based). */ virtual unsigned twoToneIDIndex() const; /** Sets the two-tone ID index (0-based). */ virtual void setTwoToneIDIndex(unsigned idx); /** Returns the five-tone ID index (0-based). */ virtual unsigned fiveToneIDIndex() const; /** Sets the five-tone ID index (0-based). */ virtual void setFiveToneIDIndex(unsigned idx); /** Returns the DTFM ID index (0-based). */ virtual unsigned dtmfIDIndex() const; /** Sets the DTMF ID index (0-based). */ virtual void setDTMFIDIndex(unsigned idx); /** Returns the color code. */ virtual unsigned colorCode() const; /** Sets the color code. */ virtual void setColorCode(unsigned code); /** Returns the time slot. */ virtual DMRChannel::TimeSlot timeSlot() const; /** Sets the time slot. */ virtual void setTimeSlot(DMRChannel::TimeSlot ts); /** Returns @c true if SMS confirmation is enabled. */ virtual bool smsConfirm() const; /** Enables/disables SMS confirmation. */ virtual void enableSMSConfirm(bool enable); /** Returns @c true if simplex TDMA is enabled. */ virtual bool simplexTDMA() const; /** Enables/disables simplex TDMA confirmation. */ virtual void enableSimplexTDMA(bool enable); /** Returns @c true if adaptive TDMA is enabled. */ virtual bool adaptiveTDMA() const; /** Enables/disables adaptive TDMA. */ virtual void enableAdaptiveTDMA(bool enable); /** Returns @c true if RX APRS is enabled. */ virtual bool rxAPRS() const; /** Enables/disables RX APRS. */ virtual void enableRXAPRS(bool enable); /** Returns @c true if lone worker is enabled. */ virtual bool loneWorker() const; /** Enables/disables lone worker. */ virtual void enableLoneWorker(bool enable); /** Returns the channel name. */ virtual QString name() const; /** Sets the channel name. */ virtual void setName(const QString &name); /** Constructs a generic @c Channel object from the codeplug channel. */ virtual Channel *toChannelObj(Context &ctx) const; /** Links a previously constructed channel to the rest of the configuration. */ virtual bool linkChannelObj(Channel *c, Context &ctx) const; /** Initializes this codeplug channel from the given generic configuration. */ virtual bool fromChannelObj(const Channel *c, Context &ctx); public: /** Some limits for the channel element. */ struct Limit: Element::Limit { /// Maximum name length. static constexpr unsigned int nameLength() { return 16; } }; protected: /** Internal used offsets within the channel element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int rxFrequency() { return 0x0000; } static constexpr unsigned int txFrequencyOffset() { return 0x0004; } static constexpr Bit channelMode() { return {0x0008, 0}; } static constexpr Bit power() { return {0x0008, 2}; } static constexpr Bit bandwidth() { return {0x0008, 4}; } static constexpr Bit repeaterMode() { return {0x0008, 6}; } static constexpr Bit rxSignalingMode() { return {0x0009, 0}; } static constexpr Bit txSignalingMode() { return {0x0009, 2}; } static constexpr Bit swapRXTX() { return {0x0009, 4}; } static constexpr Bit rxOnly() { return {0x0009, 5}; } static constexpr Bit callConfirm() { return {0x0009, 6}; } static constexpr Bit talkaround() { return {0x0009, 7}; } static constexpr unsigned int txCTCSS() { return 0x000a; } static constexpr unsigned int rxCTCSS() { return 0x000b; } static constexpr unsigned int txDCS() { return 0x000c; } static constexpr unsigned int rxDCS() { return 0x000e; } static constexpr unsigned int customCTCSS() { return 0x0010; } static constexpr unsigned int twoFunctionIndex() { return 0x0012; } static constexpr unsigned int contactIndex() { return 0x0014; } static constexpr unsigned int radioIdIndex() { return 0x0018; } static constexpr Bit squelchMode() { return {0x0019, 4}; } static constexpr Bit admitCriterion() { return {0x001a, 0}; } static constexpr Bit optionalSingnaling() { return {0x001a, 4}; } static constexpr unsigned int scanListIndex() { return 0x001b; } static constexpr unsigned int groupListIndex() { return 0x001c; } static constexpr unsigned int twoToneIDIndex() { return 0x001d; } static constexpr unsigned int fiveToneIDIndex() { return 0x001e; } static constexpr unsigned int dtmfIDIndex() { return 0x001f; } static constexpr unsigned int colorCode() { return 0x0020; } static constexpr Bit timeSlot() { return {0x0021, 0}; } static constexpr Bit smsConfirm() { return {0x0021, 1}; } static constexpr Bit simplexTDMA() { return {0x0021, 2}; } static constexpr Bit adaptiveTDMA() { return {0x0021, 4}; } static constexpr Bit rxAPRS() { return {0x0021, 5}; } static constexpr Bit loneWorker() { return {0x0021, 7}; } static constexpr unsigned int name() { return 0x0023; } /// @endcond }; }; /** Represents the channel bitmaps in all AnyTone codeplugs. */ class ChannelBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ ChannelBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ChannelBitmapElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0200; } }; /** Represents the base class for contacts in all AnyTone codeplugs. * * Memory layout of encoded contact (0x64 bytes): * @verbinclude anytone_contact.txt */ class ContactElement: public Element { protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ContactElement(uint8_t *ptr); /** Destructor. */ virtual ~ContactElement(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0064; } /** Resets the contact element. */ void clear(); /** Returns @c true if the element is valid. */ bool isValid() const; /** Returns the contact type. */ virtual DMRContact::Type type() const; /** Sets the contact type. */ virtual void setType(DMRContact::Type type); /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString &name); /** Returns the contact number. */ virtual unsigned number() const; /** Sets the contact number. */ virtual void setNumber(unsigned number); /** Returns the alert type. */ virtual AnytoneContactExtension::AlertType alertType() const; /** Sets the alert type. */ virtual void setAlertType(AnytoneContactExtension::AlertType type); /** Assembles a @c DigitalContact from this contact. */ virtual DMRContact *toContactObj(Context &ctx) const; /** Constructs this contact from the give @c DigitalContact. */ virtual bool fromContactObj(const DMRContact *contact, Context &ctx); public: /** Some limits for the contact. */ struct Limit: public Element::Limit { /// Maximum name length. static constexpr unsigned int nameLength() { return 16; } }; protected: /** Internal offsets within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int type() { return 0x0000; } static constexpr unsigned int name() { return 0x0001; } static constexpr unsigned int number() { return 0x0023; } static constexpr unsigned int alertType() { return 0x0027; } /// @endcond }; }; /** Represents the contact bitmaps in all AnyTone codeplugs. */ class ContactBitmapElement: public InvertedBitmapElement { protected: /** Hidden constructor. */ ContactBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactBitmapElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0500; } }; /** Represents the base class for analog (DTMF) contacts in all AnyTone codeplugs. * * Encoding of the DTMF contact (0x30 bytes): * @verbinclude anytone_dtmfcontact.txt */ class DTMFContactElement: public Element { protected: /** Hidden constructor. */ DTMFContactElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit DTMFContactElement(uint8_t *ptr); /** Destructor. */ virtual ~DTMFContactElement(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0018; } /** Resets the contact element. */ void clear(); /** Returns the number of the contact. */ virtual QString number() const; /** Sets the number of the contact. */ virtual void setNumber(const QString &number); /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString &name); /** Creates an DTMF contact from the entry. */ virtual DTMFContact *toContact() const; /** Encodes an DTMF contact from the given one. */ virtual bool fromContact(const DTMFContact *contact); public: /** Some limits for the element. */ struct Limit { static constexpr unsigned int digitCount() { return 14; } ///< The max number of digits. static constexpr unsigned int nameLength() { return 15; } ///< Maximum name length. }; protected: /** Internal used offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int digits() { return 0x0000; } static constexpr unsigned int numDigits() { return 0x0007; } static constexpr unsigned int name() { return 0x0008; } /// @endcond }; }; /** Represents the DTMF contact byte map, indicating which contacts are valid. */ class DTMFContactBytemapElement: public InvertedBytemapElement { protected: /** Hidden constructor. */ DTMFContactBytemapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit DTMFContactBytemapElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0100; } }; /** Represents the base class for group lists in all AnyTone codeplugs. * * Encoding of a group list (0x120 bytes): * @verbinclude anytone_grouplist.txt */ class GroupListElement: public Element { protected: /** Hidden constructor. */ GroupListElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ GroupListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0120; } /** Clears the group list. */ void clear(); /** Returns @c true if the group list is valid. */ bool isValid() const; /** Returns the name of the group list. */ virtual QString name() const; /** Sets the name of the group list. */ virtual void setName(const QString &name); /** Returns @c true if the n-th member index is valid. */ virtual bool hasMemberIndex(unsigned n) const; /** Returns the n-th member index. */ virtual unsigned memberIndex(unsigned n) const; /** Sets the n-th member index. */ virtual void setMemberIndex(unsigned n, unsigned idx); /** Clears the n-th member index. */ virtual void clearMemberIndex(unsigned n); /** Constructs a new @c RXGroupList from this group list. * None of the members are added yet. Call @c linkGroupList * to do that. */ virtual RXGroupList *toGroupListObj() const; /** Populates the @c RXGroupList from this group list. The CodeplugContext * is used to map the member indices. */ virtual bool linkGroupList(RXGroupList *lst, Context &ctx) const; /** Constructs this group list from the given @c RXGroupList. */ virtual bool fromGroupListObj(const RXGroupList *lst, Context &ctx); public: /** Some limits for the group list. */ struct Limit: public Element::Limit { /** Maximum number of members. */ static constexpr unsigned int members() { return 64; } /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int members() { return 0x0000; } static constexpr unsigned int betweenMembers() { return 0x0004; } static constexpr unsigned int name() { return 0x0100; } /// @endcond }; }; /** Represents the bitmap indicating which group list element is valid. */ class GroupListBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ GroupListBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit GroupListBitmapElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0020; } }; /** Represents the base class for scan lists in all AnyTone codeplugs. * * Memory layout of encoded scanlist (0x90 bytes): * @verbinclude anytone_scanlist.txt */ class ScanListElement: public Element { public: /** Defines all possible priority channel selections. */ enum class PriChannel { Off = 0, ///< Off. Primary = 1, ///< Priority Channel Select 1. Secondary = 2, ///< Priority Channel Select 2. Both = 3 ///< Priority Channel Select 1 + Priority Channel Select 2. }; /** Defines all possible reply channel selections. */ enum class RevertChannel { Selected = 0, ///< Selected channel. SelectedActive = 1, ///< Selected + active channel. Primary = 2, ///< Primary channel. Secondary = 3, ///< Secondary channel. LastCalled = 4, ///< Last Called. LastUsed = 5, ///< Last Used. PrimaryActive = 6, ///< Primary + active channel. SecondaryActive = 7 ///< Secondary + active channel. }; protected: /** Hidden constructor. */ ScanListElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ScanListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0090; } /** Resets the scan list. */ void clear(); /** Returns the priority channel selection. */ virtual PriChannel priorityChannels() const; /** Sets the priority channel selection. */ virtual void setPriorityChannels(PriChannel sel); /** Returns @c true if the primary channel is set. */ virtual bool hasPrimary() const; /** Returns @c true if the primary channel is set to the selected channel. */ virtual bool primaryIsSelected() const; /** Returns the primary channel index. */ virtual unsigned primary() const; /** Sets the primary channel index. */ virtual void setPrimary(unsigned idx); /** Sets the primary channel to be selected channel. */ virtual void setPrimarySelected(); /** Clears the primary channel index. */ virtual void clearPrimaryChannel(); /** Returns @c true if the secondary channel is set. */ virtual bool hasSecondary() const; /** Returns @c true if the secondary channel is set to the selected channel. */ virtual bool secondaryIsSelected() const; /** Returns the secondary channel index. */ virtual unsigned secondary() const; /** Sets the secondary channel index. */ virtual void setSecondary(unsigned idx); /** Sets the secondary channel to be selected channel. */ virtual void setSecondarySelected(); /** Clears the secondary channel index. */ virtual void clearSecondaryChannel(); /** Returns the look back time A in seconds. */ virtual Interval lookBackTimeA() const; /** Sets the look back time A in seconds. */ virtual void setLookBackTimeA(const Interval &sec); /** Returns the look back time B in seconds. */ virtual Interval lookBackTimeB() const; /** Sets the look back time B in seconds. */ virtual void setLookBackTimeB(const Interval& sec); /** Returns the drop out delay in seconds. */ virtual Interval dropOutDelay() const; /** Sets the drop out delay in seconds. */ virtual void setDropOutDelay(const Interval& sec); /** Returns the dwell time in seconds. */ virtual Interval dwellTime() const; /** Sets the dwell time in seconds. */ virtual void setDwellTime(const Interval& sec); /** Returns the revert channel type. */ virtual RevertChannel revertChannel() const; /** Sets the revert channel type. */ virtual void setRevertChannel(RevertChannel type); /** Returns the name of the scan list. */ virtual QString name() const; /** Sets the name of the scan list. */ virtual void setName(const QString &name); /** Returns @c true if the n-th member index is set. */ virtual bool hasMemberIndex(unsigned n) const; /** Returns the n-th member index. */ virtual unsigned memberIndex(unsigned n) const; /** Sets the n-th member index. */ virtual void setMemberIndex(unsigned n, unsigned idx); /** Clears the n-th member index. */ virtual void clearMemberIndex(unsigned n); /** Constructs a ScanList object from this definition. This only sets the properties of * the scan list. To associate all members with the scan list object, call @c linkScanListObj. */ virtual ScanList *toScanListObj() const; /** Links all channels (members and primary channels) with the given scan-list object. */ virtual bool linkScanListObj(ScanList *lst, Context &ctx) const; /** Constructs the binary representation from the give config. */ virtual bool fromScanListObj(ScanList *lst, Context &ctx); public: /** Some limits for the scan list. */ struct Limit: public Element::Limit { /// Maximum number of members. static constexpr unsigned int members() { return 50; } /// Maximum name length. static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int priorityChannel() { return 0x0001; } static constexpr unsigned int primaryChannel() { return 0x0002; } static constexpr unsigned int secondaryChannel() { return 0x0004; } static constexpr unsigned int lookBackTimeA() { return 0x0006; } static constexpr unsigned int lookBackTimeB() { return 0x0008; } static constexpr unsigned int dropOutDelay() { return 0x000a; } static constexpr unsigned int dwellTime() { return 0x000c; } static constexpr unsigned int revertChannel() { return 0x000e; } static constexpr unsigned int name() { return 0x000f; } static constexpr unsigned int members() { return 0x0020; } static constexpr unsigned int betweenMembers() { return 0x0002; } /// @endcond }; }; /** Represents the bitmap indicating which scanlist elements are valid. */ class ScanListBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ ScanListBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ScanListBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x00000020; } }; /** Represents the base class for radio IDs in all AnyTone codeplugs. * * Memory layout of encoded scanlist (0x20 bytes): * @verbinclude anytone_radioid.txt */ class RadioIDElement: public Element { protected: /** Hidden constructor. */ RadioIDElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ RadioIDElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0020; } /** Resets the radio ID. */ void clear(); /** Returns the number of the radio ID. */ virtual unsigned number() const; /** Sets the number of the radio ID. */ virtual void setNumber(unsigned number); /** Returns the name of the radio ID. */ virtual QString name() const; /** Sets the name of the radio ID. */ virtual void setName(const QString &name); /** Encodes the given RadioID. */ virtual bool fromRadioID(DMRRadioID *id); /** Constructs a new radio id. */ virtual DMRRadioID *toRadioID() const; public: /** Some limits for the radio ID element. */ struct Limit: public Element::Limit { /// Maximum name length static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int number() { return 0x0000; } static constexpr unsigned int name() { return 0x0005; } /// @endcond }; }; /** Represents the bitmap indicating which radio IDs are valid. */ class RadioIDBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ RadioIDBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ RadioIDBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } }; /** Encodes the primary radio ID setting. */ class PrimaryRadioIdElement: public Element { public: /** Constructor. */ explicit PrimaryRadioIdElement(uint8_t *ptr); static constexpr unsigned int size() { return 0x0020; } void clear() override; bool isValid() const override; /** Returns the DMR id. */ virtual unsigned int number() const; /** Sets the DMR id. */ virtual void setNumber(unsigned int number); /** Returns the primary radio name. */ virtual QString name() const; /** Sets the primary radio name. */ virtual void setName(const QString &name); /** Returns @c true if the id is enabled. That is, overrides all other DMR ids. */ virtual bool enabled() const; /** Enables/disables primary DMR id. */ virtual void enable(bool enable); /** Encodes default DMR id as primary DMR id. */ virtual bool encode(const Flags &flags, Context &ctx, const ErrorStack &err); /** Decodes primary DMR id. Actually does nothing. * The decoding is delayed to the linking stage. */ virtual bool decode(Context &ctx, const ErrorStack &err); /** Links primary DMR id as default DMR id. */ virtual bool link(Context &ctx, const ErrorStack &err); public: /** Some limits for the primary DMR id. */ struct Limit: Element::Limit { /// Maximum name length. static constexpr unsigned int name() { return 26; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int id() { return 0x0000; } static constexpr unsigned int enabled() { return 0x0004; } static constexpr unsigned int name() { return 0x0005; } /// @endcond }; }; /** Represents the base class for the settings elements in all AnyTone codeplugs. * This class only implements those few settings, common to all devices and encoded the same way. * It also defines all common settings as interfaces. * * Memory layout of encoded general settings (0xd0 bytes): * @verbinclude anytone_generalsettings.txt */ class GeneralSettingsElement: public Element { public: /** Possible automatic shutdown delays. */ enum class AutoShutdown { Off = 0, After10min = 1, After30min = 2, After60min = 3, After120min = 4, }; /** Possible encoding of boot display settings. */ enum class BootDisplay { Default = 0, CustomText = 1, CustomImage = 2 }; protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, unsigned size); public: /** Resets the general settings. */ void clear(); /** Returns @c true, if the key tone is enabled. */ virtual bool keyToneEnabled() const = 0; /** Enables/disables the key-tone. */ virtual void enableKeyTone(bool enable) = 0; /** Returns @c true if the radio displays frequencies instead of channels is enabled. */ virtual bool displayFrequency() const; /** Enables/disables the frequency display. */ virtual void enableDisplayFrequency(bool enable); /** Returns @c true if auto key-lock is enabled. */ virtual bool autoKeyLock() const; /** Enables/disables auto key-lock. */ virtual void enableAutoKeyLock(bool enable); /** Returns the auto-shutdown delay in minutes. */ virtual Interval autoShutdownDelay() const; /** Sets the auto-shutdown delay in minutes. */ virtual void setAutoShutdownDelay(Interval min); /** Returns the boot display mode. */ virtual BootSettings::BootDisplay bootDisplay() const; /** Sets the boot display mode. */ virtual void setBootDisplay(BootSettings::BootDisplay mode); /** Returns @c true if boot password is enabled. */ virtual bool bootPassword() const; /** Enables/disables boot password. */ virtual void enableBootPassword(bool enable); /** Squelch level of VFO A, (0=off). */ virtual Level squelchLevelA() const; /** Returns the squelch level for VFO A, (0=off). */ virtual void setSquelchLevelA(Level level); /** Squelch level of VFO B, (0=off). */ virtual Level squelchLevelB() const; /** Returns the squelch level for VFO B, (0=off). */ virtual void setSquelchLevelB(Level level); /** Returns the VFO scan type. */ virtual AnytoneSettingsExtension::VFOScanType vfoScanType() const = 0; /** Sets the VFO scan type. */ virtual void setVFOScanType(AnytoneSettingsExtension::VFOScanType type) = 0; /** Returns the mirophone gain. */ virtual Level dmrMicGain() const = 0; /** Sets the microphone gain. */ virtual void setDMRMicGain(Level gain) = 0; /** Returns the key function for a short press on the function key 1/A. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKeyAShort() const = 0; /** Sets the key function for a short press on the function key 1/A. */ virtual void setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a short press on the function key 2/B. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKeyBShort() const = 0; /** Sets the key function for a short press on the function key 2/B. */ virtual void setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a short press on the function key 3/C. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKeyCShort() const = 0; /** Sets the key function for a short press on the function key 3/C. */ virtual void setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a short press on the function key 1. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey1Short() const = 0; /** Sets the key function for a short press on the function key 1. */ virtual void setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a short press on the function key 2. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey2Short() const = 0; /** Sets the key function for a short press on the function key 2. */ virtual void setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a long press on the function key 1. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKeyALong() const = 0; /** Sets the key function for a long press on the function key 1. */ virtual void setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a long press on the function key 2. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKeyBLong() const = 0; /** Sets the key function for a long press on the function key 2. */ virtual void setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a long press on the function key 3. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKeyCLong() const = 0; /** Sets the key function for a long press on the function key 3. */ virtual void setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a long press on the function key 1. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey1Long() const = 0; /** Sets the key function for a long press on the function key 1. */ virtual void setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the key function for a long press on the function key 2. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey2Long() const = 0; /** Sets the key function for a long press on the function key 2. */ virtual void setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func) = 0; /** Returns the long-press duration in ms. */ virtual Interval longPressDuration() const = 0; /** Sets the long-press duration in ms. */ virtual void setLongPressDuration(Interval ms) = 0; /** Returns @c true if the knob is locked. */ virtual bool knobLock() const = 0; /** Enables/disables the knob lock. */ virtual void enableKnobLock(bool enable) = 0; /** Returns @c true if the keypad is locked. */ virtual bool keypadLock() const = 0; /** Enables/disables the keypad lock. */ virtual void enableKeypadLock(bool enable) = 0; /** Returns @c true if the sidekeys are locked. */ virtual bool sidekeysLock() const = 0; /** Enables/disables the sidekeys lock. */ virtual void enableSidekeysLock(bool enable) = 0; /** Returns @c true if the "professional" key is locked. */ virtual bool keyLockForced() const = 0; /** Enables/disables the "professional" key lock. */ virtual void enableKeyLockForced(bool enable) = 0; public: /** Returns @c true if the VFO A is in VFO mode. */ virtual bool vfoModeA() const = 0; /** Enables/disables VFO mode for VFO A. */ virtual void enableVFOModeA(bool enable) = 0; /** Returns @c true if the VFO B is in VFO mode. */ virtual bool vfoModeB() const = 0; /** Enables/disables VFO mode for VFO B. */ virtual void enableVFOModeB(bool enable) = 0; /** Returns the memory zone for VFO A. */ virtual unsigned memoryZoneA() const = 0; /** Sets the memory zone for VFO A. */ virtual void setMemoryZoneA(unsigned zone) = 0; /** Returns the memory zone for VFO B. */ virtual unsigned memoryZoneB() const = 0; /** Sets the memory zone for VFO B. */ virtual void setMemoryZoneB(unsigned zone) = 0; /** Returns @c true if recording is enabled. */ virtual bool recording() const = 0; /** Enables/disables recording. */ virtual void enableRecording(bool enable) = 0; /** Returns the display brightness. */ virtual unsigned brightness() const = 0; /** Sets the display brightness. */ virtual void setBrightness(unsigned level) = 0; /** Returns @c true if GPS is enabled. */ virtual bool gps() const = 0; /** Enables/disables recording. */ virtual void enableGPS(bool enable) = 0; /** Returns @c true if SMS alert is enabled. */ virtual bool smsAlert() const = 0; /** Enables/disables SMS alert. */ virtual void enableSMSAlert(bool enable) = 0; /** Returns @c true if the active channel is VFO B. */ virtual bool activeChannelB() const = 0; /** Enables/disables VFO B as the active channel. */ virtual void enableActiveChannelB(bool enable) = 0; /** Returns @c true if sub channel is enabled. */ virtual bool subChannel() const = 0; /** Enables/disables sub channel. */ virtual void enableSubChannel(bool enable) = 0; /** Returns @c true if call alert is enabled. */ virtual bool callAlert() const = 0; /** Enables/disables call alert. */ virtual void enableCallAlert(bool enable) = 0; /** Returns the GPS time zone. */ virtual QTimeZone gpsTimeZone() const = 0; /** Sets the GPS time zone. */ virtual void setGPSTimeZone(const QTimeZone &zone) = 0; /** Returns @c true if the talk permit tone is enabled for digital channels. */ virtual bool dmrTalkPermit() const = 0; /** Returns @c true if the talk permit tone is enabled for digital channels. */ virtual bool fmTalkPermit() const = 0; /** Enables/disables the talk permit tone for digital channels. */ virtual void enableDMRTalkPermit(bool enable) = 0; /** Enables/disables the talk permit tone for analog channels. */ virtual void enableFMTalkPermit(bool enable) = 0; /** Returns @c true if the reset tone is enabled for digital calls. */ virtual bool dmrResetTone() const = 0; /** Enables/disables the reset tone for digital calls. */ virtual void enableDMRResetTone(bool enable) = 0; /** Returns @c true if the idle channel tone is enabled. */ virtual bool idleChannelTone() const = 0; /** Enables/disables the idle channel tone. */ virtual void enableIdleChannelTone(bool enable) = 0; /** Returns the menu exit time in seconds. */ virtual Interval menuExitTime() const = 0; /** Sets the menu exit time in seconds. */ virtual void setMenuExitTime(Interval intv) = 0; /** Returns @c true if the startup tone is enabled. */ virtual bool startupTone() const = 0; /** Enables/disables the startup tone. */ virtual void enableStartupTone(bool enable) = 0; /** Returns @c true if the call-end prompt is enabled. */ virtual bool callEndPrompt() const = 0; /** Enables/disables the call-end prompt. */ virtual void enableCallEndPrompt(bool enable) = 0; /** Returns the maximum volume. */ virtual Level maxSpeakerVolume() const = 0; /** Sets the maximum volume. */ virtual void setMaxSpeakerVolume(Level level) = 0; /** Returns @c true if get GPS position is enabled. */ virtual bool getGPSPosition() const = 0; /** Enables/disables get GPS position. */ virtual void enableGetGPSPosition(bool enable) = 0; /** Returns @c true if the volume change prompt is enabled. */ virtual bool volumeChangePrompt() const = 0; /** Enables/disables the volume change prompt. */ virtual void enableVolumeChangePrompt(bool enable) = 0; /** Returns the auto repeater offset direction for VFO A. */ virtual AnytoneAutoRepeaterSettingsExtension::Direction autoRepeaterDirectionA() const = 0; /** Sets the auto-repeater offset direction for VFO A. */ virtual void setAutoRepeaterDirectionA(AnytoneAutoRepeaterSettingsExtension::Direction dir) = 0; /** Returns the last-caller display mode. */ virtual AnytoneDisplaySettingsExtension::LastCallerDisplayMode lastCallerDisplayMode() const = 0; /** Sets the last-caller display mode. */ virtual void setLastCallerDisplayMode(AnytoneDisplaySettingsExtension::LastCallerDisplayMode mode) = 0; /** Returns @c true if the clock is shown. */ virtual bool displayClock() const = 0; /** Enables/disables clock display. */ virtual void enableDisplayClock(bool enable) = 0; /** Returns @c true if the audio is "enhanced". */ virtual bool enhanceAudio() const = 0; /** Enables/disables "enhanced" audio. */ virtual void enableEnhancedAudio(bool enable) = 0; /** Returns the minimum VFO scan frequency for the UHF band in Hz. */ virtual Frequency minVFOScanFrequencyUHF() const = 0; /** Sets the minimum VFO scan frequency for the UHF band in Hz. */ virtual void setMinVFOScanFrequencyUHF(Frequency hz) = 0; /** Returns the maximum VFO scan frequency for the UHF band in Hz. */ virtual Frequency maxVFOScanFrequencyUHF() const = 0; /** Sets the maximum VFO scan frequency for the UHF band in Hz. */ virtual void setMaxVFOScanFrequencyUHF(Frequency hz) = 0; /** Returns the minimum VFO scan frequency for the VHF band in Hz. */ virtual Frequency minVFOScanFrequencyVHF() const = 0; /** Sets the minimum VFO scan frequency for the VHF band in Hz. */ virtual void setMinVFOScanFrequencyVHF(Frequency hz) = 0; /** Returns the maximum VFO scan frequency for the VHF band in Hz. */ virtual Frequency maxVFOScanFrequencyVHF() const = 0; /** Sets the maximum VFO scan frequency for the VHF band in Hz. */ virtual void setMaxVFOScanFrequencyVHF(Frequency hz) = 0; /** Returns @c true if the auto-repeater offset frequency for UHF is set. */ virtual bool hasAutoRepeaterOffsetFrequencyIndexUHF() const = 0; /** Returns the auto-repeater offset frequency index for UHF. */ virtual unsigned autoRepeaterOffsetFrequencyIndexUHF() const = 0; /** Sets the auto-repeater offset frequency index for UHF. */ virtual void setAutoRepeaterOffsetFrequenyIndexUHF(unsigned idx) = 0; /** Clears the auto-repeater offset frequency index for UHF. */ virtual void clearAutoRepeaterOffsetFrequencyIndexUHF() = 0; /** Returns @c true if the auto-repeater offset frequency for VHF is set. */ virtual bool hasAutoRepeaterOffsetFrequencyIndexVHF() const = 0; /** Returns the auto-repeater offset frequency index for UHF. */ virtual unsigned autoRepeaterOffsetFrequencyIndexVHF() const = 0; /** Sets the auto-repeater offset frequency index for VHF. */ virtual void setAutoRepeaterOffsetFrequenyIndexVHF(unsigned idx) = 0; /** Clears the auto-repeater offset frequency index for VHF. */ virtual void clearAutoRepeaterOffsetFrequencyIndexVHF() = 0; /** Returns @c true if the current contact is shown. */ virtual bool showCurrentContact() const = 0; /** Enables/disables display of current contact. */ virtual void enableShowCurrentContact(bool enable) = 0; /** Returns the call-tone melody. */ virtual void callToneMelody(Melody &melody) const = 0; /** Sets the call-tone melody. */ virtual void setCallToneMelody(const Melody &melody) = 0; /** Returns the idle-tone melody. */ virtual void idleToneMelody(Melody &melody) const = 0; /** Sets the idle-tone melody. */ virtual void setIdleToneMelody(const Melody &melody) = 0; /** Returns the reset-tone melody. */ virtual void resetToneMelody(Melody &melody) const = 0; /** Sets the reset-tone melody. */ virtual void setResetToneMelody(const Melody &melody) = 0; /** Returns @c true if the default boot channel is enabled. */ virtual bool defaultChannel() const = 0; /** Enables/disables default boot channel. */ virtual void enableDefaultChannel(bool enable) = 0; /** Returns the default zone index (0-based) for VFO A. */ virtual unsigned defaultZoneIndexA() const = 0; /** Sets the default zone (0-based) for VFO A. */ virtual void setDefaultZoneIndexA(unsigned idx) = 0; /** Returns the default zone index (0-based) for VFO B. */ virtual unsigned defaultZoneIndexB() const = 0; /** Sets the default zone (0-based) for VFO B. */ virtual void setDefaultZoneIndexB(unsigned idx) = 0; /** Returns @c true if the default channel for VFO A is VFO. */ virtual bool defaultChannelAIsVFO() const = 0; /** Returns the default channel index for VFO A. * Must be within default zone. If 0xff, default channel is VFO. */ virtual unsigned defaultChannelAIndex() const = 0; /** Sets the default channel index for VFO A. */ virtual void setDefaultChannelAIndex(unsigned idx) = 0; /** Sets the default channel for VFO A to be VFO. */ virtual void setDefaultChannelAToVFO() = 0; /** Returns @c true if the default channel for VFO B is VFO. */ virtual bool defaultChannelBIsVFO() const = 0; /** Returns the default channel index for VFO B. * Must be within default zone. If 0xff, default channel is VFO. */ virtual unsigned defaultChannelBIndex() const = 0; /** Sets the default channel index for VFO B. */ virtual void setDefaultChannelBIndex(unsigned idx) = 0; /** Sets the default channel for VFO B to be VFO. */ virtual void setDefaultChannelBToVFO() = 0; /** Returns @c true if the call is displayed instead of the name. */ virtual bool displayCall() const = 0; /** Enables/disables call display. */ virtual void enableDisplayCall(bool enable) = 0; /** Returns the display color for callsigns. */ virtual AnytoneDisplaySettingsExtension::Color callDisplayColor() const = 0; /** Sets the display color for callsigns. */ virtual void setCallDisplayColor(AnytoneDisplaySettingsExtension::Color color) = 0; /** Returns @c true if the GPS units are imperial. */ virtual bool gpsUnitsImperial() const = 0; /** Enables/disables imperial GPS units. */ virtual void enableGPSUnitsImperial(bool enable) = 0; /** Returns the minimum frequency in Hz for the auto-repeater range in VHF band. */ virtual Frequency autoRepeaterMinFrequencyVHF() const = 0; /** Sets the minimum frequency in Hz for the auto-repeater range in VHF band. */ virtual void setAutoRepeaterMinFrequencyVHF(Frequency Hz) = 0; /** Returns the maximum frequency in Hz for the auto-repeater range in VHF band. */ virtual Frequency autoRepeaterMaxFrequencyVHF() const = 0; /** Sets the maximum frequency in Hz for the auto-repeater range in VHF band. */ virtual void setAutoRepeaterMaxFrequencyVHF(Frequency Hz) = 0; /** Returns the minimum frequency in Hz for the auto-repeater range in UHF band. */ virtual Frequency autoRepeaterMinFrequencyUHF() const = 0; /** Sets the minimum frequency in Hz for the auto-repeater range in UHF band. */ virtual void setAutoRepeaterMinFrequencyUHF(Frequency Hz) = 0; /** Returns the maximum frequency in Hz for the auto-repeater range in UHF band. */ virtual Frequency autoRepeaterMaxFrequencyUHF() const = 0; /** Sets the maximum frequency in Hz for the auto-repeater range in UHF band. */ virtual void setAutoRepeaterMaxFrequencyUHF(Frequency Hz) = 0; /** Returns the auto-repeater direction for VFO B. */ virtual AnytoneAutoRepeaterSettingsExtension::Direction autoRepeaterDirectionB() const = 0; /** Sets the auto-repeater direction for VFO B. */ virtual void setAutoRepeaterDirectionB(AnytoneAutoRepeaterSettingsExtension::Direction dir) = 0; /** Returns @c true if the last heard is shown while pressing PTT. */ virtual bool showLastHeard() const = 0; /** Enables/disables showing last heard. */ virtual void enableShowLastHeard(bool enable) = 0; /** Returns @c true if the last caller is kept when changing channel. */ virtual bool keepLastCaller() const = 0; /** Enables/disables keeping the last caller when changing the channel. */ virtual void enableKeepLastCaller(bool enable) = 0; /** Encodes the general settings. */ virtual bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err); /** Updates the abstract config from general settings. */ virtual bool updateConfig(Context &ctx, const ErrorStack &err); /** Links the general settings. */ virtual bool linkSettings(RadioSettings *settings, Context &ctx, const ErrorStack &err); protected: /** Internal used offsets within the element. */ struct Offset : public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int displayMode() { return 0x0001; } static constexpr unsigned int autoKeyLock() { return 0x0002; } static constexpr unsigned int autoShutDown() { return 0x0003; } static constexpr unsigned int bootDisplay() { return 0x0006; } static constexpr unsigned int bootPassword() { return 0x0007; } static constexpr unsigned int squelchLevelA() { return 0x0009; } static constexpr unsigned int squelchLevelB() { return 0x000a; } /// @endcond }; }; /** Represents the base class for the extended settings element in many AnyTone codeplugs. That * is, every device after the D868UVE. It provides additional settings to the * @c AnytoneGeneralSettingsElement. * * As these elements differ heavily from device to device, there is no common encoding. This * class only defines an interface to get/set common settings. */ class ExtendedSettingsElement: public Element { protected: /** Hidden constructor. */ ExtendedSettingsElement(uint8_t *ptr, unsigned size); public: /** Returns the color of the channel name for VFO B. */ virtual AnytoneDisplaySettingsExtension::Color channelBNameColor() const = 0; /** Sets the channel name color for the VFO B. */ virtual void setChannelBNameColor(AnytoneDisplaySettingsExtension::Color) = 0; /** Returns the color of the zone name for VFO A. */ virtual AnytoneDisplaySettingsExtension::Color zoneANameColor() const = 0; /** Sets the zone name color for the VFO A. */ virtual void setZoneANameColor(AnytoneDisplaySettingsExtension::Color) = 0; /** Returns the color of the zone name for VFO B. */ virtual AnytoneDisplaySettingsExtension::Color zoneBNameColor() const = 0; /** Sets the zone name color for the VFO B. */ virtual void setZoneBNameColor(AnytoneDisplaySettingsExtension::Color) = 0; /** Encodes the settings from the config. */ virtual bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Update config from settings. */ virtual bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link config from settings extension. */ virtual bool linkConfig(Context &ctx, const ErrorStack &err=ErrorStack()); }; /** Represents the base class for zone channel list for all AnyTone codeplugs. * Zone channel lists assign a default channel to each zone for VFO A and B. * * Memory layout of ecoded zone channel lists (size 0x400 bytes): * @verbinclude anytone_zonechannellist.txt */ class ZoneChannelListElement: public Element { protected: /** Hidden constructor. */ ZoneChannelListElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ZoneChannelListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0400; } /** Resets the zone channel list. */ void clear(); /** Returns @c true if the channel index for VFO A is set for the n-th zone. */ virtual bool hasChannelA(unsigned n) const; /** Returns the channel index (0-based) for VFO A for the n-th zone. */ virtual unsigned channelIndexA(unsigned n) const; /** Sets the channel index (0-based) for VFO A for the n-th zone. */ virtual void setChannelIndexA(unsigned n, unsigned idx); /** Clears the channel index (0-based) for VFO A for the n-th zone. */ virtual void clearChannelIndexA(unsigned n); /** Returns @c true if the channel index for VFO B is set for the n-th zone. */ virtual bool hasChannelB(unsigned n) const; /** Returns the channel index (0-based) for VFO B for the n-th zone. */ virtual unsigned channelIndexB(unsigned n) const; /** Sets the channel index (0-based) for VFO B for the n-th zone. */ virtual void setChannelIndexB(unsigned n, unsigned idx); /** Clears the channel index (0-based) for VFO B for the n-th zone. */ virtual void clearChannelIndexB(unsigned n); public: /** Some limits for the channel lists. */ struct Limit: public Element::Limit { /// Maximum number of channels per VFO. static constexpr unsigned int zones() { return 250; } }; protected: /** Internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int channelsA() { return 0x0000;} static constexpr unsigned int channelsB() { return 0x0200;} static constexpr unsigned int betweenChannels() { return 0x0002;} /// @endcond }; }; /** Represents the bitmap indicating which zones are valid. */ class ZoneBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ ZoneBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ZoneBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } }; /** Represents the base class of the boot settings for all AnyTone codeplug. * * Memory layout of encoded boot settings (size 0x0030): * @verbinclude anytone_bootsettings.txt */ class BootSettingsElement: public Element { protected: /** Hidden constructor. */ BootSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ BootSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0030; } /** Resets the boot settings. */ void clear(); /** Returns the first intro line. */ virtual QString introLine1() const; /** Sets the first intro line. */ virtual void setIntroLine1(const QString &txt); /** Returns the second intro line. */ virtual QString introLine2() const; /** Sets the second intro line. */ virtual void setIntroLine2(const QString &txt); /** Returns the password. */ virtual QString password() const; /** Sets the password. */ virtual void setPassword(const QString &txt); /** Updates the general settings from the given abstract configuration. */ virtual bool fromConfig(const Flags &flags, Context &ctx); /** Updates the abstract configuration from this general settings. */ virtual bool updateConfig(Context &ctx); public: /** Some limits for boot settings. */ struct Limit: public Element::Limit { /** Maximum intro line length. */ static constexpr unsigned int introLineLength() { return 16; } /** Maximum password length. */ static constexpr unsigned int passwordLength() { return 8; } }; protected: /** Some internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int introLine1() { return 0x0000; } static constexpr unsigned int introLine2() { return 0x0010; } static constexpr unsigned int password() { return 0x0020; } /// @endcond }; }; /** Represents the base class of DMR APRS settings for all AnyTone codeplugs. * * Memory encoding of the DMR APRS settings (size 0x0030 bytes): * @verbinclude anytone_dmraprssettings.txt */ class DMRAPRSSettingsElement: public Element { protected: /** Hidden constructor. */ DMRAPRSSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit DMRAPRSSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0030; } /** Resets the APRS settings. */ void clear(); /** Returns the Manual TX interval in seconds. */ virtual Interval manualInterval() const; /** Sets the manual TX interval in seconds. */ virtual void setManualInterval(const Interval& sec); /** Returns @c true if the automatic APRS is enabled. */ virtual bool automatic() const; /** Returns the automatic transmit interval in seconds. */ virtual Interval automaticInterval() const; /** Sets the automatic transmit interval in seconds. */ virtual void setAutomaticInterval(const Interval& sec); /** Disables the automatic APRS. To enable it, set an interval. */ virtual void disableAutomatic(); /** Returns @c true if the fixed location beacon is enabled. */ virtual bool fixedLocation() const; /** Returns the location of the fixed position. */ virtual QGeoCoordinate location() const; /** Sets the location of the fixed position. */ virtual void setLocation(const QGeoCoordinate &pos); /** Enables/disables fixed location beacon. */ virtual void enableFixedLocation(bool enable); /** Returns the transmit power. */ virtual Channel::Power power() const; /** Sets the transmit power. */ virtual void setPower(Channel::Power power); /** Returns @c true if the n-th channel is set. */ virtual bool hasChannel(unsigned n) const; /** Returns @c true if the n-th channel is VFO A. */ virtual bool channelIsVFOA(unsigned n) const; /** Returns @c true if the n-th channel is VFO B. */ virtual bool channelIsVFOB(unsigned n) const; /** Returns @c true if the n-th channel is selected channel. */ virtual bool channelIsSelected(unsigned n) const; /** Returns the index of the n-th channel. */ virtual unsigned channelIndex(unsigned n) const; /** Sets the n-th channel index. */ virtual void setChannelIndex(unsigned n, unsigned idx); /** Sets the n-th channel to VFO A. */ virtual void setChannelVFOA(unsigned n); /** Sets the n-th channel to VFO B. */ virtual void setChannelVFOB(unsigned n); /** Sets the n-th channel to selected channel. */ virtual void setChannelSelected(unsigned n); /** Resets the n-th channel. */ virtual void clearChannel(unsigned n); /** Returns the destination DMR ID to send the APRS information to. */ virtual unsigned destination() const; /** Sets the destination DMR ID to send the APRS information to. */ virtual void setDestination(unsigned id); /** Returns the call type. */ virtual DMRContact::Type callType() const; /** Sets the call type. */ virtual void setCallType(DMRContact::Type type); /** Returns @c true if the timeslot of the channel is overridden. */ virtual bool timeSlotOverride() const; /** Returns the timeslot (only valid if @c timeSlotOverride returns @c true). */ virtual DMRChannel::TimeSlot timeslot() const; /** Sets the timeslot. */ virtual void overrideTimeSlot(DMRChannel::TimeSlot ts); /** Disables TS override. */ virtual void disableTimeSlotOverride(); /** Updates the GPS settings from the given config. */ virtual bool fromConfig(const Flags &flags, Context &ctx); /** Updates the global config. */ virtual bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); /** Creates GPS system from this GPS settings. */ virtual bool createGPSSystem(uint8_t i, Context &ctx); /** Links GPS system from this GPS settings. */ virtual bool linkGPSSystem(uint8_t i, Context &ctx); protected: /** Internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int manualInterval() { return 0x0000; } static constexpr unsigned int automaticInterval() { return 0x0001; } static constexpr unsigned int fixedLocation() { return 0x0002; } static constexpr unsigned int latitudeDeg() { return 0x0003; } static constexpr unsigned int latitudeMin() { return 0x0004; } static constexpr unsigned int latitudeSec() { return 0x0005; } static constexpr unsigned int latitudeHemi() { return 0x0006; } static constexpr unsigned int longitudeDeg() { return 0x0007; } static constexpr unsigned int longitudeMin() { return 0x0008; } static constexpr unsigned int longitudeSec() { return 0x0009; } static constexpr unsigned int longitudeHemi() { return 0x000a; } static constexpr unsigned int power() { return 0x000b; } static constexpr unsigned int channelIndices() { return 0x000c; } static constexpr unsigned int betweenChannelIndices() { return 0x0002; } static constexpr unsigned int destinationId() { return 0x001c; } static constexpr unsigned int callType() { return 0x0020; } static constexpr unsigned int timeSlot() { return 0x0021; } /// @endcond }; }; /** Represents the base class of a DMR APRS message for all AnyTone codeplugs. */ class DMRAPRSMessageElement: public Element { protected: /** Hidden constructor. */ DMRAPRSMessageElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DMRAPRSMessageElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0030; } void clear(); /** Returns the message. */ virtual QString message() const; /** Sets the message. */ void setMessage(const QString &message); /** Encodes the message. */ virtual bool fromConfig(Codeplug::Flags flags, Context &ctx); /** Decodes the message. */ virtual bool updateConfig(Context &ctx) const; public: /** Some limits for the message. */ struct Limit { static constexpr unsigned int length() { return 32; } ///< Maximum message length. }; protected: /** Some internal used offset. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int message() { return 0x0000; } /// @endcond }; }; /** Represents the table of repeater offset frequencies. * * Memory representation of the offset frequency table (size 0x03F0 bytes): * @verbinclude anytone_repeateroffsetfrequencies.txt */ class RepeaterOffsetListElement: public Element { protected: /** Hidden constructor. */ RepeaterOffsetListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit RepeaterOffsetListElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x03f0; } void clear(); /** Returns @c true, if the n-th offset frequency is set. */ virtual bool isSet(unsigned int n) const; /** Returns the n-th offset frequency. */ virtual Frequency offset(unsigned int n) const; /** Sets the n-th offset frequency. */ virtual void setOffset(unsigned int n, Frequency freq); /** Clears the n-th offset frequency. */ virtual void clearOffset(unsigned int n); public: /** Some limits for the offset frequency table. */ struct Limit { static constexpr unsigned int numEntries() { return 250; } ///< Max number of entries in the table. }; protected: /** Some internal used offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int frequencies() { return 0x0000; } static constexpr unsigned int betweenFrequencies() { return sizeof(uint32_t); } /// @endcond }; }; /** Represents the base class of prefabricated message linked list for all AnyTone codeplugs. * This element is some weird linked list that determines some order for the prefabricated * SMS messages. * * Memory encoding of the message list (size 0x0010 bytes): * @verbinclude anytone_messagelist.txt */ class MessageListElement: public Element { protected: /** Hidden constructor. */ MessageListElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit MessageListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0010; } /** Clears the message list item. */ void clear(); /** Returns @c true if there is a next message (EOL otherwise). */ virtual bool hasNext() const; /** Returns the index of the next message in list. */ virtual unsigned next() const; /** Sets the index of the next message in list. */ virtual void setNext(unsigned idx); /** Clears the next message index. */ virtual void clearNext(); /** Returns @c true if there is a message index. */ virtual bool hasIndex() const; /** Returns the index of the message. */ virtual unsigned index() const; /** Sets the index of the message. */ virtual void setIndex(unsigned idx); /** Clears the index of the message. */ virtual void clearIndex(); protected: /** Some internal offsets. */ struct Offset: public Element::Offset { static constexpr unsigned int next() { return 0x0002; } static constexpr unsigned int index() { return 0x0003; } }; }; /** Represents base class of a preset message for all AnyTone codeplugs. * * Memory encoding of the message (0x100 bytes): * @verbinclude anytone_message.txt */ class MessageElement: public Element { protected: /** Hidden constructor. */ MessageElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ MessageElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0100; } /** Resets the message. */ void clear(); /** Returns the message text. */ virtual QString message() const; /** Sets the message text. */ virtual void setMessage(const QString &msg); public: /** Some limits for the message. */ struct Limit: public Element::Limit { /** Maximum message length. */ static constexpr unsigned int messageLength() { return 99; } }; protected: /** Some internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int message() { return 0x0000; } /// @endcond }; }; /** Represents the bytemap indicating which message is valid. */ class MessageBytemapElement: public InvertedBytemapElement { protected: /** Hidden constructor. */ MessageBytemapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ MessageBytemapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0090; } }; /** Represents base class of a analog quick call entry for all AnyTone codeplugs. * * Memory encoding of the message (0x0002 bytes): * @verbinclude anytone_analogquickcall.txt */ class AnalogQuickCallElement: public Element { public: /** Analog quick-call types. */ enum class Type { None = 0, ///< None, quick-call disabled. DTMF = 1, ///< DTMF call. TwoTone = 2, ///< 2-tone call. FiveTone = 3 ///< 5-tone call }; protected: /** Hidden constructor. */ AnalogQuickCallElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit AnalogQuickCallElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0002; } /** Resets the quick call entry. */ void clear(); /** Returns the call type. */ virtual Type type() const; /** Sets the type of the quick call. */ virtual void setType(Type type); /** Returns @c true if an analog contact index is set. */ virtual bool hasContactIndex() const; /** Returns the analog contact index. */ virtual unsigned contactIndex() const; /** Sets the analog contact index. */ virtual void setContactIndex(unsigned idx); /** Clears the contact index. */ virtual void clearContactIndex(); protected: /** Some internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int type() { return 0x0000; } static constexpr unsigned int contactIndex() { return 0x0001; } /// @endcond }; }; /** Implements the list of analog quick-call settings for all AnyTone codeplugs. * * Memory reresentation of the quick-call settings (size 0x0100 bytes): * @verbinclude anytone_analogquickcalls.txt */ class AnalogQuickCallsElement: public Element { protected: /** Hidden constructor. */ AnalogQuickCallsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ AnalogQuickCallsElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0100; } /** Clears the quick calls. */ void clear(); /** Returns a pointer to the n-th entry. */ uint8_t *quickCall(unsigned int n) const; public: /** Some limits for the quick calls. */ struct Limit { static constexpr unsigned int numEntries() { return 4; } ///< The maximum number of quick-call entries. }; protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int quickCalls() { return 0x0000; } /// @endcond }; }; /** Implements the list of status messages for all AnyTone codeplugs. * * Memory reresentation of the status messages (size 0x0400 bytes): * @verbinclude anytone_statusmessages.txt */ class StatusMessagesElement: public Element { protected: /** Hidden constructor. */ StatusMessagesElement(uint8_t *ptr, size_t size); public: /** Constructor. */ StatusMessagesElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0400; } void clear(); /** Returns the n-th status message. */ virtual QString message(unsigned int n) const; /** Sets the n-th status message. */ virtual void setMessage(unsigned int n, const QString &msg); public: /** Some limits. */ struct Limit { static constexpr unsigned int numMessages() { return 32; } ///< Maximum number of messages. static constexpr unsigned int messageLength() { return 32; } ///< Maximum length of the messages. }; protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int messages() { return 0x0000; } static constexpr unsigned int betweenMessages() { return 0x0020; } /// @endcond }; }; /** Represents the bitmap, indicating which status messages are valid. */ class StatusMessageBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ StatusMessageBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ StatusMessageBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0010; } }; /** Represents the base class of a hot-key setting entry for all AnyTone codeplugs. * * Memory encoding of a hot-key setting (size 0x0030 bytes): * @verbinclude anytone_hotkey.txt */ class HotKeyElement: public Element { public: /** Hot-key types. */ enum class Type { Call = 0, ///< Perform a call. Menu = 1 ///< Show a menu item. }; /** Possible menu items. */ enum class MenuItem { SMS = 1, ///< Show SMS menu. NewSMS = 2, ///< Create new SMS. HotText = 3, ///< Send a hot-text. Inbox = 4, ///< Show SMS inbox. Outbox = 5, ///< Show SMS outbox. Contacts = 6, ///< Show contact list. ManualDial = 7, ///< Show manual dial. CallLog = 8 ///< Show call log. }; /** Possible call types. */ enum class CallType { Analog = 0, ///< Perform an analog call. Digital = 1 ///< Perform a digital call. }; /** Possible digital call sub-types. */ enum class DigiCallType { Off = 0xff, ///< Call disabled. GroupCall = 0, ///< Perform a group call. PrivateCall= 1, ///< Perform private call. AllCall = 2, ///< Perform all call. HotText = 3, ///< Send a text message. CallTip = 4, ///< Send a call tip (?). StatusMessage = 5 ///< Send a state message. }; protected: /** Hidden constructor. */ HotKeyElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit HotKeyElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0030; } /** Resets the hot-key entry. */ void clear(); /** Returns the type of the hot-key entry. */ virtual Type type() const; /** Sets the type of the hot-key entry. */ virtual void setType(Type type); /** If @c type returns @c Type::Menu, returns the menu item. */ virtual MenuItem menuItem() const; /** Sets the menu item. For this setting to have an effect, the @c type must be set to * @c Type::Menu. */ virtual void setMenuItem(MenuItem item); /** If @c type returns @c Type::Call, returns the type of the call. */ virtual CallType callType() const; /** Sets the call type. For this settings to have an effect, the type must be set to * @c Type::Call. */ virtual void setCallType(CallType type); /** If @c type returns @c Type::Call and @c callType @c CalLType::Digital, returns the digital * call type. */ virtual DigiCallType digiCallType() const; /** Sets the digital call type. For this setting to have an effect, the @c type must be * @c Type::Call and @c callType must be @c CallType::Digital. */ virtual void setDigiCallType(DigiCallType type); /** Returns @c true if the contact index is set. */ virtual bool hasContactIndex() const; /** If @c type is @c Type::Call, returns the contact index. This is either an index of an * analog quick call if @c callType is CallType::Analog or a contact index if @c callType is * @c CallType::Digital. If set to 0xffffffff the index is invalid. */ virtual unsigned contactIndex() const; /** Sets the contact index. This can either be an index of an analog quick-call or a contact * index. */ virtual void setContactIndex(unsigned idx); /** Clears the contact index. */ virtual void clearContactIndex(); /** Returns @c true if a message index is set. */ virtual bool hasMessageIndex() const; /** Returns the message index. This can either be an index of an SMS or an index of a status * message. */ virtual unsigned messageIndex() const; /** Sets the message index. */ virtual void setMessageIndex(unsigned idx); /** Clears the message index. */ virtual void clearMessageIndex(); protected: /** Some internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int type() { return 0x0000; } static constexpr unsigned int menuItem() { return 0x0001; } static constexpr unsigned int callType() { return 0x0002; } static constexpr unsigned int digiCallType() { return 0x0003; } static constexpr unsigned int contactIndex() { return 0x0004; } static constexpr unsigned int messageIndex() { return 0x0008; } /// @endcond }; }; /** Represents the list of hot-key settings for all AnyTone codeplugs. * * See @c HotKeyElement for encoding of each element. * * Memory encoding of the hot-key settings (size 0x0360 bytes): * @verbinclude anytone_hotkeysettings.txt */ class HotKeySettingsElement: public Element { protected: /** Hidden constructor. */ HotKeySettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ HotKeySettingsElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0360; } void clear(); /** Returns a pointer to the n-th hot key setting. */ virtual uint8_t *hotKeySetting(unsigned int n) const; public: /** Some limits for this element. */ struct Limit { static constexpr unsigned int numEntries() { return 18; } ///< Maximum number of hot-key entries. }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int hotKeySettings() { return 0x0000; } static constexpr unsigned int betweenHotKeySettings() { return HotKeySettingsElement::size(); } /// @endcond }; }; /** Represents the base class of alarm setting entry for all AnyTone codeplugs. * * Memory encoding of an alarm setting (size 0x0020 bytes): * @verbinclude anytone_alarmsetting.txt */ class AlarmSettingElement: public Element { public: /** Represents the base class of an analog alarm setting for all AnyTone codeplugs. * * Memory representation of an analog alarm setting (size 0x000a bytes): * @verbinclude anytone_analogalarm.txt */ class AnalogAlarm: public Element { public: /** Possible analog alarm types. */ enum class Action { None = 0, ///< No alarm at all. Background = 1, ///< Transmit and background. TXAlarm = 2, ///< Transmit and alarm Both = 3, ///< Both? }; /** Possible alarm signalling types. */ enum class ENIType { None = 0, ///< No alarm code signalling. DTMF = 1, ///< Send alarm code as DTMF. FiveTone = 2 ///< Send alarm code as 5-tone. }; protected: /** Hidden constructor. */ AnalogAlarm(uint8_t *ptr, unsigned size); public: /** Constructor. */ AnalogAlarm(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x000a; } /** Resets the alarm. */ void clear(); /** Returns the alarm action. */ virtual Action action() const; /** Sets the alarm action. */ virtual void setAction(Action action); /** Returns the encoding type. */ virtual ENIType encodingType() const; /** Sets the encoding type. */ virtual void setEncodingType(ENIType type); /** Returns the emergency ID index. */ virtual unsigned emergencyIndex() const; /** Sets the emergency ID index. */ virtual void setEmergencyIndex(unsigned idx); /** Returns the alarm duration in seconds. */ virtual Interval duration() const; /** Sets the alarm duration in seconds. */ virtual void setDuration(const Interval &sec); /** Returns the TX duration in seconds. */ virtual Interval txDuration() const; /** Sets the TX duration in seconds. */ virtual void setTXDuration(const Interval &sec); /** Returns the RX duration in seconds. */ virtual Interval rxDuration() const; /** Sets the RX duration in seconds. */ virtual void setRXDuration(const Interval &sec); /** Returns @c true if the alarm channel is the selected channel. */ virtual bool channelIsSelected() const; /** Returns the channel index. */ virtual unsigned channelIndex() const; /** Sets the channel index. */ virtual void setChannelIndex(unsigned idx); /** Sets the alarm channel to the selected channel. */ virtual void setChannelSelected(); /** Returns @c true if the alarm is repeated continuously. */ virtual bool repeatContinuously() const; /** Returns the number of alarm repetitions. */ virtual unsigned repetitions() const; /** Sets the number of alarm repetitions. */ virtual void setRepetitions(unsigned num); /** Sets the alarm to be repeated continuously. */ virtual void setRepatContinuously(); protected: /** Some internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int action() { return 0x0000; } static constexpr unsigned int encodingType() { return 0x0001; } static constexpr unsigned int emergencyIndex() { return 0x0002; } static constexpr unsigned int duration() { return 0x0003; } static constexpr unsigned int txDuration() { return 0x0004; } static constexpr unsigned int rxDuration() { return 0x0005; } static constexpr unsigned int channelIndex() { return 0x0006; } static constexpr unsigned int channelIsSelected() { return 0x0008; } static constexpr unsigned int repetitions() { return 0x0009; } /// @endcond }; }; /** Represents the base class of an digital alarm setting for all AnyTone codeplugs. * * Memory representation of a digital alarm setting (size 0x000c bytes): * @verbinclude anytone_digitalalarm.txt */ class DigitalAlarm: public Element { public: /** Possible alarm types. */ enum class Action { None = 0, ///< No alarm at all. Background = 1, ///< Transmit and background. NonLocal = 2, ///< Transmit and non-local alarm. Local = 3, ///< Transmit and local alarm. }; protected: /** Hidden constructor. */ DigitalAlarm(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit DigitalAlarm(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x000c; } /** Resets the digital alarm settings. */ void clear(); /** Returns the alarm action. */ virtual Action action() const; /** Sets the alarm action. */ virtual void setAction(Action action); /** Returns the alarm duration in seconds. */ virtual Interval duration() const; /** Sets the alarm duration in seconds. */ virtual void setDuration(const Interval &sec); /** Returns the TX duration in seconds. */ virtual Interval txDuration() const; /** Sets the TX duration in seconds. */ virtual void setTXDuration(const Interval &sec); /** Returns the RX duration in seconds. */ virtual Interval rxDuration() const; /** Sets the RX duration in seconds. */ virtual void setRXDuration(const Interval &sec); /** Returns @c true if the alarm channel is the selected channel. */ virtual bool channelIsSelected() const; /** Returns the channel index. */ virtual unsigned channelIndex() const; /** Sets the channel index. */ virtual void setChannelIndex(unsigned idx); /** Sets the alarm channel to the selected channel. */ virtual void setChannelSelected(); /** Returns @c true if the alarm is repeated continuously. */ virtual bool repeatContinuously() const; /** Returns the number of alarm repetitions. */ virtual unsigned repetitions() const; /** Sets the number of alarm repetitions. */ virtual void setRepetitions(unsigned num); /** Sets the alarm to be repeated continuously. */ virtual void setRepatContinuously(); /** Returns voice broadcast duration in minutes. */ virtual Interval voiceBroadcastDuration() const; /** Sets voice broadcast duration in minutes. */ virtual void setVoiceBroadcastDuration(const Interval &min); /** Returns area broadcast duration in minutes. */ virtual Interval areaBroadcastDuration() const; /** Sets area broadcast duration in minutes. */ virtual void setAreaBroadcastDuration(const Interval &min); /** Returns @c true if the VOX gets enabled. */ virtual bool vox() const; /** Enables/disables the VOX for alarms. */ virtual void enableVOX(bool enable); /** Returns @c true if alarms gets received enabled. */ virtual bool rxAlarm() const; /** Enables/disables the reception of alarms. */ virtual void enableRXAlarm(bool enable); protected: /** Internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int action() { return 0x0000; } static constexpr unsigned int duration() { return 0x0001; } static constexpr unsigned int txDuration() { return 0x0002; } static constexpr unsigned int rxDuration() { return 0x0003; } static constexpr unsigned int channelIndex() { return 0x0004; } static constexpr unsigned int channelIsSelected() { return 0x0006; } static constexpr unsigned int repetitions() { return 0x0007; } static constexpr unsigned int voiceBroadcastDuration() { return 0x0008; } static constexpr unsigned int areaBroadcastDuration() { return 0x0009; } static constexpr unsigned int vox() { return 0x000a; } static constexpr unsigned int rxAlarm() { return 0x000b; } /// @endcond }; }; protected: /** Hidden constructor. */ AlarmSettingElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ AlarmSettingElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0020; } /** Clears the alarm settings. */ void clear(); /** Returns a pointer to the analog alarm settings. */ virtual uint8_t *analog() const; /** Returns a pointer to the digital alarm settings. */ virtual uint8_t *digital() const; protected: /** Internal offsets within the element */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int analog() { return 0x0000; } static constexpr unsigned int digital() { return 0x000a; } /// @endcond }; }; /** Represents the base class of digital alarm setting extension for all AnyTone codeplugs. * * Memory encoding of a digital alarm setting extension (size 0x0030 bytes): * @verbinclude anytone_digitalalarmextension.txt */ class DigitalAlarmExtensionElement: public Element { protected: /** Hidden constructor. */ DigitalAlarmExtensionElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ DigitalAlarmExtensionElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0030; } /** Clears the settings. */ void clear(); /** Returns the call type. */ virtual DMRContact::Type callType() const; /** Sets the call type. */ virtual void setCallType(DMRContact::Type type); /** Returns the destination DMR number. */ virtual unsigned destination() const; /** Sets the destination DMR number. */ virtual void setDestination(unsigned number); protected: /** Internal used offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int callType() { return 0x0000; } static constexpr unsigned int destination() { return 0x0023; } /// @endcond }; }; /** Represents the base-class for 5Tone IDs for all AnyTone codeplugs. * * Memory encoding of the ID (size 0x0020 bytes): * @verbinclude anytone_5toneid.txt */ class FiveToneIDElement: public Element { public: /** Possible 5-tone encoding standards. */ enum class Standard { ZVEI1 = 0, ZVEI2, ZVEI3, PZVEI, DZVEI, PDZVEI, CCIR1, CCIR2, PCCIR, EEA, EuroSignal, NATEL, MODAT, CCITT, EIA }; protected: /** Hidden constructor. */ FiveToneIDElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ FiveToneIDElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0020; } /** Clears the ID. */ void clear(); /** Returns the 5Tone encoding standard. */ virtual Standard standard() const; /** Sets the encoding standard. */ virtual void setStandard(Standard std); /** Returns the tone duration in ms. */ virtual Interval toneDuration() const; /** Sets the tone duration in ms. */ virtual void setToneDuration(const Interval &ms); /** Returns the ID. */ virtual QString id() const; /** Sets the ID. */ virtual void setID(const QString &id); /** Returns the name. */ virtual QString name() const; /** Sets the name. */ virtual void setName(const QString &name); public: /** Some limits of the element. */ struct Limit: public Element::Limit { /** Maximum ID length. */ static constexpr unsigned int idLength() { return 80; } /** Maximum name length. */ static constexpr unsigned int nameLength() { return 7; } }; protected: /** Some internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int standard() { return 0x0001; } static constexpr unsigned int idLength() { return 0x0002; } static constexpr unsigned int toneDuration() { return 0x0003; } static constexpr unsigned int id() { return 0x0004; } static constexpr unsigned int name() { return 0x0018; } /// @endcond }; }; /** Represents the bitmap indicating which five-tone IDs are valid. */ class FiveToneIDBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ FiveToneIDBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ FiveToneIDBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0010; } }; /** Represents the list of five-tone IDs. * * Memory encoding of the ID list (size 0x0c80 bytes): * @verbinclude anytone_5toneidlist.txt */ class FiveToneIDListElement: public Element { protected: /** Hidden constructor. */ FiveToneIDListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ FiveToneIDListElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0c80; } void clear(); /** Returns a pointer to the n-th five-tone ID. */ virtual uint8_t *member(unsigned int n) const; public: /** Some limits for the list. */ struct Limit { static constexpr unsigned int numEntries() { return 100; } ///< Maximum number of entries. }; }; /** Represents the base-class for 5Tone function for all AnyTone codeplugs. * * Memory encoding of the function (size 0x0020 bytes): * @verbinclude anytone_5tonefunction.txt */ class FiveToneFunctionElement: public Element { public: /** Possible function being performed on 5-tone decoding. */ enum class Function { OpenSquelch=0, CallAll, EmergencyAlarm, RemoteKill, RemoteStun, RemoteWakeup, GroupCall }; /** Possible responses to 5-tone decoding. */ enum class Response { None=0, Tone, ToneRespond }; protected: /** Hidden constructor. */ FiveToneFunctionElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit FiveToneFunctionElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0020; } /** Clears the function settings. */ void clear(); /** Returns the function. */ virtual Function function() const; /** Sets the function. */ virtual void setFunction(Function function); /** Returns the response. */ virtual Response response() const; /** Sets the response. */ virtual void setResponse(Response response); /** Returns the ID. */ virtual QString id() const; /** Sets the ID. */ virtual void setID(const QString &id); /** Returns the name. */ virtual QString name() const; /** Sets the name. */ virtual void setName(const QString &name); public: /** Some limits for the function element. */ struct Limit: public Element::Limit { /** Maximum name length. */ static constexpr unsigned int idLength() { return 24; } /** Maximum name length. */ static constexpr unsigned int nameLength() { return 7; } }; protected: /** Some internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int function() { return 0x0000; } static constexpr unsigned int response() { return 0x0001; } static constexpr unsigned int idLength() { return 0x0002; } static constexpr unsigned int id() { return 0x0003; } static constexpr unsigned int name() { return 0x000f; } /// @endcond }; }; /** Represents the list of five-tone functions for all AnyTone codeplugs. * * Memory representation of the function list (size 0x0200 bytes): * @verbinclude anytone_5tonefunctionlist.txt */ class FiveToneFunctionListElement: public Element { protected: /** Hidden constructor. */ FiveToneFunctionListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ FiveToneFunctionListElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0200; } void clear(); /** Returns the pointer to the n-th function setting. */ virtual uint8_t *function(unsigned int n) const; public: /** Some limits for the list. */ struct Limit { static constexpr unsigned int numFunctions() { return 16; } ///< The max number of functions. }; }; /** Represents the base-class for 5Tone settings for all AnyTone codeplugs. * * Memory encoding of the settings (size 0x0080 bytes): * @verbinclude anytone_5tonesettings.txt */ class FiveToneSettingsElement: public Element { public: /** Possible responses to decoded 5-tone codes. */ enum class Response { None = 0, Tone, ToneRespond }; /** Possible 5-tone encoding standards. */ typedef enum FiveToneIDElement::Standard Standard; protected: /** Hidden constructor. */ FiveToneSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ FiveToneSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0080; } /** Resets the 5tone settings. */ void clear(); /** Returns the decoding response. */ virtual Response decodingResponse() const; /** Sets the decoding response. */ virtual void setDecodingResponse(Response response); /** Returns the decoding standard. */ virtual Standard decodingStandard() const; /** Sets the decoding standard. */ virtual void setDecodingStandard(Standard standard); /** Returns the decoding tone duration in ms. */ virtual Interval decodingToneDuration() const; /** Sets the decoding tone duration in ms. */ virtual void setDecodingToneDuration(const Interval &ms); /** Returns the 5tone radio ID. */ virtual QString id() const; /** Sets the 5tone radio ID. */ virtual void setID(const QString &id); /** Returns the post-encode delay in ms. */ virtual Interval postEncodeDelay() const; /** Sets the post-encode delay in ms. */ virtual void setPostEncodeDelay(const Interval &ms); /** Returns @c true if the PTT ID is set. */ virtual bool hasPTTID() const; /** Returns the PTT ID. */ virtual unsigned pttID() const; /** Sets the PTT ID [5,75]. */ virtual void setPTTID(unsigned id); /** Clears the PTT ID. */ virtual void clearPTTID(); /** Returns the auto-reset time in seconds. */ virtual Interval autoResetTime() const; /** Sets the auto-reset time in seconds. */ virtual void setAutoResetTime(const Interval &s); /** Returns the first delay in ms. */ virtual Interval firstDelay() const; /** Sets the first delay in ms. */ virtual void setFirstDelay(const Interval &ms); /** Returns @c true if the sidetone is enabled. */ virtual bool sidetoneEnabled() const; /** Enables/disables side tone. */ virtual void enableSidetone(bool enable); /** Returns the stop code [0,15]. */ virtual unsigned stopCode() const; /** Sets the stop code. */ virtual void setStopCode(unsigned code); /** Returns the stop time in ms. */ virtual Interval stopTime() const; /** Sets the stop time in ms. */ virtual void setStopTime(const Interval &ms); /** Returns the decode time in ms. */ virtual Interval decodeTime() const; /** Sets the decode time in ms. */ virtual void setDecodeTime(const Interval &ms); /** Returns the delay after stop in ms. */ virtual Interval delayAfterStop() const; /** Sets the delay after stop in ms. */ virtual void setDelayAfterStop(const Interval &ms); /** Returns the pre time in ms. */ virtual Interval preTime() const; /** Sets the pre time in ms. */ virtual void setPreTime(const Interval &ms); /** Returns the BOT standard. */ virtual Standard botStandard() const; /** Sets the BOT standard. */ virtual void setBOTStandard(Standard standard); /** Returns the BOT tone duration in ms. */ virtual Interval botToneDuration() const; /** Sets the BOT tone duration in ms. */ virtual void setBOTToneDuration(const Interval &ms); /** Returns the 5tone BOT ID. */ virtual QString botID() const; /** Sets the 5tone BOT ID. */ virtual void setBOTID(const QString &id); /** Returns the EOT standard. */ virtual Standard eotStandard() const; /** Sets the EOT standard. */ virtual void setEOTStandard(Standard standard); /** Returns the EOT tone duration in ms. */ virtual Interval eotToneDuration() const; /** Sets the EOT tone duration in ms. */ virtual void setEOTToneDuration(const Interval &ms); /** Returns the 5tone EOT ID. */ virtual QString eotID() const; /** Sets the 5tone EOT ID. */ virtual void setEOTID(const QString &id); public: /** Some limits for the settings. */ struct Limit: public Element::Limit { /** Maximum ID length. */ static constexpr unsigned int idLength() { return 14; } /** Maximum BOT ID length. */ static constexpr unsigned int botIdLength() { return 24; } /** Maximum EOT ID length. */ static constexpr unsigned int eotIdLength() { return 24; } }; protected: /** Some internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int decodingResponse() { return 0x0021; } static constexpr unsigned int decodingStandard() { return 0x0022; } static constexpr unsigned int idLength() { return 0x0023; } static constexpr unsigned int decodingToneDuration() { return 0x0024; } static constexpr unsigned int id() { return 0x0025; } static constexpr unsigned int postDecodeDelay() { return 0x002c; } static constexpr unsigned int pttId() { return 0x002d; } static constexpr unsigned int autoResetTime() { return 0x002e; } static constexpr unsigned int firstDelay() { return 0x002f; } static constexpr unsigned int sidetoneEnabled() { return 0x0030; } static constexpr unsigned int stopCode() { return 0x0032; } static constexpr unsigned int stopTime() { return 0x0033; } static constexpr unsigned int decodeTime() { return 0x0034; } static constexpr unsigned int delayAfterStop() { return 0x0035; } static constexpr unsigned int preTime() { return 0x0036; } static constexpr unsigned int botStandard() { return 0x0041; } static constexpr unsigned int botIdLength() { return 0x0042; } static constexpr unsigned int botToneDuration() { return 0x0043; } static constexpr unsigned int botId() { return 0x0044; } static constexpr unsigned int eotStandard() { return 0x0061; } static constexpr unsigned int eotIdLength() { return 0x0062; } static constexpr unsigned int eotToneDuration() { return 0x0063; } static constexpr unsigned int eotId() { return 0x0064; } /// @endcond }; }; /** Represents the base-class for a 2-tone ID for all AnyTone codeplugs. * * Memory encoding of the ID (size 0x0020 bytes): * @verbinclude anytone_2toneid.txt */ class TwoToneIDElement: public Element { protected: /** Hidden constructor. */ TwoToneIDElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ TwoToneIDElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0010; } /** Resets the ID. */ void clear(); /** Returns the first tone of the sequence. */ virtual double firstTone() const; /** Sets the first tone of the sequence. */ virtual void setFirstTone(double f); /** Returns the second tone of the sequence. */ virtual double secondTone() const; /** Sets the second tone of the sequence. */ virtual void setSecondTone(double f); /** Returns the name of the function. */ virtual QString name() const; /** Sets the name of the function. */ virtual void setName(const QString &name); public: /** Some limits for the element. */ struct Limit { static constexpr unsigned int nameLength() { return 7; } ///< Maximum name length. }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int firstTone() { return 0x0000; } static constexpr unsigned int secondTone() { return 0x0002; } static constexpr unsigned int name() { return 0x0008; } /// @endcond }; }; /** Represents the two-tone ID bitmap, indicating the which two-tone IDs are valid. */ class TwoToneIDBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ TwoToneIDBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ TwoToneIDBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0010; } }; /** Represents the base-class for a 2-tone function for all AnyTone codeplugs. * * Memory encoding of the function (size 0x0020 bytes): * @verbinclude anytone_2tonefunction.txt */ class TwoToneFunctionElement: public Element { public: /** Possible responses to a decode. */ enum class Response { None = 0, Tone, ToneRespond }; protected: /** Hidden constructor. */ TwoToneFunctionElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ TwoToneFunctionElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0020; } /** Resets the function. */ void clear(); /** Returns the first tone of the sequence. */ virtual double firstTone() const; /** Sets the first tone of the sequence. */ virtual void setFirstTone(double f); /** Returns the second tone of the sequence. */ virtual double secondTone() const; /** Sets the second tone of the sequence. */ virtual void setSecondTone(double f); /** Returns the response. */ virtual Response response() const; /** Sets the response. */ virtual void setResponse(Response resp); /** Returns the name of the function. */ virtual QString name() const; /** Sets the name of the function. */ virtual void setName(const QString &name); public: /** Some limits of the element. */ struct Limit { static constexpr unsigned int nameLength() { return 7; } ///< Maximum name length. }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int firstTone() { return 0x0000; } static constexpr unsigned int secondTone() { return 0x0002; } static constexpr unsigned int response() { return 0x0004; } static constexpr unsigned int name() { return 0x0005; } /// @endcond }; }; /** Rerpesents the two-tone function bitmap, indicating which two-tone functions are valid. */ class TwoToneFunctionBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ TwoToneFunctionBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ TwoToneFunctionBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0010; } }; /** Represents the base class of 2-tone settings for all AnyTone codeplugs. * * Memory encoding of the settings (size 0x0010 bytes): * @verbinclude anytone_2tonesettings.txt */ class TwoToneSettingsElement : public Element { protected: /** Hidden constructor. */ TwoToneSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ TwoToneSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0010; } /** Resets the settings. */ void clear(); /** Returns the first tone duration in ms. */ virtual Interval firstToneDuration() const; /** Sets the first tone duration in ms. */ virtual void setFirstToneDuration(const Interval &ms); /** Returns the second tone duration in ms. */ virtual Interval secondToneDuration() const; /** Sets the second tone duration in ms. */ virtual void setSecondToneDuration(const Interval &ms); /** Returns the long tone duration in ms. */ virtual Interval longToneDuration() const; /** Sets the long tone duration in ms. */ virtual void setLongToneDuration(const Interval &ms); /** Returns the gap duration in ms. */ virtual Interval gapDuration() const; /** Sets the gap duration in ms. */ virtual void setGapDuration(const Interval &ms); /** Returns the auto-reset time in seconds. */ virtual Interval autoResetTime() const; /** Sets the auto-reset time in seconds. */ virtual void setAutoResetTime(const Interval &sec); /** Returns @c true if the sidetone is enabled. */ virtual bool sidetone() const; /** Enables/disables the sidetone. */ virtual void enableSidetone(bool enable); protected: /** Internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int firstToneDuration() { return 0x0009; } static constexpr unsigned int secondToneDuration() { return 0x000a; } static constexpr unsigned int longToneDuration() { return 0x000b; } static constexpr unsigned int gapDuration() { return 0x000c; } static constexpr unsigned int autoResetTime() { return 0x000d; } static constexpr unsigned int sidetone() { return 0x000e; } /// @endcond }; }; /** Represents the base class of DTMF settings for all AnyTone codeplugs. * * Memory representation of the settings (size 0x0050): * @verbinclude anytone_dtmfsettings.txt */ class DTMFSettingsElement: public Element { public: /** Possible responses to a DTMF decode. */ enum Response { None=0, Tone, ToneRespond }; protected: /** Hidden constructor. */ DTMFSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit DTMFSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0050; } /** Resets the settings. */ void clear(); /** Returns the interval/repeat symbol [0,15]. */ virtual unsigned intervalSymbol() const; /** Sets the interval/repeat symbol [0,15]. */ virtual void setIntervalSymbol(unsigned symb); /** Returns the group code [0,15]. */ virtual unsigned groupCode() const; /** Sets the group code [0,15]. */ virtual void setGroupCode(unsigned symb); /** Returns the response to a DMTF decode. */ virtual Response response() const; /** Sets the response to a DTMF decode. */ virtual void setResponse(Response resp); /** Returns the pre time in ms. */ virtual Interval preTime() const; /** Sets the pre time in ms. */ virtual void setPreTime(const Interval &ms); /** Returns the first digit duration in ms. */ virtual Interval firstDigitDuration() const; /** Sets the first digit duration in ms. */ virtual void setFirstDigitDuration(const Interval &ms); /** Returns the auto reset time in seconds. */ virtual Interval autoResetTime() const; /** Sets the auto reset time in seconds. */ virtual void setAutoResetTime(const Interval &sec); /** Returns the radio ID. */ virtual QString id() const; /** Sets the radio ID. */ virtual void setID(const QString &id); /** Returns the post encoding delay in ms. */ virtual Interval postEncodingDelay() const; /** Sets the post encoding delay in ms. */ virtual void setPostEncodingDelay(const Interval &ms); /** Returns the PTT ID pause in seconds. */ virtual Interval pttIDPause() const; /** Sets the PTT ID pause in seconds. */ virtual void setPTTIDPause(const Interval &sec); /** Returns @c true if the PTT ID is enabled. */ virtual bool pttIDEnabled() const; /** Enables/disables the PTT ID. */ virtual void enablePTTID(bool enable); /** Returns the D-code pause in seconds. */ virtual Interval dCodePause() const; /** Sets the D-code pause in seconds. */ virtual void setDCodePause(const Interval &sec); /** Returns @c true if the sidetone is enabled. */ virtual bool sidetone() const; /** Enables/disables the sidetone. */ virtual void enableSidetone(bool enable); /** Returns the BOT ID. */ virtual QString botID() const; /** Sets the BOT ID. */ virtual void setBOTID(const QString &id); /** Returns the EOT ID. */ virtual QString eotID() const; /** Sets the EOT ID. */ virtual void setEOTID(const QString &id); /** Returns the remote kill ID. */ virtual QString remoteKillID() const; /** Sets the remote kill ID. */ virtual void setRemoteKillID(const QString &id); /** Returns the remote stun ID. */ virtual QString remoteStunID() const; /** Sets the remote stun ID. */ virtual void setRemoteStunID(const QString &id); public: /** Some limits for the settings. */ struct Limit: public Element::Limit { /** Maximum ID length. */ static constexpr unsigned int idLength() { return 3; } /** Maximum BOT ID length. */ static constexpr unsigned int botIdLength() { return 16; } /** Maximum EOT ID length. */ static constexpr unsigned int eotIdLength() { return 16; } /** Maximum remote kill ID length. */ static constexpr unsigned int remoteKillIdLength() { return 16; } /** Maximum remote stun ID length. */ static constexpr unsigned int remteStunIdLength() { return 16; } }; protected: /** Some internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int intervalSymbol() { return 0x0000; } static constexpr unsigned int groupCode() { return 0x0001; } static constexpr unsigned int response() { return 0x0002; } static constexpr unsigned int preTime() { return 0x0003; } static constexpr unsigned int firstDigitDuration() { return 0x0004; } static constexpr unsigned int autoResetTime() { return 0x0005; } static constexpr unsigned int id() { return 0x0006; } static constexpr unsigned int postEncodingDelay() { return 0x0009; } static constexpr unsigned int pttIDPause() { return 0x000a; } static constexpr unsigned int pttIDEnabled() { return 0x000b; } static constexpr unsigned int dCodePause() { return 0x000c; } static constexpr unsigned int sidetone() { return 0x000d; } static constexpr unsigned int botID() { return 0x0010; } static constexpr unsigned int eotID() { return 0x0020; } static constexpr unsigned int remoteKillID() { return 0x0030; } static constexpr unsigned int remoteStunID() { return 0x0040; } /// @endcond }; }; /** Represents a list of DTMF IDs to be send. * * Memory encoding of the DTMF IDs (size 0x0100 bytes): * @verbinclude anytone_dtmfidlist.txt */ class DTMFIDListElement: public Element { protected: /** Hidden constructor. */ DTMFIDListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFIDListElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0100; } void clear(); /** Returns @c true, if the n-th number is set. */ virtual bool hasNumber(unsigned int n) const; /** Returns the n-th number. */ virtual QString number(unsigned int n) const; /** Sets the n-th number. */ virtual void setNumber(unsigned int n, const QString &number); /** Clears the n-th number. */ virtual void clearNumber(unsigned int n); public: /** Some limits of the list. */ struct Limit { static constexpr unsigned int numEntries() { return 16; } ///< The maximum number of entries in the list. static constexpr unsigned int numberLength() { return 16; } ///< The maximum length of the numbers. }; }; /** Represents a list of 100 FM broad cast channels. * * Memory representation of the channel list (size 0x0200 bytes): * @verbinclude anytone_wfmchannellist.txt */ class WFMChannelListElement: public Element { protected: /** Hidden constructor. */ WFMChannelListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit WFMChannelListElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0200; } void clear(); /** Returns @c true, if the n-th channel is set. */ virtual bool hasChannel(unsigned int n) const; /** Returns the n-th channel frequency. */ virtual Frequency channel(unsigned int n) const; /** Sets the n-th channel frequency. */ virtual void setChannel(unsigned int n, Frequency freq); /** Clears the n-th channel frequency. */ virtual void clearChannel(unsigned int n); public: /** Some limits for the channel list. */ struct Limit { static constexpr unsigned int numEntries() { return 100; } ///< Maximum number of channels in the list. }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int betweenChannels() { return 0x0004; } /// @endcond }; }; /** Represents the bitmap, indicating which WFM (FM broadcast) channels are valid. */ class WFMChannelBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ WFMChannelBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ WFMChannelBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } }; /** Represents the WFM (FM broadcast) VFO frequency. */ class WFMVFOElement: public Element { protected: /** Hidden constructor. */ WFMVFOElement(uint8_t *ptr, size_t size); public: /** Constructor. */ WFMVFOElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0010; } void clear(); /** Returns the VFO frequency. */ virtual Frequency frequency() const; /** Sets the VFO frequency. */ virtual void setFrequency(Frequency freq); }; /** Represents a list of DMR encryption keys. */ class DMREncryptionKeyListElement: public Element { protected: /** Hidden constructor. */ DMREncryptionKeyListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DMREncryptionKeyListElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0040; } void clear(); /** Returns @c true if the n-th id is set. */ virtual bool hasKey(unsigned int n) const; /** Returns the ID of the encryption key. */ virtual QByteArray key(unsigned int n) const; /** Sets the ID of the encryption key. */ virtual void setKey(unsigned int n, const BasicEncryptionKey &key); /** Clears the n-th id. */ virtual void clearKey(unsigned int n); public: /** Some limits for the list. */ struct Limit { static constexpr unsigned int numEntries() { return 32; } ///< Maximum number of DMR encryption key IDs. }; protected: /** Some internal used offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int betweenKeys() { return 0x0002; } /// @endcond }; }; /** Represents a list of 'enhanced' DMR encryption keys. * Important, there is no enhancement in this encryption, the * used key is still derived from a 16bit seed. The effective encryption is still only 16bit. */ class EnhancedEncryptionKeyListElement: public Element { protected: /** Hidden constructor. */ EnhancedEncryptionKeyListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ EnhancedEncryptionKeyListElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0500; } void clear(); /** Returns the n-th key. */ QByteArray key(unsigned int n) const; /** Sets the n-th key. */ void setKey(unsigned int n, const QByteArray &key); public: /** Some limits of the list. */ struct Limit { static constexpr unsigned numEntries() { return DMREncryptionKeyListElement::Limit::numEntries(); } ///< Maximum number of keys. }; protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int keys() { return 0x0010; } static constexpr unsigned int betweenKeys() { return 0x0028; } /// @endcond }; }; /** Represents the base class for entries to the contact indices in all AnyTone codeplugs. * * Memory representation of the entry (size 0x0008): * @verbinclude anytone_contactmapentry.txt */ class ContactMapElement: public Element { protected: /** Hidden constructor. */ ContactMapElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ContactMapElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0008; } /** Clears the entry. */ void clear(); /** Returns @c true if the contact map is valid. */ bool isValid() const; /** Returns @c true if the entry is a group call. */ virtual bool isGroup() const; /** Returns the id. */ virtual unsigned id() const; /** Encodes ID and group call flag. */ virtual void setID(unsigned id, bool group=false); /** Returns the index. */ virtual unsigned index() const; /** Sets the index. */ virtual void setIndex(unsigned idx); protected: /** Some internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int id() { return 0x0000; } static constexpr unsigned int index() { return 0x0004; } /// @endcond }; }; protected: /** Hidden constructor. */ AnytoneCodeplug(const QString &label, QObject *parent=nullptr); public: /** Destructor. */ virtual ~AnytoneCodeplug(); /** Clears and resets the complete codeplug to some default values. */ virtual void clear(); Config *preprocess(Config *config, const ErrorStack &err) const; bool encode(Config *config, const Flags &flags, const ErrorStack &err); bool decode(Config *config, const ErrorStack &err); bool postprocess(Config *config, const ErrorStack &err) const; protected: virtual bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Allocates the bitmaps. This is also performed during a clear. */ virtual bool allocateBitmaps() = 0; /** Sets all bitmaps for the given config. */ virtual void setBitmaps(Context &ctx) = 0; /** Allocate all code-plug elements that must be written back to the device to maintain a working * codeplug. These elements might be updated during encoding. */ virtual void allocateUpdated() = 0; /** Allocate all code-plug elements that must be downloaded for decoding. All code-plug elements * within the radio that are not represented within the common Config are omitted. */ virtual void allocateForDecoding() = 0; /** Allocate all code-plug elements that are defined through the common Config. */ virtual void allocateForEncoding() = 0; /** Encodes the given config (via context) to the binary codeplug. */ virtual bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Decodes the downloaded codeplug. * * Decoding consists of two steps: First, creation of all config objects and in a second step * resolving all references within the codeplug. The latter step is called linking. */ virtual bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Creates all config objects from the downloaded codeplug. */ virtual bool createElements(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all previously created config objects. */ virtual bool linkElements(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; protected: /** Holds the image label. */ QString _label; // Allow access to protected allocation methods. friend class AnytoneRadio; }; #endif // ANYTONECODEPLUG_HH ================================================ FILE: lib/anytone_extension.cc ================================================ #include "anytone_extension.hh" /* ********************************************************************************************* * * Implementation of AnytoneAPRSFrequency * ********************************************************************************************* */ AnytoneAPRSFrequency::AnytoneAPRSFrequency(QObject *parent) : ConfigObject(parent), _frequency(Frequency::fromHz(0)) { // pass... } ConfigItem * AnytoneAPRSFrequency::clone() const { AnytoneAPRSFrequency *obj = new AnytoneAPRSFrequency(); if (! obj->copy(*this)) { obj->deleteLater(); return nullptr; } return obj; } Frequency AnytoneAPRSFrequency::frequency() const { return _frequency; } void AnytoneAPRSFrequency::setFrequency(Frequency freq) { if (_frequency == freq) return; _frequency = freq; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneAPRSFrequencyRef * ********************************************************************************************* */ AnytoneAPRSFrequencyRef::AnytoneAPRSFrequencyRef(QObject *parent) : ConfigObjectReference(AnytoneAPRSFrequency::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneAPRSFrequencyList * ********************************************************************************************* */ AnytoneAPRSFrequencyList::AnytoneAPRSFrequencyList(QObject *parent) : ConfigObjectList(AnytoneAPRSFrequency::staticMetaObject, parent) { // pass... } ConfigItem * AnytoneAPRSFrequencyList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(node); Q_UNUSED(ctx); Q_UNUSED(err); return new AnytoneAPRSFrequency(); } /* ********************************************************************************************* * * Implementation of AnytoneChannelExtension * ********************************************************************************************* */ AnytoneChannelExtension::AnytoneChannelExtension(QObject *parent) : ConfigExtension(parent), _frequencyCorrection(0), _handsFree(false), _fmAPRSFrequency(new AnytoneAPRSFrequencyRef(this)), _aprsPTT(APRSPTT::Off) { // pass... } int AnytoneChannelExtension::frequencyCorrection() const { return _frequencyCorrection; } void AnytoneChannelExtension::setFrequencyCorrection(int corr) { if (corr == _frequencyCorrection) return; _frequencyCorrection = corr; emit modified(this); } bool AnytoneChannelExtension::handsFree() const { return _handsFree; } void AnytoneChannelExtension::enableHandsFree(bool enable) { if (enable == _handsFree) return; _handsFree = enable; emit modified(this); } AnytoneAPRSFrequencyRef * AnytoneChannelExtension::fmAPRSFrequency() const { return _fmAPRSFrequency; } AnytoneChannelExtension::APRSPTT AnytoneChannelExtension::aprsPTT() const { return _aprsPTT; } void AnytoneChannelExtension::setAPRSPTT(APRSPTT mode) { if (_aprsPTT == mode) return; _aprsPTT = mode; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneAnalogChannelExtension * ********************************************************************************************* */ AnytoneFMChannelExtension::AnytoneFMChannelExtension(QObject *parent) : AnytoneChannelExtension(parent), _rxCustomCTCSS(false), _txCustomCTCSS(false), _customCTCSS(0), _squelchMode(SquelchMode::Carrier), _scramblerFrequency() { // pass... } ConfigItem * AnytoneFMChannelExtension::clone() const { AnytoneFMChannelExtension *ext = new AnytoneFMChannelExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool AnytoneFMChannelExtension::rxCustomCTCSS() const { return _rxCustomCTCSS; } void AnytoneFMChannelExtension::enableRXCustomCTCSS(bool enable) { if (enable == _rxCustomCTCSS) return; _rxCustomCTCSS = enable; emit modified(this); } bool AnytoneFMChannelExtension::txCustomCTCSS() const { return _txCustomCTCSS; } void AnytoneFMChannelExtension::enableTXCustomCTCSS(bool enable) { if (enable == _txCustomCTCSS) return; _txCustomCTCSS = enable; emit modified(this); } double AnytoneFMChannelExtension::customCTCSS() const { return _customCTCSS; } void AnytoneFMChannelExtension::setCustomCTCSS(double freq) { if (freq == _customCTCSS) return; _customCTCSS = freq; emit modified(this); } AnytoneFMChannelExtension::SquelchMode AnytoneFMChannelExtension::squelchMode() const { return _squelchMode; } void AnytoneFMChannelExtension::setSquelchMode(SquelchMode mode) { if (mode == _squelchMode) return; _squelchMode = mode; emit modified(this); } Frequency AnytoneFMChannelExtension::scramblerFrequency() const { return _scramblerFrequency; } void AnytoneFMChannelExtension::setScramblerFrequency(const Frequency &f) { if (f == _scramblerFrequency) return; _scramblerFrequency = f; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneDigitalChannelExtension * ********************************************************************************************* */ AnytoneDMRChannelExtension::AnytoneDMRChannelExtension(QObject *parent) : AnytoneChannelExtension(parent), _adaptiveTDMA(false), _throughMode(false), _crc(true) { // pass... } ConfigItem * AnytoneDMRChannelExtension::clone() const { AnytoneDMRChannelExtension *ext = new AnytoneDMRChannelExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool AnytoneDMRChannelExtension::adaptiveTDMA() const { return _adaptiveTDMA; } void AnytoneDMRChannelExtension::enableAdaptiveTDMA(bool enable) { if (enable == _adaptiveTDMA) return; _adaptiveTDMA = enable; emit modified(this); } bool AnytoneDMRChannelExtension::throughMode() const { return _throughMode; } void AnytoneDMRChannelExtension::enableThroughMode(bool enable) { if (enable == _throughMode) return; _throughMode = enable; emit modified(this); } bool AnytoneDMRChannelExtension::crcEnabled() const { return _crc; } void AnytoneDMRChannelExtension::enableCRC(bool enable) { if (enable == _crc) return; _crc = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneZoneExtension * ********************************************************************************************* */ AnytoneZoneExtension::AnytoneZoneExtension(QObject *parent) : ConfigExtension(parent), _hidden(false) { // pass... } ConfigItem * AnytoneZoneExtension::clone() const { AnytoneZoneExtension *obj = new AnytoneZoneExtension(); if (! obj->copy(*this)) { obj->deleteLater(); return nullptr; } return obj; } bool AnytoneZoneExtension::hidden() const { return _hidden; } void AnytoneZoneExtension::enableHidden(bool enable) { if (_hidden == enable) return; _hidden = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneContactExtension * ********************************************************************************************* */ AnytoneContactExtension::AnytoneContactExtension(QObject *parent) : ConfigExtension(parent), _alertType(AlertType::None) { // pass... } ConfigItem * AnytoneContactExtension::clone() const { AnytoneContactExtension *obj = new AnytoneContactExtension(); if (! obj->copy(*this)) { obj->deleteLater(); return nullptr; } return obj; } AnytoneContactExtension::AlertType AnytoneContactExtension::alertType() const { return _alertType; } void AnytoneContactExtension::setAlertType(AlertType type) { _alertType = type; } /* ********************************************************************************************* * * Implementation of AnytoneFMAPRSSettingsExtension * ********************************************************************************************* */ AnytoneFMAPRSSettingsExtension::AnytoneFMAPRSSettingsExtension(QObject *parent) : ConfigExtension(parent), _txDelay(Interval::fromMilliseconds(60)), _preWaveDelay(Interval::fromMilliseconds(0)), _passAll(false), _reportPosition(false), _reportMicE(false), _reportObject(false), _reportItem(false), _reportMessage(false), _reportWeather(false), _reportNMEA(false), _reportStatus(false), _reportOther(false), _frequencies(new AnytoneAPRSFrequencyList(this)) { // pass... } ConfigItem * AnytoneFMAPRSSettingsExtension::clone() const { AnytoneFMAPRSSettingsExtension *ext = new AnytoneFMAPRSSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } Interval AnytoneFMAPRSSettingsExtension::txDelay() const { return _txDelay; } void AnytoneFMAPRSSettingsExtension::setTXDelay(Interval intv) { if (_txDelay == intv) return; _txDelay = intv; emit modified(this); } Interval AnytoneFMAPRSSettingsExtension::preWaveDelay() const { return _preWaveDelay; } void AnytoneFMAPRSSettingsExtension::setPreWaveDelay(Interval intv) { if (_preWaveDelay == intv) return; _preWaveDelay = intv; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::passAll() const { return _passAll; } void AnytoneFMAPRSSettingsExtension::enablePassAll(bool enable) { if (_passAll == enable) return; _passAll = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportPosition() const { return _reportPosition; } void AnytoneFMAPRSSettingsExtension::enableReportPosition(bool enable) { if (_reportPosition == enable) return; _reportPosition = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportMicE() const { return _reportMicE; } void AnytoneFMAPRSSettingsExtension::enableReportMicE(bool enable) { if (_reportMicE == enable) return; _reportMicE = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportObject() const { return _reportObject; } void AnytoneFMAPRSSettingsExtension::enableReportObject(bool enable) { if (_reportObject == enable) return; _reportObject = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportItem() const { return _reportItem; } void AnytoneFMAPRSSettingsExtension::enableReportItem(bool enable) { if (_reportItem == enable) return; _reportItem = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportMessage() const { return _reportMessage; } void AnytoneFMAPRSSettingsExtension::enableReportMessage(bool enable) { if (_reportMessage == enable) return; _reportMessage = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportWeather() const { return _reportWeather; } void AnytoneFMAPRSSettingsExtension::enableReportWeather(bool enable) { if (_reportWeather == enable) return; _reportWeather = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportNMEA() const { return _reportNMEA; } void AnytoneFMAPRSSettingsExtension::enableReportNMEA(bool enable) { if (_reportNMEA == enable) return; _reportNMEA = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportStatus() const { return _reportStatus; } void AnytoneFMAPRSSettingsExtension::enableReportStatus(bool enable) { if (_reportStatus == enable) return; _reportStatus = enable; emit modified(this); } bool AnytoneFMAPRSSettingsExtension::reportOther() const { return _reportOther; } void AnytoneFMAPRSSettingsExtension::enableReportOther(bool enable) { if (_reportOther == enable) return; _reportOther = enable; emit modified(this); } AnytoneAPRSFrequencyList * AnytoneFMAPRSSettingsExtension::frequencies() const { return _frequencies; } ================================================ FILE: lib/anytone_extension.hh ================================================ #ifndef ANYTONEEXTENSION_HH #define ANYTONEEXTENSION_HH #include "configobject.hh" #include "configreference.hh" #include "frequency.hh" #include "interval.hh" #include /** Implements the config representation of an FM APRS frequency. * @ingroup anytone */ class AnytoneAPRSFrequency: public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "af") Q_CLASSINFO("frequencyDecription", "Transmit-frequency.") /** The frequency. */ Q_PROPERTY(Frequency frequency READ frequency WRITE setFrequency) public: /** Default constructor. */ Q_INVOKABLE explicit AnytoneAPRSFrequency(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the transmit frequency. */ Frequency frequency() const; /** Sets the transmit frequency. */ void setFrequency(Frequency freq); protected: /** The transmit frequency. */ Frequency _frequency; }; /** Represents a reference to an APRS frequency. * @ingroup anytone */ class AnytoneAPRSFrequencyRef: public ConfigObjectReference { Q_OBJECT public: /** Default constructor. */ explicit AnytoneAPRSFrequencyRef(QObject *parent=nullptr); }; /** Represents a list of APRS transmit frequencies. * @ingroup anytone */ class AnytoneAPRSFrequencyList: public ConfigObjectList { Q_OBJECT public: /** Empty constructor. */ explicit AnytoneAPRSFrequencyList(QObject *parent=nullptr); ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err); }; /** Implements the common properties for analog and digital AnyTone channels. * This class cannot be instantiated directly, use one of the derived classes. * @ingroup anytone */ class AnytoneChannelExtension: public ConfigExtension { Q_OBJECT /** Holds the frequency correction in some unknown units. */ Q_PROPERTY(int frequencyCorrection READ frequencyCorrection WRITE setFrequencyCorrection) /** If @c true, the hands-free feature is enabled for this channel. */ Q_PROPERTY(bool handsFree READ handsFree WRITE enableHandsFree) /** A reference to the FM APRS frequency. If not set, the default will be used. */ Q_PROPERTY(AnytoneAPRSFrequencyRef *fmAPRSFrequency READ fmAPRSFrequency()) /** Specifies if and when the position is send via the associated APRS system, once the PTT is * pressed. */ Q_PROPERTY(APRSPTT aprsPTT READ aprsPTT WRITE setAPRSPTT) public: /** Possible APRS PTT modes. */ enum class APRSPTT{ Off, Start, End }; Q_ENUM(APRSPTT) protected: /** Hidden constructor. */ explicit AnytoneChannelExtension(QObject *parent=nullptr); public: /** Returns the frequency correction in some unknown units. */ int frequencyCorrection() const; /** Sets the frequency correction. */ void setFrequencyCorrection(int corr); /** Returns @c true if the hands-free feature is enabled. */ bool handsFree() const; /** Enables/disables the hands-free feature for this channel. */ void enableHandsFree(bool enable); /** Holds a reference to the FM APRS frequency to be used if FM APRS is enabled on the channel. */ AnytoneAPRSFrequencyRef *fmAPRSFrequency() const; /** Holds the APRS PTT mode. That his, if and when the APRS information is send via the * associated APRS system. */ APRSPTT aprsPTT() const; /** Sets the APRS PTT mode. */ void setAPRSPTT(APRSPTT mode); protected: /** The frequency correction. */ int _frequencyCorrection; /** If @c true, the hands-free feature is enabled for this channel. */ bool _handsFree; /** A reference to the FM APRS frequency. */ AnytoneAPRSFrequencyRef *_fmAPRSFrequency; /** Holds the APRS PTT mode. */ APRSPTT _aprsPTT; }; /** Implements the settings extension for FM channels on AnyTone devices. * @ingroup anytone */ class AnytoneFMChannelExtension: public AnytoneChannelExtension { Q_OBJECT /** If @c true, the custom CTCSS tone is used for RX (open squelch). */ Q_PROPERTY(bool rxCustomCTCSS READ rxCustomCTCSS WRITE enableRXCustomCTCSS) /** If @c true, the custom CTCSS tone is transmitted. */ Q_PROPERTY(bool txCustomCTCSS READ txCustomCTCSS WRITE enableTXCustomCTCSS) /** Holds the custom CTCSS tone frequency in Hz. Resolution is 0.1Hz */ Q_PROPERTY(double customCTCSS READ customCTCSS WRITE setCustomCTCSS) /** Holds the squelch mode. */ Q_PROPERTY(SquelchMode squelchMode READ squelchMode WRITE setSquelchMode) /** If @c true, the analog scrabler is enabled. */ Q_PROPERTY(Frequency scramblerFrequency READ scramblerFrequency WRITE setScramblerFrequency) public: /** Possible squelch mode settings. */ enum class SquelchMode { Carrier = 0, SubTone = 1, OptSig = 2, SubToneAndOptSig = 3, SubToneOrOptSig = 4 }; Q_ENUM(SquelchMode) public: /** Default constructor. */ Q_INVOKABLE explicit AnytoneFMChannelExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true, if the custom CTCSS frequency is used for RX (open squelch). */ bool rxCustomCTCSS() const; /** Enables/disables usage of custom CTCSS frequency for RX. */ void enableRXCustomCTCSS(bool enable); /** Returns @c true, if the custom CTCSS frequency is used for TX (open squelch). */ bool txCustomCTCSS() const; /** Enables/disables usage of custom CTCSS frequency for TX. */ void enableTXCustomCTCSS(bool enable); /** Returns the custom CTCSS frequency in Hz. Resolution is 0.1Hz. */ double customCTCSS() const; /** Sets the custom CTCSS frequency in Hz. Resolution is 0.1Hz. */ void setCustomCTCSS(double freq); /** Returns the squelch mode. */ SquelchMode squelchMode() const; /** Sets the squelch mode. */ void setSquelchMode(SquelchMode mode); /** Reuturns the FM scrabler carrier frequency. */ Frequency scramblerFrequency() const; /** Sets the FM scrambler carrier frequency. */ void setScramblerFrequency(const Frequency &freq); protected: /** If @c true, the custom CTCSS tone is used for RX (open squelch). */ bool _rxCustomCTCSS; /** If @c true, the custom CTCSS tone is transmitted. */ bool _txCustomCTCSS; /** Holds the custom CTCSS tone frequency in Hz. Resolution is 0.1Hz */ double _customCTCSS; /** Holds the squelch mode. */ SquelchMode _squelchMode; /** Sets the FM scrambler frequency. */ Frequency _scramblerFrequency; }; /** Implements the settings extension for DMR channels on AnyTone devices. * @ingroup anytone */ class AnytoneDMRChannelExtension: public AnytoneChannelExtension { Q_OBJECT /** If @c true, the adaptive TDMA mode is enabled. This makes only sense, if @c simplexTDMA is * enabled too. In this case, the radio is able to receive both simplex TDMA as well as "normal" * simplex DMR on the channel. */ Q_PROPERTY(bool adaptiveTDMA READ adaptiveTDMA WRITE enableAdaptiveTDMA) /** If @c true, the through mode is enabled (what ever that means). */ Q_PROPERTY(bool throughMode READ throughMode WRITE enableThroughMode) /** If @c true, the DMR CRC check is enabled (default). */ Q_PROPERTY(bool crc READ crcEnabled WRITE enableCRC); public: /** Default constructor. */ Q_INVOKABLE explicit AnytoneDMRChannelExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true if the adaptive TDMA mode is enabled. */ bool adaptiveTDMA() const; /** Enables/disables the adaptive TDMA mode. */ void enableAdaptiveTDMA(bool enable); /** Returns @c true if the through mode is enabled. */ bool throughMode() const; /** Enables/disables the through mode. */ void enableThroughMode(bool enable); /** Returns @c true if the DMR CRC check is enabled (default). */ bool crcEnabled() const; /** Enables DMR CRC check. */ void enableCRC(bool enable); protected: /** If @c true, the adaptive TDMA mode is enabled. */ bool _adaptiveTDMA; /** If @c true the through mode is enabled. */ bool _throughMode; /** If @c true, DMR CRC check is enabled. */ bool _crc; }; /** Implements the AnyTone extensions for zones. * @ingroup anytone */ class AnytoneZoneExtension : public ConfigExtension { Q_OBJECT /** If @c true, the zone is hidden in the menu. */ Q_PROPERTY(bool hidden READ hidden WRITE enableHidden) public: /** Default constructor. */ Q_INVOKABLE explicit AnytoneZoneExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true if the zone is hidden. */ bool hidden() const; /** Enables/disables hidden zone. */ void enableHidden(bool enable); protected: /** If @c true, the zone is hidden in the menu. */ bool _hidden; }; /** Implements the AnyTone contact extension. * @ingroup anytone */ class AnytoneContactExtension: public ConfigExtension { Q_OBJECT /** Overrides the ring flag, allows to set None, Ring and Online. */ Q_PROPERTY(AlertType alertType READ alertType WRITE setAlertType) public: /** Possible ring-tone types. */ enum class AlertType { None = 0, ///< Alert disabled. Ring = 1, ///< Ring tone. Online = 2 ///< WTF? }; Q_ENUM(AlertType) public: /** Default constructor. */ Q_INVOKABLE explicit AnytoneContactExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the alert type for the contact. */ AlertType alertType() const; /** Sets the alert type for the contact. */ void setAlertType(AlertType type); protected: /** Holds the alert type for the contact. */ AlertType _alertType; }; /** Implements some additional settings for the FM APRS system. * This extension gets attached to a @c APRSSystem instance. */ class AnytoneFMAPRSSettingsExtension: public ConfigExtension { Q_OBJECT /** The transmit delay in milliseconds. */ Q_PROPERTY(Interval txDelay READ txDelay WRITE setTXDelay) /** The transmit pre-wave delay in milliseconds. */ Q_PROPERTY(Interval preWaveDelay READ preWaveDelay WRITE setPreWaveDelay) /** If @c true, all APRS messages are processed, including those with invalid CRC. */ Q_PROPERTY(bool passAll READ passAll WRITE enablePassAll) /** If @c true, the report position flag is set. */ Q_PROPERTY(bool reportPosition READ reportPosition WRITE enableReportPosition) /** If @c true, the report Mic-E flag is set. */ Q_PROPERTY(bool reportMicE READ reportMicE WRITE enableReportMicE) /** If @c true, the report object flag is set. */ Q_PROPERTY(bool reportObject READ reportObject WRITE enableReportObject) /** If @c true, the report item flag is set. */ Q_PROPERTY(bool reportItem READ reportItem WRITE enableReportItem) /** If @c true, the report message flag is set. */ Q_PROPERTY(bool reportMessage READ reportMessage WRITE enableReportMessage) /** If @c true, the report weather flag is set. */ Q_PROPERTY(bool reportWeather READ reportWeather WRITE enableReportWeather) /** If @c true, the report NMEA flag is set. */ Q_PROPERTY(bool reportNMEA READ reportNMEA WRITE enableReportNMEA) /** If @c true, the report status flag is set. */ Q_PROPERTY(bool reportStatus READ reportStatus WRITE enableReportStatus) /** If @c true, the report other flag is set. */ Q_PROPERTY(bool reportOther READ reportOther WRITE enableReportOther) /** The list of additional APRS frequencies. */ Q_PROPERTY(AnytoneAPRSFrequencyList *frequencies READ frequencies) public: /** Possible bandwidth settings. */ enum class Bandwidth { Narrow = 0, Wide = 1 }; Q_ENUM(Bandwidth) public: /** Default constructor. */ explicit Q_INVOKABLE AnytoneFMAPRSSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the transmit delay. */ Interval txDelay() const; /** Sets the transmit delay. */ void setTXDelay(Interval intv); /** Returns the pre-wave delay in ms. */ Interval preWaveDelay() const; /** Sets the pre-wave delay in ms. */ void setPreWaveDelay(Interval ms); /** Returns @c true if all received APRS messages are processed, even those with invalid CRC. */ bool passAll() const; /** Enables processing of all received APRS messages, including those with invalid CRC. */ void enablePassAll(bool enable); /** Returns @c true if the report position flag is set. */ bool reportPosition() const; /** Enables/disables report position flag. */ void enableReportPosition(bool enable); /** Returns @c true if the report Mic-E flag is set. */ bool reportMicE() const; /** Enables/disables report Mic-E flag. */ void enableReportMicE(bool enable); /** Returns @c true if the report object flag is set. */ bool reportObject() const; /** Enables/disables report object flag. */ void enableReportObject(bool enable); /** Returns @c true if the report item flag is set. */ bool reportItem() const; /** Enables/disables report item flag. */ void enableReportItem(bool enable); /** Returns @c true if the report message flag is set. */ bool reportMessage() const; /** Enables/disables report message flag. */ void enableReportMessage(bool enable); /** Returns @c true if the report weather flag is set. */ bool reportWeather() const; /** Enables/disables report weather flag. */ void enableReportWeather(bool enable); /** Returns @c true if the report NMEA flag is set. */ bool reportNMEA() const; /** Enables/disables report NMEA flag. */ void enableReportNMEA(bool enable); /** Returns @c true if the report status flag is set. */ bool reportStatus() const; /** Enables/disables report status flag. */ void enableReportStatus(bool enable); /** Returns @c true if the report other flag is set. */ bool reportOther() const; /** Enables/disables report other flag. */ void enableReportOther(bool enable); /** Returns the list of additional FM APRS frequencies. */ AnytoneAPRSFrequencyList *frequencies() const; protected: /** The transmit delay. */ Interval _txDelay; /** The pre-wave delay. */ Interval _preWaveDelay; /** If @c true, all APRS messages are processed. */ bool _passAll; /** If @c true the report position flag is set. */ bool _reportPosition; /** The report Mic-E flag. */ bool _reportMicE; /** The report object flag. */ bool _reportObject; /** The report item flag. */ bool _reportItem; /** The report message flag. */ bool _reportMessage; /** The report weather flag. */ bool _reportWeather; /** The report NMEA flag. */ bool _reportNMEA; /** The report status flag. */ bool _reportStatus; /** The report other flag. */ bool _reportOther; /** The list of additional FM APRS frequencies. */ AnytoneAPRSFrequencyList *_frequencies; }; #endif // ANYTONEEXTENSION_HH ================================================ FILE: lib/anytone_filereader.cc ================================================ #include "anytone_filereader.hh" #include #include #include #include "d868uv_filereader.hh" #include "d878uv_filereader.hh" /** Internal used struct to read a CPS file header. */ struct __attribute__((packed)) file_header { char version[5]; ///< The version number. uint32_t payload_size; ///< The content size. char modelname[7]; ///< The model name. uint8_t _unused0010[3]; ///< Unused/reserved. char hw_version[4]; ///< The hardware version. }; /* ********************************************************************************************* * * Implementation of AnytoneFileReader::Element * ********************************************************************************************* */ AnytoneFileReader::Element::Element(const uint8_t *ptr) : _data(ptr) { // pass... } AnytoneFileReader::Element::~Element() { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneFileReader * ********************************************************************************************* */ AnytoneFileReader::AnytoneFileReader(Config *config, const uint8_t *data, size_t size, QString &message) : _context(config), _start(data), _data(data), _size(size), _message(message) { // pass... } AnytoneFileReader::~AnytoneFileReader() { // pass... } bool AnytoneFileReader::read() { // Set pointer to start _data = _start; if (! this->readHeader()) return false; if (! this->readChannels()) return false; if (! this->readRadioIDs()) return false; if (! this->readZones()) return false; if (! this->readScanLists()) return false; if (! this->readAnalogContacts()) return false; // 2nd pass, link. _data = _start; if (! this->linkHeader()) return false; if (! this->linkChannels()) return false; if (! this->linkRadioIDs()) return false; if (! this->linkZones()) return false; if (! this->linkScanLists()) return false; if (! this->linkAnalogContacts()) return false; return true; } bool AnytoneFileReader::read(const QString &filename, Config *config, QString &message) { QFileInfo info(filename); if (! info.exists()) { message = QObject::tr("Cannot open file '%1': File does not exist.").arg(filename); return false; } QFile file(filename); if (! file.open(QFile::ReadOnly)) { message = QObject::tr("Cannot open file '%1': %2.").arg(filename).arg(file.errorString()); return false; } file_header head; if (sizeof(file_header) != file.read((char *)&head, sizeof(file_header))) { message = QObject::tr("Cannot read header from file '%1': %2.") .arg(filename).arg(file.errorString()); file.close(); return false; } size_t size = qFromLittleEndian(head.payload_size)+14; if (size != (size_t)info.size()) { message = QObject::tr("Malformed header in file '%1': Mismatching content size. Expected %2, got %3.") .arg(filename).arg(info.size()-14).arg(size-14); file.close(); return false; } uint8_t *data = file.map(0, size); if (nullptr == data) { message = QObject::tr("Cannot mmap file '%1': %2.") .arg(filename).arg(file.errorString()); file.close(); return false; } QString cps_version = QString::fromLocal8Bit(head.version, strnlen(head.version,5)); QString model = QString::fromLocal8Bit(head.modelname, strnlen(head.modelname,7)); // Dispatch by model name AnytoneFileReader *reader = nullptr; if ("D868UVE" == model) { reader = new D868UVFileReader(config, data, size, message); } else if ("D878UV" == model) { reader = new D878UVFileReader(config, data, size, message); } else if ("D878UV2" == model) { } else if ("D578UV" == model) { } else { message = QObject::tr("Cannot read codeplug file '%1': Unknown model '%2'.") .arg(filename).arg(model); file.unmap(data); file.close(); return false; } if (nullptr == reader) { message = QObject::tr("Cannot read codeplug file '%1': Model '%2' not implemented yet.") .arg(filename).arg(model); file.unmap(data); file.close(); return false; } // Clear config config->reset(); if (! reader->read()) { message = QObject::tr("Cannot read codeplug file '%1': %2").arg(filename).arg(message); file.unmap(data); file.close(); return false; } file.unmap(data); file.close(); return true; } ================================================ FILE: lib/anytone_filereader.hh ================================================ #ifndef ANYTONEFILEREADER_HH #define ANYTONEFILEREADER_HH #include #include "config.hh" /** This class implements a reader of AnyTone codeplug files. * @warning This is mostly incomplete. * @ingroup anytone */ class AnytoneFileReader { public: /** Base class for all elements in the codeplug file. */ class Element { protected: /** Hidden constructor. */ Element(const uint8_t *ptr); public: /** Destructor. */ virtual ~Element(); /** Returns the storage size of the element. */ virtual size_t size() const = 0; protected: /** Pointer to the actual element. */ const uint8_t *_data; }; protected: /** Constructs a configuration from the given codeplug file in memory (@c data, @c size). */ AnytoneFileReader(Config *config, const uint8_t *data, size_t size, QString &message); public: /** Destructor. */ virtual ~AnytoneFileReader(); protected: /** Read all elements. */ virtual bool read(); /** Read header of file. */ virtual bool readHeader() = 0; /** Link elements. */ virtual bool linkHeader() = 0; /** Read all channels. */ virtual bool readChannels() = 0; /** Link elements. */ virtual bool linkChannels() = 0; /** Read all radio IDs. */ virtual bool readRadioIDs() = 0; /** Link elements. */ virtual bool linkRadioIDs() = 0; /** Read all zones. */ virtual bool readZones() = 0; /** Link elements. */ virtual bool linkZones() = 0; /** Read all scal lists. */ virtual bool readScanLists() = 0; /** Link elements. */ virtual bool linkScanLists() = 0; /** Read all DTMF contacts. */ virtual bool readAnalogContacts() = 0; /** Link elements. */ virtual bool linkAnalogContacts() = 0; public: /** Use this static function to read a codeplug from the manufacturer CPS file. */ static bool read(const QString &filename, Config *config, QString &message); protected: /** Pointer to the start. */ const uint8_t * const _start; /** Pointer to the entire data. */ const uint8_t *_data; /** Size of the entire blob. */ size_t _size; /** Error message. */ QString &_message; }; #endif // ANYTONEFILEREADER_HH ================================================ FILE: lib/anytone_interface.cc ================================================ #include "anytone_interface.hh" #include "logger.hh" #include #define USB_VID_GD32 0x28e9 #define USB_PID_GD32 0x018a #define USB_VID_STM32 0x2e3c #define USB_PID_STM32 0x5740 /* ********************************************************************************************* * * Implementation of AnytoneInterface::ReadRequest * ********************************************************************************************* */ AnytoneInterface::ReadRequest::ReadRequest(uint32_t addr) { cmd = 'R'; this->addr = qToBigEndian(addr); size = 16; } /* ********************************************************************************************* * * Implementation of AnytoneInterface::ReadResponse * ********************************************************************************************* */ bool AnytoneInterface::ReadResponse::check(uint32_t addr, QString &msg) const { if ('W' != cmd) { msg = QObject::tr("Invalid read response: Expected command 'W' got '%1'").arg(cmd); return false; } if (qFromBigEndian(this->addr) != addr) { msg = QObject::tr("Invalid read response: Expected address '%1' got '%2'") .arg(addr, 8, 16, QChar('0')).arg(qFromLittleEndian(this->addr), 8, 16, QChar('0')); return false; } if (16 != size) { msg = QObject::tr("Invalid read response: Expected size 16 got %1").arg((int)size); return false; } // Compute checksum uint8_t crc=((const uint8_t *)this)[1]; for (uint8_t i=2; i<(size+6); i++) crc += ((const uint8_t *)this)[i]; // compare if (crc != sum) { msg = QObject::tr("Invalid read response: Expected check-sum %1 got %2") .arg((int)crc).arg((int)sum); return false; } // check ACK flag. if (6 != ack) { msg = QObject::tr("Invalid read response: Expected ACK 6 got %1").arg((int)ack); return false; } // ok return true; } /* ********************************************************************************************* * * Implementation of AnytoneInterface::WriteRequest * ********************************************************************************************* */ AnytoneInterface::WriteRequest::WriteRequest(uint32_t addr, const char *data) { cmd = 'W'; this->addr = qToBigEndian(addr); size = 16; memcpy(this->data, data, size); sum = 0; uint8_t *b=(uint8_t *)this; for (uint8_t i=1; i<(size+6); i++) sum += b[i]; ack = 6; } /* ********************************************************************************************* * * Implementation of AnytoneInterface::RadioInfo * ********************************************************************************************* */ AnytoneInterface::RadioVariant::RadioVariant() : name(""), bands(0x00), version("") { // pass... } bool AnytoneInterface::RadioVariant::isValid() const { return ! name.isEmpty(); } /* ********************************************************************************************* * * Implementation of AnytoneInterface * ********************************************************************************************* */ AnytoneInterface::AnytoneInterface(const USBDeviceDescriptor &descriptor, const ErrorStack &err, QObject *parent) : USBSerial(descriptor, QSerialPort::Baud115200, err, parent), _state(STATE_INITIALIZED), _info() { if (isOpen()) { _state = STATE_OPEN; } else { _state = STATE_ERROR; return; } // enter program mode if (! this->enter_program_mode()) return; // identify device if (! this->request_identifier(_info)) { _info = RadioVariant(); _state = STATE_ERROR; } } AnytoneInterface::~AnytoneInterface() { if (isOpen()) AnytoneInterface::close(); } void AnytoneInterface::close() { switch (_state) { case STATE_INITIALIZED: case STATE_OPEN: USBSerial::close(); break; case STATE_PROGRAM: this->reboot(); break; case STATE_CLOSED: case STATE_ERROR: break; } } bool AnytoneInterface::getInfo(RadioVariant &info) { if (_info.isValid()) { info = _info; return true; } return false; } bool AnytoneInterface::write_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { Q_UNUSED(bank); Q_UNUSED(addr) if ((STATE_PROGRAM != _state) && (! enter_program_mode(err))) return false; return true; } bool AnytoneInterface::write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err) { if (0 != bank) { errMsg(err) << "Anytone: Cannot write to bank " << bank << ". There is only one (idx=0)."; return false; } if (STATE_PROGRAM != _state) { errMsg(err) << "Anytone: Cannot write data to device: Not in programming mode."; return false; } //logDebug() << "Anytone: Write " << nbytes << "b to addr 0x" << QString::number(addr, 16) << "..."; for (int i=0; iclose(); _state = STATE_CLOSED; } return true; } bool AnytoneInterface::enter_program_mode(const ErrorStack &err) { if (STATE_PROGRAM == _state) { logDebug() << "Already in program mode. Skip."; return true; } else if (STATE_OPEN != _state) { errMsg(err) << "Anytone: Cannot enter program mode. Device is in state " << _state << "."; return false; } char ack[3]; // send "enter program mode" command if (! send_receive("PROGRAM", 7, ack, 3, err)) { errMsg(err) << "Anytone: Cannot enter program mode."; return false; } // check response if (0 != memcmp(ack, "QX\6", 3)) { errMsg(err) << "Anytone: Cannot enter program mode: Unexpected response. " << "Expected 515806 got " << QString::number(ack[0], 16) << QString::number(ack[1], 16) << QString::number(ack[1], 16) << "."; close(); _state = STATE_ERROR; return false; } logDebug() << "Anytone: In program-mode now."; _state = STATE_PROGRAM; return true; } bool AnytoneInterface::request_identifier(RadioVariant &info, const ErrorStack &err) { if (STATE_PROGRAM != _state) { errMsg(err) << "Anytone: Cannot request identifier. Device not in program mode, is in state " << _state << "."; return false; } RadioInfoResponse resp; // send "identify" command (in program mode) if (! send_receive("\2", 1, (char *)&resp, sizeof(RadioInfoResponse))) { errMsg(err) << "Anytone: Cannot request identifier."; return false; } // check response if (('I'!=resp.prefix) || (0x06 != resp.eot)) { errMsg(err) << "Anytone: Cannot request identifier: Unexpected response."; close(); _state = STATE_ERROR; return false; } info.name = QString::fromLocal8Bit(resp.model, strnlen(resp.model, sizeof(resp.model))).simplified(); info.bands = resp.bands; info.version = QString::fromLocal8Bit(resp.version, strnlen(resp.version, sizeof(resp.version))).simplified(); logDebug() << "Found radio '" << info.name << "', version '" << info.version << "'."; return true; } bool AnytoneInterface::leave_program_mode(const ErrorStack &err) { if (STATE_OPEN == _state) { logDebug() << "Device in open mode -> no need to leave program mode."; return true; } else if (STATE_PROGRAM != _state) { errMsg(err) << "Anytone: Cannot leave program mode. Device in state " << _state << "."; return false; } char ack[1]; if (! send_receive("END", 3, ack, 1)) { errMsg(err) << "Anytone: Cannot leave program mode."; return false; } logDebug() << "Anytone: Left program-mode."; _state = STATE_OPEN; return true; } bool AnytoneInterface::send_receive(const char *cmd, int clen, char *resp, int rlen, const ErrorStack &err) { // Try to write command to device if (clen != QSerialPort::write(cmd, clen)) { errMsg(err) << "Cannot send command to device."; close(); _state = STATE_ERROR; return false; } // Read from device until complete response has been read char *p = resp; int len = rlen; while (len > 0) { if (! waitForReadyRead(1000)) { errMsg(err) << "No response from device: Timeout."; close(); _state = STATE_ERROR; return false; } int r = QSerialPort::read(p, len); if (r < 0) { errMsg(err) << "Cannot read response from device."; close(); _state = STATE_ERROR; return false; } p += r; len-=r; } // done return true; } /* ********************************************************************************************* * * Implementation of AnytoneGD32Interface * ********************************************************************************************* */ AnytoneGD32Interface::AnytoneGD32Interface(const USBDeviceDescriptor &descriptor, const ErrorStack &err, QObject *parent) : AnytoneInterface(descriptor, err, parent) { // pass... } RadioInfo AnytoneGD32Interface::identifier(const ErrorStack &err) { if (! _info.isValid()) return RadioInfo(); if ("D868UVE" == _info.name) { return RadioInfo::byID(RadioInfo::D868UVE); } else if ("D6X2UV" == _info.name) { return RadioInfo::byID(RadioInfo::DMR6X2UV); } else if ("D6X2UV2" == _info.name) { return RadioInfo::byID(RadioInfo::DMR6X2UV2); } else if ("D878UV" == _info.name) { return RadioInfo::byID(RadioInfo::D878UV); } else if ("D878UV2" == _info.name) { return RadioInfo::byID(RadioInfo::D878UVII); } else if ("D578UV" == _info.name) { return RadioInfo::byID(RadioInfo::D578UV); } else if ("D578UV2" == _info.name) { return RadioInfo::byID(RadioInfo::D578UVII); } errMsg(err) << tr("Unsupported AnyTone radio '%1', HW rev. '%2'.") .arg(_info.name).arg(_info.version); return RadioInfo(); } USBDeviceInfo AnytoneGD32Interface::interfaceInfo() { return USBDeviceInfo(USBDeviceInfo::Class::Serial, USB_VID_GD32, USB_PID_GD32); } QList AnytoneGD32Interface::detect(bool saveOnly) { Q_UNUSED(saveOnly); return USBSerial::detect(USB_VID_GD32, USB_PID_GD32, true); } /* ********************************************************************************************* * * Implementation of AnytoneSTM32Interface * ********************************************************************************************* */ AnytoneSTM32Interface::AnytoneSTM32Interface(const USBDeviceDescriptor &descriptor, const ErrorStack &err, QObject *parent) : AnytoneInterface(descriptor, err, parent) { // pass... } RadioInfo AnytoneSTM32Interface::identifier(const ErrorStack &err) { if (! _info.isValid()) return RadioInfo(); if ("D578UV" == _info.name) { return RadioInfo::byID(RadioInfo::D578UV); } else if ("D578UV2" == _info.name) { return RadioInfo::byID(RadioInfo::D578UVII); } else if ("D168UV" == _info.name) { return RadioInfo::byID(RadioInfo::D168UV); } errMsg(err) << tr("Unsupported AnyTone radio '%1', HW rev. '%2'.") .arg(_info.name).arg(_info.version); return RadioInfo(); } USBDeviceInfo AnytoneSTM32Interface::interfaceInfo() { return USBDeviceInfo(USBDeviceInfo::Class::Serial, USB_VID_STM32, USB_PID_STM32); } QList AnytoneSTM32Interface::detect(bool saveOnly) { Q_UNUSED(saveOnly); return USBSerial::detect(USB_VID_STM32, USB_PID_STM32, true); } ================================================ FILE: lib/anytone_interface.hh ================================================ #ifndef ANYTONEINTERFACE_HH #define ANYTONEINTERFACE_HH #include "usbserial.hh" /** Implements the interface to Anytone D868UV, D878UV, etc radios. * * This interface uses a USB serial-port to communicate with the device. To find the corresponding * port, the device-specific VID @c 0x28e9 and PID @c 0x018a are used. Hence no udev rules are * needed to access these devices. The user, however, should be a member of the @c dialout group * to get access to the serial interfaces. * * @ingroup anytone */ class AnytoneInterface : public USBSerial { Q_OBJECT public: /** Collects information about the particular radio being accessed. */ struct RadioVariant { /** The name of the radio. */ QString name; /** A code for which bands are supported. */ char bands; /** The (firmware/hardware) version. */ QString version; /** Empty constructor. */ RadioVariant(); /** Returns @c true if the radio info is valid. */ bool isValid() const; }; public: /** Constructs a new interface to Anytone radios. If a matching device was found, @c isOpen * returns @c true. */ explicit AnytoneInterface(const USBDeviceDescriptor &descriptor, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Destructor. */ virtual ~AnytoneInterface(); /** Closes the interface to the device. */ void close(); /** Reads the radio info from the device and returns it. * The information is only read once. */ bool getInfo(RadioVariant &info); bool read_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool read_finish(const ErrorStack &err=ErrorStack()); bool write_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool write_finish(const ErrorStack &err=ErrorStack()); bool reboot(const ErrorStack &err=ErrorStack()); protected: /** Send command message to radio to ender program state. */ bool enter_program_mode(const ErrorStack &err=ErrorStack()); /** Sends a request to radio to identify itself. */ bool request_identifier(RadioVariant &info, const ErrorStack &err=ErrorStack()); /** Sends a command message to radio to leave program state and reboot. */ bool leave_program_mode(const ErrorStack &err=ErrorStack()); /** Internal used method to send messages to and receive responses from radio. */ bool send_receive(const char *cmd, int clen, char *resp, int rlen, const ErrorStack &err=ErrorStack()); protected: /** Binary representation of a read request to the radio. */ struct __attribute__((packed)) ReadRequest { char cmd; ///< Fixed to 'R'. uint32_t addr; ///< Memory address in little-endian. uint8_t size; ///< Fixed to 16. /// Constructs a read request for the specified address. ReadRequest(uint32_t addr); }; /** Binary representation of a read response from the radio. */ struct __attribute__((packed)) ReadResponse { char cmd; ///< Fixed to 'W'. uint32_t addr; ///< Memory address in big-endian. uint8_t size; ///< Fixed to 16. char data[16]; ///< The actual data. uint8_t sum; ///< Sum over address, size and data. uint8_t ack; ///< Fixed to 0x06. /** Check the response, returns @c true if read request was successful. * @param addr The read address to verify. * @param msg On error, contains a message describing the issue. */ bool check(uint32_t addr, QString &msg) const; }; /** Binary representation of a write request to the radio. */ struct __attribute__((packed)) WriteRequest { char cmd; ///< Fixed to 'W' uint32_t addr; ///< Memory address in big-endian. uint8_t size; ///< Fixed to 16 char data[16]; ///< The actual data. uint8_t sum; ///< Sum over addr, size and data. uint8_t ack; ///< Fixed to 0x06; /** Assembles a write request message to the given address with the given data. * @param addr Specifies the address to write to. * @param data 16 bytes of payload. */ WriteRequest(uint32_t addr, const char *data); }; /** Structure of radio information response. */ struct __attribute__((packed)) RadioInfoResponse { char prefix; ///< Fixed prefix. Set to 'I' on success. char model[7]; ///< Model name. uint8_t bands; ///< Frequency bands supported by radio. char version[6]; ///< Version number. Either hardware or 'radio' version. uint8_t eot; ///< Fixed 0x06 on success. }; /** Possible states of the radio interface. */ enum State { STATE_INITIALIZED, ///< Initial state. STATE_OPEN, ///< Interface to radio is open. STATE_PROGRAM, ///< Radio is in program mode. STATE_CLOSED, ///< Interface to radio is closed (captive final state). STATE_ERROR ///< An error occurred (captive final state), /// use @c errorMessage() to get an error message. }; /** Holds the state of the interface. */ State _state; /** Holds the radio info. */ RadioVariant _info; }; /** Implements the interface to Anytone GD32 revisions of D578UV radios. * * This interface is identical to the previous ones, except it uses a different VID/PID combo. * That is VID @c 0x2e3c and PID @c 0x5740 are used. * * @ingroup anytone */ class AnytoneGD32Interface: public AnytoneInterface { Q_OBJECT public: /** Constructs a new interface to Anytone radios. If a matching device was found, @c isOpen * returns @c true. */ explicit AnytoneGD32Interface(const USBDeviceDescriptor &descriptor, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Returns an identifier of the radio. */ RadioInfo identifier(const ErrorStack &err=ErrorStack()); public: /** Returns some information about this interface. */ static USBDeviceInfo interfaceInfo(); /** Tries to find all interfaces connected AnyTone radios. */ static QList detect(bool saveOnly=true); }; /** Implements the interface to Anytone SMT32 revisions of D578UV radios. * * This interface is identical to the previous ones, except it uses a different VID/PID combo. * That is VID @c 0x2e3c and PID @c 0x5740 are used. * * @ingroup anytone */ class AnytoneSTM32Interface: public AnytoneInterface { Q_OBJECT public: /** Constructs a new interface to Anytone radios. If a matching device was found, @c isOpen * returns @c true. */ explicit AnytoneSTM32Interface(const USBDeviceDescriptor &descriptor, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Returns an identifier of the radio. */ RadioInfo identifier(const ErrorStack &err=ErrorStack()); public: /** Returns some information about this interface. */ static USBDeviceInfo interfaceInfo(); /** Tries to find all interfaces connected AnyTone radios. */ static QList detect(bool saveOnly=true); }; #endif // ANYTONEINTERFACE_HH ================================================ FILE: lib/anytone_limits.cc ================================================ #include "anytone_limits.hh" AnytoneLimits::AnytoneLimits(const QString &hardwareRevision, const QString &supportedRevision, bool betaWarning, QObject *parent) : RadioLimits(betaWarning, parent), _hardwareRevision(hardwareRevision), _supportedRevision(supportedRevision) { // pass... } bool AnytoneLimits::verifyConfig(const Config *config, RadioLimitContext &context) const { bool success = RadioLimits::verifyConfig(config, context); if (_hardwareRevision.isEmpty()) return success; if (_supportedRevision < _hardwareRevision) { auto &msg = context.newMessage(RadioLimitIssue::Warning); msg = tr("You are likely using a newer radio reversion (%1) than supported (%2) by qdmr. " "The codeplug might be incompatible. " "Notify the developers of qdmr about the new reversion.").arg(_hardwareRevision, _supportedRevision); } else if (_supportedRevision > _hardwareRevision) { auto &msg = context.newMessage(RadioLimitIssue::Warning); msg = tr("You are likely using an older hardware reversion (%1) than supported (%2) by qdmr. " "The codeplug might be incompatible.").arg(_hardwareRevision, _supportedRevision); } return success; } ================================================ FILE: lib/anytone_limits.hh ================================================ #ifndef ANYTONELIMITS_HH #define ANYTONELIMITS_HH #include "radiolimits.hh" /** Base class of limits for all AnyTone radios. * This class extends @c RadioLimits and implements the hardware revision check. * @ingroup anytone */ class AnytoneLimits: public RadioLimits { Q_OBJECT protected: /** Constructor. * @param hardwareRevision Specifies the hardware revision as reported by the radio. If empty, * no check is perfored. * @param supportedRevision Specifies the supported hardware revision. * @param betaWarning If @c true, a warning is issued that the radio is still under development. * @param parent Specifies the QObject parent. */ AnytoneLimits(const QString &hardwareRevision, const QString &supportedRevision, bool betaWarning, QObject *parent=nullptr); public: bool verifyConfig(const Config *config, RadioLimitContext &context) const; protected: /** Holds the hardware revision of the radio. */ QString _hardwareRevision; /** Holds the supported hardware revision of the radio. */ QString _supportedRevision; }; #endif // ANYTONELIMITS_HH ================================================ FILE: lib/anytone_radio.cc ================================================ #include "anytone_radio.hh" #include "d868uv.hh" #include "config.hh" #include "logger.hh" #include "configcopyvisitor.hh" #define RBSIZE 16 #define WBSIZE 16 AnytoneRadio::AnytoneRadio(const QString &name, AnytoneInterface *device, QObject *parent) : Radio(parent), _name(name), _dev(device), _codeplugFlags(), _config(nullptr), _codeplug(nullptr), _callsigns(nullptr), _satellites(nullptr) { // Check if device is open if ((nullptr==_dev) || (! _dev->isOpen())) { _task = StatusError; return; } } AnytoneRadio::~AnytoneRadio() { if (_dev && _dev->isOpen()) { _dev->reboot(); _dev->close(); } if (_dev) { _dev->deleteLater(); _dev = nullptr; } } const QString & AnytoneRadio::name() const { return _name; } const Codeplug & AnytoneRadio::codeplug() const { return *_codeplug; } Codeplug & AnytoneRadio::codeplug() { return *_codeplug; } bool AnytoneRadio::startDownload(const TransferFlags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; _task = StatusDownload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); start(); return true; } bool AnytoneRadio::startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; if (_config) delete _config; // Cannot upload null-pointer if (nullptr == (_config = config)) return false; _task = StatusUpload; _codeplugFlags = flags; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); // also, move config to thread _config->moveToThread(this); start(); return true; } bool AnytoneRadio::startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err) { _callsigns->encode(db, selection); _task = StatusUploadCallsigns; _errorStack = err; if (selection.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); start(); return true; } bool AnytoneRadio::startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err) { if (! _satellites->encode(db, err)) { errMsg(err) << "Cannot encode satellite config for AnyTone device."; return false; } _task = StatusUploadSatellites; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); start(); return true; } void AnytoneRadio::run() { if (StatusDownload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { _task = StatusError; emit downloadError(this); return; } emit downloadStarted(); if (! download()) { _dev->reboot(); _dev->close(); _task = StatusError; emit downloadError(this); return; } _dev->close(); _task = StatusIdle; emit downloadFinished(this, _codeplug); _config = nullptr; } else if (StatusUpload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { _task = StatusError; emit uploadError(this); return; } emit uploadStarted(); if (! upload()) { _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadCallsigns == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { _task = StatusError; emit uploadError(this); return; } emit uploadStarted(); if (! uploadCallsigns()) { _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadSatellites == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { _task = StatusError; emit uploadError(this); return; } emit uploadStarted(); if (! uploadSatellites()) { _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } } bool AnytoneRadio::download() { if (nullptr == _codeplug) { errMsg(_errorStack) << "Cannot download codeplug: Object not created yet."; return false; } logDebug() << "Download of " << _codeplug->image(0).numElements() << " bitmaps."; // Download bitmaps for (int n=0; n<_codeplug->image(0).numElements(); n++) { unsigned addr = _codeplug->image(0).element(n).address(); unsigned size = _codeplug->image(0).element(n).data().size(); if (! _dev->read(0, addr, _codeplug->data(addr), size, _errorStack)) { errMsg(_errorStack) << "Cannot download codeplug."; return false; } emit downloadProgress(float(n*100)/_codeplug->image(0).numElements()); } // Allocate remaining memory sections unsigned nstart = _codeplug->image(0).numElements(); _codeplug->allocateForDecoding(); logDebug() << "Download of " << _codeplug->image(0).numElements()-nstart << " discovered elements."; // Check every segment in the remaining codeplug for (int n=nstart; n<_codeplug->image(0).numElements(); n++) { if (! _codeplug->image(0).element(n).isAligned(RBSIZE)) { errMsg(_errorStack) << "Cannot download codeplug: Codeplug element " << n << " (addr=" << _codeplug->image(0).element(n).address() << ", size=" << _codeplug->image(0).element(n).data().size() << ") is not aligned with blocksize " << RBSIZE << "."; return false; } } // Download remaining memory sections for (int n=nstart; n<_codeplug->image(0).numElements(); n++) { unsigned addr = _codeplug->image(0).element(n).address(); unsigned size = _codeplug->image(0).element(n).data().size(); if (! _dev->read(0, addr, _codeplug->data(addr), size, _errorStack)) { errMsg(_errorStack) << "Cannot download codeplug."; return false; } logDebug() << "Read " << Qt::hex << size << "h bytes from address " << Qt::hex << addr << "."; emit downloadProgress(float(n*100)/_codeplug->image(0).numElements()); } return true; } bool AnytoneRadio::upload() { if (nullptr == _codeplug) { errMsg(_errorStack) << "Cannot write codeplug: Object not created yet."; return false; } // Download bitmaps first size_t nbitmaps = _codeplug->numImages(); for (int n=0; n<_codeplug->image(0).numElements(); n++) { unsigned addr = _codeplug->image(0).element(n).address(); unsigned size = _codeplug->image(0).element(n).data().size(); if (! _dev->read(0, addr, _codeplug->data(addr), size, _errorStack)) { errMsg(_errorStack) << "Cannot read codeplug for update."; return false; } emit uploadProgress(float(n*25)/_codeplug->image(0).numElements()); } // Allocate all memory sections that must be read first // and written back to the device more or less untouched _codeplug->allocateUpdated(); // Download new memory sections for update for (int n=nbitmaps; n<_codeplug->image(0).numElements(); n++) { unsigned addr = _codeplug->image(0).element(n).address(); unsigned size = _codeplug->image(0).element(n).data().size(); if (! _dev->read(0, addr, _codeplug->data(addr), size, _errorStack)) { errMsg(_errorStack) << "Cannot read codeplug for update."; return false; } emit uploadProgress(25+float(n*25)/_codeplug->image(0).numElements()); } // Update binary codeplug from config if (! _codeplug->encode(_config, _codeplugFlags, _errorStack)) { errMsg(_errorStack) << "Cannot encode codeplug."; return false; } // Sort all elements before uploading _codeplug->image(0).sort(); // Upload all elements back to the device for (int n=0; n<_codeplug->image(0).numElements(); n++) { unsigned addr = _codeplug->image(0).element(n).address(); unsigned size = _codeplug->image(0).element(n).data().size(); if (! _dev->write(0, addr, _codeplug->data(addr), size, _errorStack)) { errMsg(_errorStack) << "Cannot write codeplug."; return false; } emit uploadProgress(50+float(n*50)/_codeplug->image(0).numElements()); } return true; } bool AnytoneRadio::uploadCallsigns() { // Sort all elements before uploading _callsigns->image(0).sort(); size_t totalBlocks = _callsigns->memSize()/WBSIZE; size_t blkWritten = 0; // Upload all elements back to the device for (int n=0; n<_callsigns->image(0).numElements(); n++) { unsigned addr = _callsigns->image(0).element(n).address(); unsigned size = _callsigns->image(0).element(n).data().size(); unsigned nblks = size/WBSIZE; for (unsigned i=0; iwrite(0, addr+i*WBSIZE, _callsigns->data(addr)+i*WBSIZE, WBSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot write callsign db."; _task = StatusError; return false; } blkWritten++; emit uploadProgress(float(blkWritten*100)/totalBlocks); } } return true; } bool AnytoneRadio::uploadSatellites() { // Sort all elements before uploading _satellites->image(0).sort(); size_t totalBlocks = _satellites->memSize()/WBSIZE; size_t blkWritten = 0; // Upload all elements back to the device for (int n=0; n<_satellites->image(0).numElements(); n++) { unsigned addr = _satellites->image(0).element(n).address(); unsigned size = _satellites->image(0).element(n).data().size(); unsigned nblks = size/WBSIZE; for (unsigned i=0; iwrite(0, addr+i*WBSIZE, _satellites->data(addr)+i*WBSIZE, WBSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot write satellite config."; _task = StatusError; return false; } blkWritten++; emit uploadProgress(float(blkWritten*100)/totalBlocks); } } return true; } ================================================ FILE: lib/anytone_radio.hh ================================================ /** @defgroup anytone Anytone Radios * Base classes for all anytone radios. * * Anytone radios share a lot of common formats and interfaces. * Consequently there are base-classes for radio interfaces (@c AnytoneInterface), codeplugs * (@c AnytoneCodeplug) and radios (@c AnytoneRadio). This helps to keep the burden of implementing * the support for each radio at a minimum. * * @ingroup dsc */ #ifndef __ANYTONE_RADIO_HH__ #define __ANYTONE_RADIO_HH__ #include "radio.hh" #include "anytone_interface.hh" #include "anytone_codeplug.hh" #include "anytone_satelliteconfig.hh" /** Implements an interface to Anytone radios. * * The transfer of the codeplug to the device is performed in 4 steps. * * First only the bitmaps of all lists are downloaded from the device. Then all elements that are * not touched or only updated by the common codeplug config are downloaded. Then, the common * config gets applied to the binary codeplug. That is, all channels, contacts, zones, group-lists * and scan-lists are generated and their bitmaps gets updated accordingly. Also the general config * gets updated from the common codeplug settings. Finally, the resulting binary codeplug gets * written back to the device. * * This rather complex method of writing a codeplug to the device is needed to maintain all * settings within the radio that are not defined within the common codeplug config while keeping * the amount of data being read from and written to the device small. * * @ingroup anytone */ class AnytoneRadio: public Radio { Q_OBJECT protected: /** Do not construct this class directly. */ explicit AnytoneRadio(const QString &name, AnytoneInterface *device=nullptr, QObject *parent=nullptr); public: /** Destructor. */ virtual ~AnytoneRadio(); const QString &name() const; const Codeplug &codeplug() const; Codeplug &codeplug(); public slots: /** Starts the download of the codeplug and derives the generic configuration from it. */ bool startDownload(const TransferFlags &flags, const ErrorStack &err=ErrorStack()); /** Derives the device-specific codeplug from the generic configuration and uploads that * codeplug to the radio. */ bool startUpload(Config *config, const Codeplug::Flags &flags = Codeplug::Flags(), const ErrorStack &err=ErrorStack()); /** Encodes the given user-database and uploads it to the device. */ bool startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection=CallsignDB::Flags(), const ErrorStack &err=ErrorStack()); bool startUploadSatelliteConfig( SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err=ErrorStack()); protected: /** Thread main routine, performs all blocking IO operations for codeplug up- and download. */ void run(); private: /** Downloads the codeplug from the radio. This method block until the download is complete. */ virtual bool download(); /** Uploads the encoded codeplug to the radio. This method block until the upload is complete. */ virtual bool upload(); /** Uploads the encoded callsign database to the radio. * This method block until the upload is complete. */ virtual bool uploadCallsigns(); /** Uploads the encoded satellite config to the radio. * This method block until the upload is complete. */ virtual bool uploadSatellites(); protected: /** The device identifier. */ QString _name; /** The interface to the radio. */ AnytoneInterface *_dev; /** If @c true, the codeplug on the radio gets updated upon upload. If @c false, it gets * overridden. */ Codeplug::Flags _codeplugFlags; /** Owns the generic configuration. */ Config *_config; /** A weak reference to the user-database. */ UserDatabase *_userDB; /** The actual binary codeplug representation. */ AnytoneCodeplug *_codeplug; /** The actual binary callsign database representation. */ CallsignDB *_callsigns; /** The actual binary callsign database representation. */ AnytoneSatelliteConfig *_satellites; }; #endif // __D868UV_HH__ ================================================ FILE: lib/anytone_satelliteconfig.cc ================================================ #include "anytone_satelliteconfig.hh" #include "anytone_codeplug.hh" #include "satellitedatabase.hh" #include "logger.hh" /* ********************************************************************************************* * * Implementation of AnytoneSatelliteConfig::Satellite * ********************************************************************************************* */ AnytoneSatelliteConfig::SatelliteElement::SatelliteElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } AnytoneSatelliteConfig::SatelliteElement::SatelliteElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void AnytoneSatelliteConfig::SatelliteElement::clear() { memset(_data, 0, size()); memset(_data, 0x20, 0x50); } void AnytoneSatelliteConfig::SatelliteElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::name(), 0); } void AnytoneSatelliteConfig::SatelliteElement::setEpoch(const OrbitalElement::Epoch &epoch) { double tmp = epoch.toEpoch(); int year = epoch.year; int day = tmp; tmp -= day; tmp *= 100000000; int fday = tmp; QString fmt_year = QString("%1").arg(year%100, 2, 10, QChar('0')); QString fmt_day = QString("%1.%2") .arg(day, 3, 10, QChar('0')) .arg(fday, 8, 10, QChar('0')); writeASCII(Offset::epochYear(), fmt_year, 2); writeASCII(Offset::epochDay(), fmt_day, 12, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setMeanMotionDerivative(double dmm) { QString fmt = QString("%1.%2") .arg(dmm < 0 ? '-' : ' ') .arg((int)(std::abs(dmm) * 100000000), 8, 10, QChar('0')); writeASCII(Offset::meanMotionDerivative(), fmt, 10, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setInclination(double incl) { int ddd = incl; incl -= ddd; int ffff = incl*10000; QString fmt = QString("%1.%2") .arg(ddd, 3, 10, QChar(' ')) .arg(ffff, 4, 10, QChar('0')); writeASCII(Offset::inclination(), fmt, 8, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setAscension(double asc) { int ddd = asc; asc -= ddd; int ffff = asc*10000; QString fmt = QString("%1.%2") .arg(ddd, 3, 10, QChar(' ')) .arg(ffff, 4, 10, QChar('0')); writeASCII(Offset::ascension(), fmt, 8, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setEccentricity(double ecc) { QString fmt = QString("%1").arg((int)(ecc * 10000000), 7, 10, QChar('0')); writeASCII(Offset::eccentricity(), fmt, 7, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setPerigee(double peri) { int ddd = peri; peri -= ddd; int ffff = peri*10000; QString fmt = QString("%1.%2") .arg(ddd, 3, 10, QChar(' ')) .arg(ffff, 4, 10, QChar('0')); writeASCII(Offset::perigee(), fmt, 8, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setAnomaly(double ma) { int ddd = ma; ma -= ddd; int ffff = ma*10000; QString fmt = QString("%1.%2") .arg(ddd, 3, 10, QChar(' ')) .arg(ffff, 4, 10, QChar('0')); writeASCII(Offset::anomaly(), fmt, 8, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setMeanMotion(double mm) { int dd = mm; mm -= dd; int ffffffff = mm*100000000; QString fmt = QString("%1.%2") .arg(dd, 2, 10, QChar(' ')) .arg(ffffffff, 8, 10, QChar('0')); writeASCII(Offset::meanMotion(), fmt, 11, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setRevolution(unsigned int num) { writeASCII(Offset::revolution(), QString("%1").arg(num, 5, 10, QChar('0')), 5, 0x20); } void AnytoneSatelliteConfig::SatelliteElement::setDownlink(const Frequency &f) { setUInt32_le(Offset::downlinkFrequency(), f.inHz()/10); } void AnytoneSatelliteConfig::SatelliteElement::setDownlinkTone(const SelectiveCall &tone) { if (tone.isInvalid()) { setUInt8(Offset::downlinkToneType(), (int)ToneType::None); } else if (tone.isCTCSS()) { setUInt8(Offset::downlinkToneType(), (int)ToneType::CTCSS); setUInt8(Offset::downlinkCTCSS(), AnytoneCodeplug::CTCSS::encode(tone)); } else if (tone.isDCS()) { uint16_t val = tone.binCode(); if (tone.isInverted()) val |= (1<<9); setUInt8(Offset::downlinkToneType(), (int)ToneType::DCS); setUInt16_le(Offset::downlinkDCS(), val); } } void AnytoneSatelliteConfig::SatelliteElement::setUplink(const Frequency &f) { setUInt32_le(Offset::uplinkFrequency(), f.inHz()/10); } void AnytoneSatelliteConfig::SatelliteElement::setUplinkTone(const SelectiveCall &tone) { if (tone.isInvalid()) { setUInt8(Offset::uplinkToneType(), (int)ToneType::None); } else if (tone.isCTCSS()) { setUInt8(Offset::uplinkToneType(), (int)ToneType::CTCSS); setUInt8(Offset::uplinkCTCSS(), AnytoneCodeplug::CTCSS::encode(tone)); } else if (tone.isDCS()) { uint16_t val = tone.binCode(); if (tone.isInverted()) val |= (1<<9); setUInt8(Offset::uplinkToneType(), (int)ToneType::DCS); setUInt16_le(Offset::uplinkDCS(), val); } } bool AnytoneSatelliteConfig::SatelliteElement::encode(const Satellite &sat, const ErrorStack &err) { Q_UNUSED(err) setName(sat.name()); setEpoch(sat.epoch()); setMeanMotionDerivative(sat.meanMotionDerivative()); setInclination(sat.inclination()); setAscension(sat.ascension()); setEccentricity(sat.eccentricity()); setPerigee(sat.perigee()); setAnomaly(sat.meanAnomaly()); setMeanMotion(sat.meanMotion()); setRevolution(sat.revolutionNumber()); setDownlink(sat.fmDownlink()); setDownlinkTone(sat.fmDownlinkTone()); setUplink(sat.fmUplink()); setUplinkTone(sat.fmUplinkTone()); return true; } /* ********************************************************************************************* * * Implementation of AnytoneSatelliteConfig * ********************************************************************************************* */ AnytoneSatelliteConfig::AnytoneSatelliteConfig(QObject *parent) : SatelliteConfig{parent} { addImage("AnyTone Satellite Configuration"); } AnytoneSatelliteConfig::SatelliteElement AnytoneSatelliteConfig::satellite(unsigned int idx) { return SatelliteElement(data(Offset::satellites() + idx*Offset::betweenSatellites())); } bool AnytoneSatelliteConfig::encode(SatelliteDatabase *db, const ErrorStack &err) { unsigned int numSat = std::min(Limit::satellites(), db->count()); image(0).addElement(Offset::satellites(), numSat*SatelliteElement::size()); for (unsigned int i=0; igetAt(i).name() << "' at index " << i << "."; if (! satellite(i).encode(db->getAt(i), err)) { errMsg(err) << "Cannot encode satellite '" << db->getAt(i).name() << "at index " << i << "."; return false; } } return true; } ================================================ FILE: lib/anytone_satelliteconfig.hh ================================================ #ifndef ANYTONE_SATELLITECONFIG_HH #define ANYTONE_SATELLITECONFIG_HH #include "satelliteconfig.hh" #include "frequency.hh" #include "signaling.hh" #include "codeplug.hh" #include "orbitalelementsdatabase.hh" // Forward declarations class Satellite; /** Implementation of satellite configuration for all AnyTone devices. * * For now only the D878UV, D878UV2 and the DMR6X2 support sat tracking. * * @ingroup anytone */ class AnytoneSatelliteConfig : public SatelliteConfig { Q_OBJECT public: class SatelliteElement: Codeplug::Element { protected: /** Internal encoding of sub tone type. */ enum class ToneType { None = 0, CTCSS = 1, DCS = 2 }; protected: /** Hidden constructor. */ SatelliteElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit SatelliteElement(uint8_t *ptr); static constexpr unsigned int size() { return 0x200; } void clear(); /** Sets the satellite name. */ void setName(const QString &name); /** Sets the epoch of the orbital element. */ void setEpoch(const OrbitalElement::Epoch &epoch); /** Sets the derivative of the mean motion. */ void setMeanMotionDerivative(double dmm); /** Sets the inclination. */ void setInclination(double incl); /** Sets the right ascension of the ascending node. */ void setAscension(double asc); /** Sets the eccentricity */ void setEccentricity(double ecc); /** Sets the argument of perigee. */ void setPerigee(double peri); /** Sets the mean anomaly. */ void setAnomaly(double ma); /** Sets the mean motion. */ void setMeanMotion(double mm); /** Sets the revolution number. */ void setRevolution(unsigned int num); /** Sets the downlink frequency. */ void setDownlink(const Frequency &f); /** Sets the downlink sub tone. */ void setDownlinkTone(const SelectiveCall &tone); /** Sets the uplink frequency. */ void setUplink(const Frequency &f); /** Sets the uplink sub tone. */ void setUplinkTone(const SelectiveCall &tone); /** Encodes the given satellite. */ bool encode(const Satellite &sat, const ErrorStack &err=ErrorStack()); public: /** Some limits for the satellite. */ struct Limit: Element::Limit { /** Maximum size of satellite name. */ static constexpr unsigned int name() { return 8; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int epochYear() { return 0x0008; } static constexpr unsigned int epochDay() { return 0x000a; } static constexpr unsigned int meanMotionDerivative() { return 0x0017; } static constexpr unsigned int inclination() { return 0x0021; } static constexpr unsigned int ascension() { return 0x002a; } static constexpr unsigned int eccentricity() { return 0x0033; } static constexpr unsigned int perigee() { return 0x003b; } static constexpr unsigned int anomaly() { return 0x0044; } static constexpr unsigned int meanMotion() { return 0x004d; } static constexpr unsigned int revolution() { return 0x0058; } static constexpr unsigned int downlinkFrequency() { return 0x0060; } static constexpr unsigned int uplinkFrequency() { return 0x0064; } static constexpr unsigned int uplinkToneType() { return 0x0068; } static constexpr unsigned int downlinkToneType() { return 0x0069; } static constexpr unsigned int uplinkCTCSS() { return 0x006a; } static constexpr unsigned int downlinkCTCSS() { return 0x006b; } static constexpr unsigned int uplinkDCS() { return 0x006c; } static constexpr unsigned int downlinkDCS() { return 0x006e; } /// @endcond }; }; public: /** Default constructor. */ explicit AnytoneSatelliteConfig(QObject *parent = nullptr); SatelliteElement satellite(unsigned int idx); bool encode(SatelliteDatabase *db, const ErrorStack &err) override; public: /** Some limits for the satellite config. */ struct Limit { static constexpr unsigned int satellites() { return 200; } }; protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int satellites() { return 0x2ec0000; } static constexpr unsigned int betweenSatellites() { return SatelliteElement::size(); } /// @endcond }; }; #endif // ANYTONE_SATELLITECONFIG_HH ================================================ FILE: lib/anytone_settingsextension.cc ================================================ #include "anytone_settingsextension.hh" #include /* ********************************************************************************************* * * Implementation of AnytoneDMRSettingsExtension * ********************************************************************************************* */ AnytoneDMRSettingsExtension::AnytoneDMRSettingsExtension(QObject *parent) : ConfigItem(parent), _manualGroupCallHangTime(Interval::fromSeconds(5)), _manualPrivateCallHangTime(Interval::fromSeconds(7)), _wakeHeadPeriod(Interval::fromMilliseconds(100)), _filterOwnID(true), _monitorSlotMatch(SlotMatch::Off), _monitorColorCodeMatch(false), _monitorIDMatch(false), _monitorTimeSlotHold(true), _talkerAliasSource(TalkerAliasSource::UserDB), _encryption(EncryptionType::DMR) { // pass... } ConfigItem * AnytoneDMRSettingsExtension::clone() const { AnytoneDMRSettingsExtension *ext = new AnytoneDMRSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } Interval AnytoneDMRSettingsExtension::manualGroupCallHangTime() const { return _manualGroupCallHangTime; } void AnytoneDMRSettingsExtension::setManualGroupCallHangTime(Interval sec) { if (_manualGroupCallHangTime == sec) return; _manualGroupCallHangTime = sec; emit modified(this); } Interval AnytoneDMRSettingsExtension::manualPrivateCallHangTime() const { return _manualPrivateCallHangTime; } void AnytoneDMRSettingsExtension::setManualPrivateCallHangTime(Interval sec) { if (_manualPrivateCallHangTime == sec) return; _manualPrivateCallHangTime = sec; emit modified(this); } Interval AnytoneDMRSettingsExtension::wakeHeadPeriod() const { return _wakeHeadPeriod; } void AnytoneDMRSettingsExtension::setWakeHeadPeriod(Interval ms) { if (_wakeHeadPeriod == ms) return; _wakeHeadPeriod = ms; emit modified(this); } bool AnytoneDMRSettingsExtension::filterOwnIDEnabled() const { return _filterOwnID; } void AnytoneDMRSettingsExtension::enableFilterOwnID(bool enable) { if (_filterOwnID == enable) return; _filterOwnID = enable; emit modified(this); } AnytoneDMRSettingsExtension::SlotMatch AnytoneDMRSettingsExtension::monitorSlotMatch() const { return _monitorSlotMatch; } void AnytoneDMRSettingsExtension::setMonitorSlotMatch(SlotMatch match) { if (_monitorSlotMatch == match) return; _monitorSlotMatch = match; emit modified(this); } bool AnytoneDMRSettingsExtension::monitorColorCodeMatchEnabled() const { return _monitorColorCodeMatch; } void AnytoneDMRSettingsExtension::enableMonitorColorCodeMatch(bool enable) { if (_monitorColorCodeMatch == enable) return; _monitorColorCodeMatch = enable; emit modified(this); } bool AnytoneDMRSettingsExtension::monitorIDMatchEnabled() const { return _monitorIDMatch; } void AnytoneDMRSettingsExtension::enableMonitorIDMatch(bool enable) { if (_monitorIDMatch == enable) return; _monitorIDMatch = enable; emit modified(this); } bool AnytoneDMRSettingsExtension::monitorTimeSlotHoldEnabled() const { return _monitorTimeSlotHold; } void AnytoneDMRSettingsExtension::enableMonitorTimeSlotHold(bool enable) { if (_monitorTimeSlotHold == enable) return; _monitorTimeSlotHold = enable; emit modified(this); } AnytoneDMRSettingsExtension::TalkerAliasSource AnytoneDMRSettingsExtension::talkerAliasSource() const { return _talkerAliasSource; } void AnytoneDMRSettingsExtension::setTalkerAliasSource(TalkerAliasSource mode) { if (mode == _talkerAliasSource) return; _talkerAliasSource = mode; emit modified(this); } AnytoneDMRSettingsExtension::EncryptionType AnytoneDMRSettingsExtension::encryption() const { return _encryption; } void AnytoneDMRSettingsExtension::setEncryption(EncryptionType type) { if (type == _encryption) return; _encryption = type; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneGPSSettingsExtension * ********************************************************************************************* */ AnytoneGPSSettingsExtension::AnytoneGPSSettingsExtension(QObject *parent) : ConfigItem(parent), _timeZone(QTimeZone::utc()), _gpsRangeReporting(false), _gpsRangingInterval(Interval::fromSeconds(300)) { // pass... } ConfigItem * AnytoneGPSSettingsExtension::clone() const { auto *ext = new AnytoneGPSSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } QTimeZone AnytoneGPSSettingsExtension::timeZone() const { return _timeZone; } QString AnytoneGPSSettingsExtension::ianaTimeZone() const { return QString::fromLocal8Bit(_timeZone.id()); } void AnytoneGPSSettingsExtension::setTimeZone(const QTimeZone &zone) { if (_timeZone == zone) return; _timeZone = zone; emit modified(this); } void AnytoneGPSSettingsExtension::setIANATimeZone(const QString &id) { setTimeZone(QTimeZone(id.toLocal8Bit())); } bool AnytoneGPSSettingsExtension::positionReportingEnabled() const { return _gpsRangeReporting; } void AnytoneGPSSettingsExtension::enablePositionReporting(bool enable) { if (_gpsRangeReporting == enable) return; _gpsRangeReporting = enable; emit modified(this); } Interval AnytoneGPSSettingsExtension::updatePeriod() const { return _gpsRangingInterval; } void AnytoneGPSSettingsExtension::setUpdatePeriod(Interval sec) { if (_gpsRangingInterval == sec) return; _gpsRangingInterval = sec; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneRangingSettingsExtension * ********************************************************************************************* */ AnytoneRoamingSettingsExtension::AnytoneRoamingSettingsExtension(QObject *parent) : ConfigItem(parent), _autoRoam(false), _autoRoamPeriod(Interval::fromMinutes(1)), _autoRoamDelay(), _repeaterRangeCheck(false), _repeaterCheckInterval(Interval::fromSeconds(5)), _repeaterRangeCheckCount(3), _outOfRangeAlert(OutOfRangeAlert::None), _roamingStartCondition(RoamStart::Periodic), _roamingReturnCondition(RoamStart::Periodic), _notification(false), _notificationCount(1), _gpsRoaming(false), _defaultRoamingZone(new RoamingZoneReference(this)) { // pass... } ConfigItem * AnytoneRoamingSettingsExtension::clone() const { AnytoneRoamingSettingsExtension *ext = new AnytoneRoamingSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool AnytoneRoamingSettingsExtension::autoRoam() const { return _autoRoam; } void AnytoneRoamingSettingsExtension::enableAutoRoam(bool enable) { if (enable == _autoRoam) return; _autoRoam = enable; emit modified(this); } Interval AnytoneRoamingSettingsExtension::autoRoamPeriod() const { return _autoRoamPeriod; } void AnytoneRoamingSettingsExtension::setAutoRoamPeriod(Interval min) { if (_autoRoamPeriod == min) return; _autoRoamPeriod = min; emit modified(this); } Interval AnytoneRoamingSettingsExtension::autoRoamDelay() const { return _autoRoamDelay; } void AnytoneRoamingSettingsExtension::setAutoRoamDelay(Interval min) { if (_autoRoamDelay == min) return; _autoRoamDelay = min; emit modified(this); } bool AnytoneRoamingSettingsExtension::repeaterRangeCheckEnabled() const { return _repeaterRangeCheck; } void AnytoneRoamingSettingsExtension::enableRepeaterRangeCheck(bool enable) { if (_repeaterRangeCheck == enable) return; _repeaterRangeCheck = enable; emit modified(this); } Interval AnytoneRoamingSettingsExtension::repeaterCheckInterval() const { return _repeaterCheckInterval; } void AnytoneRoamingSettingsExtension::setRepeaterCheckInterval(Interval sec) { if (_repeaterCheckInterval == sec) return; _repeaterCheckInterval = sec; emit modified(this); } unsigned int AnytoneRoamingSettingsExtension::repeaterRangeCheckCount() const { return _repeaterRangeCheckCount; } void AnytoneRoamingSettingsExtension::setRepeaterRangeCheckCount(unsigned int sec) { if (_repeaterRangeCheckCount == sec) return; _repeaterRangeCheckCount = sec; emit modified(this); } AnytoneRoamingSettingsExtension::OutOfRangeAlert AnytoneRoamingSettingsExtension::outOfRangeAlert() const { return _outOfRangeAlert; } void AnytoneRoamingSettingsExtension::setOutOfRangeAlert(OutOfRangeAlert type) { if (type == _outOfRangeAlert) return; _outOfRangeAlert = type; emit modified(this); } AnytoneRoamingSettingsExtension::RoamStart AnytoneRoamingSettingsExtension::roamingStartCondition() const { return _roamingStartCondition; } void AnytoneRoamingSettingsExtension::setRoamingStartCondition(RoamStart start) { if (_roamingStartCondition == start) return; _roamingStartCondition = start; emit modified(this); } AnytoneRoamingSettingsExtension::RoamStart AnytoneRoamingSettingsExtension::roamingReturnCondition() const { return _roamingReturnCondition; } void AnytoneRoamingSettingsExtension::setRoamingReturnCondition(RoamStart end) { if (_roamingReturnCondition == end) return; _roamingReturnCondition = end; emit modified(this); } bool AnytoneRoamingSettingsExtension::notificationEnabled() const { return _notification; } void AnytoneRoamingSettingsExtension::enableNotification(bool enable) { if (_notification == enable) return; _notification = enable; emit modified(this); } unsigned int AnytoneRoamingSettingsExtension::notificationCount() const { return _notificationCount; } void AnytoneRoamingSettingsExtension::setNotificationCount(unsigned int n) { if (_notificationCount == n) return; _notificationCount = n; emit modified(this); } bool AnytoneRoamingSettingsExtension::gpsRoaming() const { return _gpsRoaming; } void AnytoneRoamingSettingsExtension::enableGPSRoaming(bool enable) { if (enable == _gpsRoaming) return; _gpsRoaming = enable; emit modified(this); } RoamingZoneReference * AnytoneRoamingSettingsExtension::defaultZone() const { return _defaultRoamingZone; } /* ********************************************************************************************* * * Implementation of AnytoneSettingsExtension * ********************************************************************************************* */ AnytoneSettingsExtension::AnytoneSettingsExtension(QObject *parent) : ConfigExtension(parent), _bootSettings(new AnytoneBootSettingsExtension(this)), _powerSaveSettings(new AnytonePowerSaveSettingsExtension(this)), _keySettings(new AnytoneKeySettingsExtension(this)), _toneSettings(new AnytoneToneSettingsExtension(this)), _displaySettings(new AnytoneDisplaySettingsExtension(this)), _audioSettings(new AnytoneAudioSettingsExtension(this)), _menuSettings(new AnytoneMenuSettingsExtension(this)), _autoRepeaterSettings(new AnytoneAutoRepeaterSettingsExtension(this)), _dmrSettings(new AnytoneDMRSettingsExtension(this)), _gpsSettings(new AnytoneGPSSettingsExtension(this)), _roamingSettings(new AnytoneRoamingSettingsExtension(this)), _bluetoothSettings(new AnytoneBluetoothSettingsExtension(this)), _repeaterSettings(new AnytoneRepeaterSettingsExtension(this)), _satelliteSettings(new AnytoneSatelliteSettingsExtension(this)), _vfoScanType(VFOScanType::Time), _modeA(VFOMode::Memory), _modeB(VFOMode::Memory), _zoneA(), _zoneB(), _selectedVFO(VFO::A), _subChannel(true), _minVFOScanFrequencyUHF(Frequency::fromMHz(430)), _maxVFOScanFrequencyUHF(Frequency::fromMHz(440)), _minVFOScanFrequencyVHF(Frequency::fromMHz(144)), _maxVFOScanFrequencyVHF(Frequency::fromMHz(146)), _keepLastCaller(false), _vfoStep(Frequency::fromkHz(5)), _steType(STEType::Off), _steFrequency(0), _steDuration(Interval::fromMilliseconds(300)), _tbstFrequency(Frequency::fromHz(1750)), _proMode(false), _maintainCallChannel(false), _fan(FanControl::Temperature) { connect(_bootSettings, &AnytoneBootSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_keySettings, &AnytoneKeySettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_toneSettings, &AnytoneToneSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_displaySettings, &AnytoneDisplaySettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_audioSettings, &AnytoneAudioSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_menuSettings, &AnytoneMenuSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_autoRepeaterSettings, &AnytoneAutoRepeaterSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_dmrSettings, &AnytoneDMRSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_roamingSettings, &AnytoneRoamingSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_repeaterSettings, &AnytoneRepeaterSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); connect(_satelliteSettings, &AnytoneSatelliteSettingsExtension::modified, this, &AnytoneSettingsExtension::modified); } ConfigItem * AnytoneSettingsExtension::clone() const { AnytoneSettingsExtension *ext = new AnytoneSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } AnytoneBootSettingsExtension * AnytoneSettingsExtension::bootSettings() const { return _bootSettings; } AnytonePowerSaveSettingsExtension * AnytoneSettingsExtension::powerSaveSettings() const { return _powerSaveSettings; } AnytoneKeySettingsExtension * AnytoneSettingsExtension::keySettings() const { return _keySettings; } AnytoneToneSettingsExtension * AnytoneSettingsExtension::toneSettings() const { return _toneSettings; } AnytoneDisplaySettingsExtension * AnytoneSettingsExtension::displaySettings() const { return _displaySettings; } AnytoneAudioSettingsExtension * AnytoneSettingsExtension::audioSettings() const { return _audioSettings; } AnytoneMenuSettingsExtension * AnytoneSettingsExtension::menuSettings() const { return _menuSettings; } AnytoneAutoRepeaterSettingsExtension * AnytoneSettingsExtension::autoRepeaterSettings() const { return _autoRepeaterSettings; } AnytoneDMRSettingsExtension * AnytoneSettingsExtension::dmrSettings() const { return _dmrSettings; } AnytoneGPSSettingsExtension * AnytoneSettingsExtension::gpsSettings() const { return _gpsSettings; } AnytoneRoamingSettingsExtension * AnytoneSettingsExtension::roamingSettings() const { return _roamingSettings; } AnytoneBluetoothSettingsExtension * AnytoneSettingsExtension::bluetoothSettings() const { return _bluetoothSettings; } AnytoneRepeaterSettingsExtension * AnytoneSettingsExtension::repeaterSettings() const { return _repeaterSettings; } AnytoneSatelliteSettingsExtension * AnytoneSettingsExtension::satelliteSettings() const { return _satelliteSettings; } AnytoneSettingsExtension::VFOScanType AnytoneSettingsExtension::vfoScanType() const { return _vfoScanType; } void AnytoneSettingsExtension::setVFOScanType(VFOScanType type) { if (_vfoScanType == type) return; _vfoScanType = type; emit modified(this); } AnytoneSettingsExtension::VFOMode AnytoneSettingsExtension::modeA() const { return _modeA; } void AnytoneSettingsExtension::setModeA(VFOMode mode) { if (_modeA == mode) return; _modeA = mode; emit modified(this); } AnytoneSettingsExtension::VFOMode AnytoneSettingsExtension::modeB() const { return _modeB; } void AnytoneSettingsExtension::setModeB(VFOMode mode) { if (_modeB == mode) return; _modeB = mode; emit modified(this); } ZoneReference * AnytoneSettingsExtension::zoneA() { return &_zoneA; } const ZoneReference * AnytoneSettingsExtension::zoneA() const { return &_zoneA; } ZoneReference * AnytoneSettingsExtension::zoneB() { return &_zoneB; } const ZoneReference * AnytoneSettingsExtension::zoneB() const { return &_zoneB; } AnytoneSettingsExtension::VFO AnytoneSettingsExtension::selectedVFO() const { return _selectedVFO; } void AnytoneSettingsExtension::setSelectedVFO(VFO vfo) { if (_selectedVFO == vfo) return; _selectedVFO = vfo; emit modified(this); } bool AnytoneSettingsExtension::subChannelEnabled() const { return _subChannel; } void AnytoneSettingsExtension::enableSubChannel(bool enable) { if (_subChannel == enable) return; _subChannel = enable; emit modified(this); } Frequency AnytoneSettingsExtension::minVFOScanFrequencyUHF() const { return _minVFOScanFrequencyUHF; } void AnytoneSettingsExtension::setMinVFOScanFrequencyUHF(Frequency hz) { if (_minVFOScanFrequencyUHF == hz) return; _minVFOScanFrequencyUHF = hz; emit modified(this); } Frequency AnytoneSettingsExtension::maxVFOScanFrequencyUHF() const { return _maxVFOScanFrequencyUHF; } void AnytoneSettingsExtension::setMaxVFOScanFrequencyUHF(Frequency hz) { if (_maxVFOScanFrequencyUHF == hz) return; _maxVFOScanFrequencyUHF = hz; emit modified(this); } Frequency AnytoneSettingsExtension::minVFOScanFrequencyVHF() const { return _minVFOScanFrequencyVHF; } void AnytoneSettingsExtension::setMinVFOScanFrequencyVHF(Frequency hz) { if (_minVFOScanFrequencyVHF == hz) return; _minVFOScanFrequencyVHF = hz; emit modified(this); } Frequency AnytoneSettingsExtension::maxVFOScanFrequencyVHF() const { return _maxVFOScanFrequencyVHF; } void AnytoneSettingsExtension::setMaxVFOScanFrequencyVHF(Frequency hz) { if (_maxVFOScanFrequencyVHF == hz) return; _maxVFOScanFrequencyVHF = hz; emit modified(this); } bool AnytoneSettingsExtension::keepLastCallerEnabled() const { return _keepLastCaller; } void AnytoneSettingsExtension::enableKeepLastCaller(bool enable) { if (_keepLastCaller == enable) return; _keepLastCaller = enable; emit modified(this); } Frequency AnytoneSettingsExtension::vfoStep() const { return _vfoStep; } void AnytoneSettingsExtension::setVFOStep(Frequency step) { if (_vfoStep == step) return; _vfoStep = step; emit modified(this); } AnytoneSettingsExtension::STEType AnytoneSettingsExtension::steType() const { return _steType; } void AnytoneSettingsExtension::setSTEType(STEType type) { if (_steType == type) return; _steType = type; emit modified(this); } double AnytoneSettingsExtension::steFrequency() const { return _steFrequency; } void AnytoneSettingsExtension::setSTEFrequency(double freq) { if (_steFrequency == freq) return; _steFrequency = freq; emit modified(this); } Interval AnytoneSettingsExtension::steDuration() const { return _steDuration; } void AnytoneSettingsExtension::setSTEDuration(Interval intv) { if (intv == _steDuration) return; _steDuration = intv; emit modified(this); } Frequency AnytoneSettingsExtension::tbstFrequency() const { return _tbstFrequency; } void AnytoneSettingsExtension::setTBSTFrequency(Frequency Hz) { if (_tbstFrequency == Hz) return; _tbstFrequency = Hz; emit modified(this); } bool AnytoneSettingsExtension::proModeEnabled() const { return _proMode; } void AnytoneSettingsExtension::enableProMode(bool enable) { if (_proMode == enable) return; _proMode = enable; emit modified(this); } bool AnytoneSettingsExtension::maintainCallChannelEnabled() const { return _maintainCallChannel; } void AnytoneSettingsExtension::enableMaintainCallChannel(bool enable) { if (_maintainCallChannel == enable) return; _maintainCallChannel = enable; emit modified(this); } AnytoneSettingsExtension::FanControl AnytoneSettingsExtension::fan() const { return _fan; } void AnytoneSettingsExtension::setFan(FanControl ctrl) { if (_fan == ctrl) return; _fan = ctrl; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneBootSettingsExtension * ********************************************************************************************* */ AnytoneBootSettingsExtension::AnytoneBootSettingsExtension(QObject *parent) : ConfigItem(parent), _priorityZoneA(new ZoneReference(this)), _priorityZoneB(new ZoneReference(this)), _gpsCheck(false) { // pass... } ConfigItem * AnytoneBootSettingsExtension::clone() const { AnytoneBootSettingsExtension *ext = new AnytoneBootSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } ZoneReference * AnytoneBootSettingsExtension::priorityZoneA() const { return _priorityZoneA; } ZoneReference * AnytoneBootSettingsExtension::priorityZoneB() const { return _priorityZoneB; } bool AnytoneBootSettingsExtension::gpsCheckEnabled() const { return _gpsCheck; } void AnytoneBootSettingsExtension::enableGPSCheck(bool enable) { if (_gpsCheck == enable) return; _gpsCheck = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytonePowerSaveSettingsExtension * ********************************************************************************************* */ AnytonePowerSaveSettingsExtension::AnytonePowerSaveSettingsExtension(QObject *parent) : ConfigItem(parent), _autoShutDownDelay(), _resetAutoShutdownOnCall(true), _powerSave(PowerSave::Save50), _atpc(false) { // pass... } ConfigItem * AnytonePowerSaveSettingsExtension::clone() const { AnytonePowerSaveSettingsExtension *ext = new AnytonePowerSaveSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } Interval AnytonePowerSaveSettingsExtension::autoShutdown() const { return _autoShutDownDelay; } void AnytonePowerSaveSettingsExtension::setAutoShutdown(Interval intv) { if (_autoShutDownDelay == intv) return; _autoShutDownDelay = intv; emit modified(this); } bool AnytonePowerSaveSettingsExtension::resetAutoShutdownOnCall() const { return _resetAutoShutdownOnCall; } void AnytonePowerSaveSettingsExtension::enableResetAutoShutdownOnCall(bool enable) { if (enable == _resetAutoShutdownOnCall) return; _resetAutoShutdownOnCall = enable; emit modified(this); } AnytonePowerSaveSettingsExtension::PowerSave AnytonePowerSaveSettingsExtension::powerSave() const { return _powerSave; } void AnytonePowerSaveSettingsExtension::setPowerSave(PowerSave save) { if (_powerSave == save) return; _powerSave = save; emit modified(this); } bool AnytonePowerSaveSettingsExtension::atpc() const { return _atpc; } void AnytonePowerSaveSettingsExtension::enableATPC(bool enable) { if (enable == _atpc) return; _atpc = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneKeySettingsExtension * ********************************************************************************************* */ AnytoneKeySettingsExtension::AnytoneKeySettingsExtension(QObject *parent) : ConfigItem(parent), _funcKey1Short(KeyFunction::VOX), _funcKey1Long(KeyFunction::ToggleVFO), _funcKey2Short(KeyFunction::Reverse), _funcKey2Long(KeyFunction::Off), _funcKey3Short(KeyFunction::Power), _funcKey3Long(KeyFunction::Record), _funcKey4Short(KeyFunction::Repeater), _funcKey4Long(KeyFunction::SMS), _funcKey5Short(KeyFunction::Reverse), _funcKey5Long(KeyFunction::Dial), _funcKey6Short(KeyFunction::Encryption), _funcKey6Long(KeyFunction::Off), _funcKeyAShort(KeyFunction::Off), _funcKeyALong(KeyFunction::Encryption), _funcKeyBShort(KeyFunction::Voltage), _funcKeyBLong(KeyFunction::Call), _funcKeyCShort(KeyFunction::Power), _funcKeyCLong(KeyFunction::VOX), _funcKeyDShort(KeyFunction::Off), _funcKeyDLong(KeyFunction::Off), _funcKnobShort(KeyFunction::Off), _funcKnobLong(KeyFunction::Off), _longPressDuration(Interval::fromSeconds(1)), _upDownFunction(UpDownKeyFunction::Channel), _autoKeyLock(false), _knobLock(false), _keypadLock(false), _sideKeysLock(false), _forcedKeyLock(false) { // pass... } ConfigItem * AnytoneKeySettingsExtension::clone() const { AnytoneKeySettingsExtension *ext = new AnytoneKeySettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey1Short() const { return _funcKey1Short; } void AnytoneKeySettingsExtension::setFuncKey1Short(KeyFunction func) { if (_funcKey1Short == func) return; _funcKey1Short = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey1Long() const { return _funcKey1Long; } void AnytoneKeySettingsExtension::setFuncKey1Long(KeyFunction func) { if (_funcKey1Long == func) return; _funcKey1Long = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey2Short() const { return _funcKey2Short; } void AnytoneKeySettingsExtension::setFuncKey2Short(KeyFunction func) { if (_funcKey2Short == func) return; _funcKey2Short = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey2Long() const { return _funcKey2Long; } void AnytoneKeySettingsExtension::setFuncKey2Long(KeyFunction func) { if (_funcKey2Long == func) return; _funcKey2Long = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey3Short() const { return _funcKey3Short; } void AnytoneKeySettingsExtension::setFuncKey3Short(KeyFunction func) { if (_funcKey3Short == func) return; _funcKey3Short = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey3Long() const { return _funcKey3Long; } void AnytoneKeySettingsExtension::setFuncKey3Long(KeyFunction func) { if (_funcKey3Long == func) return; _funcKey3Long = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey4Short() const { return _funcKey4Short; } void AnytoneKeySettingsExtension::setFuncKey4Short(KeyFunction func) { if (_funcKey4Short == func) return; _funcKey4Short = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey4Long() const { return _funcKey4Long; } void AnytoneKeySettingsExtension::setFuncKey4Long(KeyFunction func) { if (_funcKey4Long == func) return; _funcKey4Long = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey5Short() const { return _funcKey5Short; } void AnytoneKeySettingsExtension::setFuncKey5Short(KeyFunction func) { if (_funcKey5Short == func) return; _funcKey5Short = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey5Long() const { return _funcKey5Long; } void AnytoneKeySettingsExtension::setFuncKey5Long(KeyFunction func) { if (_funcKey5Long == func) return; _funcKey5Long = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey6Short() const { return _funcKey6Short; } void AnytoneKeySettingsExtension::setFuncKey6Short(KeyFunction func) { if (_funcKey6Short == func) return; _funcKey6Short = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKey6Long() const { return _funcKey6Long; } void AnytoneKeySettingsExtension::setFuncKey6Long(KeyFunction func) { if (_funcKey6Long == func) return; _funcKey6Long = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKeyAShort() const { return _funcKeyAShort; } void AnytoneKeySettingsExtension::setFuncKeyAShort(KeyFunction func) { if (_funcKeyAShort == func) return; _funcKeyAShort = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKeyALong() const { return _funcKeyALong; } void AnytoneKeySettingsExtension::setFuncKeyALong(KeyFunction func) { if (_funcKeyALong == func) return; _funcKeyALong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKeyBShort() const { return _funcKeyBShort; } void AnytoneKeySettingsExtension::setFuncKeyBShort(KeyFunction func) { if (_funcKeyBShort == func) return; _funcKeyBShort = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKeyBLong() const { return _funcKeyBLong; } void AnytoneKeySettingsExtension::setFuncKeyBLong(KeyFunction func) { if (_funcKeyBLong == func) return; _funcKeyBLong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKeyCShort() const { return _funcKeyCShort; } void AnytoneKeySettingsExtension::setFuncKeyCShort(KeyFunction func) { if (_funcKeyCShort == func) return; _funcKeyCShort = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKeyCLong() const { return _funcKeyCLong; } void AnytoneKeySettingsExtension::setFuncKeyCLong(KeyFunction func) { if (_funcKeyCLong == func) return; _funcKeyCLong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKeyDShort() const { return _funcKeyDShort; } void AnytoneKeySettingsExtension::setFuncKeyDShort(KeyFunction func) { if (_funcKeyDShort == func) return; _funcKeyDShort = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKeyDLong() const { return _funcKeyDLong; } void AnytoneKeySettingsExtension::setFuncKeyDLong(KeyFunction func) { if (_funcKeyDLong == func) return; _funcKeyDLong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKnobShort() const { return _funcKnobShort; } void AnytoneKeySettingsExtension::setFuncKnobShort(KeyFunction func) { if (_funcKnobShort == func) return; _funcKnobShort = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneKeySettingsExtension::funcKnobLong() const { return _funcKnobLong; } void AnytoneKeySettingsExtension::setFuncKnobLong(KeyFunction func) { if (_funcKnobLong == func) return; _funcKnobLong = func; emit modified(this); } Interval AnytoneKeySettingsExtension::longPressDuration() const { return _longPressDuration; } void AnytoneKeySettingsExtension::setLongPressDuration(Interval ms) { if (_longPressDuration == ms) return; _longPressDuration = ms; emit modified(this); } AnytoneKeySettingsExtension::UpDownKeyFunction AnytoneKeySettingsExtension::upDownKeyFunction() const { return _upDownFunction; } void AnytoneKeySettingsExtension::setUpDownKeyFunction(UpDownKeyFunction func) { if (_upDownFunction == func) return; _upDownFunction = func; emit modified(this); } bool AnytoneKeySettingsExtension::autoKeyLockEnabled() const { return _autoKeyLock; } void AnytoneKeySettingsExtension::enableAutoKeyLock(bool enabled) { if (_autoKeyLock==enabled) return; _autoKeyLock = enabled; emit modified(this); } bool AnytoneKeySettingsExtension::knobLockEnabled() const { return _knobLock; } void AnytoneKeySettingsExtension::enableKnobLock(bool enable) { if (_knobLock == enable) return; _knobLock = enable; emit modified(this); } bool AnytoneKeySettingsExtension::keypadLockEnabled() const { return _keypadLock; } void AnytoneKeySettingsExtension::enableKeypadLock(bool enable) { if (_keypadLock == enable) return; _keypadLock = enable; emit modified(this); } bool AnytoneKeySettingsExtension::sideKeysLockEnabled() const { return _sideKeysLock; } void AnytoneKeySettingsExtension::enableSideKeysLock(bool enable) { if (_sideKeysLock == enable) return; _sideKeysLock = enable; emit modified(this); } bool AnytoneKeySettingsExtension::forcedKeyLockEnabled() const { return _forcedKeyLock; } void AnytoneKeySettingsExtension::enableForcedKeyLock(bool enable) { if (_forcedKeyLock == enable) return; _forcedKeyLock = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneToneSettingsExtension * ********************************************************************************************* */ AnytoneToneSettingsExtension::AnytoneToneSettingsExtension(QObject *parent) : ConfigItem(parent), _totNotification(false), _wxAlarm(false) { } ConfigItem * AnytoneToneSettingsExtension::clone() const { AnytoneToneSettingsExtension *ext = new AnytoneToneSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool AnytoneToneSettingsExtension::totNotification() const { return _totNotification; } void AnytoneToneSettingsExtension::enableTOTNotification(bool enable) { if (enable == _totNotification) return; _totNotification = enable; emit modified(this); } bool AnytoneToneSettingsExtension::wxAlarm() const { return _wxAlarm; } void AnytoneToneSettingsExtension::enableWXAlarm(bool enable) { if (enable == _wxAlarm) return; _wxAlarm = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneDisplaySettingsExtension * ********************************************************************************************* */ AnytoneDisplaySettingsExtension::AnytoneDisplaySettingsExtension(QObject *parent) : ConfigItem(parent), _displayFrequency(false), _brightness(5), _volumeChangePrompt(true), _callEndPrompt(true), _lastCallerDisplay(LastCallerDisplayMode::Both), _showClock(true), _showCall(true), _callColor(Color::Orange), _language(Language::English), _dateFormat(DateFormat::DayFirst), _showChannelNumber(true), _showGlobalChannelNumber(false), _showColorCode(true), _showTimeSlot(true), _showChannelType(true), _showContact(true), _standbyTextColor(Color::White), _standbyBackgroundColor(Color::Black), _showLastHeard(false), _backlightDuration(Interval::infinity()), _backlightDurationTX(), _backlightDurationRX(Interval::infinity()), _customChannelBackground(false), _channelNameColor(Color::Orange), _channelBNameColor(Color::Orange), _zoneNameColor(Color::White), _zoneBNameColor(Color::White) { // pass... } ConfigItem * AnytoneDisplaySettingsExtension::clone() const { AnytoneDisplaySettingsExtension *ext = new AnytoneDisplaySettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } unsigned int AnytoneDisplaySettingsExtension::brightness() const { return _brightness; } void AnytoneDisplaySettingsExtension::setBrightness(unsigned int level) { if (_brightness == level) return; _brightness = level; emit modified(this); } bool AnytoneDisplaySettingsExtension::displayFrequencyEnabled() const { return _displayFrequency; } void AnytoneDisplaySettingsExtension::enableDisplayFrequency(bool enable) { if (_displayFrequency == enable) return; _displayFrequency = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::volumeChangePromptEnabled() const { return _volumeChangePrompt; } void AnytoneDisplaySettingsExtension::enableVolumeChangePrompt(bool enable) { if (_volumeChangePrompt == enable) return; _volumeChangePrompt = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::callEndPromptEnabled() const { return _callEndPrompt; } void AnytoneDisplaySettingsExtension::enableCallEndPrompt(bool enable) { if (_callEndPrompt == enable) return; _callEndPrompt = enable; emit modified(this); } AnytoneDisplaySettingsExtension::LastCallerDisplayMode AnytoneDisplaySettingsExtension::lastCallerDisplay() const { return _lastCallerDisplay; } void AnytoneDisplaySettingsExtension::setLastCallerDisplay(LastCallerDisplayMode mode) { if (_lastCallerDisplay == mode) return; _lastCallerDisplay = mode; emit modified(this); } bool AnytoneDisplaySettingsExtension::showClockEnabled() const { return _showClock; } void AnytoneDisplaySettingsExtension::enableShowClock(bool enable) { if (_showClock == enable) return; _showClock = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::showCallEnabled() const { return _showCall; } void AnytoneDisplaySettingsExtension::enableShowCall(bool enable) { if (_showCall == enable) return; _showCall = enable; emit modified(this); } AnytoneDisplaySettingsExtension::Color AnytoneDisplaySettingsExtension::callColor() const { return _callColor; } void AnytoneDisplaySettingsExtension::setCallColor(Color color) { if (_callColor == color) return; _callColor = color; emit modified(this); } AnytoneDisplaySettingsExtension::Language AnytoneDisplaySettingsExtension::language() const { return _language; } void AnytoneDisplaySettingsExtension::setLanguage(Language lang) { if (_language == lang) return; _language = lang; emit modified(this); } AnytoneDisplaySettingsExtension::DateFormat AnytoneDisplaySettingsExtension::dateFormat() const { return _dateFormat; } void AnytoneDisplaySettingsExtension::setDateFormat(DateFormat format) { if (format == _dateFormat) return; _dateFormat = format; emit modified(this); } bool AnytoneDisplaySettingsExtension::showChannelNumberEnabled() const { return _showChannelNumber; } void AnytoneDisplaySettingsExtension::enableShowChannelNumber(bool enable) { if (_showChannelNumber == enable) return; _showChannelNumber = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::showGlobalChannelNumber() const { return _showGlobalChannelNumber; } void AnytoneDisplaySettingsExtension::enableShowGlobalChannelNumber(bool enable) { if (_showGlobalChannelNumber == enable) return; _showGlobalChannelNumber = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::showColorCode() const { return _showColorCode; } void AnytoneDisplaySettingsExtension::enableShowColorCode(bool enable) { if (_showColorCode == enable) return; _showColorCode = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::showTimeSlot() const { return _showTimeSlot; } void AnytoneDisplaySettingsExtension::enableShowTimeSlot(bool enable) { if (_showTimeSlot == enable) return; _showTimeSlot = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::showChannelType() const { return _showChannelType; } void AnytoneDisplaySettingsExtension::enableShowChannelType(bool enable) { if (_showChannelType == enable) return; _showChannelType = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::showContact() const { return _showContact; } void AnytoneDisplaySettingsExtension::enableShowContact(bool enable) { if (_showContact == enable) return; _showContact = enable; emit modified(this); } bool AnytoneDisplaySettingsExtension::showLastHeardEnabled() const { return _showLastHeard; } void AnytoneDisplaySettingsExtension::enableShowLastHeard(bool enable) { if (_showLastHeard == enable) return; _showLastHeard = enable; emit modified(this); } AnytoneDisplaySettingsExtension::Color AnytoneDisplaySettingsExtension::standbyTextColor() const { return _standbyTextColor; } void AnytoneDisplaySettingsExtension::setStandbyTextColor(Color color) { if (_standbyTextColor == color) return; _standbyTextColor = color; emit modified(this); } AnytoneDisplaySettingsExtension::Color AnytoneDisplaySettingsExtension::standbyBackgroundColor() const { return _standbyBackgroundColor; } void AnytoneDisplaySettingsExtension::setStandbyBackgroundColor(Color color) { if (_standbyBackgroundColor == color) return; _standbyBackgroundColor = color; emit modified(this); } Interval AnytoneDisplaySettingsExtension::backlightDuration() const { return _backlightDuration; } void AnytoneDisplaySettingsExtension::setBacklightDuration(Interval sec) { if (_backlightDuration == sec) return; _backlightDuration = sec; emit modified(this); } Interval AnytoneDisplaySettingsExtension::backlightDurationTX() const { return _backlightDurationTX; } void AnytoneDisplaySettingsExtension::setBacklightDurationTX(Interval sec) { if (_backlightDurationTX == sec) return; _backlightDurationTX = sec; emit modified(this); } AnytoneDisplaySettingsExtension::Color AnytoneDisplaySettingsExtension::channelNameColor() const { return _channelNameColor; } void AnytoneDisplaySettingsExtension::setChannelNameColor(Color color) { if (_channelNameColor == color) return; _channelNameColor = color; emit modified(this); } AnytoneDisplaySettingsExtension::Color AnytoneDisplaySettingsExtension::channelBNameColor() const { return _channelBNameColor; } void AnytoneDisplaySettingsExtension::setChannelBNameColor(Color color) { if (_channelBNameColor == color) return; _channelBNameColor = color; emit modified(this); } AnytoneDisplaySettingsExtension::Color AnytoneDisplaySettingsExtension::zoneNameColor() const { return _zoneNameColor; } void AnytoneDisplaySettingsExtension::setZoneNameColor(Color color) { if (_zoneNameColor == color) return; _zoneNameColor = color; emit modified(this); } AnytoneDisplaySettingsExtension::Color AnytoneDisplaySettingsExtension::zoneBNameColor() const { return _zoneBNameColor; } void AnytoneDisplaySettingsExtension::setZoneBNameColor(Color color) { if (_zoneBNameColor == color) return; _zoneBNameColor = color; emit modified(this); } Interval AnytoneDisplaySettingsExtension::backlightDurationRX() const { return _backlightDurationRX; } void AnytoneDisplaySettingsExtension::setBacklightDurationRX(Interval sec) { if (_backlightDurationRX == sec) return; _backlightDurationRX = sec; emit modified(this); } bool AnytoneDisplaySettingsExtension::customChannelBackground() const { return _customChannelBackground; } void AnytoneDisplaySettingsExtension::enableCustomChannelBackground(bool enable) { if (enable == _customChannelBackground) return; _customChannelBackground = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneAudioSettingsExtension * ********************************************************************************************* */ AnytoneAudioSettingsExtension::AnytoneAudioSettingsExtension(QObject *parent) : ConfigItem(parent), _voxDelay(), _recording(false), _voxSource(VoxSource::Both), _enhanceAudio(true), _muteDelay(Interval::fromMinutes(1)), _speaker(Speaker::Radio), _handsetSpeaker(HandsetSpeakerSource::MainChannel), _handsetType(HandsetType::Anytone) { // pass... } ConfigItem * AnytoneAudioSettingsExtension::clone() const { AnytoneAudioSettingsExtension *ext = new AnytoneAudioSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool AnytoneAudioSettingsExtension::recordingEnabled() const { return _recording; } void AnytoneAudioSettingsExtension::enableRecording(bool enable) { if (_recording == enable) return; _recording = enable; emit modified(this); } AnytoneAudioSettingsExtension::VoxSource AnytoneAudioSettingsExtension::voxSource() const { return _voxSource; } void AnytoneAudioSettingsExtension::setVOXSource(VoxSource source) { if (_voxSource == source) return; _voxSource = source; emit modified(this); } bool AnytoneAudioSettingsExtension::enhanceAudioEnabled() const { return _enhanceAudio; } void AnytoneAudioSettingsExtension::enableEnhanceAudio(bool enable) { if (_enhanceAudio == enable) return; _enhanceAudio = enable; emit modified(this); } Interval AnytoneAudioSettingsExtension::muteDelay() const { return _muteDelay; } void AnytoneAudioSettingsExtension::setMuteDelay(Interval intv) { if (_muteDelay == intv) return; _muteDelay = intv; emit modified(this); } AnytoneAudioSettingsExtension::Speaker AnytoneAudioSettingsExtension::speaker() const { return _speaker; } void AnytoneAudioSettingsExtension::setSpeaker(Speaker speaker) { if (_speaker == speaker) return; _speaker = speaker; emit modified(this); } AnytoneAudioSettingsExtension::HandsetSpeakerSource AnytoneAudioSettingsExtension::handsetSpeaker() const { return _handsetSpeaker; } void AnytoneAudioSettingsExtension::setHandsetSpeaker(HandsetSpeakerSource src) { if (_handsetSpeaker == src) return; _handsetSpeaker = src; emit modified(this); } AnytoneAudioSettingsExtension::HandsetType AnytoneAudioSettingsExtension::handsetType() const { return _handsetType; } void AnytoneAudioSettingsExtension::setHandsetType(HandsetType type) { if (_handsetType == type) return; _handsetType = type; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneMenuSettingsExtension * ********************************************************************************************* */ AnytoneMenuSettingsExtension::AnytoneMenuSettingsExtension(QObject *parent) : ConfigItem(parent), _menuDuration(Interval::fromSeconds(15)), _showSeparator(false) { // pass... } ConfigItem * AnytoneMenuSettingsExtension::clone() const { AnytoneMenuSettingsExtension *ext = new AnytoneMenuSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } Interval AnytoneMenuSettingsExtension::duration() const { return _menuDuration; } void AnytoneMenuSettingsExtension::setDuration(Interval sec) { if (_menuDuration == sec) return; _menuDuration = sec; emit modified(this); } bool AnytoneMenuSettingsExtension::separatorEnabled() const { return _showSeparator; } void AnytoneMenuSettingsExtension::enableSeparator(bool enable) { if (_showSeparator == enable) return; _showSeparator = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneAutoRepeaterSettingsExtension * ********************************************************************************************* */ AnytoneAutoRepeaterSettingsExtension::AnytoneAutoRepeaterSettingsExtension(QObject *parent) : ConfigItem(parent), _directionA(Direction::Off), _directionB(Direction::Off), _minVHF(Frequency::fromMHz(136)), _maxVHF(Frequency::fromMHz(174)), _minUHF(Frequency::fromMHz(400)), _maxUHF(Frequency::fromMHz(480)), _vhfOffset(new AnytoneAutoRepeaterOffsetRef(this)), _uhfOffset(new AnytoneAutoRepeaterOffsetRef(this)), _minVHF2(Frequency::fromMHz(136)), _maxVHF2(Frequency::fromMHz(174)), _minUHF2(Frequency::fromMHz(400)), _maxUHF2(Frequency::fromMHz(480)), _vhf2Offset(new AnytoneAutoRepeaterOffsetRef(this)), _uhf2Offset(new AnytoneAutoRepeaterOffsetRef(this)), _offsets(new AnytoneAutoRepeaterOffsetList(this)) { // pass... } ConfigItem * AnytoneAutoRepeaterSettingsExtension::clone() const { AnytoneAutoRepeaterSettingsExtension *ext = new AnytoneAutoRepeaterSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } AnytoneAutoRepeaterSettingsExtension::Direction AnytoneAutoRepeaterSettingsExtension::directionA() const { return _directionA; } void AnytoneAutoRepeaterSettingsExtension::setDirectionA(Direction dir) { if (_directionA == dir) return; _directionA = dir; emit modified(this); } AnytoneAutoRepeaterSettingsExtension::Direction AnytoneAutoRepeaterSettingsExtension::directionB() const { return _directionB; } void AnytoneAutoRepeaterSettingsExtension::setDirectionB(Direction dir) { if (_directionB == dir) return; _directionB = dir; emit modified(this); } Frequency AnytoneAutoRepeaterSettingsExtension::vhfMin() const { return _minVHF; } void AnytoneAutoRepeaterSettingsExtension::setVHFMin(Frequency Hz) { if (_minVHF == Hz) return; _minVHF = Hz; emit modified(this); } Frequency AnytoneAutoRepeaterSettingsExtension::vhfMax() const { return _maxVHF; } void AnytoneAutoRepeaterSettingsExtension::setVHFMax(Frequency Hz) { if (_maxVHF == Hz) return; _maxVHF = Hz; emit modified(this); } Frequency AnytoneAutoRepeaterSettingsExtension::uhfMin() const { return _minUHF; } void AnytoneAutoRepeaterSettingsExtension::setUHFMin(Frequency Hz) { if (_minUHF == Hz) return; _minUHF = Hz; emit modified(this); } Frequency AnytoneAutoRepeaterSettingsExtension::uhfMax() const { return _maxUHF; } void AnytoneAutoRepeaterSettingsExtension::setUHFMax(Frequency Hz) { if (_maxUHF == Hz) return; _maxUHF = Hz; emit modified(this); } AnytoneAutoRepeaterOffsetRef * AnytoneAutoRepeaterSettingsExtension::vhfRef() const { return _vhfOffset; } AnytoneAutoRepeaterOffsetRef * AnytoneAutoRepeaterSettingsExtension::uhfRef() const { return _uhfOffset; } Frequency AnytoneAutoRepeaterSettingsExtension::vhf2Min() const { return _minVHF2; } void AnytoneAutoRepeaterSettingsExtension::setVHF2Min(Frequency Hz) { if (_minVHF2 == Hz) return; _minVHF2 = Hz; emit modified(this); } Frequency AnytoneAutoRepeaterSettingsExtension::vhf2Max() const { return _maxVHF2; } void AnytoneAutoRepeaterSettingsExtension::setVHF2Max(Frequency Hz) { if (_maxVHF2 == Hz) return; _maxVHF2 = Hz; emit modified(this); } Frequency AnytoneAutoRepeaterSettingsExtension::uhf2Min() const { return _minUHF2; } void AnytoneAutoRepeaterSettingsExtension::setUHF2Min(Frequency Hz) { if (_minUHF2 == Hz) return; _minUHF2 = Hz; emit modified(this); } Frequency AnytoneAutoRepeaterSettingsExtension::uhf2Max() const { return _maxUHF2; } void AnytoneAutoRepeaterSettingsExtension::setUHF2Max(Frequency Hz) { if (_maxUHF2 == Hz) return; _maxUHF2 = Hz; emit modified(this); } AnytoneAutoRepeaterOffsetRef * AnytoneAutoRepeaterSettingsExtension::vhf2Ref() const { return _vhf2Offset; } AnytoneAutoRepeaterOffsetRef * AnytoneAutoRepeaterSettingsExtension::uhf2Ref() const { return _uhf2Offset; } AnytoneAutoRepeaterOffsetList * AnytoneAutoRepeaterSettingsExtension::offsets() const { return _offsets; } /* ********************************************************************************************* * * Implementation of AnytoneAutoRepeaterOffset * ********************************************************************************************* */ AnytoneAutoRepeaterOffset::AnytoneAutoRepeaterOffset(QObject *parent) : ConfigObject(parent), _offset() { // pass... } ConfigItem * AnytoneAutoRepeaterOffset::clone() const { AnytoneAutoRepeaterOffset *off = new AnytoneAutoRepeaterOffset(); if (! off->copy(*this)) { off->deleteLater(); return nullptr; } return off; } Frequency AnytoneAutoRepeaterOffset::offset() const { return _offset; } void AnytoneAutoRepeaterOffset::setOffset(Frequency offset) { if (_offset == offset) return; _offset = offset; emit modified(this); } ConfigItem * AnytoneAutoRepeaterOffsetList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(node); Q_UNUSED(ctx); Q_UNUSED(err); return new AnytoneAutoRepeaterOffset(); } /* ********************************************************************************************* * * Implementation of AnytoneAutoRepeaterOffsetRef * ********************************************************************************************* */ AnytoneAutoRepeaterOffsetRef::AnytoneAutoRepeaterOffsetRef(QObject *parent) : ConfigObjectReference(AnytoneAutoRepeaterOffset::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneAutoRepeaterOffsetList * ********************************************************************************************* */ AnytoneAutoRepeaterOffsetList::AnytoneAutoRepeaterOffsetList(QObject *parent) : ConfigObjectList(AnytoneAutoRepeaterOffset::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of AnytoneBluetoothHandsetSettingsExtension * ********************************************************************************************* */ AnytoneBluetoothHandsetSettingsExtension::AnytoneBluetoothHandsetSettingsExtension(QObject *parent) : ConfigItem{parent}, _shutdown(true), _volumeLevelA(0), _volumeLevelB(0), _micGain(1), _squelch(1), _txNoiseReduction(0), _voxLevel(0), _voxDelay(Interval::null()), _funcKeyAShort(AnytoneKeySettingsExtension::KeyFunction::Off), _funcKeyBShort(AnytoneKeySettingsExtension::KeyFunction::Off), _funcKeyCShort(AnytoneKeySettingsExtension::KeyFunction::Off), _funcKeyALong(AnytoneKeySettingsExtension::KeyFunction::Off), _funcKeyBLong(AnytoneKeySettingsExtension::KeyFunction::Off), _funcKeyCLong(AnytoneKeySettingsExtension::KeyFunction::Off), _funcKeyAVeryLong(AnytoneKeySettingsExtension::KeyFunction::Off), _funcKeyBVeryLong(AnytoneKeySettingsExtension::KeyFunction::Off), _funcKeyCVeryLong(AnytoneKeySettingsExtension::KeyFunction::Off), _backlight(Interval::fromSeconds(30)) { // pass... } ConfigItem * AnytoneBluetoothHandsetSettingsExtension::clone() const { auto obj = new AnytoneBluetoothHandsetSettingsExtension(); if (! obj->copy(*this)) { delete obj; return nullptr; } return obj; } bool AnytoneBluetoothHandsetSettingsExtension::shutdownEnabled() const { return _shutdown; } void AnytoneBluetoothHandsetSettingsExtension::enableShutdown(bool enable) { if (_shutdown == enable) return; _shutdown = enable; emit modified(this); } unsigned int AnytoneBluetoothHandsetSettingsExtension::volumeLevelA() const { return _volumeLevelA; } void AnytoneBluetoothHandsetSettingsExtension::setVolumeLevelA(unsigned int level) { level = std::min(10U, level); if (_volumeLevelA == level) return; _volumeLevelA = level; emit modified(this); } unsigned int AnytoneBluetoothHandsetSettingsExtension::volumeLevelB() const { return _volumeLevelB; } void AnytoneBluetoothHandsetSettingsExtension::setVolumeLevelB(unsigned int level) { level = std::min(10U, level); if (_volumeLevelB == level) return; _volumeLevelB = level; emit modified(this); } unsigned int AnytoneBluetoothHandsetSettingsExtension::micGain() const { return _micGain; } void AnytoneBluetoothHandsetSettingsExtension::setMicGain(unsigned int gain) { gain = std::max(1u, std::min(10U, gain)); if (_micGain == gain) return; _micGain = gain; emit modified(this); } unsigned int AnytoneBluetoothHandsetSettingsExtension::squelch() const { return _squelch; } void AnytoneBluetoothHandsetSettingsExtension::setSquelch(unsigned int level) { level = std::min(10U, level); if (_squelch == level) return; _squelch = level; emit modified(this); } unsigned int AnytoneBluetoothHandsetSettingsExtension::txNoiseReduction() const { return _txNoiseReduction; } void AnytoneBluetoothHandsetSettingsExtension::setTxNoiseReduction(unsigned int level) { level = std::max(10U, level); if (_txNoiseReduction == level) return; _txNoiseReduction = level; emit modified(this); } unsigned int AnytoneBluetoothHandsetSettingsExtension::voxLevel() const { return _voxLevel; } void AnytoneBluetoothHandsetSettingsExtension::setVoxLevel(unsigned int level) { level = std::min(10U, level); if (_voxLevel == level) return; _voxLevel = level; emit modified(this); } Interval AnytoneBluetoothHandsetSettingsExtension::voxDelay() const { return _voxDelay; } void AnytoneBluetoothHandsetSettingsExtension::setVoxDelay(Interval delay) { if (_voxDelay == delay) return; _voxDelay = delay; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyAShort() const { return _funcKeyAShort; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyAShort == func) return; _funcKeyAShort = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyBShort() const { return _funcKeyBShort; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyBShort == func) return; _funcKeyBShort = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyCShort() const { return _funcKeyCShort; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyCShort == func) return; _funcKeyCShort = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyALong() const { return _funcKeyALong; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyALong == func) return; _funcKeyALong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyBLong() const { return _funcKeyBLong; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyBLong == func) return; _funcKeyBLong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyCLong() const { return _funcKeyCLong; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyCLong == func) return; _funcKeyCLong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyAVeryLong() const { return _funcKeyAVeryLong; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyAVeryLong(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyAVeryLong == func) return; _funcKeyAVeryLong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyBVeryLong() const { return _funcKeyBVeryLong; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyBVeryLong(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyBVeryLong == func) return; _funcKeyBVeryLong = func; emit modified(this); } AnytoneKeySettingsExtension::KeyFunction AnytoneBluetoothHandsetSettingsExtension::funcKeyCVeryLong() const { return _funcKeyCVeryLong; } void AnytoneBluetoothHandsetSettingsExtension::setFuncKeyCVeryLong(AnytoneKeySettingsExtension::KeyFunction func) { if (_funcKeyCVeryLong == func) return; _funcKeyCVeryLong = func; emit modified(this); } Interval AnytoneBluetoothHandsetSettingsExtension::backlight() const { return _backlight; } void AnytoneBluetoothHandsetSettingsExtension::setBacklight(Interval dur) { if (_backlight == dur) return; _backlight = dur; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneBluetoothSettingsExtension * ********************************************************************************************* */ AnytoneBluetoothSettingsExtension::AnytoneBluetoothSettingsExtension(QObject *parent) : ConfigItem(parent), _handset(new AnytoneBluetoothHandsetSettingsExtension(this)), _bluetoothEnabled(false), _pttLatch(false), _pttSleep(Interval::fromMilliseconds(0)), _internalMic(false), _internalSpeaker(false), _micGain(0), _speakerGain(0), _holdDuration(), _holdDelay() { connect(_handset, &AnytoneBluetoothHandsetSettingsExtension::modified, this, &AnytoneBluetoothSettingsExtension::modified); } ConfigItem * AnytoneBluetoothSettingsExtension::clone() const { AnytoneBluetoothSettingsExtension *ext = new AnytoneBluetoothSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool AnytoneBluetoothSettingsExtension::pttLatch() const { return _pttLatch; } void AnytoneBluetoothSettingsExtension::enablePTTLatch(bool enable) { if (enable == _pttLatch) return; _pttLatch = enable; emit modified(this); } AnytoneBluetoothHandsetSettingsExtension * AnytoneBluetoothSettingsExtension::handset() const { return _handset; } bool AnytoneBluetoothSettingsExtension::bluetoothEnabled() const { return _bluetoothEnabled; } void AnytoneBluetoothSettingsExtension::enableBluetooth(bool enable) { if (enable == _bluetoothEnabled) return; _bluetoothEnabled = enable; emit modified(this); } Interval AnytoneBluetoothSettingsExtension::pttSleepTimer() const { return _pttSleep; } void AnytoneBluetoothSettingsExtension::setPTTSleepTimer(Interval intv) { if (intv == _pttSleep) return; _pttSleep = intv; emit modified(this); } bool AnytoneBluetoothSettingsExtension::internalMicEnabled() const { return _internalMic; } void AnytoneBluetoothSettingsExtension::enableInternalMic(bool enable) { if (enable == _internalMic) return; _internalMic = enable; emit modified(this); } bool AnytoneBluetoothSettingsExtension::internalSpeakerEnabled() const { return _internalSpeaker; } void AnytoneBluetoothSettingsExtension::enableInternalSpeaker(bool enable) { if (enable == _internalSpeaker) return; _internalSpeaker = enable; emit modified(this); } unsigned int AnytoneBluetoothSettingsExtension::micGain() const { return _micGain; } void AnytoneBluetoothSettingsExtension::setMicGain(unsigned int gain) { if (_micGain == gain) return; _micGain = gain; emit modified(this); } unsigned int AnytoneBluetoothSettingsExtension::speakerGain() const { return _speakerGain; } void AnytoneBluetoothSettingsExtension::setSpeakerGain(unsigned int gain) { if (_speakerGain == gain) return; _speakerGain = gain; emit modified(this); } const Interval & AnytoneBluetoothSettingsExtension::holdDuration() const { return _holdDuration; } void AnytoneBluetoothSettingsExtension::setHoldDuration(const Interval &dur) { if (dur == _holdDuration) return; _holdDuration = dur; emit modified(this); } const Interval & AnytoneBluetoothSettingsExtension::holdDelay() const { return _holdDelay; } void AnytoneBluetoothSettingsExtension::setHoldDelay(const Interval &dur) { if (dur == _holdDelay) return; _holdDelay = dur; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneSimplexRepeaterSettingsExtension * ********************************************************************************************* */ AnytoneRepeaterSettingsExtension::AnytoneRepeaterSettingsExtension(QObject *parent) : ConfigItem(parent), _enabled(false), _monitor(false), _timeSlot(TimeSlot::Channel), _secTimeSlot(TimeSlot::Channel), _colorCode(ColorCode::Ignored) { // pass... } ConfigItem * AnytoneRepeaterSettingsExtension::clone() const { AnytoneRepeaterSettingsExtension *ext = new AnytoneRepeaterSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool AnytoneRepeaterSettingsExtension::enabled() const { return _enabled; } void AnytoneRepeaterSettingsExtension::enable(bool enable) { if (_enabled == enable) return; _enabled = enable; emit modified(this); } bool AnytoneRepeaterSettingsExtension::monitorEnabled() const { return _monitor; } void AnytoneRepeaterSettingsExtension::enableMonitor(bool enable) { if (_monitor == enable) return; _monitor = enable; emit modified(this); } AnytoneRepeaterSettingsExtension::TimeSlot AnytoneRepeaterSettingsExtension::timeSlot() const { return _timeSlot; } void AnytoneRepeaterSettingsExtension::setTimeSlot(TimeSlot ts) { if (_timeSlot == ts) return; _timeSlot = ts; emit modified(this); } AnytoneRepeaterSettingsExtension::TimeSlot AnytoneRepeaterSettingsExtension::secTimeSlot() const { return _secTimeSlot; } void AnytoneRepeaterSettingsExtension::setSecTimeSlot(TimeSlot ts) { if (_secTimeSlot == ts) return; _secTimeSlot = ts; emit modified(this); } AnytoneRepeaterSettingsExtension::ColorCode AnytoneRepeaterSettingsExtension::colorCode() const { return _colorCode; } void AnytoneRepeaterSettingsExtension::setColorCode(ColorCode code) { if (_colorCode == code) return; _colorCode = code; emit modified(this); } /* ********************************************************************************************* * * Implementation of AnytoneSatelliteSettingsExtension * ********************************************************************************************* */ AnytoneSatelliteSettingsExtension::AnytoneSatelliteSettingsExtension(QObject *parent) : ConfigItem{parent}, _power(Channel::Power::High), _squelch(0) { // pass... } ConfigItem * AnytoneSatelliteSettingsExtension::clone() const { auto ext = new AnytoneSatelliteSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } Channel::Power AnytoneSatelliteSettingsExtension::power() const { return _power; } void AnytoneSatelliteSettingsExtension::setPower(Channel::Power power) { if (_power == power) return; _power = power; emit modified(this); } unsigned int AnytoneSatelliteSettingsExtension::squelch() const { return _squelch; } void AnytoneSatelliteSettingsExtension::setSquelch(unsigned int squelch) { if (_squelch == squelch) return; _squelch = squelch; emit modified(this); } ================================================ FILE: lib/anytone_settingsextension.hh ================================================ #ifndef ANYTONE_SETTINGSEXTENSION_H #define ANYTONE_SETTINGSEXTENSION_H #include #include "configobject.hh" #include "configreference.hh" #include "interval.hh" #include "melody.hh" #include "frequency.hh" #include "channel.hh" /** Implements the boot settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneBootSettingsExtension: public ConfigItem { Q_OBJECT /** The priority zone for VFO A. */ Q_PROPERTY(ZoneReference* priorityZoneA READ priorityZoneA) /** The priority zone for VFO B. */ Q_PROPERTY(ZoneReference* priorityZoneB READ priorityZoneB) /** Enables the GPS check. */ Q_PROPERTY(bool gpsCheck READ gpsCheckEnabled WRITE enableGPSCheck) public: /** Constructor. */ explicit AnytoneBootSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns a reference to the priority zone for VFO A. */ ZoneReference *priorityZoneA() const; /** Returns a reference to the priority zone for VFO B. */ ZoneReference *priorityZoneB() const; /** Returns @c true if the GPS check is enabled. */ bool gpsCheckEnabled() const; /** Enables/disables the GPS check. */ void enableGPSCheck(bool enable); protected: ZoneReference *_priorityZoneA; ///< Priority zone for VFO A. ZoneReference *_priorityZoneB; ///< Priority zone for VFO B. bool _gpsCheck; ///< Enables GPS check. }; /** Implements the power-save settings for AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytonePowerSaveSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("autoShutDownDelayDescription", "The auto shut-down delay in minutes.") /** The auto shut-down delay in minutes. */ Q_PROPERTY(Interval autoShutdown READ autoShutdown WRITE setAutoShutdown) /** Resets the auto shut-down timer on every call. */ Q_PROPERTY(bool resetAutoShutdownOnCall READ resetAutoShutdownOnCall WRITE enableResetAutoShutdownOnCall) Q_CLASSINFO("powerSaveDescription", "Specifies the power save mode. " "D686UV, D878UV(2) and DMR-6X2UV only.") /** The power-save mode. */ Q_PROPERTY(PowerSave powerSave READ powerSave WRITE setPowerSave) /** If @c true, the adaptive transmission power control is enabled. */ Q_PROPERTY(bool atpc READ atpc WRITE enableATPC) public: /** Possible power save modes. */ enum class PowerSave { Off = 0, Save50 = 1, Save66 = 2 }; Q_ENUM(PowerSave) public: /** Default constructor. */ explicit AnytonePowerSaveSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the auto shut-down delay in minutes. */ Interval autoShutdown() const; /** Sets the auto shut-down delay. */ void setAutoShutdown(Interval min); /** Returns @c true, if the auto shut-down timer is reset on every call. */ bool resetAutoShutdownOnCall() const; /** Enables/disables reset of auto shut-down timer on every call. */ void enableResetAutoShutdownOnCall(bool enable); /** Returns the power-save mode. */ PowerSave powerSave() const; /** Sets the power-save mode. */ void setPowerSave(PowerSave mode); /** Returns @c true if the adaptive transmission power control is enabled. */ bool atpc() const; /** Enables/disables the adaptive transmission power control. */ void enableATPC(bool enable); protected: Interval _autoShutDownDelay; ///< The auto shut-down delay in minutes. bool _resetAutoShutdownOnCall; ///< Enables reset of auto shut-down timer on every call. PowerSave _powerSave; ///< Power save mode property. bool _atpc; ///< Adaptive Transmission Power Control. }; /** Implements the key settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneKeySettingsExtension: public ConfigItem { Q_OBJECT /** Function key 1 (P1 below or next to screen), short press function. */ Q_PROPERTY(KeyFunction funcKey1Short READ funcKey1Short WRITE setFuncKey1Short) /** Function key 1 (P1 below or next to screen), long press function. */ Q_PROPERTY(KeyFunction funcKey1Long READ funcKey1Long WRITE setFuncKey1Long) /** Function key 2 (P2 below or next to screen), short press function. */ Q_PROPERTY(KeyFunction funcKey2Short READ funcKey2Short WRITE setFuncKey2Short) /** Function key 2 (P2 below or next to screen), long press function. */ Q_PROPERTY(KeyFunction funcKey2Long READ funcKey2Long WRITE setFuncKey2Long) /** Function key 3 (P3 next to screen, D578UV only), short press function. */ Q_PROPERTY(KeyFunction funcKey3Short READ funcKey3Short WRITE setFuncKey3Short) /** Function key 3 (P3 next to screen, D578UV only), long press function. */ Q_PROPERTY(KeyFunction funcKey3Long READ funcKey3Long WRITE setFuncKey3Long) /** Function key 4 (P4 next to screen, D578UV only), short press function. */ Q_PROPERTY(KeyFunction funcKey4Short READ funcKey4Short WRITE setFuncKey4Short) /** Function key 4 (P4 next to screen, D578UV only), long press function. */ Q_PROPERTY(KeyFunction funcKey4Long READ funcKey4Long WRITE setFuncKey4Long) /** Function key 5 (P5 next to screen, D578UV only), short press function. */ Q_PROPERTY(KeyFunction funcKey5Short READ funcKey5Short WRITE setFuncKey5Short) /** Function key 5 (P5 next to screen, D578UV only), long press function. */ Q_PROPERTY(KeyFunction funcKey5Long READ funcKey5Long WRITE setFuncKey5Long) /** Function key 6 (P6 next to screen, D578UV only), short press function. */ Q_PROPERTY(KeyFunction funcKey6Short READ funcKey6Short WRITE setFuncKey6Short) /** Function key 6 (P6 next to screen, D578UV only), long press function. */ Q_PROPERTY(KeyFunction funcKey6Long READ funcKey6Long WRITE setFuncKey6Long) /** Function key A (PF1 below PTT or A on mic), short press function. */ Q_PROPERTY(KeyFunction funcKeyAShort READ funcKeyAShort WRITE setFuncKeyAShort) /** Function key A (PF1 below PTT or A on mic), long press function. */ Q_PROPERTY(KeyFunction funcKeyALong READ funcKeyALong WRITE setFuncKeyALong) /** Function key B (PF2 second below PTT or B on mic), short press function. */ Q_PROPERTY(KeyFunction funcKeyBShort READ funcKeyBShort WRITE setFuncKeyBShort) /** Function key B (PF2 second below PTT or B on mic), long press function. */ Q_PROPERTY(KeyFunction funcKeyBLong READ funcKeyBLong WRITE setFuncKeyBLong) /** Function key C (PF3 on top or C on mic), short press function. */ Q_PROPERTY(KeyFunction funcKeyCShort READ funcKeyCShort WRITE setFuncKeyCShort) /** Function key C (PF3 on top or B on mic), long press function. */ Q_PROPERTY(KeyFunction funcKeyCLong READ funcKeyCLong WRITE setFuncKeyCLong) /** Function key D (D on mic, D578UV only), short press function. */ Q_PROPERTY(KeyFunction funcKeyDShort READ funcKeyDShort WRITE setFuncKeyDShort) /** Function key D (D on mic, D578UV only), long press function. */ Q_PROPERTY(KeyFunction funcKeyDLong READ funcKeyDLong WRITE setFuncKeyDLong) /** Function knob (channel/frequency knob, D578UV only), short press function. */ Q_PROPERTY(KeyFunction funcKnobShort READ funcKnobShort WRITE setFuncKnobShort) /** Function knob (channel/frequency knob, D578UV only), long press function. */ Q_PROPERTY(KeyFunction funcKnobLong READ funcKnobLong WRITE setFuncKnobLong) /** The long press duration in ms. */ Q_PROPERTY(Interval longPressDuration READ longPressDuration WRITE setLongPressDuration) /** Up/down key functions (D578UV only). */ Q_PROPERTY(UpDownKeyFunction upDownKeys READ upDownKeyFunction WRITE setUpDownKeyFunction); /** The auto key-lock property. */ Q_PROPERTY(bool autoKeyLock READ autoKeyLockEnabled WRITE enableAutoKeyLock) Q_CLASSINFO("knobLockDescription", "If enabled, the knob gets locked too.") /** If @c true, the knob gets locked too. */ Q_PROPERTY(bool knobLock READ knobLockEnabled WRITE enableKnobLock) Q_CLASSINFO("keypadLockDescription", "If enabled, the key-pad gets locked.") /** If @c true, the key-pad gets locked too. */ Q_PROPERTY(bool keypadLock READ keypadLockEnabled WRITE enableKeypadLock) Q_CLASSINFO("sideKeysLockDescription", "If enabled, the side-keys get locked.") /** If @c true, the side-keys get locked too. */ Q_PROPERTY(bool sideKeysLock READ sideKeysLockEnabled WRITE enableSideKeysLock) Q_CLASSINFO("forcedKeyLockDescription", "If enabled, the key-lock is forced.") /** If @c true, the key-lock is forced. */ Q_PROPERTY(bool forcedKeyLock READ forcedKeyLockEnabled WRITE enableForcedKeyLock) public: /** All possible key functions. */ enum class KeyFunction { Off, Voltage, Power, Repeater, Reverse, Encryption, Call, VOX, ToggleVFO, SubPTT, Scan, WFM, Alarm, RecordSwitch, Record, SMS, Dial, GPSInformation, Monitor, ToggleMainChannel, HotKey1, HotKey2, HotKey3, HotKey4, HotKey5, HotKey6, WorkAlone, SkipChannel, DMRMonitor, SubChannel, PriorityZone, VFOScan, MICSoundQuality, LastCallReply, ChannelType, Ranging, Roaming, ChannelRanging, MaxVolume, Slot, APRSTypeSwitch, Zone, ZoneUp, ZoneDown, Exit, Menu, RoamingSet, APRSSet, Mute, MuteA, MuteB, CtcssDcsSet, TBSTSend, Bluetooth, GPS, ChannelName, CDTScan, APRSSend, APRSInfo, Speaker, XBandRepeater, SimplexRepeater, GPSRoaming, Squelch, NoiseReductionTX, DIMShut, SatPredict }; Q_ENUM(KeyFunction) /** Possible up/down key functions. */ enum class UpDownKeyFunction { Channel, Volume }; Q_ENUM(UpDownKeyFunction) public: /** Empty constructor. */ explicit AnytoneKeySettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the key function for a short press on the function key 1. */ KeyFunction funcKey1Short() const; /** Sets the key function for a short press on the function key 1. */ void setFuncKey1Short(KeyFunction func); /** Returns the key function for a long press on the function key 1. */ KeyFunction funcKey1Long() const; /** Sets the key function for a long press on the function key 1. */ void setFuncKey1Long(KeyFunction func); /** Returns the key function for a short press on the programmable function key 2. */ KeyFunction funcKey2Short() const; /** Sets the key function for a short press on the programmable function key 2. */ void setFuncKey2Short(KeyFunction func); /** Returns the key function for a long press on the programmable function key 2. */ KeyFunction funcKey2Long() const; /** Sets the key function for a long press on the programmable function key 2. */ void setFuncKey2Long(KeyFunction func); /** Returns the key function for a short press on the programmable function key 3. */ KeyFunction funcKey3Short() const; /** Sets the key function for a short press on the programmable function key 3. */ void setFuncKey3Short(KeyFunction func); /** Returns the key function for a long press on the programmable function key 3. */ KeyFunction funcKey3Long() const; /** Sets the key function for a long press on the programmable function key 3. */ void setFuncKey3Long(KeyFunction func); /** Returns the key function for a short press on the programmable function key 4. */ KeyFunction funcKey4Short() const; /** Sets the key function for a short press on the programmable function key 4. */ void setFuncKey4Short(KeyFunction func); /** Returns the key function for a long press on the programmable function key 4. */ KeyFunction funcKey4Long() const; /** Sets the key function for a long press on the programmable function key 4. */ void setFuncKey4Long(KeyFunction func); /** Returns the key function for a short press on the programmable function key 5. */ KeyFunction funcKey5Short() const; /** Sets the key function for a short press on the programmable function key 5. */ void setFuncKey5Short(KeyFunction func); /** Returns the key function for a long press on the programmable function key 5. */ KeyFunction funcKey5Long() const; /** Sets the key function for a long press on the programmable function key 5. */ void setFuncKey5Long(KeyFunction func); /** Returns the key function for a short press on the programmable function key 6. */ KeyFunction funcKey6Short() const; /** Sets the key function for a short press on the programmable function key 6. */ void setFuncKey6Short(KeyFunction func); /** Returns the key function for a long press on the programmable function key 6. */ KeyFunction funcKey6Long() const; /** Sets the key function for a long press on the programmable function key 6. */ void setFuncKey6Long(KeyFunction func); /** Returns the key function for a short press on the function key A. */ KeyFunction funcKeyAShort() const; /** Sets the key function for a short press on the function key A. */ void setFuncKeyAShort(KeyFunction func); /** Returns the key function for a long press on the function key A. */ KeyFunction funcKeyALong() const; /** Sets the key function for a long press on the function key A. */ void setFuncKeyALong(KeyFunction func); /** Returns the key function for a short press on the function key B. */ KeyFunction funcKeyBShort() const; /** Sets the key function for a short press on the function key B. */ void setFuncKeyBShort(KeyFunction func); /** Returns the key function for a long press on the function key B. */ KeyFunction funcKeyBLong() const; /** Sets the key function for a long press on the function key B. */ void setFuncKeyBLong(KeyFunction func); /** Returns the key function for a short press on the function key C. */ KeyFunction funcKeyCShort() const; /** Sets the key function for a short press on the function key C. */ void setFuncKeyCShort(KeyFunction func); /** Returns the key function for a long press on the function key C. */ KeyFunction funcKeyCLong() const; /** Sets the key function for a long press on the function key C. */ void setFuncKeyCLong(KeyFunction func); /** Returns the key function for a short press on the function key D. */ KeyFunction funcKeyDShort() const; /** Sets the key function for a short press on the function key D. */ void setFuncKeyDShort(KeyFunction func); /** Returns the key function for a long press on the function key D. */ KeyFunction funcKeyDLong() const; /** Sets the key function for a long press on the function key D. */ void setFuncKeyDLong(KeyFunction func); /** Returns the key function for a short press on the knob. */ KeyFunction funcKnobShort() const; /** Sets the key function for a short press on the knob. */ void setFuncKnobShort(KeyFunction func); /** Returns the key function for a long press on the knob. */ KeyFunction funcKnobLong() const; /** Sets the key function for a long press on the knob. */ void setFuncKnobLong(KeyFunction func); /** Returns the long-press duration in ms. */ Interval longPressDuration() const; /** Sets the long-press duration in ms. */ void setLongPressDuration(Interval ms); /** Returns the up/down key function. */ UpDownKeyFunction upDownKeyFunction() const; /** Sets the up/down key function. */ void setUpDownKeyFunction(UpDownKeyFunction func); /** Returns @c true, if the automatic key-lock feature is enabled. */ bool autoKeyLockEnabled() const; /** Enables/disables auto key-lock. */ void enableAutoKeyLock(bool enabled); /** Returns @c true if the knob gets locked too. */ bool knobLockEnabled() const; /** Enables/disables the knob lock. */ void enableKnobLock(bool enable); /** Returns @c true if the key-pad gets locked too. */ bool keypadLockEnabled() const; /** Enables/disables the key-pad lock. */ void enableKeypadLock(bool enable); /** Returns @c true if the side-keys gets locked too. */ bool sideKeysLockEnabled() const; /** Enables/disables the side-keys lock. */ void enableSideKeysLock(bool enable); /** Returns @c true if the key-lock is forced. */ bool forcedKeyLockEnabled() const; /** Enables/disables the forced key-lock. */ void enableForcedKeyLock(bool enable); protected: KeyFunction _funcKey1Short; ///< Function of the function key 1, short press. KeyFunction _funcKey1Long; ///< Function of the function key 1, long press. KeyFunction _funcKey2Short; ///< Function of the function key 2, short press. KeyFunction _funcKey2Long; ///< Function of the function key 2, long press. KeyFunction _funcKey3Short; ///< Function of the function key 3, short press. KeyFunction _funcKey3Long; ///< Function of the function key 3, long press. KeyFunction _funcKey4Short; ///< Function of the function key 4, short press. KeyFunction _funcKey4Long; ///< Function of the function key 4, long press. KeyFunction _funcKey5Short; ///< Function of the function key 5, short press. KeyFunction _funcKey5Long; ///< Function of the function key 5, long press. KeyFunction _funcKey6Short; ///< Function of the function key 6, short press. KeyFunction _funcKey6Long; ///< Function of the function key 6, long press. KeyFunction _funcKeyAShort; ///< Function of the function key A, short press. KeyFunction _funcKeyALong; ///< Function of the function key A, long press. KeyFunction _funcKeyBShort; ///< Function of the function key B, short press. KeyFunction _funcKeyBLong; ///< Function of the function key B, long press. KeyFunction _funcKeyCShort; ///< Function of the function key C, short press. KeyFunction _funcKeyCLong; ///< Function of the function key C, long press. KeyFunction _funcKeyDShort; ///< Function of the function key D, short press. KeyFunction _funcKeyDLong; ///< Function of the function key D, long press. KeyFunction _funcKnobShort; ///< Function of the knob, short press. KeyFunction _funcKnobLong; ///< Function of the knob, long press. Interval _longPressDuration; ///< The long-press duration in ms. UpDownKeyFunction _upDownFunction; ///< The up/down key function. bool _autoKeyLock; ///< Auto key-lock property. bool _knobLock; ///< Knob locked too. bool _keypadLock; ///< Key-pad is locked. bool _sideKeysLock; ///< Side-keys are locked. bool _forcedKeyLock; ///< Forced key-lock. }; /** Implements the tone settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneToneSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("description", "Tone settings for AnyTone devices.") /** Enables transmit timeout notification (5s before TOT). */ Q_PROPERTY(bool tot READ totNotification WRITE enableTOTNotification) /** Enables weather alarm tone. */ Q_PROPERTY(bool wxAlarm READ wxAlarm WRITE enableWXAlarm) public: /** Empty constructor. */ explicit AnytoneToneSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true if the transmit timeout notification is enabled (5s before TOT). */ bool totNotification() const; /** Enables/disables the transmit timeout notification (5s before TOT). */ void enableTOTNotification(bool enable); /** Returns @c true, if the weather alarm is enabled. */ bool wxAlarm() const; /** Enables/disables weather alarm. */ void enableWXAlarm(bool enable); protected: bool _totNotification; ///< TOT notification enabled. bool _wxAlarm; ///< Weather alarm. }; /** Implements the display settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneDisplaySettingsExtension: public ConfigItem { Q_OBJECT /** The display frequency setting. */ Q_PROPERTY(bool displayFrequency READ displayFrequencyEnabled WRITE enableDisplayFrequency) /** The display brightness [1-10]. */ Q_PROPERTY(unsigned int brightness READ brightness WRITE setBrightness) Q_CLASSINFO("backlightDuration", "The duration in seconds, the backlight keeps lit after any action. ") /** Backlight duration. */ Q_PROPERTY(Interval backlightDuration READ backlightDuration WRITE setBacklightDuration) Q_CLASSINFO("backlightDurationTX", "The duration in seconds, the backlight is lit during TX. " "A value of 0 means off.") /** TX backlight duration. */ Q_PROPERTY(Interval backlightDurationTX READ backlightDurationTX WRITE setBacklightDurationTX) Q_CLASSINFO("backlightDurationRX", "The duration in seconds, the backlight is lit during RX.") /** RX backlight duration. */ Q_PROPERTY(Interval backlightDurationRX READ backlightDurationRX WRITE setBacklightDurationRX) /** Enables custom channel background. */ Q_PROPERTY(bool customChannelBackground READ customChannelBackground WRITE enableCustomChannelBackground) /** The volume-change prompt is shown. */ Q_PROPERTY(bool volumeChangePrompt READ volumeChangePromptEnabled WRITE enableVolumeChangePrompt) /** The call-end prompt is shown. */ Q_PROPERTY(bool callEndPrompt READ callEndPromptEnabled WRITE enableCallEndPrompt) /** If @c true, the clock is shown. */ Q_PROPERTY(bool showClock READ showClockEnabled WRITE enableShowClock) /** If @c true, the call is shown. */ Q_PROPERTY(bool showCall READ showCallEnabled WRITE enableShowCall) /** Shows the contact. */ Q_PROPERTY(bool showContact READ showContact WRITE enableShowContact) /** Shows the channel number. */ Q_PROPERTY(bool showChannelNumber READ showChannelNumberEnabled WRITE enableShowChannelNumber) /** Shows the global channel number. */ Q_PROPERTY(bool showGLobalChannelNumber READ showGlobalChannelNumber WRITE enableShowGlobalChannelNumber) /** Shows the color code. */ Q_PROPERTY(bool showColorCode READ showColorCode WRITE enableShowColorCode) /** Shows the time slot. */ Q_PROPERTY(bool showTimeSlot READ showTimeSlot WRITE enableShowTimeSlot) /** Shows the channel type. */ Q_PROPERTY(bool showChannelType READ showChannelType WRITE enableShowChannelType) /** Shows the last caller. */ Q_PROPERTY(bool showLastHeard READ showLastHeardEnabled WRITE enableShowLastHeard) /** The last-caller display mode. */ Q_PROPERTY(LastCallerDisplayMode lastCallerDisplay READ lastCallerDisplay WRITE setLastCallerDisplay) /** The color of the call. */ Q_PROPERTY(Color callColor READ callColor WRITE setCallColor) /** The standby text color. */ Q_PROPERTY(Color standbyTextColor READ standbyTextColor WRITE setStandbyTextColor) /** The standby background color. */ Q_PROPERTY(Color standbyBackgroundColor READ standbyBackgroundColor WRITE setStandbyBackgroundColor) Q_CLASSINFO("channelNameColorDescription", "Specifies the color of the channel name.") /** The channel name color. */ Q_PROPERTY(Color channelNameColor READ channelNameColor WRITE setChannelNameColor) Q_CLASSINFO("channelBNameColorDescription", "Specifies the color of the channel name for VFO B.") /** The channel name color for VFO B. */ Q_PROPERTY(Color channelBNameColor READ channelBNameColor WRITE setChannelBNameColor) Q_CLASSINFO("zoneNameColorDescription", "Specifies the color of the zone name.") /** The zone name color. */ Q_PROPERTY(Color zoneNameColor READ zoneNameColor WRITE setZoneNameColor) Q_CLASSINFO("zoneBNameColorDescription", "Specifies the color of the zone name for VFO B.") /** The zone name color for VFO B. */ Q_PROPERTY(Color zoneBNameColor READ zoneBNameColor WRITE setZoneBNameColor) /** Specifies the UI language. */ Q_PROPERTY(Language language READ language WRITE setLanguage) /** Specifies the date format. */ Q_PROPERTY(DateFormat dateFormat READ dateFormat WRITE setDateFormat) public: /** What to show from the last caller. */ enum class LastCallerDisplayMode { Off = 0, ID = 1, Call = 2, Both = 3 }; Q_ENUM(LastCallerDisplayMode) /** Possible display colors. */ enum class Color { White = 0, Black = 1, Orange=2, Red=3, Yellow=4, Green=5, Turquoise=6, Blue=7 }; Q_ENUM(Color) /** Possible UI languages. */ enum class Language { English = 0, ///< UI Language is english. German = 1 ///< UI Language is german. }; Q_ENUM(Language) /** Possible date formats. */ enum class DateFormat { YearFirst = 0, ///< yyyy/mm/dd DayFirst = 1 ///< dd/mm/yyyy }; Q_ENUM(DateFormat) public: /** Constructor. */ explicit AnytoneDisplaySettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true, if the frequency is displayed instead of the channel name. */ bool displayFrequencyEnabled() const; /** Enables/disables display of frequency. */ void enableDisplayFrequency(bool enable); /** Returns the display brightness [1-10]. */ unsigned int brightness() const; /** Sets the display brightness [1-10]. */ void setBrightness(unsigned int level); /** Returns @c true if the volume-change prompt is shown. */ bool volumeChangePromptEnabled() const; /** Enables/disables the volume-change prompt. */ void enableVolumeChangePrompt(bool enable); /** Returns @c true if the call-end prompt is shown. */ bool callEndPromptEnabled() const; /** Enables/disables the call-end prompt. */ void enableCallEndPrompt(bool enable); /** Returns the last caller display mode. */ LastCallerDisplayMode lastCallerDisplay() const; /** Sets the last caller display mode. */ void setLastCallerDisplay(LastCallerDisplayMode mode); /** Returns @c true if the clock is shown. */ bool showClockEnabled() const; /** Enables/disables clock. */ void enableShowClock(bool enable); /** Returns @c true if the call is shown. */ bool showCallEnabled() const; /** Enables/disables display of call. */ void enableShowCall(bool enable); /** Returns the color of the call. */ Color callColor() const; /** Sets the color of the call. */ void setCallColor(Color color); /** Returns the UI language. */ Language language() const; /** Sets the UI language. */ void setLanguage(Language lang); /** Returns the date format */ DateFormat dateFormat() const; /** Sets the date format. */ void setDateFormat(DateFormat format); /** Returns @c true if the channel number is shown. */ bool showChannelNumberEnabled() const; /** Enables/disables the display of the channel number. */ void enableShowChannelNumber(bool enable); /** Returns @c true, if the global channel number is shown. Otherwise, * the channel number within the current zone is shown. */ bool showGlobalChannelNumber() const; /** Enables/disables showing the global channel number. */ void enableShowGlobalChannelNumber(bool enable); /** Returns @c true if the color code is shown. */ bool showColorCode() const; /** Shows/hides color code. */ void enableShowColorCode(bool enable); /** Returns @c true if the time slot is shown. */ bool showTimeSlot() const; /** Shows/hides time slot. */ void enableShowTimeSlot(bool enable); /** Returns @c true if the channel type is shown. */ bool showChannelType() const; /** Shows/hides channel type. */ void enableShowChannelType(bool enable); /** Returns @c true if the contact is shown. */ bool showContact() const; /** Enables/disables the display of calling contact. */ void enableShowContact(bool enable); /** Returns the standby text color. */ Color standbyTextColor() const; /** Sets the standby text color. */ void setStandbyTextColor(Color color); /** Returns the standby background color. */ Color standbyBackgroundColor() const; /** Sets the standby background color. */ void setStandbyBackgroundColor(Color color); /** Shows the last caller. */ bool showLastHeardEnabled() const; /** Enables/disables display of last caller. */ void enableShowLastHeard(bool enable); /** Returns backlight duration. */ Interval backlightDuration() const; /** Sets the backlight duration in seconds. */ void setBacklightDuration(Interval sec); /** Returns backlight duration during TX. */ Interval backlightDurationTX() const; /** Sets the backlight duration during TX in seconds. */ void setBacklightDurationTX(Interval sec); /** Returns the color of the channel name. */ Color channelNameColor() const; /** Sets the color of the channel name. */ void setChannelNameColor(Color color); /** Returns the color of the channel name for VFO B. */ Color channelBNameColor() const; /** Sets the channel name color for VFO B. */ void setChannelBNameColor(Color color); /** Returns the color of the zone name. */ Color zoneNameColor() const; /** Sets the color of the zone name. */ void setZoneNameColor(Color color); /** Returns the color of the zone name for VFO B. */ Color zoneBNameColor() const; /** Sets the zone name color for VFO B. */ void setZoneBNameColor(Color color); /** Returns backlight duration during RX. */ Interval backlightDurationRX() const; /** Sets the backlight duration during RX in seconds. */ void setBacklightDurationRX(Interval sec); /** Returns @c true if the custom channel background is enabled. */ bool customChannelBackground() const; /** Enables/disables the custom channel background. */ void enableCustomChannelBackground(bool enable); protected: bool _displayFrequency; ///< Display frequency property. unsigned int _brightness; ///< The display brightness. bool _volumeChangePrompt; ///< Volume-change prompt enabled. bool _callEndPrompt; ///< Call-end prompt enabled. LastCallerDisplayMode _lastCallerDisplay; ///< Last-caller display mode. bool _showClock; ///< Display clock. bool _showCall; ///< Display call. Color _callColor; ///< Color of call. Language _language; ///< UI language. DateFormat _dateFormat; ///< The date format. bool _showChannelNumber; ///< Show channel number. bool _showGlobalChannelNumber; ///< Show global channel number. bool _showColorCode; ///< Show color code. bool _showTimeSlot; ///< Show time slot. bool _showChannelType; ///< Show channel type. bool _showContact; ///< Enables showing the contact. Color _standbyTextColor; ///< Standby text color. Color _standbyBackgroundColor; ///< Standby background color. bool _showLastHeard; ///< Shows the last caller. Interval _backlightDuration; ///< Backlight duration. Interval _backlightDurationTX; ///< Backlight duration during TX (0=off). Interval _backlightDurationRX; ///< Backlight duration during RX. bool _customChannelBackground; ///< Custom channel background enabled. Color _channelNameColor; ///< Color of channel name. Color _channelBNameColor; ///< Color of channel name for VFO B. Color _zoneNameColor; ///< Color of zone name. Color _zoneBNameColor; ///< Color of zone name for VFO B. }; /** Implements the audio settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneAudioSettingsExtension: public ConfigItem { Q_OBJECT /** The VOX source. */ Q_PROPERTY(VoxSource voxSource READ voxSource WRITE setVOXSource) /** If @c true, recording is enabled. */ Q_PROPERTY(bool recording READ recordingEnabled WRITE enableRecording) /** If @c true, the audio is "enhanced". */ Q_PROPERTY(bool enhance READ enhanceAudioEnabled WRITE enableEnhanceAudio) /** The mute delay in minutes. */ Q_PROPERTY(Interval muteDelay READ muteDelay WRITE setMuteDelay) /** The enables speakers. */ Q_PROPERTY(Speaker speaker READ speaker WRITE setSpeaker) /** The source for the handset speaker. */ Q_PROPERTY(HandsetSpeakerSource handsetSpeaker READ handsetSpeaker WRITE setHandsetSpeaker) /** The handset type. */ Q_PROPERTY(HandsetType handsetType READ handsetType WRITE setHandsetType) public: /** Source for the VOX. */ enum class VoxSource { Internal = 0, External = 1, Both = 2 }; Q_ENUM(VoxSource) /** Possible speaker settings. */ enum class Speaker { Handset, Radio, Both }; Q_ENUM(Speaker); /** Possible handset speaker sources. */ enum class HandsetSpeakerSource { MainChannel, SubChannel }; Q_ENUM(HandsetSpeakerSource) /** Specifies possible handset types. */ enum class HandsetType { Anytone, Generic }; Q_ENUM(HandsetType) public: /** Default constructor. */ explicit AnytoneAudioSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the VOX source. */ VoxSource voxSource() const; /** Sets the VOX source. */ void setVOXSource(VoxSource source); /** Returns @c true if recording is enabled. */ bool recordingEnabled() const; /** Enables/disables recording. */ void enableRecording(bool enable); /** Returns @c true if the audio is "enhanced". */ bool enhanceAudioEnabled() const; /** Enables/disables enhanced audio. */ void enableEnhanceAudio(bool enable); /** Returns the mute delay. */ Interval muteDelay() const; /** Sets the mute delay. */ void setMuteDelay(Interval intv); /** Returns the speaker that are enabled. */ Speaker speaker() const; /** Sets, which speacker are enabled. */ void setSpeaker(Speaker speaker); /** Returns the handset speaker source. */ HandsetSpeakerSource handsetSpeaker() const; /** Sets the handset speaker source. */ void setHandsetSpeaker(HandsetSpeakerSource src); /* Returns the handset type. */ HandsetType handsetType() const; /** Sets the handset type. */ void setHandsetType(HandsetType type); protected: Interval _voxDelay; ///< VOX delay in ms. bool _recording; ///< Recording enabled. VoxSource _voxSource; ///< The VOX source. bool _enhanceAudio; ///< Enhance audio. Interval _muteDelay; ///< Mute delay in minutes. Speaker _speaker; ///< Specifies which speaker are enabled. HandsetSpeakerSource _handsetSpeaker; ///< Specifies the handset speaker source. HandsetType _handsetType; ///< Handset type. }; /** Implements the menu settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneMenuSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("durationDescription", "The time in seconds, the menu is shown.") /** Menu exit time in seconds. */ Q_PROPERTY(Interval duration READ duration WRITE setDuration) Q_CLASSINFO("separatorDescription", "If enabled, the menu items are separated by a line.") /** Menu separator. */ Q_PROPERTY(bool separator READ separatorEnabled WRITE enableSeparator) public: /** Default constructor. */ explicit AnytoneMenuSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the menu duration in seconds. */ Interval duration() const; /** Sets the menu duration in seconds. */ void setDuration(Interval sec); /** Returns @c true, if the menu separator lines are shown. */ bool separatorEnabled() const; /** Enables/disables the menu separator lines. */ void enableSeparator(bool enable); protected: Interval _menuDuration; ///< Menu display duration in seconds. bool _showSeparator; ///< Show menu separator lines. }; /** Implements the config representation of a repeater offset. This is just a transmit * offset-frequency in Hz. * @ingroup anytone */ class AnytoneAutoRepeaterOffset: public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "off") Q_CLASSINFO("offsetDecription", "Transmit-frequency offset in Hz.") Q_CLASSINFO("offsetLongDecription", "The transmit-frequency offset is specified as a positive integer in Hz. The offset " "direction is specified for each VFO separately.") /** The offset frequency. */ Q_PROPERTY(Frequency offset READ offset WRITE setOffset) public: /** Default constructor. */ explicit Q_INVOKABLE AnytoneAutoRepeaterOffset(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the transmit frequency offset in Hz. */ Frequency offset() const; /** Sets the transmit frequency offset in Hz. */ void setOffset(Frequency offset); protected: /** The transmit frequency offset in Hz. */ Frequency _offset; }; /** Represents a reference to a repeater offset. * @ingroup anytone */ class AnytoneAutoRepeaterOffsetRef: public ConfigObjectReference { Q_OBJECT public: /** Default constructor. */ explicit AnytoneAutoRepeaterOffsetRef(QObject *parent=nullptr); }; /** Represents a list of auto-repeater offsets. * @ingroup anytone */ class AnytoneAutoRepeaterOffsetList: public ConfigObjectList { Q_OBJECT public: /** Empty constructor. */ explicit AnytoneAutoRepeaterOffsetList(QObject *parent=nullptr); ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err); }; /** Implements the auto-repeater settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneAutoRepeaterSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("description", "Auto-repeater settings for AnyTone devices.") Q_CLASSINFO("directionADescription", "Auto-repeater transmit-frequency offset direction for VFO A.") /** Specifies the auto-repeater transmit-frequency offset direction for VFO A. */ Q_PROPERTY(Direction directionA READ directionA WRITE setDirectionA) Q_CLASSINFO("directionBDescription", "Auto-repeater transmit-frequency offset direction for VFO B.") /** Specifies the auto-repeater transmit-frequency offset direction for VFO B. */ Q_PROPERTY(Direction directionB READ directionB WRITE setDirectionB) Q_CLASSINFO("vhfMin", "The minimum frequency in Hz of the VHF auto-repeater frequency range.") /** The minimum frequency in Hz of the VHF auto-repeater frequency range. */ Q_PROPERTY(Frequency vhfMin READ vhfMin WRITE setVHFMin) Q_CLASSINFO("vhfMax", "The maximum frequency in Hz of the VHF auto-repeater frequency range.") /** The maximum frequency in Hz of the VHF auto-repeater frequency range. */ Q_PROPERTY(Frequency vhfMax READ vhfMax WRITE setVHFMax) Q_CLASSINFO("uhfMin", "The minimum frequency in Hz of the UHF auto-repeater frequency range.") /** The minimum frequency in Hz of the UHF auto-repeater frequency range. */ Q_PROPERTY(Frequency uhfMin READ uhfMin WRITE setUHFMin) Q_CLASSINFO("uhfMax", "The maximum frequency in Hz of the UHF auto-repeater frequency range.") /** The maximum frequency in Hz of the UHF auto-repeater frequency range. */ Q_PROPERTY(Frequency uhfMax READ uhfMax WRITE setUHFMax) Q_CLASSINFO("vhfDescription", "A reference to an offset frequency for the VHF band.") /** A reference to the auto-repeater frequency for VHF. */ Q_PROPERTY(AnytoneAutoRepeaterOffsetRef* vhf READ vhfRef) Q_CLASSINFO("uhfDescription", "A reference to an offset frequency for the UHF band.") /** A reference to the auto-repeater frequency for UHF. */ Q_PROPERTY(AnytoneAutoRepeaterOffsetRef* uhf READ uhfRef) Q_CLASSINFO("vhf2Min", "The minimum frequency in Hz of the second VHF auto-repeater frequency range.") /** The minimum frequency in Hz of the second VHF auto-repeater frequency range. */ Q_PROPERTY(Frequency vhf2Min READ vhf2Min WRITE setVHF2Min) Q_CLASSINFO("vhf2Max", "The maximum frequency in Hz of the second VHF auto-repeater frequency range.") /** The maximum frequency in Hz of the second VHF auto-repeater frequency range. */ Q_PROPERTY(Frequency vhf2Max READ vhf2Max WRITE setVHF2Max) Q_CLASSINFO("uhf2Min", "The minimum frequency in Hz of the second UHF auto-repeater frequency range.") /** The minimum frequency in Hz of the second UHF auto-repeater frequency range. */ Q_PROPERTY(Frequency uhf2Min READ uhf2Min WRITE setUHF2Min) Q_CLASSINFO("uhf2Max", "The maximum frequency in Hz of the second UHF auto-repeater frequency range.") /** The maximum frequency in Hz of the second UHF auto-repeater frequency range. */ Q_PROPERTY(Frequency uhf2Max READ uhf2Max WRITE setUHF2Max) Q_CLASSINFO("vhf2Description", "A reference to an offset frequency for the second VHF band.") /** A reference to the auto-repeater frequency for the second VHF band. */ Q_PROPERTY(AnytoneAutoRepeaterOffsetRef* vhf2 READ vhf2Ref) Q_CLASSINFO("uhf2Description", "A reference to an offset frequency for the second UHF band.") /** A reference to the auto-repeater frequency for the second UHF band. */ Q_PROPERTY(AnytoneAutoRepeaterOffsetRef* uhf2 READ uhf2Ref) Q_CLASSINFO("offsetDescription", "The lists of offset frequencies.") /** The repeater transmit offset frequencies. */ Q_PROPERTY(AnytoneAutoRepeaterOffsetList* offsets READ offsets) public: /** Encodes the auto-repeater offset sign. */ enum class Direction { Off = 0, ///< Disabled. Positive = 1, ///< Positive frequency offset. Negative = 2 ///< Negative frequency offset. }; Q_ENUM(Direction) public: /** Default constructor. */ explicit AnytoneAutoRepeaterSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** The auto-repeater offset direction for VFO A. */ Direction directionA() const; /** Set the auto-repeater offset direction for VFO A. */ void setDirectionA(Direction dir); /** The auto-repeater offset direction for VFO B. */ Direction directionB() const; /** Set the auto-repeater offset direction for VFO V. */ void setDirectionB(Direction dir); /** Returns the minimum frequency (in Hz) of the auto-repeater frequency range in the VHF band. */ Frequency vhfMin() const; /** Sets the minimum frequency (in Hz) of the auto-repeater frequency range in the VHF band. */ void setVHFMin(Frequency Hz); /** Returns the maximum frequency (in Hz) of the auto-repeater frequency range in the VHF band. */ Frequency vhfMax() const; /** Sets the maximum frequency (in Hz) of the auto-repeater frequency range in the VHF band. */ void setVHFMax(Frequency Hz); /** Returns the minimum frequency (in Hz) of the auto-repeater frequency range in the UHF band. */ Frequency uhfMin() const; /** Sets the minimum frequency (in Hz) of the auto-repeater frequency range in the UHF band. */ void setUHFMin(Frequency Hz); /** Returns the maximum frequency (in Hz) of the auto-repeater frequency range in the UHF band. */ Frequency uhfMax() const; /** Sets the maximum frequency (in Hz) of the auto-repeater frequency range in the UHF band. */ void setUHFMax(Frequency Hz); /** Returns the reference for the UHF offset freuqency. */ AnytoneAutoRepeaterOffsetRef *uhfRef() const; /** Returns the reference for the VHF offset freuqency. */ AnytoneAutoRepeaterOffsetRef *vhfRef() const; /** Returns the minimum frequency (in Hz) of the auto-repeater frequency range in the second VHF band. */ Frequency vhf2Min() const; /** Sets the minimum frequency (in Hz) of the auto-repeater frequency range in the second VHF band. */ void setVHF2Min(Frequency Hz); /** Returns the maximum frequency (in Hz) of the auto-repeater frequency range in the second VHF band. */ Frequency vhf2Max() const; /** Sets the maximum frequency (in Hz) of the auto-repeater frequency range in the second VHF band. */ void setVHF2Max(Frequency Hz); /** Returns the minimum frequency (in Hz) of the auto-repeater frequency range in the second UHF band. */ Frequency uhf2Min() const; /** Sets the minimum frequency (in Hz) of the auto-repeater frequency range in the second UHF band. */ void setUHF2Min(Frequency Hz); /** Returns the maximum frequency (in Hz) of the auto-repeater frequency range in the second UHF band. */ Frequency uhf2Max() const; /** Sets the maximum frequency (in Hz) of the auto-repeater frequency range in the second UHF band. */ void setUHF2Max(Frequency Hz); /** Returns the reference for the second UHF offset freuqency. */ AnytoneAutoRepeaterOffsetRef *uhf2Ref() const; /** Returns the reference for the second VHF offset freuqency. */ AnytoneAutoRepeaterOffsetRef *vhf2Ref() const; /** Returns a weak reference to the offset list. */ AnytoneAutoRepeaterOffsetList *offsets() const; protected: /** The auto-repeater direction for VFO A. */ Direction _directionA; /** The auto-repeater direction for VFO B. */ Direction _directionB; /** Minimum frequency of the VHF auto-repeater range. */ Frequency _minVHF; /** Maximum frequency of the VHF auto-repeater range. */ Frequency _maxVHF; /** Minimum frequency of the UHF auto-repeater range. */ Frequency _minUHF; /** Maximum frequency of the UHF auto-repeater range. */ Frequency _maxUHF; /** A reference to the VHF offset frequency. */ AnytoneAutoRepeaterOffsetRef *_vhfOffset; /** A reference to the UHF offset frequency. */ AnytoneAutoRepeaterOffsetRef *_uhfOffset; /** Minimum frequency of the second VHF auto-repeater range. */ Frequency _minVHF2; /** Maximum frequency of the second VHF auto-repeater range. */ Frequency _maxVHF2; /** Minimum frequency of the second UHF auto-repeater range. */ Frequency _minUHF2; /** Maximum frequency of the second UHF auto-repeater range. */ Frequency _maxUHF2; /** A reference to the second VHF offset frequency. */ AnytoneAutoRepeaterOffsetRef *_vhf2Offset; /** A reference to the second UHF offset frequency. */ AnytoneAutoRepeaterOffsetRef *_uhf2Offset; /** The list of repeater offsets. */ AnytoneAutoRepeaterOffsetList *_offsets; }; /** Implements the DMR settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneDMRSettingsExtension: public ConfigItem { Q_OBJECT /** Manual dialed group-call hang-time in seconds. */ Q_PROPERTY(Interval manualGroupCallHangTime READ manualGroupCallHangTime WRITE setManualGroupCallHangTime) /** Manual dialed private-call hang-time in seconds. */ Q_PROPERTY(Interval manualPrivateCallHangTime READ manualPrivateCallHangTime WRITE setManualPrivateCallHangTime) Q_CLASSINFO("wakeHeadPeriod", "Sets the wake head-period in ms. Should be set to 100ms.") /** Wake head-period in ms. */ Q_PROPERTY(Interval wakeHeadPeriod READ wakeHeadPeriod WRITE setWakeHeadPeriod) Q_CLASSINFO("filterOwnIDDescription", "If enabled, own ID is not shown in call lists.") /** Filter own ID from call lists. */ Q_PROPERTY(bool filterOwnID READ filterOwnIDEnabled WRITE enableFilterOwnID) Q_CLASSINFO("monitorSlotMatchDescription", "Time-slot match-mode for DMR monitor.") /** Slot-match mode for DMR monitor. */ Q_PROPERTY(SlotMatch monitorSlotMatch READ monitorSlotMatch WRITE setMonitorSlotMatch) Q_CLASSINFO("monitorColorCodeMatchDescription", "If enabled, the DMR monitor will only open for " "matching color-codes.") /** Color-code match for DMR monitor. */ Q_PROPERTY(bool monitorColorCodeMatch READ monitorColorCodeMatchEnabled WRITE enableMonitorColorCodeMatch) Q_CLASSINFO("monitorIDMatchDescription", "If enabled, the DMR monitor will only open for matching IDs.") /** ID match for DMR monitor. */ Q_PROPERTY(bool monitorIDMatch READ monitorIDMatchEnabled WRITE enableMonitorIDMatch) Q_CLASSINFO("monitorTimeSlotHold", "Whether the DMR monitor holds the time-slot.") /** The DMR monitor holds the time-slot. */ Q_PROPERTY(bool monitorTimeSlotHold READ monitorTimeSlotHoldEnabled WRITE enableMonitorTimeSlotHold) /** Specifies the talker alias source. */ Q_PROPERTY(TalkerAliasSource talkerAliasSource READ talkerAliasSource WRITE setTalkerAliasSource) /** The encryption type to be used. */ Q_PROPERTY(EncryptionType encryption READ encryption WRITE setEncryption) public: /** Possible monitor slot matches. */ enum class SlotMatch { Off = 0, Single = 1, Both = 2 }; Q_ENUM(SlotMatch) /** Talker alias display preference. */ enum class TalkerAliasSource { Off = 0, UserDB = 1, Air = 2 }; Q_ENUM(TalkerAliasSource) /** Possible encryption types. */ enum class EncryptionType { AES=0, DMR=1 }; Q_ENUM(EncryptionType) public: /** Constructor. */ explicit AnytoneDMRSettingsExtension(QObject *parent = nullptr); ConfigItem *clone() const; /** Returns the manual dialed group-call hang-time in seconds. */ Interval manualGroupCallHangTime() const; /** Sets the manual dialed group-call hang-time in seconds. */ void setManualGroupCallHangTime(Interval sec); /** Returns the manual dialed private-call hang-time in seconds. */ Interval manualPrivateCallHangTime() const; /** Sets the manual dialed private-call hang-time in seconds. */ void setManualPrivateCallHangTime(Interval sec); /** Returns the wake head-period in ms. */ Interval wakeHeadPeriod() const; /** Sets the wake head-period in ms. */ void setWakeHeadPeriod(Interval ms); /** If @c true, the own ID is not shown in call lists. */ bool filterOwnIDEnabled() const; /** Enables/disables filtering of own ID. */ void enableFilterOwnID(bool enable); /** Returns the slot-match mode for the DMR monitor. */ SlotMatch monitorSlotMatch() const; /** Sets the slot-match mode for the DMR monitor. */ void setMonitorSlotMatch(SlotMatch match); /** Returns @c true if the CC match is enabled for the DMR monitor. */ bool monitorColorCodeMatchEnabled() const; /** Enables/disables the CC match for the DMR monitor. */ void enableMonitorColorCodeMatch(bool enable); /** Returns @c true if the ID match is enabled for the DMR monitor. */ bool monitorIDMatchEnabled() const; /** Enables/disables ID match for the DMR monitor. */ void enableMonitorIDMatch(bool enable); /** Returns @c true if the time-slot is held by the DMR monitor. */ bool monitorTimeSlotHoldEnabled() const; /** Enables/disables the time-slot hold for the DMR monitor. */ void enableMonitorTimeSlotHold(bool enable); /** Returns the talker alias source. */ TalkerAliasSource talkerAliasSource() const; /** Sets the talker alias source. */ void setTalkerAliasSource(TalkerAliasSource mode); /** Returns the encryption type. */ EncryptionType encryption() const; /** Sets the encryption type. */ void setEncryption(EncryptionType type); protected: Interval _manualGroupCallHangTime; ///< Hang-time for manual dialed group-calls in seconds. Interval _manualPrivateCallHangTime; ///< Hang-time for manual dialed private-calls in seconds. Interval _wakeHeadPeriod; ///< Wake head-period in ms, should be 100ms. bool _filterOwnID; ///< If enabled, the own ID is not shown in call lists. SlotMatch _monitorSlotMatch; ///< Slot-match mode for DMR monitor. bool _monitorColorCodeMatch; ///< Enables CC match for DMR monitor. bool _monitorIDMatch; ///< Enables ID match for DMR monitor. bool _monitorTimeSlotHold; ///< Enables the time-slot hold for the DMR monitor. TalkerAliasSource _talkerAliasSource; ///< Source for the talker alias. EncryptionType _encryption; ///< DMR encryption type. }; /** Implements the GPS settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneGPSSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("timeZoneDescription", "Specifies the GPS time-zone (IANA name).") /** The time-zone IANA Id. */ Q_PROPERTY(QString timeZone READ ianaTimeZone WRITE setIANATimeZone) Q_CLASSINFO("positionReportingDescription", "Enables GPS range reporting.") /** Enables GPS range reporting. */ Q_PROPERTY(bool reportPosition READ positionReportingEnabled WRITE enablePositionReporting) Q_CLASSINFO("updatePeriodDescription", "Specifies the GPS reporting interval in seconds.") /** GPS ranging interval in seconds. */ Q_PROPERTY(Interval updatePeriod READ updatePeriod WRITE setUpdatePeriod) public: /** Constructor. */ explicit AnytoneGPSSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the IANA ID of the time zone. */ QString ianaTimeZone() const; /** Returns the time-zone. */ QTimeZone timeZone() const; /** Sets the time zone by IANA ID. */ void setIANATimeZone(const QString &id); /** Sets the time zone. */ void setTimeZone(const QTimeZone &zone); /** Returns @c true if the GPS range reporting is enabled. */ bool positionReportingEnabled() const; /** Enables/disables the GPS range reporting. */ void enablePositionReporting(bool enable); /** Returns the GPS ranging interval in seconds. */ Interval updatePeriod() const; /** Sets the GPS ranging interval in seconds. */ void setUpdatePeriod(Interval sec); protected: QTimeZone _timeZone; ///< The time zone. bool _gpsRangeReporting; ///< Enables GPS range reporting. Interval _gpsRangingInterval; ///< The GPS ranging interval in seconds. }; /** Implements the ranging/roaming settings extension of AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneRoamingSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("Description", "Collects all ranging/roaming settings for AnyTone devices.") /** Enables auto-roaming. */ Q_PROPERTY(bool autoRoam READ autoRoam WRITE enableAutoRoam) Q_CLASSINFO("autoRoamPeriodDescription", "Specifies the auto-roaming period in minutes.") /** The auto-roaming period in minutes. */ Q_PROPERTY(Interval autoRoamPeriod READ autoRoamPeriod WRITE setAutoRoamPeriod) Q_CLASSINFO("autoRoamDelayDescription", "A delay in seconds before starting the auto-roaming.") /** The auto-roam delay. */ Q_PROPERTY(Interval autoRoamDelay READ autoRoamDelay WRITE setAutoRoamDelay) Q_CLASSINFO("roamStart", "Start condition for auto-roaming.") /** Auto-roaming start condition. */ Q_PROPERTY(RoamStart roamStart READ roamingStartCondition WRITE setRoamingStartCondition) Q_CLASSINFO("roamReturn", "Condition to return to the original repeater.") /** Auto-roaming end/return condition. */ Q_PROPERTY(RoamStart roamReturn READ roamingReturnCondition WRITE setRoamingReturnCondition) Q_CLASSINFO("rangeCheckDescription", "Repeater range check.") /** Repeater range check. */ Q_PROPERTY(bool rangeCheck READ repeaterRangeCheckEnabled WRITE enableRepeaterRangeCheck) Q_CLASSINFO("checkIntervalDescription", "Repeater range check interval in seconds.") /** Repeater range check interval in seconds. */ Q_PROPERTY(Interval checkInterval READ repeaterCheckInterval WRITE setRepeaterCheckInterval) Q_CLASSINFO("retryCount", "Number of retries to connect to a repeater before giving up.") /** Retry count. */ Q_PROPERTY(unsigned int retryCount READ repeaterRangeCheckCount WRITE setRepeaterRangeCheckCount) /** Repeater out-of-range alert type. */ Q_PROPERTY(OutOfRangeAlert outOfRangeAlert READ outOfRangeAlert WRITE setOutOfRangeAlert) Q_CLASSINFO("notificationDescription", "Enables the repeater-check notification.") /** Repeater-check notification. */ Q_PROPERTY(bool notification READ notificationEnabled WRITE enableNotification) Q_CLASSINFO("notificationCountDescription", "The number of repeater-check notifications.") /** Repeater-check notification count. */ Q_PROPERTY(unsigned int notificationCount READ notificationCount WRITE setNotificationCount) /** The default roaming zone. */ Q_PROPERTY(RoamingZoneReference* defaultZone READ defaultZone) /** GPS roaming enabled. */ Q_PROPERTY(bool gpsRoaming READ gpsRoaming WRITE enableGPSRoaming) public: /** Possible roaming start conditions. */ enum class RoamStart { Periodic=0, OutOfRange=1 }; Q_ENUM(RoamStart) /** Possible repeater out-of-range alerts. */ enum class OutOfRangeAlert { None = 0x00, Bell = 0x01, Voice = 0x02 }; Q_ENUM(OutOfRangeAlert) public: /** Constructor. */ explicit AnytoneRoamingSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true if auto-roaming is enabled. */ bool autoRoam() const; /** Enables/disables auto-roaming. */ void enableAutoRoam(bool enable); /** Returns the auto-roaming period in minutes. */ Interval autoRoamPeriod() const; /** Sets the auto-roam period in minutes. */ void setAutoRoamPeriod(Interval min); /** Returns the auto-roam delay in seconds. */ Interval autoRoamDelay() const; /** Sets the auto-roam delay in seconds. */ void setAutoRoamDelay(Interval sec); /** Returns @c true if the repeater range check is enabled. */ bool repeaterRangeCheckEnabled() const; /** Enables/disables repeater range check. */ void enableRepeaterRangeCheck(bool enable); /** Returns the repeater check interval in seconds. */ Interval repeaterCheckInterval() const; /** Sets the repeater check interval in seconds. */ void setRepeaterCheckInterval(Interval sec); /** Number of retries before givnig up. */ unsigned int repeaterRangeCheckCount() const; /** Sets the number of retries before giving up. */ void setRepeaterRangeCheckCount(unsigned int count); /** Returns the repeater out-of-range alert type. */ OutOfRangeAlert outOfRangeAlert() const; /** Sets the repeater out-of-range alert type. */ void setOutOfRangeAlert(OutOfRangeAlert type); /** Returns the auto-roaming start condition. */ RoamStart roamingStartCondition() const; /** Sets the auto-roaming start condition. */ void setRoamingStartCondition(RoamStart start); /** Returns the auto-roaming return condition. */ RoamStart roamingReturnCondition() const; /** Sets the auto-roaming return condition. */ void setRoamingReturnCondition(RoamStart start); /** Returns @c true, if the repeater check notification is enabled. */ bool notificationEnabled() const; /** Enables/disables the repeater-check notification. */ void enableNotification(bool enable); /** Returns the number of notifications. */ unsigned int notificationCount() const; /** Sets the number of repeater-check notifications. */ void setNotificationCount(unsigned int n); /** Returns @c true if GPS roaming is enabled. */ bool gpsRoaming() const; /** Enables/disables GPS roaming. */ void enableGPSRoaming(bool enable); /** Returns a reference to the default roaming zone. */ RoamingZoneReference *defaultZone() const; protected: bool _autoRoam; ///< Enables auto roaming. Interval _autoRoamPeriod; ///< The auto-roam period in minutes. Interval _autoRoamDelay; ///< The auto-roam delay in seconds. bool _repeaterRangeCheck; ///< Enables the repeater range-check. Interval _repeaterCheckInterval; ///< The repeater check interval in seconds. unsigned int _repeaterRangeCheckCount; ///< Number of range checks before giving up. OutOfRangeAlert _outOfRangeAlert; ///< Type of the out-out-range alert. RoamStart _roamingStartCondition; ///< Auto-roaming start condition. RoamStart _roamingReturnCondition; ///< Auto-roaming return condition. bool _notification; ///< Repeater check notification. unsigned int _notificationCount; ///< Number of notifications. bool _gpsRoaming; ///< Enables GPS roaming. RoamingZoneReference *_defaultRoamingZone; ///< The default roaming zone. }; /** Implements the bluetooth handset settings for AnyTone devices. * This extension is part of the @c AnyToneBlueoothSettingsExtension. * * @ingroup anytone */ class AnytoneBluetoothHandsetSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("Description", "Collects all bluetooth handset settings for AnyTone devices.") Q_CLASSINFO("shutdownDescription", "Shuts the handset off too, when the radio is turned off.") Q_PROPERTY(bool shutdown READ shutdownEnabled WRITE enableShutdown) Q_CLASSINFO("volumeLevelADescription", "Sets the volume level for VFO A.") Q_PROPERTY(unsigned int volumeLevelA READ volumeLevelA WRITE setVolumeLevelA) Q_CLASSINFO("volumeLevelBDescription", "Sets the volume level for VFO B.") Q_PROPERTY(unsigned int volumeLevelB READ volumeLevelB WRITE setVolumeLevelB) Q_CLASSINFO("micGainDescription", "Specifies the microphone gain.") Q_PROPERTY(unsigned int micGain READ micGain WRITE setMicGain) Q_PROPERTY(unsigned int squelch READ squelch WRITE setSquelch) Q_PROPERTY(unsigned int txNoiseReduction READ txNoiseReduction WRITE setTxNoiseReduction) Q_PROPERTY(unsigned int voxLevel READ voxLevel WRITE setVoxLevel) Q_PROPERTY(Interval voxDelay READ voxDelay WRITE setVoxDelay) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyAShort READ funcKeyAShort WRITE setFuncKeyAShort) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyBShort READ funcKeyBShort WRITE setFuncKeyBShort) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyCShort READ funcKeyCShort WRITE setFuncKeyCShort) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyALong READ funcKeyALong WRITE setFuncKeyALong) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyBLong READ funcKeyBLong WRITE setFuncKeyBLong) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyCLong READ funcKeyCLong WRITE setFuncKeyCLong) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyAVeryLong READ funcKeyAVeryLong WRITE setFuncKeyAVeryLong) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyBVeryLong READ funcKeyBVeryLong WRITE setFuncKeyBVeryLong) Q_PROPERTY(AnytoneKeySettingsExtension::KeyFunction funcKeyCVeryLong READ funcKeyCVeryLong WRITE setFuncKeyCVeryLong) Q_PROPERTY(Interval backlight READ backlight WRITE setBacklight) public: /** Default constructor. */ explicit AnytoneBluetoothHandsetSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const override; /** Returns @c true, if the handset is shutdown along with the radio. */ bool shutdownEnabled() const; /** Enables/disables shutting the handset down along with the radio. */ void enableShutdown(bool enable); /** Returns the volume level for VFO A. */ unsigned int volumeLevelA() const; /** Sets the volume level for VFO A. */ void setVolumeLevelA(unsigned int level); /** Returns the volume level for VFO B. */ unsigned int volumeLevelB() const; /** Sets the volume level for VFO B. */ void setVolumeLevelB(unsigned int level); /** Returns the mic gain. */ unsigned int micGain() const; /** Sets the mic gain. */ void setMicGain(unsigned int gain); /** Returns the squlech level. */ unsigned int squelch() const; /** Sets the squelch level. */ void setSquelch(unsigned int level); /** Returns the transmit noise reduction level. */ unsigned int txNoiseReduction() const; /** Sets the transmit noise reduction level. */ void setTxNoiseReduction(unsigned int level); /** Returns the VOX level. */ unsigned int voxLevel() const; /** Set the VOX level. */ void setVoxLevel(unsigned int level); /** Returns the VOX delay. */ Interval voxDelay() const; /** Sets the VOX delay. */ void setVoxDelay(Interval delay); /** Returns function key A short press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyAShort() const; /** Set function key A short press function. */ void setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func); /** Returns function key B short press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyBShort() const; /** Set function key B short press function. */ void setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func); /** Returns function key C short press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyCShort() const; /** Set function key C short press function. */ void setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func); /** Returns function key A long press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyALong() const; /** Set function key A long press function. */ void setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func); /** Returns function key B long press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyBLong() const; /** Set function key B long press function. */ void setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func); /** Returns function key C long press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyCLong() const; /** Set function key C long press function. */ void setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func); /** Returns function key A very long press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyAVeryLong() const; /** Set function key A very long press function. */ void setFuncKeyAVeryLong(AnytoneKeySettingsExtension::KeyFunction func); /** Returns function key B very long press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyBVeryLong() const; /** Set function key B very long press function. */ void setFuncKeyBVeryLong(AnytoneKeySettingsExtension::KeyFunction func); /** Returns function key C very long press function. */ AnytoneKeySettingsExtension::KeyFunction funcKeyCVeryLong() const; /** Set function key C very long press function. */ void setFuncKeyCVeryLong(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the backlight duration. */ Interval backlight() const; /** Sets the backlight duration. */ void setBacklight(Interval dur); protected: bool _shutdown; ///< Shut the handset down along with the radio. unsigned int _volumeLevelA; ///< Specifies the volume level for VFO A. unsigned int _volumeLevelB; ///< Specifies the volume level for VFO B. unsigned int _micGain; ///< Specifies the mic gain. unsigned int _squelch; ///< Specifies the squelch level. unsigned int _txNoiseReduction; ///< Specifies the transmit noise reduction level. unsigned int _voxLevel; ///< Specifies the VOX level. Interval _voxDelay; ///< Specifies the VOX delay. /// Function key A short press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyAShort; /// Function key B short press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyBShort; /// Function key B short press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyCShort; /// Function key A long press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyALong; /// Function key B long press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyBLong; /// Function key B long press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyCLong; /// Function key A very long press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyAVeryLong; /// Function key B very long press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyBVeryLong; /// Function key B very long press function. AnytoneKeySettingsExtension::KeyFunction _funcKeyCVeryLong; Interval _backlight; ///< Backlight duration. }; /** Implements the bluetooth settings for some AnyTone devices. * This extension is part of the @c AnytoneSettingsExtension. * * @ingroup anytone */ class AnytoneBluetoothSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("Description", "Collects all bluetooth settings for AnyTone devices.") /** If @c true, bluetooth is enabled. */ Q_PROPERTY(bool enabled READ bluetoothEnabled WRITE enableBluetooth) /** If @c true, the PTT latch is enabled. */ Q_PROPERTY(bool pttLatch READ pttLatch WRITE enablePTTLatch) /** The sleep timeout of the PTT button. If 0, sleep timer is disabled. */ Q_PROPERTY(Interval pttSleepTimer READ pttSleepTimer WRITE setPTTSleepTimer) /** If @c true, the internal mic is enabled additionally to the bluetooth input. */ Q_PROPERTY(bool internalMic READ internalMicEnabled WRITE enableInternalMic) /** If @c true, the internal speaker is enabled additionally to the bluetooth output. */ Q_PROPERTY(bool internalSpeaker READ internalSpeakerEnabled WRITE enableInternalSpeaker) /** The bluetooth mic gain. */ Q_PROPERTY(unsigned int micGain READ micGain WRITE setMicGain); /** The bluetooth speaker gain. */ Q_PROPERTY(unsigned int speakerGain READ speakerGain WRITE setSpeakerGain); /** The bluetooth hold duration. */ Q_PROPERTY(Interval hold READ holdDuration WRITE setHoldDuration) /** The bluetooth hold delay. */ Q_PROPERTY(Interval holdDelay READ holdDelay WRITE setHoldDelay) /** The handset settings. */ Q_PROPERTY(AnytoneBluetoothHandsetSettingsExtension* handset READ handset) public: /** Default constructor. */ explicit AnytoneBluetoothSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the handset settings. */ AnytoneBluetoothHandsetSettingsExtension *handset() const; /** Returns @c true if bluetooth is enabled. */ bool bluetoothEnabled() const; /** Enables/disables bluetooth. */ void enableBluetooth(bool enable); /** Returns @c true if the PTT latch is enabled. */ bool pttLatch() const; /** Enables/disables the PTT latch. */ void enablePTTLatch(bool enable); /** Returns the PTT sleep timeout, 0 means off. */ Interval pttSleepTimer() const; /** Sets the PTT sleep timeout, 0 means off. */ void setPTTSleepTimer(Interval intv); /** Returns @c true if the internal mic is enabled additionally to the bluetooth input. */ bool internalMicEnabled() const; /** Enables/disables internal mic additionally to the bluetooth input. */ void enableInternalMic(bool enable); /** Returns @c true if the internal speaker is enabled additionally to the bluetooth output. */ bool internalSpeakerEnabled() const; /** Enables/disables internal speaker additionally to the bluetooth output. */ void enableInternalSpeaker(bool enable); /** Returns the bluetooth mic gain. */ unsigned int micGain() const; /** Sets the bluetooth mic gain. */ void setMicGain(unsigned int gain); /** Returns the bluetooth speaker gain. */ unsigned int speakerGain() const; /** Sets the bluetooth speaker gain. */ void setSpeakerGain(unsigned int gain); /** Returns the bluetooth hold duration. */ const Interval &holdDuration() const; /** Sets the bluetooth hold duration. */ void setHoldDuration(const Interval &dur); /** Returns the bluetooth hold delay. */ const Interval &holdDelay() const; /** Sets the bluetooth hold delay. */ void setHoldDelay(const Interval &dur); protected: /// Handset settings. AnytoneBluetoothHandsetSettingsExtension *_handset; bool _bluetoothEnabled; ///< Enables bluetooth. bool _pttLatch; ///< PTT latch flag. Interval _pttSleep; ///< PTT sleep timer, 0 is off. bool _internalMic; ///< Additionally enables internal mic. bool _internalSpeaker; ///< Additionally enables internal speaker. unsigned int _micGain; ///< The bluetooth mic gain. unsigned int _speakerGain; ///< The bluetooth speaker gain. Interval _holdDuration; ///< What ever. Interval _holdDelay; ///< What ever. }; /** Implements the repeater settings for the BTECH DMR-6X2UV and AT-D578UV. * This extension is part of the @c AnytoneSettingsExtension * * @ingroup anytone */ class AnytoneRepeaterSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("enabledDescription", "If true, the repeater feature is enabled.") /** Enables/disables the repeater feature. */ Q_PROPERTY(bool enabled READ enabled WRITE enable) Q_CLASSINFO("monitorDescription", "If true, the repeater-monitoring is enabled.") /** Enables/disables the repeater monitor. */ Q_PROPERTY(bool monitor READ monitorEnabled WRITE enableMonitor) Q_CLASSINFO("timeSlotDescription", "Specifies the time-slot of the primary repeater channel.") /** Time-slot of the primary repeater channel. */ Q_PROPERTY(TimeSlot timeSlot READ timeSlot WRITE setTimeSlot) Q_CLASSINFO("secTimeSlotDescription", "Specifies the time-slot of the secondary repeater channel.") /** Time-slot of the secondary repeater channel. */ Q_PROPERTY(TimeSlot secTimeSlot READ secTimeSlot WRITE setSecTimeSlot) Q_CLASSINFO("colorCodeDescription", "Specifies the repeater color code.") Q_PROPERTY(ColorCode colorCode READ colorCode WRITE setColorCode) public: /** Possible repeater time-slots. */ enum class TimeSlot { Any, TS1, TS2, Channel }; Q_ENUM(TimeSlot) /** Possible color code settings. */ enum class ColorCode { Ignored, VFOA, VFOB }; Q_ENUM(ColorCode) public: /** Default constructor. */ explicit AnytoneRepeaterSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** If @c true, the simplex-repeater is enabled. */ bool enabled() const; /** Enables/disables the simplex repeater. */ void enable(bool enable); /** If @c true, repeater monitoring is enabled. */ bool monitorEnabled() const; /** Enables/disables repeater monitoring. */ void enableMonitor(bool enable); /** Returns the primary (simplex) channel time-slot. */ TimeSlot timeSlot() const; /** Sets the primary (simplex) channel time-slot. */ void setTimeSlot(TimeSlot ts); /** Returns the secondary channel time-slot. */ TimeSlot secTimeSlot() const; /** Sets the secondary channel time-slot. */ void setSecTimeSlot(TimeSlot ts); /** Returns the color code match mode. */ ColorCode colorCode() const; /** Sets the color code match mode. */ void setColorCode(ColorCode code); protected: bool _enabled; ///< If @c true, the simplex repeater is enabled. bool _monitor; ///< If enabled, the radio will monitor the channel. TimeSlot _timeSlot; ///< The primary channel time-slot. TimeSlot _secTimeSlot; ///< The secondary channel time-slot. ColorCode _colorCode; ///< The color code match mode. }; /** Implements the satellite mode settings for the BTECH DMR-6X2UV PRO. * This extension is part of the @c AnytoneSettingsExtension * * @ingroup anytone */ class AnytoneSatelliteSettingsExtension: public ConfigItem { Q_OBJECT Q_CLASSINFO("powerDescription", "Specifies the transmit power in satellite mode.") /** Transmit power in satellite mode. */ Q_PROPERTY(Channel::Power power READ power WRITE setPower) Q_CLASSINFO("squelchDescription", "Specifies FM squelch level in satellite mode.") /** Enables/disables the repeater monitor. */ Q_PROPERTY(unsigned int squelch READ squelch WRITE setSquelch) public: /** Default constructor. */ explicit AnytoneSatelliteSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns transmit power in satellite mode. */ Channel::Power power() const; /** Sets transmit power in satellite mode. */ void setPower(Channel::Power power); /** Returns FM squelch in satellite mode. */ unsigned int squelch() const; /** Sets squelch in satellite mode. */ void setSquelch(unsigned int squelch); protected: /** Transmit power in satellite mode. */ Channel::Power _power; /** FM squelch in satellite mode. */ unsigned int _squelch; }; /** Implements the device specific extension for the general settings of AnyTone devices. * * As there are a huge amount of different settings, they are split into separate extensions. * One for each topic. * * @ingroup anytone */ class AnytoneSettingsExtension: public ConfigExtension { Q_OBJECT Q_CLASSINFO("description", "Device specific settings for AnyTone devices.") Q_CLASSINFO("subChannelDescription", "Enables/disables the sub-channel.") /** If @c true, the sub-channel is enabled. */ Q_PROPERTY(bool subChannel READ subChannelEnabled WRITE enableSubChannel) Q_CLASSINFO("selectedVFODecription", "Specifies the currently selected VFO (A or B).") /** The current active VFO. */ Q_PROPERTY(VFO selectedVFO READ selectedVFO WRITE setSelectedVFO) Q_CLASSINFO("modeADescription", "Specifies the mode (memory or VFO) for VFO A.") Q_CLASSINFO("modeBDescription", "Specifies the mode (memory or VFO) for VFO B.") /** The mode of VFO A. */ Q_PROPERTY(VFOMode modeA READ modeA WRITE setModeA) /** The mode of VFO B. */ Q_PROPERTY(VFOMode modeB READ modeB WRITE setModeB) Q_CLASSINFO("zoneADescription", "Specifies the current zone for VFO A.") Q_CLASSINFO("zoneBDescription", "Specifies the current zone for VFO B.") /** The current zone for VFO A. */ Q_PROPERTY(ZoneReference* zoneA READ zoneA) /** The current zone for VFO B. */ Q_PROPERTY(ZoneReference* zoneB READ zoneB) /** The VFO scan type. */ Q_PROPERTY(VFOScanType vfoScanType READ vfoScanType WRITE setVFOScanType) /** The minimum UHF VFO-scan frequency in Hz. */ Q_PROPERTY(Frequency minVFOScanFrequencyUHF READ minVFOScanFrequencyUHF WRITE setMinVFOScanFrequencyUHF) /** The maximum UHF VFO-scan frequency in Hz. */ Q_PROPERTY(Frequency maxVFOScanFrequencyUHF READ maxVFOScanFrequencyUHF WRITE setMaxVFOScanFrequencyUHF) /** The minimum VHF VFO-scan frequency in Hz. */ Q_PROPERTY(Frequency minVFOScanFrequencyVHF READ minVFOScanFrequencyVHF WRITE setMinVFOScanFrequencyVHF) /** The maximum VHF VFO-scan frequency in Hz. */ Q_PROPERTY(Frequency maxVFOScanFrequencyVHF READ maxVFOScanFrequencyVHF WRITE setMaxVFOScanFrequencyVHF) Q_CLASSINFO("keepLastCallerDescription", "Keeps the last caller on channel switch") /** The keep-last-caller setting. */ Q_PROPERTY(bool keepLastCaller READ keepLastCallerEnabled WRITE enableKeepLastCaller) Q_CLASSINFO("vfoStepDescription", "Specifies the VFO tuning steps in kHz.") /** The VFO tuning step-size in kHz. */ Q_PROPERTY(Frequency vfoStep READ vfoStep WRITE setVFOStep) Q_CLASSINFO("steTypeDescription", "Specifies the STE (squelch tail elimination) type.") Q_CLASSINFO("steTypeLongDescription", "Can also be used to disable the STE entirely.") /** The STE type. */ Q_PROPERTY(STEType steType READ steType WRITE setSTEType) Q_CLASSINFO("steFrequencyDescription", "Specifies the STE (squelch tail elimination) frequency in Hz.") /** The STE frequency in Hz. */ Q_PROPERTY(double steFrequency READ steFrequency WRITE setSTEFrequency) Q_CLASSINFO("steDurationDescription", "Specifies the duration of the STE (squelch tail elimination) tone.") Q_CLASSINFO("steDurationLongDescription", "Valid values are usually in the range between 10 and 1000ms.") /** The STE duration in ms. */ Q_PROPERTY(Interval steDuration READ steDuration WRITE setSTEDuration) Q_CLASSINFO("tbstFrequencyDescription", "Specifies the TBST frequency in Hz.") Q_CLASSINFO("tbstFrequencyDescription", "Should be one of 1000, 1450, 1750 and 2100 Hz. " "D878UV(2), D578UV and DMR-6X2UV only.") /** The TBST frequency in Hz. */ Q_PROPERTY(Frequency tbstFrequency READ tbstFrequency WRITE setTBSTFrequency) Q_CLASSINFO("proModeDescription", "Enables 'professional' mode. This limits the option reachable via the menu.") /** If @c true, the "pro mode" is enabled. */ Q_PROPERTY(bool proMode READ proModeEnabled WRITE enableProMode) /** If @c true, the call-channel is maintained (whatever that means). */ Q_PROPERTY(bool maintainCallChannel READ maintainCallChannelEnabled WRITE enableMaintainCallChannel) /** Specifies, what controls the fan. */ Q_PROPERTY(FanControl fan READ fan WRITE setFan); /** The boot settings. */ Q_PROPERTY(AnytoneBootSettingsExtension* bootSettings READ bootSettings) /** The power-save settings. */ Q_PROPERTY(AnytonePowerSaveSettingsExtension* powerSaveSettings READ powerSaveSettings) /** The key settings. */ Q_PROPERTY(AnytoneKeySettingsExtension* keySettings READ keySettings) /** The tone settings. */ Q_PROPERTY(AnytoneToneSettingsExtension* toneSettings READ toneSettings) /** The display settings. */ Q_PROPERTY(AnytoneDisplaySettingsExtension* displaySettings READ displaySettings) /** The audio settings. */ Q_PROPERTY(AnytoneAudioSettingsExtension* audioSettings READ audioSettings) /** The menu settings. */ Q_PROPERTY(AnytoneMenuSettingsExtension* menuSettings READ menuSettings) /** The auto-repeater settings. */ Q_PROPERTY(AnytoneAutoRepeaterSettingsExtension* autoRepeaterSettings READ autoRepeaterSettings) /** The DMR settings. */ Q_PROPERTY(AnytoneDMRSettingsExtension* dmrSettings READ dmrSettings) /** The GPS settings. */ Q_PROPERTY(AnytoneGPSSettingsExtension* gpsSettings READ gpsSettings) /** The Roaming settings. */ Q_PROPERTY(AnytoneRoamingSettingsExtension* roamingSettings READ roamingSettings) /** The Bluetooth settings. */ Q_PROPERTY(AnytoneBluetoothSettingsExtension* bluetoothSettings READ bluetoothSettings) Q_CLASSINFO("simplexRepeaterSettingsDescription", "Configuration for the DMR-6X2UV simplex-repeater feature.") /** The simplex-repeater settings. DMR-6X2UV only. */ Q_PROPERTY(AnytoneRepeaterSettingsExtension * repeaterSettings READ repeaterSettings) Q_CLASSINFO("satelliteSettingsDescription", "Some settings of for satellite mode.") Q_PROPERTY(AnytoneSatelliteSettingsExtension * satelliteSettings READ satelliteSettings) public: /** Encodes the possible VFO scan types. */ enum class VFOScanType { Time = 0, Carrier = 1, Stop = 2 }; Q_ENUM(VFOScanType) /** Possible VFO modes. */ enum class VFOMode { Memory = 0, VFO = 1 }; Q_ENUM(VFOMode) /** Possible VFOs. */ enum class VFO { A = 0, B = 1 }; Q_ENUM(VFO) /** All possible STE (squelch tail eliminate) types. */ enum class STEType { Off = 0, Silent = 1, Deg120 = 2, Deg180 = 3, Deg240 = 4 }; Q_ENUM(STEType) enum class FanControl { PTT, Temperature, Both }; Q_ENUM(FanControl) public: /** Constructor. */ Q_INVOKABLE explicit AnytoneSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** A reference to the boot settings. */ AnytoneBootSettingsExtension *bootSettings() const; /** A reference to the power-save settings. */ AnytonePowerSaveSettingsExtension *powerSaveSettings() const; /** A reference to the key settings. */ AnytoneKeySettingsExtension *keySettings() const; /** A reference to the tone settings. */ AnytoneToneSettingsExtension *toneSettings() const; /** A reference to the display settings. */ AnytoneDisplaySettingsExtension *displaySettings() const; /** A reference to the audio settings. */ AnytoneAudioSettingsExtension *audioSettings() const; /** A reference to the menu settings. */ AnytoneMenuSettingsExtension *menuSettings() const; /** A reference to the auto-repeater settings. */ AnytoneAutoRepeaterSettingsExtension *autoRepeaterSettings() const; /** A reference to the DMR settings. */ AnytoneDMRSettingsExtension *dmrSettings() const; /** A reference to the GPS settings. */ AnytoneGPSSettingsExtension *gpsSettings() const; /** A reference to the roaming settings. */ AnytoneRoamingSettingsExtension *roamingSettings() const; /** A reference to the bluetooth settings. */ AnytoneBluetoothSettingsExtension *bluetoothSettings() const; /** A reference to the simplex repeater settings. */ AnytoneRepeaterSettingsExtension *repeaterSettings() const; /** A reference to the satellite settings. */ AnytoneSatelliteSettingsExtension *satelliteSettings() const; /** Returns the VFO scan type. */ VFOScanType vfoScanType() const; /** Sets the VFO scan type. */ void setVFOScanType(VFOScanType type); /** Returns mode for VFO A. */ VFOMode modeA() const; /** Sets the mode for VFO A. */ void setModeA(VFOMode mode); /** Returns mode for VFO B. */ VFOMode modeB() const; /** Sets the mode for VFO B. */ void setModeB(VFOMode mode); /** Returns a reference to the current zone for VFO A. */ ZoneReference *zoneA(); /** Returns a reference to the current zone for VFO A. */ const ZoneReference *zoneA() const; /** Returns a reference to the current zone for VFO B. */ ZoneReference *zoneB(); /** Returns a reference to the current zone for VFO B. */ const ZoneReference *zoneB() const; /** Returns the selected VFO. */ VFO selectedVFO() const; /** Sets the selected VFO. */ void setSelectedVFO(VFO vfo); /** Returns @c true if the sub-channel is enabled. */ bool subChannelEnabled() const; /** Enables/disables the sub-channel. */ void enableSubChannel(bool enable); /** Returns the minimum VFO scan frequency for the UHF band in Hz. */ Frequency minVFOScanFrequencyUHF() const; /** Sets the minimum VFO scan frequency for the UHF band in Hz. */ void setMinVFOScanFrequencyUHF(Frequency hz); /** Returns the maximum VFO scan frequency for the UHF band in Hz. */ Frequency maxVFOScanFrequencyUHF() const; /** Sets the maximum VFO scan frequency for the UHF band in Hz. */ void setMaxVFOScanFrequencyUHF(Frequency hz); /** Returns the minimum VFO scan frequency for the VHF band in Hz. */ Frequency minVFOScanFrequencyVHF() const; /** Sets the minimum VFO scan frequency for the VHF band in Hz. */ void setMinVFOScanFrequencyVHF(Frequency hz); /** Returns the maximum VFO scan frequency for the VHF band in Hz. */ Frequency maxVFOScanFrequencyVHF() const; /** Sets the maximum VFO scan frequency for the VHF band in Hz. */ void setMaxVFOScanFrequencyVHF(Frequency hz); /** Returns @c true if the last caller is kept on channel switch. */ bool keepLastCallerEnabled() const; /** Enables/disables keeping the last caller on channel switch. */ void enableKeepLastCaller(bool enable); /** Returns the VFO tuning step in kHz. */ Frequency vfoStep() const; /** Sets the VFO tuning step in kHz. */ void setVFOStep(Frequency step); /** Returns the STE (squelch tail elimination) type. */ STEType steType() const; /** Sets the STE (squelch tail elimination) type. */ void setSTEType(STEType type); /** Returns the STE (squelch tail elimination) frequency in Hz. * A frequency of 0 disables the STE. Possible values are 55.2 and 259.2 Hz. */ double steFrequency() const; /** Sets the STE (squelch tail elimination) frequency in Hz. * A frequency of 0 disables the STE. Possible values are 55.2 and 259.2 Hz. */ void setSTEFrequency(double freq); /** Returns the STE duration in ms. */ Interval steDuration() const; /** Sets the STE duration. */ void setSTEDuration(Interval intv); /** Returns the TBST frequency in Hz. */ Frequency tbstFrequency() const; /** Sets the TBST frequency in Hz. Should be one of 1000, 1450, 1750 and 2100 Hz. */ void setTBSTFrequency(Frequency Hz); /** Returns @c true, if the "pro mode" is enabled. */ bool proModeEnabled() const; /** Enables/disables the "pro mode". */ void enableProMode(bool enable); /** Returns @c true if the call-channel is maintained. */ bool maintainCallChannelEnabled() const; /** Enables/disables maintaining the call-channel. */ void enableMaintainCallChannel(bool enable); /** Returns the fan control setting. */ FanControl fan() const; /** Sets the fan control. */ void setFan(FanControl ctlr); /** Returns power setting for satellite mode. */ //Channel::Power satPower(); protected: /** The boot settings. */ AnytoneBootSettingsExtension *_bootSettings; /** The power-save settings. */ AnytonePowerSaveSettingsExtension *_powerSaveSettings; /** The key settings. */ AnytoneKeySettingsExtension *_keySettings; /** The tone settings. */ AnytoneToneSettingsExtension *_toneSettings; /** The display settings. */ AnytoneDisplaySettingsExtension *_displaySettings; /** The audio settings. */ AnytoneAudioSettingsExtension *_audioSettings; /** The menu settings. */ AnytoneMenuSettingsExtension *_menuSettings; /** The auto-repeater settings. */ AnytoneAutoRepeaterSettingsExtension *_autoRepeaterSettings; /** The DMR settings. */ AnytoneDMRSettingsExtension *_dmrSettings; /** The GSP settings. */ AnytoneGPSSettingsExtension *_gpsSettings; /** The roaming settings. */ AnytoneRoamingSettingsExtension *_roamingSettings; /** The bluetooth settings. */ AnytoneBluetoothSettingsExtension *_bluetoothSettings; /** The repeater settings. */ AnytoneRepeaterSettingsExtension *_repeaterSettings; /** The satellite settings. */ AnytoneSatelliteSettingsExtension *_satelliteSettings; VFOScanType _vfoScanType; ///< The VFO scan-type property. VFOMode _modeA; ///< Mode of VFO A. VFOMode _modeB; ///< Mode of VFO B. ZoneReference _zoneA; ///< The current zone for VFO A. ZoneReference _zoneB; ///< The current zone for VFO B. VFO _selectedVFO; ///< The current VFO. bool _subChannel; ///< If @c true, the sub-channel is enabled. Frequency _minVFOScanFrequencyUHF; ///< The minimum UHF VFO-scan frequency in Hz. Frequency _maxVFOScanFrequencyUHF; ///< The maximum UHF VFO-scan frequency in Hz. Frequency _minVFOScanFrequencyVHF; ///< The minimum VHF VFO-scan frequency in Hz. Frequency _maxVFOScanFrequencyVHF; ///< The maximum VHF VFO-scan frequency in Hz. bool _keepLastCaller; ///< If @c true, the last caller is kept on channel switch. Frequency _vfoStep; ///< The VFO tuning step in kHz. STEType _steType; ///< The STE type. double _steFrequency; ///< STE frequency in Hz. Interval _steDuration; ///< STE duration Frequency _tbstFrequency; ///< The TBST frequency in Hz. bool _proMode; ///< The "pro mode" flag. bool _maintainCallChannel; ///< Maintains the call channel. FanControl _fan; }; #endif // ANYTONE_SETTINGSEXTENSION_H ================================================ FILE: lib/auctus_a6_interface.cc ================================================ #include "auctus_a6_interface.hh" #include #include #include #include "logger.hh" #define TIMEOUT 5000 AuctusA6Interface::AuctusA6Interface( const USBDeviceDescriptor &descriptor, const ErrorStack &err, QObject *parent) : USBSerial(descriptor, QSerialPort::Baud9600, err, parent), _state(CLOSED) { if (isOpen()) { _state = IDLE; } else { errMsg(err) << "Cannot construct Auctus A6 interface."; _state = ERROR; return; } if (! clear(QSerialPort::AllDirections)) { logWarn() << "Cannot clear RX/TX buffer of serial interface."; } logDebug() << "Open interface to Auctus A6 based radio."; } AuctusA6Interface::State AuctusA6Interface::state() const { return _state; } bool AuctusA6Interface::send_receive(uint16_t command, const uint8_t *params, uint8_t plen, uint8_t *response, uint8_t &rlen, const ErrorStack &err) { if (! send(command, params, plen, err)) { errMsg(err) << "Cannot send command."; return false; } uint16_t rcommand=0; if (! receive(rcommand, response, rlen, err)) { errMsg(err) << "Cannot receive response."; return false; } if ((rcommand&0x7fff) != command) { errMsg(err) << "Request and response commands mismatch. Expected " << QString::number(command, 16) << " got " << QString::number(command&0x7fff, 16) << "."; return false; } return true; } bool AuctusA6Interface::send(uint16_t command, const uint8_t *params, uint8_t plen, const ErrorStack &err) { // check parameter length if (plen>249) { errMsg(err) << "Parameter length cannot exceed 250 bytes."; errMsg(err) << "Cannot send request " << QString::number(command, 16) << "h."; return false; } // assemble request uint8_t buffer[255], *ptr=buffer; uint8_t total_length = 6 + plen; memset(buffer, 0, total_length); // start of packet *ptr = 0xaa; ptr += sizeof(uint8_t); // length *ptr = 6 + plen; ptr += sizeof(uint8_t); // total length // command (*(uint16_t *)(ptr)) = qToBigEndian(command); ptr += sizeof(uint16_t); // copy parameters memcpy(ptr, params, plen); ptr += plen; // compute CRC for (int i=0; i<(3+plen); i++) (*ptr) ^= buffer[1+i]; // Skip start-of-packet byte. ptr += sizeof(uint8_t); // end of packet *ptr = 0xbb; // send request logDebug() << "Send " << QByteArray((const char *)buffer, total_length).toHex(); if (total_length != QSerialPort::write((const char*)buffer, total_length)) { errMsg(err) << "QSerialPort: " << errorString(); errMsg(err) << "Cannot send request " << QString::number(command, 16) << "h."; return false; } if (! flush()) { logWarn() << "No data written to the device, " << bytesToWrite() << " of " << total_length << " left in buffer."; } return true; } bool AuctusA6Interface::receive(uint16_t &command, uint8_t *response, uint8_t &rlen, const ErrorStack &err) { uint8_t buffer[255]; // read start-of-packet and length bytes uint8_t total_length = 2; if (! read(buffer, total_length, TIMEOUT, err)) { errMsg(err) << "Cannot read response " << QString::number(command, 16) << "h."; return false; } // check start of packet if (0xaa != buffer[0]) { errMsg(err) << "Unexpected start-of-packet byte: Expected aah, got " << QString::number(buffer[0],16) << "h."; return false; } // get length total_length = buffer[1]; if (6 > total_length) { errMsg(err) << "Invalid packet: Expected minimum length of 6h, got " << QString::number(total_length, 16) << "h."; return false; } // read remaining packet if (! read(buffer+2, total_length-2, TIMEOUT, err)) { errMsg(err) << "Cannot read response " << QString::number(command, 16) << "h."; return false; } logDebug() << "Got response " << QByteArray((const char *)buffer, total_length).toHex() << "."; // unpack command command = qFromBigEndian((*(uint16_t *)(buffer+2))); // check CRC uint8_t crc = 0; for (int i=1; i<(total_length-1); i++) crc ^= buffer[i]; if (crc) { errMsg(err) << "Invalid response: Invalid CRC."; return false; } // Check if response buffer is sufficiently large if (rlen < (total_length-6)) { errMsg(err) << "Cannot store response in response buffer, buffer too small. " << "Got " << rlen << "bytes, needs " << (total_length-6) << "bytes."; return false; } // copy response payload rlen = total_length-6; memcpy(response, buffer+4, rlen); // done. return true; } bool AuctusA6Interface::read(uint8_t *data, qint64 n, unsigned int timeout_ms, const ErrorStack &err) { while (n > 0) { if (0 == bytesAvailable()) { if (! waitForReadyRead(timeout_ms)) { errMsg(err) << "QSerialPort: " << errorString(); return false; } } qint64 k = QSerialPort::read((char *)data, std::min(n, bytesAvailable())); if (0 > k) { errMsg(err) << "QSerialPort: " << errorString(); return false; } n -= k; data += k; } return true; } ================================================ FILE: lib/auctus_a6_interface.hh ================================================ /** @defgroup auctus Auctus A6/A7 Based devices. * * Several devices use the radio-on-a-chip Auctus A6, which enables very cheap DMR radios. * * @ingroup dsc */ #ifndef AUCTUS_A6_INTERFACE_HH #define AUCTUS_A6_INTERFACE_HH #include #include "usbserial.hh" /** Implements the communication interface to radios using the Auctus A6 chip. * * This includes devices like Cotre A1 or the Baofeng DR-1801. This class only implements * the basic communication protocol. Specifics, like particular commands are implemented in * derived interface classes. * * @ingroup auctus */ class AuctusA6Interface : public USBSerial { Q_OBJECT public: /** Possible states of the interface. */ enum State { CLOSED, ///< Connection to device is closed. IDLE, ///< Connection is open and device is ready. READ_THROUGH, ///< Read from memory. The device just sends some amount of data. WRITE_THROUGH, ///< Write to memory. The device just receives some amount of data. ERROR ///< An error state. }; protected: /** Hidden constructor. */ explicit AuctusA6Interface(const USBDeviceDescriptor &descriptor, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); public: /** Returns the interface state. */ State state() const; protected: /** Internal used method to send messages to and receive responses from radio. */ bool send_receive(uint16_t command, const uint8_t *params, uint8_t plen, uint8_t *response, uint8_t &rlen, const ErrorStack &err=ErrorStack()); /** Internal used method to send a command. */ bool send(uint16_t command, const uint8_t *params, uint8_t plen, const ErrorStack &err=ErrorStack()); /** Internal used method to receive a response. */ bool receive(uint16_t &command, uint8_t *response, uint8_t &rlen, const ErrorStack &err=ErrorStack()); /** Reads exactly @c n bytes or timeouts. */ bool read(uint8_t *data, qint64 n, unsigned int timeout_ms, const ErrorStack &err=ErrorStack()); protected: /** Holds the state of the interface. */ State _state; }; #endif // AUCTUS_A6_INTERFACE_HH ================================================ FILE: lib/audiosettings.cc ================================================ #include "audiosettings.hh" AudioSettings::AudioSettings(QObject *parent) : ConfigItem(parent), _squelch(Level::fromValue(3)), _dmrSquelch(Level::invalid()), _micGain(Level::fromValue(5)), _fmMicGain(Level::invalid()), _m17MicGain(Level::invalid()), _maxSpeakerVolume(Level::fromValue(10)), _maxHeadphoneVolume(Level::fromValue(10)), _vox(Level::null()), _voxDelay(Interval::null()), _speech(false) { // pass... } ConfigItem * AudioSettings::clone() const { auto obj = new AudioSettings(); if (! obj->copy(*this)) { delete obj; return nullptr; } return obj; } Level AudioSettings::squelch() const { return _squelch; } void AudioSettings::setSquelch(Level squelch) { if (squelch.isInvalid()) squelch = Level::fromValue(3); // squelch = 0 -> open squelch if (_squelch == squelch) return; _squelch = squelch; emit modified(this); } bool AudioSettings::dmrSquelchEnabled() const { return ! _dmrSquelch.isInvalid(); } Level AudioSettings::dmrSquelch() const { return _dmrSquelch; } void AudioSettings::setDMRSquelch(Level dmrSquelch) { if (_dmrSquelch == dmrSquelch) return; _dmrSquelch = dmrSquelch; emit modified(this); } void AudioSettings::disableDMRSquelch() { setDMRSquelch(Level::invalid()); } Level AudioSettings::micGain() const { return _micGain; } void AudioSettings::setMicGain(Level micGain) { if (! micGain.isFinite()) micGain = Level::fromValue(1); if (_micGain == micGain) return; _micGain = micGain; emit modified(this); } bool AudioSettings::fmMicGainEnabled() const { return _fmMicGain.isFinite(); } Level AudioSettings::fmMicGain() const { return _fmMicGain; } void AudioSettings::setFMMicGain(Level fmMicGain) { if (_fmMicGain == fmMicGain) return; _fmMicGain = fmMicGain; emit modified(this); } void AudioSettings::disableFMMicGain() { setFMMicGain(Level::invalid()); } bool AudioSettings::m17MicGainEnabled() const { return _m17MicGain.isFinite(); } Level AudioSettings::m17MicGain() const { return _m17MicGain; } void AudioSettings::setM17MicGain(Level m17MicGain) { if (_m17MicGain == m17MicGain) return; _m17MicGain = m17MicGain; emit modified(this); } void AudioSettings::disableM17MicGain() { setM17MicGain(Level::invalid()); } bool AudioSettings::voxEnabled() const { return _vox.isFinite(); } Level AudioSettings::vox() const { return _vox; } void AudioSettings::setVox(Level vox) { if (vox.isInvalid()) vox = Level::null(); if (_vox == vox) return; _vox = vox; emit modified(this); } void AudioSettings::disableVox() { setVox(Level::null()); } Level AudioSettings::maxSpeakerVolume() const { return _maxSpeakerVolume; } void AudioSettings::setMaxSpeakerVolume(Level maxSpeakerVolume) { if (_maxSpeakerVolume == maxSpeakerVolume) return; _maxSpeakerVolume = maxSpeakerVolume; emit modified(this); } Level AudioSettings::maxHeadphoneVolume() const { return _maxHeadphoneVolume; } void AudioSettings::setMaxHeadphoneVolume(Level maxHeadphoneVolume) { if (_maxHeadphoneVolume == maxHeadphoneVolume) return; _maxHeadphoneVolume = maxHeadphoneVolume; emit modified(this); } Interval AudioSettings::voxDelay() const { return _voxDelay; } void AudioSettings::setVOXDelay(Interval voxDelay) { if (_voxDelay == voxDelay) return; _voxDelay = voxDelay; emit modified(this); } bool AudioSettings::speechSynthesisEnabled() const { return _speech; } void AudioSettings::enableSpeechSynthesis(bool speechSynthesis) { if (_speech == speechSynthesis) return; _speech = speechSynthesis; emit modified(this); } ================================================ FILE: lib/audiosettings.hh ================================================ #ifndef AUDIOSETTINGS_HH #define AUDIOSETTINGS_HH #include "configobject.hh" #include "level.hh" #include "interval.hh" /** Collects common audio and tone settings */ class AudioSettings: public ConfigItem { Q_OBJECT Q_PROPERTY(Level squelch READ squelch WRITE setSquelch) Q_PROPERTY(Level dmrSquelch READ dmrSquelch WRITE setDMRSquelch) Q_PROPERTY(Level micGain READ micGain WRITE setMicGain) Q_PROPERTY(Level fmMicGain READ fmMicGain WRITE setFMMicGain) Q_PROPERTY(Level m17MicGain READ m17MicGain WRITE setM17MicGain) Q_PROPERTY(Level maxSpeakerVolume READ maxSpeakerVolume WRITE setMaxSpeakerVolume) Q_PROPERTY(Level maxHeadphoneVolume READ maxHeadphoneVolume WRITE setMaxHeadphoneVolume) Q_PROPERTY(Level vox READ vox WRITE setVox) Q_PROPERTY(Interval voxDelay READ voxDelay WRITE setVOXDelay) Q_PROPERTY(bool speech READ speechSynthesisEnabled WRITE enableSpeechSynthesis) public: /** Default constructor. */ explicit AudioSettings(QObject *parent = nullptr); ConfigItem *clone() const override; /** Returns the default (FM) squelch. */ Level squelch() const; /** Sets the default squelch. */ void setSquelch(Level squelch); /** Returns @c true if the DMR squelch is set. */ bool dmrSquelchEnabled() const; /** Returns the DMR squelch. */ Level dmrSquelch() const; /** Set the DMR squelch. */ void setDMRSquelch(Level dmrSquelch); /** Disables the DMR squelch setting. The global FM squelch is then used. */ void disableDMRSquelch(); /** Returns the default mic gain. */ Level micGain() const; /** Sets the default mic gain. */ void setMicGain(Level micGain); /** Returns @c true if the FM mic gain is enabled. */ bool fmMicGainEnabled() const; /** Returns the FM mic gain. If null, the dmr mic gain is used. */ Level fmMicGain() const; /** Sets the FM mic gain. If set to null, the global DMR mic gain is used. */ void setFMMicGain(Level fmMicGain); /** Disables FM mic gain. */ void disableFMMicGain(); /** Returns @c true if the M17 mic gain is enabled. */ bool m17MicGainEnabled() const; /** Returns the M17 mic gain. If null, the dmr mic gain is used. */ Level m17MicGain() const; /** Sets the M17 mic gain. If set to null, the global DMR mic gain is used. */ void setM17MicGain(Level m17MicGain); /** Disables M17 mic gain. */ void disableM17MicGain(); /** Returns @c true, if the VOX is enabled. */ bool voxEnabled() const; /** Returns the VOX sensitivity. */ Level vox() const; /** Sets the VOX sensitivity. */ void setVox(Level vox); /** Disables VOX. */ void disableVox(); /** Returns the VOX delay. */ Interval voxDelay() const; /** Sets the VOX delay. */ void setVOXDelay(Interval ms); /** Returns the maximum speaker volume. */ Level maxSpeakerVolume() const; /** Sets the maximum speaker volume. */ void setMaxSpeakerVolume(Level maxSpeakerVolume); /** Returns the maximum headphone volume. */ Level maxHeadphoneVolume() const; /** Sets the maximum headphone volume. */ void setMaxHeadphoneVolume(Level maxHeadphoneVolume); /** Returns @c true if the speech synthesis is enabled. */ bool speechSynthesisEnabled() const; /** Enable speech synthesis. */ void enableSpeechSynthesis(bool enabled); protected: /** Default (FM) squelch. */ Level _squelch; /** DMR squelch setting. */ Level _dmrSquelch; /** Default mic gain. */ Level _micGain; /** The FM mic gain. */ Level _fmMicGain; /** The M17 mic gain. */ Level _m17MicGain; /** Maximum speaker volume. */ Level _maxSpeakerVolume; /** Maximum headphone volume. */ Level _maxHeadphoneVolume; /** Specifies the VOX level. */ Level _vox; /** Specifies the VOX delay. */ Interval _voxDelay; /** Enables speech synthesis. */ bool _speech; }; #endif //QDMR_AUDIOSETTINGS_HH ================================================ FILE: lib/bootsettings.cc ================================================ #include "bootsettings.hh" BootSettings::BootSettings(QObject *parent) : ConfigExtension{parent}, _bootDisplay(BootDisplay::Logo), _message1(), _message2(), _bootPasswordEnabled(false), _bootPassword(), _defaultChannel(false), _zoneA(new ZoneReference(this)), _channelA(new ChannelReference(this)), _zoneB(new ZoneReference(this)), _channelB(new ChannelReference(this)), _reset(true) { // pass... } ConfigItem * BootSettings::clone() const { auto clone = new BootSettings(); if (! clone->copy(*this)) { delete clone; return nullptr; } return clone; } BootSettings::BootDisplay BootSettings::bootDisplay() const { return _bootDisplay; } void BootSettings::setBootDisplay(BootDisplay mode) { if (_bootDisplay == mode) return; _bootDisplay = mode; emit modified(this); } const QString & BootSettings::message1() const { return _message1; } void BootSettings::setMessage1(const QString &message1) { if (_message1 == message1) return; _message1 = message1; emit modified(this); } const QString & BootSettings::message2() const { return _message2; } void BootSettings::setMessage2(const QString &message2) { if (_message2 == message2) return; _message2 = message2; emit modified(this); } bool BootSettings::bootPasswordEnabled() const { return _bootPasswordEnabled; } void BootSettings::enableBootPassword(bool enable) { if (_bootPasswordEnabled == enable) return; _bootPasswordEnabled = enable; emit modified(this); } const QString & BootSettings::bootPassword() const { return _bootPassword; } void BootSettings::setBootPassword(const QString &pass) { if (_bootPassword == pass) return; _bootPassword = pass; emit modified(this); } bool BootSettings::defaultChannelEnabled() const { return _defaultChannel; } void BootSettings::enableDefaultChannel(bool enable) { if (_defaultChannel == enable) return; _defaultChannel = enable; emit modified(this); } ZoneReference * BootSettings::zoneA() const { return _zoneA; } ChannelReference * BootSettings::channelA() const { return _channelA; } ZoneReference * BootSettings::zoneB() const { return _zoneB; } ChannelReference * BootSettings::channelB() const { return _channelB; } bool BootSettings::resetEnabled() const { return _reset; } void BootSettings::enableReset(bool enable) { if (_reset == enable) return; _reset = enable; emit modified(this); } ================================================ FILE: lib/bootsettings.hh ================================================ // // Created by hannes on 02.04.26. // #ifndef BOOTSETTINGS_HH #define BOOTSETTINGS_HH #include "configobject.hh" #include "configreference.hh" /** Collects all common boot settings across devices. * @ingroup conf */ class BootSettings: public ConfigExtension { Q_OBJECT /** The boot display setting. */ Q_PROPERTY(BootDisplay display READ bootDisplay WRITE setBootDisplay) /** The first boot message line. */ Q_PROPERTY(QString message1 READ message1 WRITE setMessage1) /** The second boot message line. */ Q_PROPERTY(QString message2 READ message2 WRITE setMessage2) /** If @c true, the boot password is enabled. */ Q_PROPERTY(bool passwordEnabled READ bootPasswordEnabled WRITE enableBootPassword) /** Holds the boot password. */ Q_PROPERTY(QString password READ bootPassword WRITE setBootPassword) Q_CLASSINFO("defaultChannelDescription", "If enabled, the default channels are selected at boot.") /** If enabled, the default channels are selected at boot. */ Q_PROPERTY(bool defaultChannel READ defaultChannelEnabled WRITE enableDefaultChannel) Q_CLASSINFO("zoneADescription", "The default zone for VFO A.") /** The default zone for VFO A. */ Q_PROPERTY(ZoneReference* zoneA READ zoneA) Q_CLASSINFO("channelADescription", "The default channel for VFO A. Must be within zone A.") /** The default channel for VFO A. */ Q_PROPERTY(ChannelReference* channelA READ channelA) Q_CLASSINFO("zoneBDescription", "The default zone for VFO B.") /** The current zone for VFO B. */ Q_PROPERTY(ZoneReference* zoneB READ zoneB) Q_CLASSINFO("channelBDescription", "The default channel for VFO B. Must be within zone B.") /** The default channel for VFO B. */ Q_PROPERTY(ChannelReference* channelB READ channelB) /** Allows a factory reset during boot. Sometimes needed to enter FW programming mode. */ Q_PROPERTY(bool allowFactoryReset READ resetEnabled WRITE enableReset) public: /** What to display during boot. */ enum class BootDisplay { Logo, ///< Default or build in logo. Text, ///< Custom text. Image ///< Custom image. }; Q_ENUM(BootDisplay) public: /** Default constructor. */ explicit BootSettings(QObject *parent=nullptr); ConfigItem *clone() const override; /** Returns the boot display setting. */ BootDisplay bootDisplay() const; /** Sets the boot display. */ void setBootDisplay(BootDisplay mode); /** Returns the first boot message line. */ const QString &message1() const; /** Sets the first boot message line. */ void setMessage1(const QString &value); /** Returns the second boot message line. */ const QString &message2() const; /** Sets the second boot message line. */ void setMessage2(const QString &value); /** Returns @c true if the boot password is enabled.*/ bool bootPasswordEnabled() const; /** Enables the boot password. */ void enableBootPassword(bool enable); /** Returns the boot password. */ const QString &bootPassword() const; /** Sets the boot password. */ void setBootPassword(const QString &pass); /** If @c true, the radio switches to the default channel at boot. */ bool defaultChannelEnabled() const; /** Enables/disables boot default channel. */ void enableDefaultChannel(bool enable); /** Returns a reference to the default zone for VFO A. */ ZoneReference *zoneA() const; /** Returns a reference to the default channel for VFO A. */ ChannelReference *channelA() const; /** Returns a reference to the default zone for VFO B. */ ZoneReference *zoneB() const; /** Returns a reference to the default channel for VFO B. */ ChannelReference *channelB() const; /** Returns @c true if the MCU is reset on boot. */ bool resetEnabled() const; /** Enables/disables MCU reset on boot. */ void enableReset(bool enable); protected: BootDisplay _bootDisplay; ///< The boot display property. QString _message1, _message2; ///< The two boot messages. bool _bootPasswordEnabled; ///< If true, the boot password is enabled. QString _bootPassword; ///< The boot password bool _defaultChannel; ///< Change to the default channel on boot. ZoneReference *_zoneA; ///< Default zone for VFO A. ChannelReference *_channelA; ///< Default channel for VFO A, must be member of zone for VFO A. ZoneReference *_zoneB; ///< Default zone for VFO B. ChannelReference *_channelB; ///< Default channel for VFO B, must be member of zone for VFO B. bool _reset; ///< Enables MCU reset on boot. }; #endif //BOOTSETTINGS_HH ================================================ FILE: lib/c7000device.cc ================================================ #include "c7000device.hh" #include "logger.hh" #include #include #define C7000_VID 0x1206 #define C7000_PID 0x0227 /* ********************************************************************************************* * * Implementation of C7000Device::Packet * ********************************************************************************************* */ C7000Device::Packet::Packet() : _encoded() { // pass... } C7000Device::Packet::Packet(uint8_t command, uint8_t sub, uint8_t flags, const QByteArray &payload) { _encoded.resize(9 + payload.size()); _encoded.fill(0); _encoded[0] = 0x68; _encoded[1] = flags; _encoded[2] = command; _encoded[3] = sub; *((uint16_t *)(_encoded.data()+6)) = qToLittleEndian((uint16_t)payload.size()); memcpy(_encoded.data()+8,payload.constData(), payload.size()); _encoded[8+payload.size()] = 0x10; uint32_t crc = 0xffff; for (int i=0; i<(_encoded.size()/2); i++) { uint16_t v = qFromLittleEndian(*(uint16_t *)(_encoded.constData()+2*i)); if (crc < v) crc += 0xffff; crc -= v; } if (_encoded.size()%2) { uint16_t v = _encoded[_encoded.size()-1]; if (crc < v) crc += 0xffff; crc -= v; } *((uint16_t *)(_encoded.data()+4)) = qToLittleEndian((uint16_t)crc); } C7000Device::Packet::Packet(const QByteArray &buffer) : _encoded() { _encoded.append(buffer); if (! isValid()) _encoded.clear(); } bool C7000Device::Packet::isValid() const { if (_encoded.size() < 9) return false; if (0x68 != _encoded[0]) return false; if (0x10 != _encoded[8+payloadSize()]) return false; int size = _encoded.size(); //9 + payloadSize(); uint32_t crc = 0xffff; for (int i=0; i (num = libusb_get_device_list(_ctx, &lst))) { errMsg(err) << "Cannot obtain list of USB devices."; libusb_exit(_ctx); _ctx = nullptr; return; } logDebug() << "Try to detect USB C7000 interface " << descr.description() << "."; USBDeviceHandle addr = descr.device().value(); for (int i=0; (i libusb_get_device_descriptor(lst[i],&usb_descr)) continue; if (descr.vendorId() != usb_descr.idVendor) continue; if (descr.productId() != usb_descr.idProduct) continue; logDebug() << "Matching device found at bus " << addr.bus << ", device " << addr.device << " with vendor ID " << QString::number(usb_descr.idVendor, 16) << " and product ID " << QString::number(usb_descr.idProduct, 16) << "."; libusb_ref_device(lst[i]); dev = lst[i]; } // Unref all devices and free list, matching device was referenced earlier libusb_free_device_list(lst, 1); if (nullptr == dev) { errMsg(err) << "No matching device found: " << descr.description() << "."; libusb_exit(_ctx); _ctx = nullptr; return; } if (0 > (error = libusb_open(dev, &_dev))) { errMsg(err) << "Cannot open device " << descr.description() << ": " << libusb_strerror((enum libusb_error) error) << "."; libusb_unref_device(dev); libusb_exit(_ctx); _ctx = nullptr; return; } if (libusb_kernel_driver_active(_dev, 0) && libusb_detach_kernel_driver(_dev, 0)) { errMsg(err) << "Cannot detach kernel driver for device " << descr.description() << ". Interface claim will likely fail."; } if (0 > (error = libusb_claim_interface(_dev, 0))) { errMsg(err) << "Failed to claim USB interface " << descr.description() << ": " << libusb_strerror((enum libusb_error) error) << "."; libusb_close(_dev); _dev = nullptr; libusb_exit(_ctx); _ctx = nullptr; return; } logDebug() << "Connected to C7000 device " << descr.description() << "."; } C7000Device::~C7000Device() { close(); } USBDeviceInfo C7000Device::interfaceInfo() { return USBDeviceInfo(USBDeviceInfo::Class::C7K, C7000_VID, C7000_PID); } QList C7000Device::detect(bool saveOnly) { Q_UNUSED(saveOnly) QList res; int error, num; libusb_context *ctx; if (0 > (error = libusb_init(&ctx))) { logError() << "Libusb init failed (" << error << "): " << libusb_strerror((enum libusb_error) error) << "."; return res; } libusb_device **lst; if (0 == (num = libusb_get_device_list(ctx, &lst))) { logDebug() << "No USB devices found at all."; // unref devices and free list libusb_free_device_list(lst, 1); return res; } logDebug() << "Search for C7000 devices matching VID:PID " << QString::number(C7000_VID, 16) << ":" << QString::number(C7000_PID, 16) << "."; for (int i=0; (iusleep(1000); unsigned int retry_count = 0; retry_receive: ret = libusb_bulk_transfer(_dev, 0x81, buffer, 64, &bytes_received, 1000); if (ret) { errMsg(err) << "Cannot receive response from device: " << libusb_error_name(ret) << "."; return false; } if (0x68 != buffer[0]) { if ((++retry_count) > 10) { errMsg(err) << "Cannot receive response from device: Retry count of 10 exceeded."; return false; } goto retry_receive; } response = Packet(QByteArray((const char *)buffer, bytes_received)); if (! response.isValid()) { errMsg(err) << "Invalid response received."; return false; } return true; } ================================================ FILE: lib/c7000device.hh ================================================ #ifndef C7000DEVICE_HH #define C7000DEVICE_HH #include #include #include "errorstack.hh" #include "radiointerface.hh" /** Base class for all C7000 based radios. This class implements the basic communication protocol * to these devices. * @ingroup rif */ class C7000Device : public QObject { Q_OBJECT public: /** Request/response packet. */ struct Packet { public: /** Default constructor. */ Packet(); /** Copy constructor. */ Packet(const Packet &other) = default ; /** Constructs a request/response from commands and payload. */ Packet(uint8_t command, uint8_t sub, uint8_t flags=0x0f, const QByteArray &payload=QByteArray()); /** Constructs a request/response from the given encoded packet. */ Packet(const QByteArray &buffer); /** Assignment. */ Packet &operator =(const Packet &other) = default; /** Returns @c true, if the packet is valid. */ bool isValid() const; /** Returns the flags field. */ uint8_t flags() const; /** Returns the command field. */ uint8_t command() const; /** Returns the sub-command field. */ uint8_t subcommand() const; /** Returns the payload size. */ uint16_t payloadSize() const; /** Returns the payload field. */ QByteArray payload() const; /** Returns the encoded packet. */ const QByteArray &encoded() const; protected: /** Holds the encoded packet. */ QByteArray _encoded; }; public: /** Specialization to address a DFU device uniquely. */ class Descriptor: public USBDeviceDescriptor { public: /** Constructor from interface info, bus number and device address. */ Descriptor(const USBDeviceInfo &info, uint8_t bus, uint8_t device); }; public: /** Opens a connection to the C7000 device. */ C7000Device(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Destructor. */ virtual ~C7000Device(); /** Returns @c true if the C7000 interface is open. */ bool isOpen() const; /** Closes the C7000 interface. */ void close(); public: /** Returns some information about the interface. */ static USBDeviceInfo interfaceInfo(); /** Finds all C7000 interfaces. */ static QList detect(bool saveOnly=true); protected: /** Sends the given request to the device and receives the response. */ bool sendRecv(const Packet &request, Packet &response, const ErrorStack &err=ErrorStack()); protected: /** USB context. */ libusb_context *_ctx; /** USB device object. */ libusb_device_handle *_dev; }; #endif // C7000DEVICE_HH ================================================ FILE: lib/callsigndb.cc ================================================ #include "callsigndb.hh" /* ********************************************************************************************* * * Implementation of CallsignDB::Flags * ********************************************************************************************* */ CallsignDB::Flags::Flags(int64_t count) : TransferFlags(), _count(count) { // pass... } CallsignDB::Flags::Flags(const Flags &other) : TransferFlags(other), _count(other._count) { // pass... } bool CallsignDB::Flags::hasCountLimit() const { return (0 <= _count); } size_t CallsignDB::Flags::countLimit() const { if (0 > _count) return std::numeric_limits::max(); return _count; } void CallsignDB::Flags::setCountLimit(size_t n) { _count = n; } void CallsignDB::Flags::clearCountLimit() { _count = -1; } /* ********************************************************************************************* * * Implementation of CallsignDB * ********************************************************************************************* */ CallsignDB::CallsignDB(QObject *parent) : DFUFile(parent) { // pass... } CallsignDB::~CallsignDB() { // pass... } ================================================ FILE: lib/callsigndb.hh ================================================ #ifndef CALLSIGNDB_HH #define CALLSIGNDB_HH #include "dfufile.hh" #include "transferflags.hh" // Forward decl. class UserDatabase; /** Abstract base class of all callsign database implementations. * This class defines the interface for all device-specific binary encodings of call sign * databases. The interface is particularly simple: reimplement the @c encode method. * @ingroup conf */ class CallsignDB : public DFUFile { Q_OBJECT public: /** Controls the selection of callsigns from the @c UserDatabase to be encoded into the * callsign db. */ class Flags: public TransferFlags { public: /** Constructor. */ Flags(int64_t count=-1); /** Copy constructor. */ Flags(const Flags &other); /** Returns @c true if the selection has a limit on the number of callsigns to encode. */ bool hasCountLimit() const; /** Returns the limit of callsigns to encode. */ size_t countLimit() const; /** Sets the count limit. */ void setCountLimit(size_t n); /** Clears the count limit. */ void clearCountLimit(); protected: /** Specifies the maximum amount of callsigns to add. If negative, the device limit should be * used. */ int64_t _count; }; protected: /** Hidden constructor. */ explicit CallsignDB(QObject *parent=nullptr); public: /** Destructor. */ virtual ~CallsignDB(); /** Encodes the given user db into the device specific callsign db. */ virtual bool encode(UserDatabase *db, const Flags &selection=Flags(), const ErrorStack &err=ErrorStack()) = 0; }; #endif // CALLSIGNDB_HH ================================================ FILE: lib/channel.cc ================================================ #include "channel.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "scanlist.hh" #include "logger.hh" #include "radioid.hh" #include "gpssystem.hh" #include "roamingzone.hh" #include #include #include #include #include "opengd77_extension.hh" /* ********************************************************************************************* * * Implementation of Channel * ********************************************************************************************* */ Channel::Channel(QObject *parent) : ConfigObject(parent), _rxFreq(Frequency::fromHz(0)), _txFreq(Frequency::fromHz(0)), _defaultPower(true), _power(Power::Low), _txTimeOut(Interval::null()), _rxOnly(false), _vox(), _scanlist(), _openGD77ChannelExtension(nullptr), _tytChannelExtension(nullptr) { Context::setTag(staticMetaObject.className(), "timeout", "!default", QVariant::fromValue(Interval::null())); Context::setTag(staticMetaObject.className(), "vox", "!default", QVariant::fromValue(Level::invalid())); // Link scan list modification event (e.g., scan list gets deleted). connect(&_scanlist, SIGNAL(modified()), this, SLOT(onReferenceModified())); } Channel::Channel(const Channel &other, QObject *parent) : ConfigObject(parent), _scanlist(), _openGD77ChannelExtension(nullptr), _tytChannelExtension(nullptr) { Channel::copy(other); // Link scan list modification event (e.g., scan list gets deleted). connect(&_scanlist, SIGNAL(modified()), this, SLOT(onReferenceModified())); } bool Channel::copy(const ConfigItem &other) { const Channel *c = other.as(); if ((nullptr == c) || (! ConfigObject::copy(other))) return false; if (c->defaultPower()) setDefaultPower(); if (c->defaultTimeout()) setDefaultTimeout(); if (c->defaultVOX()) setVOXDefault(); return true; } void Channel::clear() { ConfigObject::clear(); setRXFrequency(Frequency::fromHz(0)); setTXFrequency(Frequency::fromHz(0)); setDefaultPower(); setDefaultTimeout(); setRXOnly(false); setVOXDefault(); _scanlist.clear(); setOpenGD77ChannelExtension(nullptr); setTyTChannelExtension(nullptr); } Frequency Channel::rxFrequency() const { return _rxFreq; } bool Channel::setRXFrequency(Frequency freq) { if (freq == _rxFreq) return true; _rxFreq = freq; emit modified(this); return true; } Frequency Channel::txFrequency() const { return _txFreq; } bool Channel::setTXFrequency(Frequency freq) { if (freq == _txFreq) return true; _txFreq = freq; emit modified(this); return true; } FrequencyOffset Channel::offsetFrequency() const { return _txFreq - _rxFreq; } Channel::OffsetShift Channel::offsetShift() const { if (_rxFreq > _txFreq) { return OffsetShift::Negative; } else if (_txFreq > _rxFreq) { return OffsetShift::Positive; } else { return OffsetShift::None; } } bool Channel::defaultPower() const { return _defaultPower; } Channel::Power Channel::power() const { return _power; } void Channel::setPower(Power power) { if ((power == _power) && (! _defaultPower)) return; _power = power; _defaultPower = false; emit modified(this); } void Channel::setDefaultPower() { if (defaultPower()) return; _defaultPower = true; emit modified(this); } bool Channel::defaultTimeout() const { return timeout().isNull(); } bool Channel::timeoutDisabled() const { return timeout().isInfinite(); } Interval Channel::timeout() const { return _txTimeOut; } bool Channel::setTimeout(const Interval& dur) { if (dur == _txTimeOut) return true; _txTimeOut = dur; emit modified(this); return true; } void Channel::setDefaultTimeout() { if (defaultTimeout()) return; setTimeout(Interval::null()); emit modified(this); } void Channel::disableTimeout() { setTimeout(Interval::infinity()); } bool Channel::rxOnly() const { return _rxOnly; } bool Channel::setRXOnly(bool enable) { _rxOnly = enable; emit modified(this); return true; } bool Channel::voxDisabled() const { return _vox.isNull(); } bool Channel::defaultVOX() const { return _vox.isInvalid(); } Level Channel::vox() const { return _vox; } void Channel::setVOX(Level level) { if (_vox == level) return; _vox = level; emit modified(this); } void Channel::setVOXDefault() { setVOX(Level::invalid()); } void Channel::disableVOX() { setVOX(Level::null()); } const ScanListReference * Channel::scanListRef() const { return &_scanlist; } ScanListReference * Channel::scanListRef() { return &_scanlist; } ScanList * Channel::scanList() const { return _scanlist.as(); } bool Channel::setScanList(ScanList *list) { return _scanlist.set(list); } void Channel::onReferenceModified() { emit modified(this); } OpenGD77ChannelExtension * Channel::openGD77ChannelExtension() const { return _openGD77ChannelExtension; } void Channel::setOpenGD77ChannelExtension(OpenGD77ChannelExtension *ext) { if (_openGD77ChannelExtension == ext) return; if (_openGD77ChannelExtension) _openGD77ChannelExtension->deleteLater(); _openGD77ChannelExtension = ext; if (_openGD77ChannelExtension) { _openGD77ChannelExtension->setParent(this); connect(_openGD77ChannelExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified())); } } TyTChannelExtension * Channel::tytChannelExtension() const { return _tytChannelExtension; } void Channel::setTyTChannelExtension(TyTChannelExtension *ext) { if (_tytChannelExtension == ext) return; if (_tytChannelExtension) _tytChannelExtension->deleteLater(); _tytChannelExtension = ext; if (_tytChannelExtension) { _tytChannelExtension->setParent(this); connect(_tytChannelExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified())); } } bool Channel::populate(YAML::Node &node, const Context &context, const ErrorStack &err) { if (! ConfigObject::populate(node, context, err)) return false; if (defaultPower()) { YAML::Node def = YAML::Node(YAML::NodeType::Scalar); def.SetTag("!default"); node["power"] = def; } else { QMetaEnum metaEnum = QMetaEnum::fromType(); node["power"] = metaEnum.valueToKey((unsigned)power()); } return true; } bool Channel::parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) { errMsg(err) << "YAML node is null!"; return false; } if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse channel: Expected object with one child."; return false; } YAML::Node ch = node.begin()->second; if ((!ch["power"]) || ("!default" == ch["power"].Tag())) { setDefaultPower(); } else if (ch["power"] && ch["power"].IsScalar()) { QMetaEnum meta = QMetaEnum::fromType(); setPower((Channel::Power)meta.keyToValue(ch["power"].as().c_str())); } return ConfigObject::parse(ch, ctx, err); } bool Channel::link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot link channel: Expected object with one child."; return false; } return ConfigObject::link(node.begin()->second, ctx, err); } /* ********************************************************************************************* * * Implementation of AnalogChannel * ********************************************************************************************* */ AnalogChannel::AnalogChannel(QObject *parent) : Channel(parent), _squelch(Level::invalid()) { Context::setTag(staticMetaObject.className(), "squelch", "!default", QVariant::fromValue(Level::invalid())); } AnalogChannel::AnalogChannel(const AnalogChannel &other, QObject *parent) : Channel(other, parent), _squelch(Level::invalid()) { Context::setTag(staticMetaObject.className(), "squelch", "!default", QVariant::fromValue(Level::invalid())); } bool AnalogChannel::defaultSquelch() const { return _squelch.isInvalid(); } bool AnalogChannel::squelchDisabled() const { return _squelch.isNull(); } Level AnalogChannel::squelch() const { return _squelch; } bool AnalogChannel::setSquelch(Level level) { if (_squelch == level) return true; _squelch = level; emit modified(this); return true; } void AnalogChannel::disableSquelch() { setSquelch(Level::null()); } void AnalogChannel::setSquelchDefault() { setSquelch(Level::invalid()); } /* ********************************************************************************************* * * Implementation of FMChannel * ********************************************************************************************* */ FMChannel::FMChannel(QObject *parent) : AnalogChannel(parent), _admit(Admit::Always), _rxTone(), _txTone(), _bw(Bandwidth::Narrow), _aprsSystem(), _extended(new FMChannelExtension(this)), _anytoneExtension(nullptr) { // Link APRS system reference connect(&_aprsSystem, &FMAPRSSystemReference::modified, this, &FMChannel::onReferenceModified); // Link extended settings connect(_extended, &FMChannelExtension::modified, this, &FMChannel::modified); } bool FMChannel::copy(const ConfigItem &other) { const FMChannel *c = other.as(); if ((nullptr==c) || (! AnalogChannel::copy(other))) return false; setRXTone(c->rxTone()); setTXTone(c->txTone()); return true; } ConfigItem * FMChannel::clone() const { FMChannel *c = new FMChannel(); if (! c->copy(*this)) { c->deleteLater(); return nullptr; } return c; } void FMChannel::clear() { AnalogChannel::clear(); setAdmit(Admit::Always); setSquelchDefault(); setRXTone(SelectiveCall()); setTXTone(SelectiveCall()); setBandwidth(Bandwidth::Narrow); setAPRS(nullptr); setAnytoneChannelExtension(nullptr); } FMChannel::Admit FMChannel::admit() const { return _admit; } void FMChannel::setAdmit(Admit admit) { _admit = admit; emit modified(this); } SelectiveCall FMChannel::rxTone() const { return _rxTone; } bool FMChannel::setRXTone(SelectiveCall code) { _rxTone = code; emit modified(this); return true; } SelectiveCall FMChannel::txTone() const { return _txTone; } bool FMChannel::setTXTone(SelectiveCall code) { _txTone = code; emit modified(this); return true; } FMChannel::Bandwidth FMChannel::bandwidth() const { return _bw; } bool FMChannel::setBandwidth(Bandwidth bw) { _bw = bw; emit modified(this); return true; } const FMAPRSSystemReference * FMChannel::aprsRef() const { return &_aprsSystem; } FMAPRSSystemReference * FMChannel::aprsRef() { return &_aprsSystem; } FMAPRSSystem * FMChannel::aprs() const { return _aprsSystem.as(); } void FMChannel::setAPRS(FMAPRSSystem *sys) { _aprsSystem.set(sys); } FMChannelExtension * FMChannel::extended() const { return _extended; } AnytoneFMChannelExtension * FMChannel::anytoneChannelExtension() const { return _anytoneExtension; } void FMChannel::setAnytoneChannelExtension(AnytoneFMChannelExtension *ext) { if (_anytoneExtension == ext) return; if (_anytoneExtension) _anytoneExtension->deleteLater(); _anytoneExtension = ext; if (_anytoneExtension) { _anytoneExtension->setParent(this); connect(_anytoneExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified())); } } YAML::Node FMChannel::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = AnalogChannel::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["fm"] = node; return type; } bool FMChannel::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse analog channel: Expected object with one child."; return false; } YAML::Node ch = node.begin()->second; return AnalogChannel::parse(node, ctx, err); } /* ********************************************************************************************* * * Implementation of AMChannel * ********************************************************************************************* */ AMChannel::AMChannel(QObject *parent) : AnalogChannel(parent) { // pass... } bool AMChannel::copy(const ConfigItem &other) { auto c = other.as(); if ((nullptr==c) || (! AnalogChannel::copy(other))) return false; return true; } ConfigItem * AMChannel::clone() const { auto c = new AMChannel(); if (! c->copy(*this)) { c->deleteLater(); return nullptr; } return c; } void AMChannel::clear() { AnalogChannel::clear(); setSquelchDefault(); } YAML::Node AMChannel::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = AnalogChannel::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["am"] = node; return type; } bool AMChannel::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse analog channel: Expected object with one child."; return false; } YAML::Node ch = node.begin()->second; return AnalogChannel::parse(node, ctx, err); } /* ********************************************************************************************* * * Implementation of DigitalChannel * ********************************************************************************************* */ DigitalChannel::DigitalChannel(QObject *parent) : Channel(parent) { // pass... } DigitalChannel::DigitalChannel(const DigitalChannel &other, QObject *parent) : Channel(other, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRChannel * ********************************************************************************************* */ DMRChannel::DMRChannel(QObject *parent) : DigitalChannel(parent), _admit(Admit::Always), _colorCode(1), _timeSlot(TimeSlot::TS1), _rxGroup(), _txContact(), _posSystem(), _roaming(), _radioId(), _extended(new DMRChannelExtension(this)), _commercialExtension(nullptr), _anytoneExtension(nullptr) { // Register default tags if (! ConfigItem::Context::tagIsSet(staticMetaObject.className(), "roaming", "!default")) ConfigItem::Context::setTag(staticMetaObject.className(), "roaming", "!default", QVariant::fromValue(DefaultRoamingZone::get())); if (! ConfigItem::Context::tagIsSet(staticMetaObject.className(), "radioId", "!default")) ConfigItem::Context::setTag(staticMetaObject.className(), "radioId", "!default", QVariant::fromValue(DefaultRadioID::get())); // Set default DMR Id _radioId.set(DefaultRadioID::get()); // Connect signals of references connect(&_rxGroup, SIGNAL(modified()), this, SLOT(onReferenceModified())); connect(&_txContact, SIGNAL(modified()), this, SLOT(onReferenceModified())); connect(&_posSystem, SIGNAL(modified()), this, SLOT(onReferenceModified())); connect(&_roaming, SIGNAL(modified()), this, SLOT(onReferenceModified())); connect(&_radioId, SIGNAL(modified()), this, SLOT(onReferenceModified())); connect(_extended, &DMRChannelExtension::modified, this, &DMRChannel::modified); } void DMRChannel::clear() { DigitalChannel::clear(); setColorCode(1); setTimeSlot(TimeSlot::TS1); setGroupList(nullptr); setContact(nullptr); setAPRS(nullptr); setRoaming(nullptr); setRadioId(DefaultRadioID::get()); setCommercialExtension(nullptr); setAnytoneChannelExtension(nullptr); } ConfigItem * DMRChannel::clone() const { DMRChannel *c = new DMRChannel(); if (! c->copy(*this)) { c->deleteLater(); return nullptr; } return c; } DMRChannel::Admit DMRChannel::admit() const { return _admit; } void DMRChannel::setAdmit(Admit admit) { _admit = admit; emit modified(this); } unsigned DMRChannel::colorCode() const { return _colorCode; } bool DMRChannel::setColorCode(unsigned cc) { if (15 < cc) { logWarn() << "Color-code " << cc << " is not in range [0,15], set to 15."; cc = 15; } _colorCode = cc; emit modified(this); return true; } DMRChannel::TimeSlot DMRChannel::timeSlot() const { return _timeSlot; } bool DMRChannel::setTimeSlot(TimeSlot slot) { _timeSlot = slot; emit modified(this); return true; } const GroupListReference * DMRChannel::groupListRef() const { return &_rxGroup; } GroupListReference * DMRChannel::groupListRef() { return &_rxGroup; } RXGroupList * DMRChannel::groupList() const { return _rxGroup.as(); } bool DMRChannel::setGroupList(RXGroupList *g) { if(! _rxGroup.set(g)) return false; emit modified(this); return true; } const DMRContactReference * DMRChannel::contactRef() const { return &_txContact; } DMRContactReference * DMRChannel::contactRef() { return &_txContact; } DMRContact * DMRChannel::contact() const { return _txContact.as(); } bool DMRChannel::setContact(DMRContact *c) { if(! _txContact.set(c)) return false; emit modified(this); return true; } const PositioningSystemReference * DMRChannel::aprsRef() const { return &_posSystem; } PositioningSystemReference * DMRChannel::aprsRef() { return &_posSystem; } PositionReportingSystem * DMRChannel::aprs() const { return _posSystem.as(); } bool DMRChannel::setAPRS(PositionReportingSystem *sys) { if (! _posSystem.set(sys)) return false; emit modified(this); return true; } const RoamingZoneReference * DMRChannel::roamingRef() const { return &_roaming; } RoamingZoneReference * DMRChannel::roamingRef() { return &_roaming; } RoamingZone * DMRChannel::roaming() const { return _roaming.as(); } bool DMRChannel::setRoaming(RoamingZone *zone) { _roaming.set(zone); emit modified(this); return true; } const DMRRadioIDReference * DMRChannel::radioIdRef() const { return &_radioId; } DMRRadioIDReference * DMRChannel::radioIdRef() { return &_radioId; } DMRRadioID * DMRChannel::radioId() const { return _radioId.as(); } bool DMRChannel::setRadioId(DMRRadioID *id) { if (! _radioId.set(id)) return false; emit modified(this); return true; } DMRChannelExtension * DMRChannel::extended() const { return _extended; } CommercialChannelExtension * DMRChannel::commercialExtension() const { return _commercialExtension; } void DMRChannel::setCommercialExtension(CommercialChannelExtension *ext) { if (_commercialExtension == ext) return; if (_commercialExtension) _commercialExtension->deleteLater(); _commercialExtension = ext; if (_commercialExtension) { _commercialExtension->setParent(this); connect(_commercialExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified())); } } AnytoneDMRChannelExtension * DMRChannel::anytoneChannelExtension() const { return _anytoneExtension; } void DMRChannel::setAnytoneChannelExtension(AnytoneDMRChannelExtension *ext) { if (_anytoneExtension == ext) return; if (_anytoneExtension) _anytoneExtension->deleteLater(); _anytoneExtension = ext; if (_anytoneExtension) { _anytoneExtension->setParent(this); connect(_anytoneExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onReferenceModified())); } } YAML::Node DMRChannel::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = DigitalChannel::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["dmr"] = node; return type; } /* ********************************************************************************************* * * Implementation of M17Channel * ********************************************************************************************* */ M17Channel::M17Channel(QObject *parent) : DigitalChannel(parent), _mode(Mode::Voice), _accessNumber(0), _txContact(), _gpsEnabled(false), _encryptionMode(EncryptionMode::None) { // Connect signals of references connect(&_txContact, SIGNAL(modified()), this, SLOT(onReferenceModified())); } M17Channel::M17Channel(const M17Channel &other, QObject *parent) : DigitalChannel(other, parent), _mode(Mode::Voice), _accessNumber(0), _txContact(), _gpsEnabled(false), _encryptionMode(EncryptionMode::None) { copy(other); // Connect signals of references connect(&_txContact, SIGNAL(modified()), this, SLOT(onReferenceModified())); } void M17Channel::clear() { DigitalChannel::clear(); setMode(Mode::Voice); setAccessNumber(0); setContact(nullptr); enableAPRS(false); setEncryptionMode(EncryptionMode::None); } ConfigItem * M17Channel::clone() const { M17Channel *c = new M17Channel(); if (! c->copy(*this)) { c->deleteLater(); return nullptr; } return c; } M17Channel::Mode M17Channel::mode() const { return _mode; } void M17Channel::setMode(Mode mode) { if (_mode == mode) return; _mode = mode; emit modified(this); } unsigned int M17Channel::accessNumber() const { return _accessNumber; } void M17Channel::setAccessNumber(unsigned int can) { can = std::min(15u, can); if (_accessNumber == can) return; _accessNumber = can; emit modified(this); } const M17ContactReference * M17Channel::contactRef() const { return &_txContact; } M17ContactReference * M17Channel::contactRef() { return &_txContact; } void M17Channel::setContactRef(M17ContactReference *ref) { if (nullptr == ref) _txContact.clear(); else _txContact.copy(ref); } M17Contact * M17Channel::contact() const { return _txContact.as(); } bool M17Channel::setContact(M17Contact *c) { if(! _txContact.set(c)) return false; emit modified(this); return true; } bool M17Channel::aprsEnabled() const { return _gpsEnabled; } void M17Channel::enableAPRS(bool enabled) { if (_gpsEnabled == enabled) return; _gpsEnabled = enabled; emit modified(this); } M17Channel::EncryptionMode M17Channel::encryptionMode() const { return _encryptionMode; } void M17Channel::setEncryptionMode(EncryptionMode mode) { if (_encryptionMode == mode) return; _encryptionMode = mode; emit modified(this); } YAML::Node M17Channel::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = DigitalChannel::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["m17"] = node; return type; } /* ********************************************************************************************* * * Implementation of SelectedChannel * ********************************************************************************************* */ SelectedChannel *SelectedChannel::_instance = nullptr; SelectedChannel::SelectedChannel() : Channel() { setName("[Selected]"); } SelectedChannel::~SelectedChannel() { SelectedChannel::_instance = nullptr; } bool SelectedChannel::copy(const ConfigItem &other) { Q_UNUSED(other); return false; } ConfigItem * SelectedChannel::clone() const { return nullptr; } SelectedChannel * SelectedChannel::get() { if (nullptr == SelectedChannel::_instance) SelectedChannel::_instance = new SelectedChannel(); return SelectedChannel::_instance; } /* ********************************************************************************************* * * Implementation of ChannelList * ********************************************************************************************* */ ChannelList::ChannelList(QObject *parent) : ConfigObjectList(Channel::staticMetaObject, parent) { // pass... } int ChannelList::add(ConfigObject *obj, int row, bool unique) { if ((nullptr == obj) || (! obj->is())) { logError() << "Cannot add nullptr or non-channel objects to channel list."; return -1; } return ConfigObjectList::add(obj, row, unique); } Channel * ChannelList::channel(int idx) const { if (ConfigItem *obj = get(idx)) return obj->as(); return nullptr; } DMRChannel * ChannelList::findDMRChannel(Frequency rx, Frequency tx, DMRChannel::TimeSlot ts, unsigned cc) const { for (int i=0; iis()) continue; /// @bug I should certainly change the frequency handling to integer values! if ((channel(i)->txFrequency()!=tx) || (channel(i)->rxFrequency()!=rx)) continue; DMRChannel *digi = channel(i)->as(); if (digi->timeSlot() != ts) continue; if (digi->colorCode() != cc) continue; return digi; } return nullptr; } FMChannel * ChannelList::findFMChannelByTxFreq(Frequency freq) const { for (int i=0; iis()) continue; if (channel(i)->txFrequency() == freq) return channel(i)->as(); } return nullptr; } ConfigItem * ChannelList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { static bool digitalDepricated = true, analogDeprecated = true; Q_UNUSED(ctx) if (! node) return nullptr; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create channel: Expected object with one child."; return nullptr; } QString type = QString::fromStdString(node.begin()->first.as()); if (("digital" == type) || ("dmr" == type)) { if (("digital" == type) && digitalDepricated) { logWarn() << node.Mark().line << ":" << node.Mark().column << ": Using 'digital' for DMR channels is deprecated. Please use 'dmr' instead."; digitalDepricated = false; } return new DMRChannel(); } else if (("analog" == type) || ("fm"==type)) { if (("analog" == type) && analogDeprecated) { logWarn() << node.Mark().line << ":" << node.Mark().column << ": Using 'analog' for FM channels is deprecated. Please use 'fm' instead."; analogDeprecated = false; } return new FMChannel(); } else if ("m17" == type) { return new M17Channel(); } else if ("am" == type) { return new AMChannel(); } errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create channel: Unknown type '" << type << "'."; return nullptr; } ================================================ FILE: lib/channel.hh ================================================ #ifndef CHANNEL_HH #define CHANNEL_HH #include #include #include "configobject.hh" #include "configreference.hh" #include "signaling.hh" #include "interval.hh" #include "level.hh" #include "channel_extension.hh" #include "opengd77_extension.hh" #include "tyt_extensions.hh" #include "opengd77_extension.hh" #include "anytone_extension.hh" #include "commercial_extension.hh" class Config; class RXGroupList; class DMRContact; class M17Contact; class ScanList; class FMAPRSSystem; class PositionReportingSystem; class RoamingZone; class DMRRadioID; /** The base class of all channels (analog and digital) of a codeplug configuration. * * This class holds the common configuration of @c AnalogChannel and @c DigitalChannel, that is * the name, RX and TX frequencies, output power, TOT and default scanlist properties. * * @ingroup conf */ class Channel: public ConfigObject { Q_OBJECT /** The receive frequency of the channel in Hz. */ Q_PROPERTY(Frequency rxFrequency READ rxFrequency WRITE setRXFrequency) /** The transmit frequency of the channel in Hz. */ Q_PROPERTY(Frequency txFrequency READ txFrequency WRITE setTXFrequency) /** The transmit power. */ Q_PROPERTY(Power power READ power WRITE setPower SCRIPTABLE false) /** The transmit timeout in seconds. */ Q_PROPERTY(Interval timeout READ timeout WRITE setTimeout) /** If true, the channel is receive only. */ Q_PROPERTY(bool rxOnly READ rxOnly WRITE setRXOnly) /** The scan list. */ Q_PROPERTY(ScanListReference* scanListRef READ scanListRef) /** The VOX setting. */ Q_PROPERTY(Level vox READ vox WRITE setVOX) /** The OpenGD77 channel extension. */ Q_PROPERTY(OpenGD77ChannelExtension* openGD77 READ openGD77ChannelExtension WRITE setOpenGD77ChannelExtension) /** The TyT channel extension. */ Q_PROPERTY(TyTChannelExtension* tyt READ tytChannelExtension WRITE setTyTChannelExtension) /** Specifies the prefix for every ID assigned to every channel during serialization. */ Q_CLASSINFO("IdPrefix", "ch") public: /** Possible power settings. */ enum class Power { Max, ///< Highest power setting (e.g. > 5W, if available). High, ///< High power setting (e.g, 5W). Mid, ///< Medium power setting (e.g., 2W, if available) Low, ///< Low power setting (e.g., 1W). Min ///< Lowest power setting (e.g., <1W, if available). }; Q_ENUM(Power) enum class OffsetShift { None, ///< No Offset between TX/RX frequencies. Positive, ///< Positive offset between TX/RX frequencies. Negative ///< Negative offset between TX/RX frequencies. }; Q_ENUM(OffsetShift) /** Options for type selection flags. */ enum class Type { None=0, DMR=1, M17=2, FM=4, AM=8, Analog=FM|AM, Digital=DMR|M17, All=Analog|Digital }; Q_ENUM(Type); Q_DECLARE_FLAGS(Types, Type); Q_FLAGS(Types); protected: /** Hidden constructor. * Constructs a new empty channel. */ explicit Channel(QObject *parent=nullptr); /** Copy constructor. */ Channel(const Channel &other, QObject *parent=nullptr); public: bool copy(const ConfigItem &other); void clear(); /** Returns the RX frequency of the channel in Hz. */ Frequency rxFrequency() const; /** (Re-)Sets the RX frequency of the channel in Hz. */ bool setRXFrequency(Frequency freq); /** Returns the TX frequency of the channel in Hz. */ Frequency txFrequency() const; /** (Re-)Sets the TX frequency of the channel in Hz. */ bool setTXFrequency(Frequency freq); /** Returns the offset between tx and rx frequency of the channel in Hz. */ FrequencyOffset offsetFrequency() const; /** Returns direction of offset if any.*/ OffsetShift offsetShift() const; /** Returns @c true if the channel uses the global default power setting. */ bool defaultPower() const; /** Returns the power setting of the channel if the channel does not use the default power. */ Power power() const; /** (Re-)Sets the power setting of the channel, overrides default power. */ void setPower(Power power); /** Sets the channel to use the default power setting. */ void setDefaultPower(); /** Returns @c true if the transmit timeout is specified by the global default value. */ bool defaultTimeout() const; /** Returns @c true if the transmit timeout is disabled. */ bool timeoutDisabled() const; /** Returns the TX timeout (TOT) in seconds. */ Interval timeout() const; /** (Re-)Sets the TX timeout (TOT) in seconds. */ bool setTimeout(const Interval &dur); /** Disables the transmit timeout. */ void disableTimeout(); /** Sets the timeout to the global default timeout. */ void setDefaultTimeout(); /** Returns @c true, if the channel is RX only. */ bool rxOnly() const; /** Set, whether the channel is RX only. */ bool setRXOnly(bool enable); /** Returns @c true if the VOX is disabled. */ bool voxDisabled() const; /** Returns @c true if the VOX is specified by the global default value. */ bool defaultVOX() const; /** Returns the VOX level [0-10]. */ Level vox() const; /** Sets the VOX level [0-10]. */ void setVOX(Level level); /** Sets the VOX level to the default value. */ void setVOXDefault(); /** Disables the VOX. */ void disableVOX(); /** Returns the reference to the scan list. */ const ScanListReference *scanListRef() const; /** Returns the reference to the scan list. */ ScanListReference *scanListRef(); /** Returns the default scan list for the channel. */ ScanList *scanList() const; /** (Re-) Sets the default scan list for the channel. */ bool setScanList(ScanList *list); /** Returns the channel extension for the OpenGD77 firmware. * If this extension is not set, returns @c nullptr. */ OpenGD77ChannelExtension *openGD77ChannelExtension() const; /** Sets the OpenGD77 channel extension. */ void setOpenGD77ChannelExtension(OpenGD77ChannelExtension *ext); /** Returns the channel extension for TyT devices. * If this extension is not set, returns @c nullptr. */ TyTChannelExtension *tytChannelExtension() const; /** Sets the TyT channel extension. */ void setTyTChannelExtension(TyTChannelExtension *ext); public: bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); bool link(const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack()); protected: bool populate(YAML::Node &node, const Context &context, const ErrorStack &err=ErrorStack()); protected slots: /** Gets called whenever a referenced object is changed or deleted. */ void onReferenceModified(); protected: /** The RX frequency in Hz. */ Frequency _rxFreq; /** The TX frequency in Hz. */ Frequency _txFreq; /** If @c true, the channel uses the global power setting. */ bool _defaultPower; /** The transmit power setting. */ Power _power; /** Transmit timeout. If set to null, the global/default ToT interval is used. If set to * infinity, ToT is disabled. */ Interval _txTimeOut; /** RX only flag. */ bool _rxOnly; /** Holds the VOX level. */ Level _vox; /** Default scan list of the channel. */ ScanListReference _scanlist; /** Owns the OpenGD77 channel extension object. */ OpenGD77ChannelExtension *_openGD77ChannelExtension; /** Owns the TyT channel extension object. */ TyTChannelExtension *_tytChannelExtension; }; /** Base class for all analog channels. * * @ingroup conf */ class AnalogChannel: public Channel { Q_OBJECT /** Specifies the squelch level for the channel. */ Q_PROPERTY(Level squelch READ squelch WRITE setSquelch FINAL) protected: /** Hidden constructor. */ explicit AnalogChannel(QObject *parent=nullptr); public: /** Copy constructor. */ AnalogChannel(const AnalogChannel &other, QObject *parent=nullptr); /** Returns @c true if the global default squelch level is used. */ bool defaultSquelch() const; /** Returns @c true if the squelch is disabled. */ bool squelchDisabled() const; /** Returns the squelch level [1,10]. */ Level squelch() const; /** (Re-)Sets the squelch level [0,10]. 0 Disables squelch (on some radios). */ bool setSquelch(Level squelch); /** Disables the quelch. */ void disableSquelch(); /** Sets the squelch to the global default value. */ void setSquelchDefault(); protected: /** Squelch. If set to 0 -> disabled. If invalid -> default squelch. */ Level _squelch; }; /** Extension to the @c AnalogChannel class to implement an analog FM channel. * * This class implements all the properties specific to an FM channel. That is, the admit * criterion, squelch, RX and TX tones and bandwidth settings. * * @ingroup conf */ class FMChannel: public AnalogChannel { Q_OBJECT /** The admit criterion of the channel. */ Q_PROPERTY(Admit admit READ admit WRITE setAdmit) /** The RX tone (CTCSS/DSC). */ Q_PROPERTY(SelectiveCall rxTone READ rxTone WRITE setRXTone) /** The TX tone (CTCSS/DSC). */ Q_PROPERTY(SelectiveCall txTone READ txTone WRITE setTXTone) /** The band width of the channel. */ Q_PROPERTY(Bandwidth bandwidth READ bandwidth WRITE setBandwidth) /** The APRS system. */ Q_PROPERTY(FMAPRSSystemReference* aprs READ aprsRef) /** Common extended channel settings. */ Q_PROPERTY(FMChannelExtension *extended READ extended); /** The AnyTone FM channel extension. */ Q_PROPERTY(AnytoneFMChannelExtension* anytone READ anytoneChannelExtension WRITE setAnytoneChannelExtension) public: /** Admit criteria of analog channel. */ enum class Admit { Always, ///< Allow always. Free, ///< Allow when channel free. Tone ///< Allow when free or wrong ctcss/dcs tone is present. }; Q_ENUM(Admit) /** Possible bandwidth of an analog channel. */ enum class Bandwidth { Narrow, ///< Narrow bandwidth (12.5kHz). Wide ///< Wide bandwidth (25kHz). }; Q_ENUM(Bandwidth) public: /** Constructs a new empty analog channel. */ Q_INVOKABLE explicit FMChannel(QObject *parent=nullptr); bool copy(const ConfigItem &other); ConfigItem *clone() const; void clear(); /** Returns the admit criterion for the analog channel. */ Admit admit() const; /** (Re-)Sets the admit criterion for the analog channel. */ void setAdmit(Admit admit); /** Returns the CTCSS/DCS RX tone, @c SIGNALING_NONE means disabled. */ SelectiveCall rxTone() const; /** (Re-)Sets the CTCSS/DCS RX tone, @c SIGNALING_NONE disables the RX tone. */ bool setRXTone(SelectiveCall code); /** Returns the CTCSS/DCS TX tone, @c SIGNALING_NONE means disabled. */ SelectiveCall txTone() const; /** (Re-)Sets the CTCSS/DCS TX tone, @c SIGNALING_NONE disables the TX tone. */ bool setTXTone(SelectiveCall code); /** Returns the bandwidth of the analog channel. */ Bandwidth bandwidth() const; /** (Re-)Sets the bandwidth of the analog channel. */ bool setBandwidth(Bandwidth bw); /** Returns the reference to the APRS system. */ const FMAPRSSystemReference *aprsRef() const; /** Returns the reference to the APRS system. */ FMAPRSSystemReference *aprsRef(); /** Returns the APRS system used for this channel or @c nullptr if disabled. */ FMAPRSSystem *aprs() const; /** Sets the APRS system. */ void setAPRS(FMAPRSSystem *sys); /** Returns the extended settings. */ FMChannelExtension *extended() const; /** Returns the FM channel extension for AnyTone devices. * If this extension is not set, returns @c nullptr. */ AnytoneFMChannelExtension *anytoneChannelExtension() const; /** Sets the AnyTone FM channel extension. */ void setAnytoneChannelExtension(AnytoneFMChannelExtension *ext); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Holds the admit criterion. */ Admit _admit; /** The RX CTCSS/DCS setting. */ SelectiveCall _rxTone; /** The TX CTCSS/DCS setting. */ SelectiveCall _txTone; /** The channel bandwidth. */ Bandwidth _bw; /** A reference to the APRS system used on the channel or @c nullptr if disabled. */ FMAPRSSystemReference _aprsSystem; /** Owns the extended settings. */ FMChannelExtension *_extended; /** Owns the AnyTone FM channel extension. */ AnytoneFMChannelExtension *_anytoneExtension; }; /** Extension to the @c AnalogChannel class to implement an analog AM channel. * * This class implements all the properties specific to an AM channel. These channels are usually * used to represent air-band channels. So there are not that many settings. * * @ingroup conf */ class AMChannel: public AnalogChannel { Q_OBJECT public: /** Constructs a new empty AM channel. */ Q_INVOKABLE explicit AMChannel(QObject *parent=nullptr); bool copy(const ConfigItem &other) override; ConfigItem *clone() const override; void clear() override; public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()) override; bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()) override; }; /** Base class of all digital channels. * * @ingroup conf */ class DigitalChannel: public Channel { Q_OBJECT protected: /** Hidden constructor. */ explicit DigitalChannel(QObject *parent=nullptr); public: /** Copy constructor. */ DigitalChannel(const DigitalChannel &other, QObject *parent=nullptr); }; /** Extension to the @c DigitalChannel class to implement an DMR channel. * * That is, the admit criterion, color code, time slot, RX group list and TX contact. * * @ingroup conf */ class DMRChannel: public DigitalChannel { Q_OBJECT /** The admit criterion of the channel. */ Q_PROPERTY(Admit admit READ admit WRITE setAdmit) /** The color code of the channel. */ Q_PROPERTY(unsigned colorCode READ colorCode WRITE setColorCode) /** The time slot of the channel. */ Q_PROPERTY(TimeSlot timeSlot READ timeSlot WRITE setTimeSlot) /** The radio ID. */ Q_PROPERTY(DMRRadioIDReference* radioId READ radioIdRef) /** The rx group list. */ Q_PROPERTY(GroupListReference* groupList READ groupListRef) /** The tx contact. */ Q_PROPERTY(DMRContactReference* contact READ contactRef) /** The positioning system. */ Q_PROPERTY(PositioningSystemReference* aprs READ aprsRef) /** The roaming zone. */ Q_PROPERTY(RoamingZoneReference* roaming READ roamingRef) /** The extended dmr channel settings. */ Q_PROPERTY(DMRChannelExtension* extended READ extended) /** The commercial channel extension. */ Q_PROPERTY(CommercialChannelExtension* commercial READ commercialExtension WRITE setCommercialExtension) /** The AnyTone DMR channel extension. */ Q_PROPERTY(AnytoneDMRChannelExtension* anytone READ anytoneChannelExtension WRITE setAnytoneChannelExtension) public: /** Possible admit criteria of digital channels. */ enum class Admit { Always, ///< No admit criteria, allows one to transmit any time. Free, ///< Transmit only if channel is free. ColorCode ///< Transmit if channel is free or differs given color code. }; Q_ENUM(Admit) /** Possible timeslots for digital channels. */ enum class TimeSlot { TS1, ///< Time/repeater slot 1 TS2 ///< Time/repeater slot 2 }; Q_ENUM(TimeSlot) public: /** Constructs a new empty digital (DMR) channel. */ Q_INVOKABLE explicit DMRChannel(QObject *parent=nullptr); ConfigItem *clone() const; void clear(); /** Returns the admit criterion for the channel. */ Admit admit() const; /** (Re-)Sets the admit criterion for the channel. */ void setAdmit(Admit admit); /** Returns the color code for the channel. */ unsigned colorCode() const; /** (Re-)Sets the color code for the channel. */ bool setColorCode(unsigned cc); /** Returns the time slot for the channel. */ TimeSlot timeSlot() const; /** (Re-)Sets the time slot for the channel. */ bool setTimeSlot(TimeSlot ts); /** Returns a reference to the group list. */ const GroupListReference *groupListRef() const; /** Returns a reference to the group list. */ GroupListReference *groupListRef(); /** Returns the RX group list for the channel. */ RXGroupList *groupList() const; /** (Re-)Sets the RX group list for the channel. */ bool setGroupList(RXGroupList *rxg); /** Returns a reference to the transmit contactRef. */ const DMRContactReference *contactRef() const; /** Returns a reference to the transmit contactRef. */ DMRContactReference *contactRef(); /** Returns the default TX contact to call on this channel. */ DMRContact *contact() const; /** (Re-) Sets the default TX contact for this channel. */ bool setContact(DMRContact *c); /** Returns a reference to the positioning system. */ const PositioningSystemReference *aprsRef() const; /** Returns a reference to the positioning system. */ PositioningSystemReference *aprsRef(); /** Returns the GPS system associated with this channel or @c nullptr if not set. */ PositionReportingSystem *aprs() const; /** Associates the GPS System with this channel. */ bool setAPRS(PositionReportingSystem *sys); /** Returns a reference to the roamingRef zone. */ const RoamingZoneReference *roamingRef() const; /** Returns a reference to the roamingRef zone. */ RoamingZoneReference *roamingRef(); /** Returns the roaming zone associated with this channel or @c nullptr if not set. */ RoamingZone *roaming() const; /** Associates the given roaming zone with this channel. */ bool setRoaming(RoamingZone *zone); /** Returns the reference to the radio ID. */ const DMRRadioIDReference *radioIdRef() const; /** Returns the reference to the radio ID. */ DMRRadioIDReference *radioIdRef(); /** Returns the radio ID associated with this channel. */ DMRRadioID *radioId() const; /** Associates the given radio ID with this channel. */ bool setRadioId(DMRRadioID *id); /** Returns the extended channel settings. */ DMRChannelExtension *extended() const; /** Returns the extension for commercial features. */ CommercialChannelExtension *commercialExtension() const; /** Sets the commercial channel extension. */ void setCommercialExtension(CommercialChannelExtension *ext); /** Returns the DMR channel extension for AnyTone devices. * If this extension is not set, returns @c nullptr. */ AnytoneDMRChannelExtension *anytoneChannelExtension() const; /** Sets the AnyTone DMR channel extension. */ void setAnytoneChannelExtension(AnytoneDMRChannelExtension *ext); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); protected: /** The admit criterion. */ Admit _admit; /** The channel color code. */ unsigned _colorCode; /** The time slot for the channel. */ TimeSlot _timeSlot; /** The RX group list for this channel. */ GroupListReference _rxGroup; /** The default TX contact. */ DMRContactReference _txContact; /** The GPS system. */ PositioningSystemReference _posSystem; /** Roaming zone for the channel. */ RoamingZoneReference _roaming; /** Radio ID to use on this channel. */ DMRRadioIDReference _radioId; /** Owns the extended channel extension. */ DMRChannelExtension *_extended; /** Owns the commercial channel extension. */ CommercialChannelExtension *_commercialExtension; /** Owns the AnyTone DMR channel extension. */ AnytoneDMRChannelExtension *_anytoneExtension; }; /** Implements an M17 channel. * @ingroup conf */ class M17Channel: public DigitalChannel { Q_OBJECT /** The channel access number. */ Q_PROPERTY(unsigned accessNumber READ accessNumber WRITE setAccessNumber) /** The transmit contact. */ Q_PROPERTY(M17ContactReference* contact READ contactRef WRITE setContactRef) /** The channel mode. */ Q_PROPERTY(Mode mode READ mode WRITE setMode) /** The encryption mode. */ Q_PROPERTY(EncryptionMode encryptionMode READ encryptionMode WRITE setEncryptionMode) /** If enabled, positioning data is send along with voice and data. */ Q_PROPERTY(bool aprs READ aprsEnabled WRITE enableAPRS) public: /** Possible channel modes. */ enum class Mode { Voice, Data, VoiceAndData }; Q_ENUM(Mode) /** Possible encryption modes. */ enum class EncryptionMode { None, AES256, Scrambled }; Q_ENUM(EncryptionMode) public: /** Constructs a new empty M17 channel. */ M17Channel(QObject *parent=nullptr); /** Copy constructor. */ M17Channel(const M17Channel &other, QObject *parent=nullptr); ConfigItem *clone() const; void clear(); /** Returns the channel mode. */ Mode mode() const; /** Sets the channel mode. */ void setMode(Mode mode); /** Returns the channel access number. */ unsigned int accessNumber() const; /** Sets the channel access number (0-15). */ void setAccessNumber(unsigned int can); /** Returns a reference to the transmit contact. */ const M17ContactReference *contactRef() const; /** Returns a reference to the transmit contact. */ M17ContactReference *contactRef(); /** Sets the reference to the transmit contact. */ void setContactRef(M17ContactReference *ref); /** Returns the default TX contact to call on this channel. */ M17Contact *contact() const; /** (Re-) Sets the default TX contact for this channel. */ bool setContact(M17Contact *c); /** Returns @c true if APRS is enabled. */ bool aprsEnabled() const; /** Enables/disables APRS. */ void enableAPRS(bool enabled); /** Returns the encryption mode of the channel. */ EncryptionMode encryptionMode() const; /** Sets the encryption mode of the channel. */ void setEncryptionMode(EncryptionMode mode); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); protected: /** Holds the channel mode. */ Mode _mode; /** Holds the channel access number. */ unsigned int _accessNumber; /** The default TX contact. */ M17ContactReference _txContact; /** If @c true, positioning information is send alonside voice and data. */ bool _gpsEnabled; /** Holds the encryption mode for the channel. */ EncryptionMode _encryptionMode; }; /** Internal singleton class representing the "currently selected" channel. * @ingroup conf */ class SelectedChannel: public Channel { Q_OBJECT protected: /** Constructs the "selected" channel. * @warning Do not use this class directly, call @c SelectedChannel::get() instead. */ explicit SelectedChannel(); public: /** Destructor. */ virtual ~SelectedChannel(); bool copy(const ConfigItem &other); ConfigItem *clone() const; /** Constructs/gets the singleton instance. */ static SelectedChannel *get(); protected: /** Holds the channel singleton instance. */ static SelectedChannel *_instance; }; /** Container class holding all channels (analog and digital) for a specific configuration * (@c Config). * * This class also implements the QAbstractTableModel and can therefore be displayed using a * default QTableView instance. * * @ingroup conf */ class ChannelList: public ConfigObjectList { Q_OBJECT public: /** Constructs an empty channel list. */ explicit ChannelList(QObject *parent=nullptr); int add(ConfigObject *obj, int row=-1, bool unique=true); /** Gets the channel at the specified index. */ Channel *channel(int idx) const; /** Finds a digital channel with the given frequencies, time slot and color code. */ DMRChannel *findDMRChannel(Frequency rx, Frequency tx, DMRChannel::TimeSlot ts, unsigned cc) const; /** Finds an analog channel with the given frequency. */ FMChannel *findFMChannelByTxFreq(Frequency freq) const; public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; Q_DECLARE_OPERATORS_FOR_FLAGS(Channel::Types) #endif // CHANNEL_HH ================================================ FILE: lib/channel_extension.cc ================================================ #include "channel_extension.hh" /* ******************************************************************************************* * * Implementation of common channel extended settings * ******************************************************************************************* */ ChannelExtension::ChannelExtension(QObject *parent) : ConfigExtension{parent}, _talkaround(false) { // pass... } bool ChannelExtension::talkaround() const { return _talkaround; } void ChannelExtension::enableTalkaround(bool enable) { if (enable == _talkaround) return; _talkaround = enable; emit modified(this); } /* ******************************************************************************************* * * Implementation of common FM channel extended settings * ******************************************************************************************* */ FMChannelExtension::FMChannelExtension(QObject *parent) : ChannelExtension{parent}, _reverseBurst(false) { // pass... } ConfigItem * FMChannelExtension::clone() const { auto ext = new FMChannelExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool FMChannelExtension::reverseBurst() const { return _reverseBurst; } void FMChannelExtension::enableReverseBurst(bool enable) { if (enable == _reverseBurst) return; _reverseBurst = enable; emit modified(this); } /* ******************************************************************************************* * * Implementation of common DMR channel extended settings * ******************************************************************************************* */ DMRChannelExtension::DMRChannelExtension(QObject *parent) : ChannelExtension{parent}, _callConfirm(false), _sms(true), _smsConfirm(false), _dataConfirm(true), _dcdm(false), _loneWorker(false) { // pass... } ConfigItem * DMRChannelExtension::clone() const { auto ext = new DMRChannelExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool DMRChannelExtension::privateCallConfirm() const { return _callConfirm; } void DMRChannelExtension::enablePrivateCallConfirm(bool enabled) { if (enabled == _callConfirm) return; _callConfirm = enabled; emit modified(this); } bool DMRChannelExtension::sms() const { return _sms; } void DMRChannelExtension::enableSMS(bool enable) { if (enable == _sms) return; _sms = enable; emit modified(this); } bool DMRChannelExtension::smsConfirm() const { return _smsConfirm; } void DMRChannelExtension::enableSMSConfirm(bool enabled) { if (enabled == _smsConfirm) return; _smsConfirm = enabled; emit modified(this); } bool DMRChannelExtension::dataConfirm() const { return _dataConfirm; } void DMRChannelExtension::enableDataConfirm(bool enable) { if (enable==_dataConfirm) return; _dataConfirm = enable; emit modified(this); } bool DMRChannelExtension::dcdm() const { return _dcdm; } void DMRChannelExtension::enableDCDM(bool enable) { if (enable == _dcdm) return; _dcdm = enable; emit modified(this); } bool DMRChannelExtension::loneWorker() const { return _loneWorker; } void DMRChannelExtension::enableLoneWorker(bool enable) { if (enable == _loneWorker) return; _loneWorker = enable; emit modified(this); } ================================================ FILE: lib/channel_extension.hh ================================================ #ifndef CHANNEL_EXTENSION_HH #define CHANNEL_EXTENSION_HH #include "configobject.hh" /** Common extended settings for all channels. * @ingroup conf */ class ChannelExtension : public ConfigExtension { Q_OBJECT /** If @c true, talkaround is enabled. */ Q_PROPERTY(bool talkaround READ talkaround WRITE enableTalkaround) protected: /** Hidden constructor. */ explicit ChannelExtension(QObject *parent = nullptr); public: /** Returns @c true, if talkaround is enabled. */ bool talkaround() const; /** Enables/disables talkaround. */ void enableTalkaround(bool enable); protected: /** If @c true, talkaround is enabled. */ bool _talkaround; }; /** Common FM channel extended settings. * @ingroup conf */ class FMChannelExtension: public ChannelExtension { Q_OBJECT /** If @c true, the CTCSS phase-reverse burst at the end of transmission is enabled. */ Q_PROPERTY(bool reverseBurst READ reverseBurst WRITE enableReverseBurst) public: /** Default constructor. */ explicit FMChannelExtension(QObject *parent = nullptr); ConfigItem *clone() const override; /** Returns @c true, if the CTCSS phase-reverse burst is enabled. */ bool reverseBurst() const; /** Enables/disables the CTCSS phase-reverse burst. */ void enableReverseBurst(bool enable); protected: /** If @c true, the CTCSS phase-reverse burst at the end of transmission is enabled. */ bool _reverseBurst; }; /** Common DMR channel extended settings. * @ingroup conf */ class DMRChannelExtension: public ChannelExtension { Q_OBJECT /** If @c true, the call confirmation is enabled. */ Q_PROPERTY(bool privateCallConfirm READ privateCallConfirm WRITE enablePrivateCallConfirm) /** If @c true, SMS reception is enabled. */ Q_PROPERTY(bool sms READ sms WRITE enableSMS) /** If @c true, the SMS confirmation is enabled. */ Q_PROPERTY(bool smsConfirm READ smsConfirm WRITE enableSMSConfirm) /** If @c true, the radio will response to received data packages. Should be enabled. */ Q_PROPERTY(bool dataConfirm READ dataConfirm WRITE enableDataConfirm) /** If @c true, the simplex DCDM mode is enabled. */ Q_PROPERTY(bool dcdm READ dcdm WRITE enableDCDM) /** If @c true, the lone-worker feature is enabled for this channel. */ Q_PROPERTY(bool loneWorker READ loneWorker WRITE enableLoneWorker) public: /** Default constructor. */ explicit DMRChannelExtension(QObject *parent=nullptr); ConfigItem *clone() const override; /** Returns @c true if the private-call confirmation is enabled. */ bool privateCallConfirm() const; /** Enables/disables the private-call confirmation. */ void enablePrivateCallConfirm(bool enabled); /** Returns @c true if SMS reception is enabled. */ bool sms() const; /** Enables/disables SMS reception. */ void enableSMS(bool enable); /** Returns @c true if the SMS confirmation is enabled. */ bool smsConfirm() const; /** Enables/disables the SMS confirmation. */ void enableSMSConfirm(bool enabled); /** Returns @c true if the data acknowledgement is enabled. */ bool dataConfirm() const; /** Enables/disables the data acknowledgement. */ void enableDataConfirm(bool enable); /** Returns @c true if the simplex DCDM mode is enabled. */ bool dcdm() const; /** Enables/disables the simplex DCDM mode. */ void enableDCDM(bool enable); /** Returns @c true if the lone-worker feature is enabled. */ bool loneWorker() const; /** Enables the lone-worker feature for this channel. */ void enableLoneWorker(bool enable); protected: /** If @c true, the call confirmation is enabled. */ bool _callConfirm; /** If @c true, the SMS reception is enabled. */ bool _sms; /** If @c true, the SMS confirmation is enabled. */ bool _smsConfirm; /** If @c true, the data acknowledgement is enabled. */ bool _dataConfirm; /** If @c true, the simplex TDMA mode is enabled. */ bool _dcdm; /** If @c true the lone-worker feature is enabled. */ bool _loneWorker; }; #endif // CHANNEL_EXTENSION_HH ================================================ FILE: lib/chirpformat.cc ================================================ #include "chirpformat.hh" #include "logger.hh" #include "frequency.hh" #include "signaling.hh" #include "channel.hh" #include "config.hh" #include /* ********************************************************************************************* * * Implementation of ChirpFormat * ********************************************************************************************* */ QSet const ChirpFormat::_mandatoryHeaders = { "Location", "Name", "Frequency", "Duplex", "Offset", "Mode" }; QSet const ChirpFormat::_knownHeaders = { "Location", "Name", "Frequency", "Duplex", "Offset", "Mode" "Tone", "rToneFreq", "cToneFreq", "DtcsCode", "DtcsPolarity", "RxDtcsCode", "CrossMode", "TStep", "Skip", "Power", "Comment", "URCALL", "RPT1CALL", "RPT2CALL", "DVCODE" }; QHash const ChirpFormat::_duplexCodes = { {"", ChirpFormat::Duplex::None}, {"+", ChirpFormat::Duplex::Positive}, {"-", ChirpFormat::Duplex::Negative}, {"Off", ChirpFormat::Duplex::Off}, }; QHash const ChirpFormat::_modeCodes = { {"FM", ChirpReader::Mode::FM}, {"NFM", ChirpReader::Mode::NFM}, {"WFM", ChirpReader::Mode::WFM}, {"AM", ChirpReader::Mode::AM}, {"DV", ChirpReader::Mode::DV}, {"DN", ChirpReader::Mode::DN}, }; QHash const ChirpFormat::_toneModeCodes = { {"", ChirpFormat::ToneMode::None}, {"Tone", ChirpFormat::ToneMode::Tone}, {"TSQL", ChirpFormat::ToneMode::TSQL}, {"TSQL-R", ChirpFormat::ToneMode::TSQL_R}, {"DTCS", ChirpFormat::ToneMode::DTCS}, {"DTCS-R", ChirpFormat::ToneMode::DTCS_R}, {"Cross", ChirpFormat::ToneMode::Cross}, }; QHash const ChirpFormat::_crossModes = { {"->Tone", ChirpFormat::CrossMode::NoneTone}, {"->DTCS", ChirpFormat::CrossMode::NoneDTCS}, {"Tone->", ChirpFormat::CrossMode::ToneNone}, {"Tone->Tone", ChirpFormat::CrossMode::ToneTone}, {"Tone->DTCS", ChirpFormat::CrossMode::ToneDTCS}, {"DTCS->", ChirpFormat::CrossMode::DTCSNone}, {"DTCS->Tone", ChirpFormat::CrossMode::DTCSTone}, {"DTCS->DTCS", ChirpFormat::CrossMode::DTCSDTCS}, }; /* ********************************************************************************************* * * Implementation of ChirpReader * ********************************************************************************************* */ bool ChirpReader::read(QTextStream &stream, Config *config, const ErrorStack &err) { // First read header QStringList header; if (! readLine(stream, header, err)) { errMsg(err) << "Cannot read CSV header."; return false; } // Some trivial sanity checks for the header if (0 == header.size()) { errMsg(err) << "Invalid CSV file header: Got empty header."; return false; } if ("Location" != header.at(0)) { errMsg(err) << "Invalid CSV file header: 'Location' is not first column!"; return false; } // check fields foreach (QString field, _mandatoryHeaders) { if (! header.contains(field)) { errMsg(err) << "Mandatory column '" << field << "' is missing."; return false; } } foreach(QString field, header) { if (! _knownHeaders.contains(field)) { logInfo() << "Unknown header field '" << field << "'."; } } int line=2; while (! stream.atEnd()) { QStringList entry; if (! readLine(stream, entry, err)) { errMsg(err) << "In CSV file line " << line << ": Cannot read line."; return false; } if (! processLine(header, entry, config, err)) { errMsg(err) << "In CSV file line " << line << ": Cannot read line."; return false; } line++; } return true; } bool ChirpReader::readLine(QTextStream &stream, QStringList &list, const ErrorStack &err) { Q_UNUSED(err) list.clear(); if (stream.atEnd()) return true; QString token; QChar ch; stream >> ch; bool string = false; while ((! stream.atEnd()) && (QChar('\n') != ch)) { if ((!string) && (QChar(',') == ch)) { list.append(token); token.clear(); } else if ((! string) && (QChar('"') == ch)) { string = true; } else if (string && (QChar('"') == ch)) { string = false; } else { token.append(ch); } stream >> ch; } list.append(token); return true; } bool ChirpReader::processLine(const QStringList &header, const QStringList &line, Config *config, const ErrorStack &err) { if (header.size() != line.size()) { errMsg(err) << "Malformed line. Expected " << header.size() << " entries, got " << line.size() << "."; return false; } bool ok; QString name; Frequency rxFrequency, txFrequency; Duplex duplex = Duplex::None; Mode mode = Mode::FM; ToneMode toneMode = ToneMode::None; CrossMode crossMode; double txTone = 67.0, rxTone = 67.0; int txDTCSCode = 000, rxDTCSCode = 000; Polarity txPol = Polarity::Normal, rxPol = Polarity::Normal; for (int i=1; isetBandwidth(Mode::FM == mode ? FMChannel::Bandwidth::Wide : FMChannel::Bandwidth::Narrow); fm->setName(name); fm->setRXFrequency(rxFrequency); switch (duplex) { case Duplex::None: fm->setTXFrequency(fm->rxFrequency()); break; case Duplex::Off: fm->setTXFrequency(fm->rxFrequency()); fm->setRXOnly(true); break; case Duplex::Split: fm->setTXFrequency(txFrequency); break; case Duplex::Negative: fm->setTXFrequency(Frequency::fromHz(rxFrequency.inHz()-txFrequency.inHz())); break; case Duplex::Positive: fm->setTXFrequency(Frequency::fromHz(rxFrequency.inHz()+txFrequency.inHz())); break; } switch (toneMode) { case ToneMode::None: fm->setTXTone(SelectiveCall()); fm->setRXTone(SelectiveCall()); break; case ToneMode::Tone: fm->setTXTone(SelectiveCall(txTone)); fm->setRXTone(SelectiveCall()); break; case ToneMode::TSQL: fm->setTXTone(SelectiveCall(rxTone)); fm->setRXTone(SelectiveCall(rxTone)); break; case ToneMode::TSQL_R: errMsg(err) << "Reversed CTCSS not supported."; return false; case ToneMode::DTCS: fm->setTXTone(SelectiveCall(txDTCSCode, Polarity::Reversed == txPol)); fm->setRXTone(SelectiveCall(txDTCSCode, Polarity::Reversed == rxPol)); break; case ToneMode::DTCS_R: errMsg(err) << "Reversed DCS not supported."; return false; case ToneMode::Cross: switch (crossMode) { case CrossMode::NoneTone: fm->setTXTone(SelectiveCall()); fm->setRXTone(SelectiveCall(rxTone)); break; case CrossMode::NoneDTCS: fm->setTXTone(SelectiveCall()); fm->setRXTone(SelectiveCall(rxDTCSCode, Polarity::Reversed == rxPol)); break; case CrossMode::ToneNone: fm->setTXTone(SelectiveCall(txTone)); fm->setRXTone(SelectiveCall()); break; case CrossMode::ToneTone: fm->setTXTone(SelectiveCall(txTone)); fm->setRXTone(SelectiveCall(rxTone)); break; case CrossMode::ToneDTCS: fm->setTXTone(SelectiveCall(txTone)); fm->setRXTone(SelectiveCall(rxDTCSCode, Polarity::Reversed == rxPol)); break; case CrossMode::DTCSNone: fm->setTXTone(SelectiveCall(txDTCSCode, Polarity::Reversed == txPol)); fm->setRXTone(SelectiveCall()); break; case CrossMode::DTCSTone: fm->setTXTone(SelectiveCall(txDTCSCode, Polarity::Reversed == txPol)); fm->setRXTone(SelectiveCall(rxTone)); break; case CrossMode::DTCSDTCS: fm->setTXTone(SelectiveCall(txDTCSCode, Polarity::Reversed == txPol)); fm->setRXTone(SelectiveCall(rxDTCSCode, Polarity::Reversed == rxPol)); break; } } config->channelList()->add(fm); return true; } errMsg(err) << "Unhandled channel format."; return false; } bool ChirpReader::processDuplex(const QString &code, Duplex &duplex, const ErrorStack &err) { if (! _duplexCodes.contains(code.simplified())) { errMsg(err) << "Cannot decode duplex '" << code << "': Unknown setting."; return false; } duplex = _duplexCodes[code.simplified()]; return true; } bool ChirpReader::processMode(const QString &code, Mode &mode, const ErrorStack &err) { if (! _modeCodes.contains(code.simplified())) { errMsg(err) << "Cannot decode mode '" << code << "': Unknown setting."; return false; } mode = _modeCodes[code.simplified()]; return true; } bool ChirpReader::processToneMode(const QString &code, ToneMode &mode, const ErrorStack &err) { if (! _toneModeCodes.contains(code.simplified())) { errMsg(err) << "Cannot decode tone mode '" << code << "': Unknown setting."; return false; } mode = _toneModeCodes[code.simplified()]; return true; } bool ChirpReader::processPolarity(const QString &code, Polarity &txPol, Polarity &rxPol, const ErrorStack &err) { if (2 != code.simplified().size()) { errMsg(err) << "Cannot parse polarity code '" << code << "': invalid format."; return false; } QChar tx = code.simplified().at(0), rx = code.simplified().at(1); if ('N' == tx) { txPol = Polarity::Normal; } else if ('R' == tx) { txPol = Polarity::Reversed; } else { errMsg(err) << "Invalid polarity code: '" << tx << "': expected 'N' or 'R'."; return false; } if ('N' == rx) { rxPol = Polarity::Normal; } else if ('R' == rx) { rxPol = Polarity::Reversed; } else { errMsg(err) << "Invalid polarity code: '" << rx << "': expected 'N' or 'R'."; return false; } return true; } bool ChirpReader::processCrossMode(const QString &code, CrossMode &crossMode, const ErrorStack &err) { if (! _crossModes.contains(code.simplified())) { errMsg(err) << "Cannot decode cross-mode '" << code << "': unknown mode."; return false; } crossMode = _crossModes[code.simplified()]; return true; } /* ********************************************************************************************* * * Implementation of ChirpWriter * ********************************************************************************************* */ bool ChirpWriter::write(QTextStream &stream, Config *config, const ErrorStack &err) { if (! writeHeader(stream, err)) { errMsg(err) << "Cannot write CHIRP CSV file."; return false; } for (int i=0, j=0; ichannelList()->count(); i++) { if (! config->channelList()->channel(i)->is()) continue; if (! writeChannel(stream, j, config->channelList()->channel(i)->as(), err)) { errMsg(err) << "Cannot encode FM channel '" << config->channelList()->channel(i)->name() << "'."; return false; } j++; } return true; } bool ChirpWriter::writeHeader(QTextStream &stream, const ErrorStack &err) { Q_UNUSED(err) stream << "Location" << "," << "Name" << "," << "Frequency" << "," << "Duplex" << "," << "Offset" << "," << "Tone" << "," << "rToneFreq" << "," << "cToneFreq" << "," << "DtcsCode" << "," "RxDtcsCode" << "," << "DtcsPolarity" << "," << "CrossMode" << "," << "Mode" << "\n"; return true; } bool ChirpWriter::writeChannel(QTextStream &stream, int i, FMChannel *channel, const ErrorStack &err) { stream << i << "," << '"' << channel->name() << '"'; if (! encodeFrequency(stream, channel, err)) { errMsg(err) << "Cannot encode frequencies of channel '" << channel->name() << "'."; return false; } if (! encodeSubTone(stream, channel, err)) { errMsg(err) << "Cannot encode sub-tone setting for channel '" << channel->name() << "'."; return false; } if (! encodeBandwidth(stream, channel, err)) { errMsg(err) << "Cannot encode sub-tone setting for channel '" << channel->name() << "'."; return false; } stream << "\n"; return true; } bool ChirpWriter::encodeFrequency(QTextStream &stream, FMChannel *channel, const ErrorStack &err) { Q_UNUSED(err) stream << "," << channel->rxFrequency().inMHz(); if (channel->rxOnly()) stream << "," << "Off" << "," << 0.0; else if (channel->rxFrequency() == channel->txFrequency()) stream << "," << "" << "," << 0.0; else if (channel->rxFrequency() > channel->txFrequency()) stream << "," << "-" << "," << channel->rxFrequency().inMHz()-channel->txFrequency().inMHz(); else stream << "," << "+" << "," << channel->txFrequency().inMHz()-channel->rxFrequency().inMHz(); return true; } bool ChirpWriter::encodeSubTone(QTextStream &stream, FMChannel *channel, const ErrorStack &err) { Q_UNUSED(err) // Serializes ", Tone, rToneFreq, cToneFreq, DtcsCode, RxDtcsCode, Polarity, CrossMode" if (channel->txTone().isInvalid()) stream << "," << "" << "," << 67.0 << "," << 67.0 << "," << "023" << "," << "023" << "," << "NN" << "," << "Tone->Tone" ; else if (channel->txTone().isCTCSS() && channel->rxTone().isInvalid()) stream << "," << "Tone" << "," << QString::number(channel->txTone().Hz(), 'f', 1) << "," << 67.0 << "," << "023" << "," << "023" << "," << "NN" << "," << "Tone->Tone"; else if (channel->txTone().isCTCSS() && (channel->txTone() == channel->rxTone())) stream << "," << "TSQL" << "," << 67.0 << "," << QString::number(channel->txTone().Hz(), 'f', 1) << "," << "023" << "," << "023" << "," << "NN" << "," << "Tone->Tone"; else if (channel->txTone().isInvalid() && channel->rxTone().isCTCSS()) stream << "," << "Cross" << "," << 67.0 << "," << QString::number(channel->rxTone().Hz(), 'f', 1) << "," << "023" << "," << "023" << "," << "NN" << "," << "->Tone"; else if (channel->txTone().isInvalid() && channel->rxTone().isDCS()) stream << "," << "Cross" << "," << 67.0 << "," << 67.0 << "," << "023" << "," << channel->rxTone().octalCode() << "," << (channel->rxTone().isInverted() ? "NR" : "NN") << "," << "->DTCS"; else if (channel->txTone().isCTCSS() && channel->rxTone().isCTCSS() && (channel->txTone() != channel->rxTone())) stream << "," << "Cross" << "," << QString::number(channel->txTone().Hz(), 'f', 1) << "," << QString::number(channel->rxTone().Hz(), 'f', 1) << "," << "023" << "," << "023" << "," << "NN" << "," << "Tone->Tone"; else if (channel->txTone().isCTCSS() && channel->rxTone().isDCS()) stream << "," << "Cross" << "," << QString::number(channel->txTone().Hz(), 'f', 1) << "," << 67.0 << "," << "023" << "," << channel->rxTone().octalCode() << "," << (channel->rxTone().isInverted() ? "NR" : "NN") << "," << "Tone->DTCS"; else if (channel->txTone().isDCS() && channel->rxTone().isCTCSS()) stream << "," << "Cross" << "," << 67.0 << "," << QString::number(channel->rxTone().Hz(), 'f', 1) << "," << channel->txTone().octalCode() << "," << "023" << "," << (channel->txTone().isInverted() ? "RN" : "NN") << "," << "DTCS->Tone"; else if (channel->txTone().isDCS() && channel->rxTone().isDCS() && (channel->txTone().binCode() == channel->rxTone().binCode())) stream << "," << "DTCS" << "," << 67.0 << "," << 67.0 << "," << channel->txTone().octalCode() << "," << channel->rxTone().octalCode() << "," << (channel->txTone().isInverted() ? 'R' : 'N') << (channel->rxTone().isInverted() ? 'R' : 'N') << "," << "Tone->Tone"; else if (channel->txTone().isDCS() && channel->rxTone().isDCS()) stream << "," << "Cross" << "," << 67.0 << "," << 67.0 << "," << channel->txTone().octalCode() << "," << channel->rxTone().octalCode() << "," << (channel->txTone().isInverted() ? 'R' : 'N') << (channel->rxTone().isInverted() ? 'R' : 'N') << "," << "DTCS->DTCS"; else stream << "," << "" << "," << 67.0 << "," << 67.0 << "," << "023" << "," << "023" << "," << "NN" << "," << "Tone->Tone" ; return true; } bool ChirpWriter::encodeBandwidth(QTextStream &stream, FMChannel *channel, const ErrorStack &err) { Q_UNUSED(err) if (FMChannel::Bandwidth::Narrow == channel->bandwidth()) stream << "," << "NFM"; else stream << "," << "FM"; return true; } ================================================ FILE: lib/chirpformat.hh ================================================ /** @defgroup chrip Import/Export from/to CHIRP CSV format * @ingroup util * * These classes implement the (partial) import and export of FM channels from and to the CHIRP * CSV format. */ #ifndef CHIRPFORMAT_HH #define CHIRPFORMAT_HH #include "errorstack.hh" #include class QTextStream; class Config; class FMChannel; /** Some common constants for the CIRP reader/writer. * @ingroup chirp */ class ChirpFormat { protected: /** Possible values for the "duplex" column. Specifies the frequency offset direction for the * transmit frequency wrt to the receive frequency. */ enum class Duplex { None, ///< No offset at all. That is, the TX and RX frequencies are equal. Positive, ///< Positive frequency offset, TX above RX frequency. Negative, ///< Negative frequency offset, TX below RX frequency. Split, ///< Explicit transmit frequency. Off ///< No transmit frequency specified. Channel is RX only. }; /** Possible CHIRP channel modes. */ enum class Mode { FM, ///< 25kHz FM (still NBFM). NFM, ///< 12.5kHz FM (also NBFM). WFM, ///< 100kHz FM (WBFM, broadcast). AM, ///< AM, usually airband (not supported by qdmr yet). DV, ///< D-STAR (not supported by qdmr yet). DN ///< SystemFusion (not supported by qdmr yet). }; /** Possible modes for transmitting and processing sub tones. */ enum class ToneMode { None, ///< No transmission of subtones. Tone, ///< Transmission of a CTCSS tone. TSQL, ///< CTCSS tone squelch. TSQL_R, ///< Inverted, CTCSS tone squelch. That is, the squelch opens if a specific CTCSS tone is not received. DTCS, ///< Transmission of a DCS code and also DCS squelch. DTCS_R, ///< Transmission of a DCS code and also DCS squelch. Cross ///< More complex setting (see @c CrossMode). }; /** Polarity of DCS codes. */ enum class Polarity { Normal, ///< Normal DCS encoding. Reversed ///< Reversed DCS encoding. }; /** Generic mode for sub tones. There is no reason to use any other mode. This one covers * everything. */ enum class CrossMode { NoneTone, ///< No TX, RX CTCSS NoneDTCS, ///< No TX, RX DCS ToneNone, ///< TX CTCSS, no RX ToneTone, ///< TX CTCSS, RX CTCSS ToneDTCS, ///< TX CTCSS, RX DCS DTCSNone, ///< TX DCS, no RX DTCSTone, ///< TX DCS, RX CTCSS DTCSDTCS ///< TX DCS, RX DCS }; protected: /** Internal set of mandatory headers. */ static const QSet _mandatoryHeaders; /** Internal used set of known headers. */ static const QSet _knownHeaders; /** Mapping of duplex codes. */ static const QHash _duplexCodes; /** Mapping of mode codes. */ static const QHash _modeCodes; /** Mapping of tone mode codes. */ static const QHash _toneModeCodes; /** Mapping of cross mode codes. */ static const QHash _crossModes; }; /** Implements the CHIRP CSV reader. * @ingroup chirp */ class ChirpReader: public ChirpFormat { public: /** Reads a CHIRP CSV file from the given stream and updates the given configuration. * Please note, that the CHIRP generic CSV does not contain a functional DMR codeplug. */ static bool read(QTextStream &stream, Config *config, const ErrorStack &err=ErrorStack()); protected: /** Internal used method to read a line from the given stream. * This method also implements the proper quotation parsing of strings. */ static bool readLine(QTextStream &stream, QStringList &list, const ErrorStack &err=ErrorStack()); /** Line parser, the header must be read before and passed to this method. The parsed channel is * added to the configuration. */ static bool processLine(const QStringList &header, const QStringList &line, Config *config, const ErrorStack &err=ErrorStack()); /** Helper function to parse a duplex column. */ static bool processDuplex(const QString &code, Duplex &duplex, const ErrorStack &err=ErrorStack()); /** Helper function to parse a mode column. */ static bool processMode(const QString &code, Mode &mode, const ErrorStack &err=ErrorStack()); /** Helper function to parse a tone mode column. */ static bool processToneMode(const QString &code, ToneMode &mode, const ErrorStack &err=ErrorStack()); /** Helper function to parse a polarity column. */ static bool processPolarity(const QString &code, Polarity &txPol, Polarity &rxPol, const ErrorStack &err=ErrorStack()); /** Helper function to parse a cross mode column. */ static bool processCrossMode(const QString &code, CrossMode &crossMode, const ErrorStack &err = ErrorStack()); }; /** Implements the chirp writer. * @ingroup chirp */ class ChirpWriter: public ChirpFormat { public: /** Writes the (FM channels) of the given codeplug as a CHIRP CSV file into the given stream. */ static bool write(QTextStream &stream, Config *config, const ErrorStack &err=ErrorStack()); protected: /** Writes the header into the given stream. */ static bool writeHeader(QTextStream &stream, const ErrorStack &err = ErrorStack()); /** Writes a channel into the given stream. */ static bool writeChannel(QTextStream &stream, int i, FMChannel *channel, const ErrorStack &err=ErrorStack()); /** Writes frequency related columns to the given stream. */ static bool encodeFrequency(QTextStream &stream, FMChannel *channel, const ErrorStack &err = ErrorStack()); /** Writes sub tone related columns to the given stream. */ static bool encodeSubTone(QTextStream &stream, FMChannel *channel, const ErrorStack &err = ErrorStack()); /** Writes the bandwidth column to the given stream */ static bool encodeBandwidth(QTextStream &stream, FMChannel *channel, const ErrorStack &err = ErrorStack()); }; #endif // CHIRPFORMAT_HH ================================================ FILE: lib/codeplug.cc ================================================ #include "codeplug.hh" #include "config.hh" #include #include "logger.hh" #include "roamingchannel.hh" #include "configcopyvisitor.hh" /* ********************************************************************************************* * * Implementation of CodePlug::Flags * ********************************************************************************************* */ Codeplug::Flags::Flags() : TransferFlags(), _updateCodeplug(true), _autoEnableGPS(false), _autoEnableRoaming(false) { // pass... } bool Codeplug::Flags::updateCodeplug() const { return _updateCodeplug; } void Codeplug::Flags::setUpdateCodeplug(bool enable) { _updateCodeplug = enable; } bool Codeplug::Flags::autoEnableGPS() const { return _autoEnableGPS; } void Codeplug::Flags::setAutoEnableGPS(bool enable) { _autoEnableGPS = enable; } bool Codeplug::Flags::autoEnableRoaming() const { return _autoEnableRoaming; } void Codeplug::Flags::setAutoEnableRoaming(bool enable) { _autoEnableRoaming = enable; } /* ********************************************************************************************* * * Implementation of CodePlug::Element * ********************************************************************************************* */ Codeplug::Element::Element(uint8_t *ptr, size_t size) : _data(ptr), _size(size) { // pass... } Codeplug::Element::Element(const Element &other) : _data(other._data), _size(other._size) { // pass... } Codeplug::Element::~Element() { // pass... } Codeplug::Element & Codeplug::Element::operator =(const Element &other) { this->_data = other._data; this->_size = other._size; return *this; } bool Codeplug::Element::isValid() const { return nullptr != _data; } void Codeplug::Element::clear() { // pass... } bool Codeplug::Element::fill(uint8_t value, unsigned offset, int size) { if (0 > size) size = _size-offset; if ((offset+size) > _size) { logFatal() << "Cannot fill codeplug element from " << QString::number(offset,16) << " size " << QString::number(size, 16) << ": overflow."; return false; } memset(_data+offset, value, size); return true; } bool Codeplug::Element::getBit(const Offset::Bit &offset) const { return getBit(offset.byte, offset.bit); } bool Codeplug::Element::getBit(unsigned offset, unsigned bit) const { if (offset >= _size) { logFatal() << "Cannot get bit at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return false; } uint8_t *ptr = (_data+offset); return (1<= _size) { logFatal() << "Cannot set bit at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return; } uint8_t *ptr = (_data+offset); if (value) (*ptr) |= (1<= _size) { logFatal() << "Cannot clear bit at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return; } uint8_t *ptr = (_data+offset); (*ptr) &= ~(1<= _size) { logFatal() << "Cannot get uint2 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return 0; } return (((*(_data+offset)) >> bit) & 0b11); } void Codeplug::Element::setUInt2(unsigned offset, unsigned bit, uint8_t value) { if (offset >= _size) { logFatal() << "Cannot set uint2 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return; } *(_data+offset) &= ~(0b11 << bit); *(_data+offset) |= ((value & 0b11)<= _size) { logFatal() << "Cannot get uint3 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return 0; } return (((*(_data+offset)) >> bit) & 0b111); } void Codeplug::Element::setUInt3(unsigned offset, unsigned bit, uint8_t value) { if (offset >= _size) { logFatal() << "Cannot set uint3 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return; } *(_data+offset) &= ~(0b111 << bit); *(_data+offset) |= ((value & 0b111)<= _size) { logFatal() << "Cannot get uint4 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return 0; } return (((*(_data+offset)) >> bit) & 0b1111); } void Codeplug::Element::setUInt4(unsigned offset, unsigned bit, uint8_t value) { if (offset >= _size) { logFatal() << "Cannot set uint4 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return; } *(_data+offset) &= ~(0b1111 << bit); *(_data+offset) |= ((value & 0b1111)<= _size) { logFatal() << "Cannot get uint5 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return 0; } return (((*(_data+offset)) >> bit) & 0b11111); } void Codeplug::Element::setUInt5(unsigned offset, unsigned bit, uint8_t value) { if (offset >= _size) { logFatal() << "Cannot get uint5 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return; } *(_data+offset) &= ~(0b11111 << bit); *(_data+offset) |= ((value & 0b11111)<= _size) { logFatal() << "Cannot get uint6 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return 0; } return (((*(_data+offset)) >> bit) & 0b111111); } void Codeplug::Element::setUInt6(unsigned offset, unsigned bit, uint8_t value) { if (offset >= _size) { logFatal() << "Cannot set uint6 at " << QString::number(offset, 16) << " bit " << bit << ": Overflow."; return; } *(_data+offset) &= ~(0b111111 << bit); *(_data+offset) |= ((value & 0b111111)<= _size) { logFatal() << "Cannot get uint8 at " << QString::number(offset, 16) << ": Overflow."; return 0; } return _data[offset]; } void Codeplug::Element::setUInt8(unsigned offset, uint8_t value) { if (offset >= _size) { logFatal() << "Cannot set uint8 at " << QString::number(offset, 16) << ": Overflow."; return; } _data[offset] = value; } int8_t Codeplug::Element::getInt8(unsigned offset) const { if (offset >= _size) { logFatal() << "Cannot get int8 at " << QString::number(offset, 16) << ": Overflow."; return 0; } return ((int8_t *)_data)[offset]; } void Codeplug::Element::setInt8(unsigned offset, int8_t value) { if (offset >= _size) { logFatal() << "Cannot set int8 at " << QString::number(offset, 16) << ": Overflow."; return; } ((int8_t *)_data)[offset] = value; } uint16_t Codeplug::Element::getUInt16_be(unsigned offset) const { if ((offset+2) > _size) { logFatal() << "Cannot get int16 (be) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint16_t *ptr = (uint16_t *)(_data+offset); return qFromBigEndian(*ptr); } uint16_t Codeplug::Element::getUInt16_le(unsigned offset) const { if ((offset+2) > _size) { logFatal() << "Cannot get int16 (le) at " << QString::number(offset, 16) << ": Overflow."; return 0; } quint16 *ptr = (quint16 *)(_data+offset); return qFromLittleEndian(*ptr); } void Codeplug::Element::setUInt16_be(unsigned offset, uint16_t value) { if ((offset+2) > _size) { logFatal() << "Cannot set int16 (be) at " << QString::number(offset, 16) << ": Overflow."; return; } quint16 *ptr = (quint16 *)(_data+offset); (*ptr) = qToBigEndian((quint16)value); } void Codeplug::Element::setUInt16_le(unsigned offset, uint16_t value) { if ((offset+2) > _size) { logFatal() << "Cannot set int16 (le) at " << QString::number(offset, 16) << ": Overflow."; return; } quint16 *ptr = (quint16 *)(_data+offset); (*ptr) = qToLittleEndian(value); } uint32_t Codeplug::Element::getUInt24_be(unsigned offset) const { if ((offset+3) > _size) { logFatal() << "Cannot get int24 (be) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint8_t *ptr = _data+offset; return uint32_t(ptr[2]) + (uint32_t(ptr[1])<<8) + (uint32_t(ptr[0])<<16); } uint32_t Codeplug::Element::getUInt24_le(unsigned offset) const { if ((offset+3) > _size) { logFatal() << "Cannot get int24 (le) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint8_t *ptr = _data+offset; return uint32_t(ptr[0]) + (uint32_t(ptr[1])<<8) + (uint32_t(ptr[2])<<16); } void Codeplug::Element::setUInt24_be(unsigned offset, uint32_t value) { if ((offset+3) > _size) { logFatal() << "Cannot set int24 (be) at " << QString::number(offset, 16) << ": Overflow."; return; } uint8_t *ptr = _data+offset; ptr[0] = ((value >> 16) & 0xff); ptr[1] = ((value >> 8) & 0xff); ptr[2] = ((value >> 0) & 0xff); } void Codeplug::Element::setUInt24_le(unsigned offset, uint32_t value) { if ((offset+3) > _size) { logFatal() << "Cannot set int24 (le) at " << QString::number(offset, 16) << ": Overflow."; return; } uint8_t *ptr = _data+offset; ptr[0] = ((value >> 0) & 0xff); ptr[1] = ((value >> 8) & 0xff); ptr[2] = ((value >> 16) & 0xff); } uint32_t Codeplug::Element::getUInt32_be(unsigned offset) const { if ((offset+4) > _size) { logFatal() << "Cannot get int32 (be) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint32_t *ptr = (uint32_t *)(_data+offset); return qFromBigEndian(*ptr); } uint32_t Codeplug::Element::getUInt32_le(unsigned offset) const { if ((offset+4) > _size) { logFatal() << "Cannot get int32 (le) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint32_t *ptr = (uint32_t *)(_data+offset); return qFromLittleEndian(*ptr); } void Codeplug::Element::setUInt32_be(unsigned offset, uint32_t value) { if ((offset+4) > _size) { logFatal() << "Cannot set int32 (be) at " << QString::number(offset, 16) << ": Overflow."; return; } uint32_t *ptr = (uint32_t *)(_data+offset); (*ptr) = qToBigEndian(value); } void Codeplug::Element::setUInt32_le(unsigned offset, uint32_t value) { if ((offset+4) > _size) { logFatal() << "Cannot set int32 (le) at " << QString::number(offset, 16) << ": Overflow."; return; } uint32_t *ptr = (uint32_t *)(_data+offset); (*ptr) = qToLittleEndian(value); } uint64_t Codeplug::Element::getUInt64_be(unsigned offset) const { if ((offset+8) > _size) { logFatal() << "Cannot get int64 (be) at " << QString::number(offset, 16) << ": Overflow."; return 0; } quint64 *ptr = (quint64 *)(_data+offset); return qFromBigEndian(*ptr); } uint64_t Codeplug::Element::getUInt64_le(unsigned offset) const { if ((offset+8) > _size) { logFatal() << "Cannot get int64 (le) at " << QString::number(offset, 16) << ": Overflow."; return 0; } quint64 *ptr = (quint64 *)(_data+offset); return qFromLittleEndian(*ptr); } void Codeplug::Element::setUInt64_be(unsigned offset, uint64_t value) { if ((offset+8) > _size) { logFatal() << "Cannot set int64 (be) at " << QString::number(offset, 16) << ": Overflow."; return; } quint64 *ptr = (quint64 *)(_data+offset); (*ptr) = qToBigEndian(value); } void Codeplug::Element::setUInt64_le(unsigned offset, uint64_t value) { if ((offset+8) > _size) { logFatal() << "Cannot set int64 (le) at " << QString::number(offset, 16) << ": Overflow."; return; } quint64 *ptr = (quint64 *)(_data+offset); (*ptr) = qToLittleEndian(value); } uint8_t Codeplug::Element::getBCD2(unsigned offset) const { if ((offset+1) > _size) { logFatal() << "Cannot get BCD2 at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint8_t val = getUInt8(offset); return (val & 0xf) + ((val>>4) & 0xf)*10; } void Codeplug::Element::setBCD2(unsigned offset, uint8_t val) { if ((offset+1) > _size) { logFatal() << "Cannot get BCD2 at " << QString::number(offset, 16) << ": Overflow."; return; } uint8_t a = (val / 10) % 10; uint8_t b = (val / 1) % 10; setUInt8(offset, (a << 4) + b); } uint16_t Codeplug::Element::getBCD4_be(unsigned offset) const { if ((offset+2) > _size) { logFatal() << "Cannot get BCD4 (be) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint32_t val = getUInt16_be(offset); return (val & 0xf) + ((val>>4) & 0xf)*10 + ((val>>8) & 0xf)*100 + ((val>>12) & 0xf)*1000; } void Codeplug::Element::setBCD4_be(unsigned offset, uint16_t val) { if ((offset+2) > _size) { logFatal() << "Cannot set BCD4 (be) at " << QString::number(offset, 16) << ": Overflow."; return; } uint32_t a = (val / 1000) % 10; uint32_t b = (val / 100) % 10; uint32_t c = (val / 10) % 10; uint32_t d = (val / 1) % 10; setUInt16_be(offset, (a << 12) + (b << 8) + (c << 4) + d); } uint16_t Codeplug::Element::getBCD4_le(unsigned offset) const { if ((offset+2) > _size) { logFatal() << "Cannot get BCD4 (le) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint32_t val = getUInt16_le(offset); return (val & 0xf) + ((val>>4) & 0xf)*10 + ((val>>8) & 0xf)*100 + ((val>>12) & 0xf)*1000; } void Codeplug::Element::setBCD4_le(unsigned offset, uint16_t val) { if ((offset+2) > _size) { logFatal() << "Cannot set BCD4 (le) at " << QString::number(offset, 16) << ": Overflow."; return; } uint32_t a = (val / 1000) % 10; uint32_t b = (val / 100) % 10; uint32_t c = (val / 10) % 10; uint32_t d = (val / 1) % 10; setUInt16_le(offset, (a << 12) + (b << 8) + (c << 4) + d); } uint32_t Codeplug::Element::getBCD8_be(unsigned offset) const { if ((offset+4) > _size) { logFatal() << "Cannot get BCD8 (be) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint32_t val = getUInt32_be(offset); return (val & 0xf) + ((val>>4) & 0xf)*10 + ((val>>8) & 0xf)*100 + ((val>>12) & 0xf)*1000 + ((val>>16) & 0xf)*10000 + ((val>>20) & 0xf)*100000 + ((val>>24) & 0xf)*1000000 + ((val>>28) & 0xf)*10000000; } void Codeplug::Element::setBCD8_be(unsigned offset, uint32_t val) { if ((offset+4) > _size) { logFatal() << "Cannot set BCD8 (be) at " << QString::number(offset, 16) << ": Overflow."; return; } uint32_t a = (val / 10000000) % 10; uint32_t b = (val / 1000000) % 10; uint32_t c = (val / 100000) % 10; uint32_t d = (val / 10000) % 10; uint32_t e = (val / 1000) % 10; uint32_t f = (val / 100) % 10; uint32_t g = (val / 10) % 10; uint32_t h = (val / 1) % 10; setUInt32_be(offset, (a << 28) + (b << 24) + (c << 20) + (d << 16) + (e << 12) + (f << 8) + (g << 4) + h); } uint32_t Codeplug::Element::getBCD8_le(unsigned offset) const { if ((offset+4) > _size) { logFatal() << "Cannot get BCD8 (le) at " << QString::number(offset, 16) << ": Overflow."; return 0; } uint32_t val = getUInt32_le(offset); return (val & 0xf) + ((val>>4) & 0xf)*10 + ((val>>8) & 0xf)*100 + ((val>>12) & 0xf)*1000 + ((val>>16) & 0xf)*10000 + ((val>>20) & 0xf)*100000 + ((val>>24) & 0xf)*1000000 + ((val>>28) & 0xf)*10000000; } void Codeplug::Element::setBCD8_le(unsigned offset, uint32_t val) { if ((offset+4) > _size) { logFatal() << "Cannot set BCD8 (le) at " << QString::number(offset, 16) << ": Overflow."; return; } uint32_t a = (val / 10000000) % 10; uint32_t b = (val / 1000000) % 10; uint32_t c = (val / 100000) % 10; uint32_t d = (val / 10000) % 10; uint32_t e = (val / 1000) % 10; uint32_t f = (val / 100) % 10; uint32_t g = (val / 10) % 10; uint32_t h = (val / 1) % 10; setUInt32_le(offset, (a << 28) + (b << 24) + (c << 20) + (d << 16) + (e << 12) + (f << 8) + (g << 4) + h); } QString Codeplug::Element::readASCII(unsigned offset, unsigned maxlen, uint8_t eos) const { QString txt; uint8_t *ptr = (uint8_t *)(_data+offset); for (unsigned i=0; (iclassName())) return true; if (exact) return false; if (obj->superClass()) return hasTable(obj->superClass(), false); return false; } Codeplug::Context::Table & Codeplug::Context::getTable(const QMetaObject *obj) { if (_tables.contains(obj->className())) return _tables[obj->className()]; return getTable(obj->superClass()); } bool Codeplug::Context::addTable(const QMetaObject *obj) { if (hasTable(obj, true)) return false; _tables.insert(obj->className(), Table()); return true; } bool Codeplug::Context::remTable(const QMetaObject *obj, bool exact) { if (! hasTable(obj, exact)) return false; if (_tables.contains(obj->className())) return _tables.remove(obj->className()); return remTable(obj->superClass()); } ConfigItem * Codeplug::Context::obj(const QMetaObject *elementType, unsigned idx, bool exact) { if (! hasTable(elementType, exact)) return nullptr; return getTable(elementType).objects.value(idx, nullptr); } int Codeplug::Context::index(ConfigItem *obj) { if (nullptr == obj) return -1; if (! hasTable(obj->metaObject())) return -1; return getTable(obj->metaObject()).indices.value(obj, -1); } bool Codeplug::Context::add(ConfigItem *obj, unsigned idx) { if (! hasTable(obj->metaObject())) return false; if (! getTable(obj->metaObject()).indices.contains(obj)) getTable(obj->metaObject()).indices.insert(obj, idx); if (! getTable(obj->metaObject()).objects.contains(idx)) getTable(obj->metaObject()).objects.insert(idx, obj); return true; } /* ********************************************************************************************* * * Implementation of CodePlug * ********************************************************************************************* */ Codeplug::Codeplug(QObject *parent) : DFUFile(parent) { // pass... } Codeplug::~Codeplug() { // pass... } Config * Codeplug::preprocess(Config *config, const ErrorStack &err) const { return ConfigCopy::copy(config, err)->as(); } bool Codeplug::postprocess(Config *config, const ErrorStack &err) const { Q_UNUSED(config); Q_UNUSED(err); return true; } ================================================ FILE: lib/codeplug.hh ================================================ #ifndef CODEPLUG_HH #define CODEPLUG_HH #include #include #include "dfufile.hh" #include "transferflags.hh" class Config; class ConfigItem; class SatelliteDatabase; /** This class defines the interface all device-specific code-plugs must implement. * Device-specific codeplugs are derived from the common configuration and implement the * construction/parsing of the device specific binary configuration. */ class Codeplug: public DFUFile { Q_OBJECT public: /** Certain flags passed to CodePlug::encode to control the transfer and encoding of the * codeplug. */ class Flags: public TransferFlags { public: /** Default constructor, enables code-plug update and disables automatic GPS/APRS and roaming. */ Flags(); public: /** If @c true, the codeplug will first be downloaded from the device, updated from the * abstract config and then written back to the device. This maintains the user-settings * made within the device or manufacturer CPS. If @c false, the code-plug gets overridden * entirely using some default settings. Default @c true. */ bool updateCodeplug() const; /** Sets if the codeplug gets updated. */ void setUpdateCodeplug(bool enable); /** If @c true enables GPS when there is a GPS or APRS system defined that is used by any * channel. This may cause automatic transmissions, hence the default is @c false. */ bool autoEnableGPS() const; /** Sets if the GPS gets enabled automatically. */ void setAutoEnableGPS(bool enable); /** If @c true enables automatic roaming when there is a roaming zone defined that is used by any * channel. This may cause automatic transmissions, hence the default is @c false. */ bool autoEnableRoaming() const; /** Sets if roaming gets enabled automatically. */ void setAutoEnableRoaming(bool enable); protected: bool _updateCodeplug; bool _autoEnableGPS; bool _autoEnableRoaming; }; /** Represents the abstract base class of all codeplug elements. * * That is a memory region within the codeplug that encodes a specific element. E.g., channels, * contacts, zones, etc. This class provides some helper methods to access specific members of * the element. * * @since 0.9.0 */ class Element { protected: /** Base class for Offsets. */ struct Offset { /** Some type to specify a bit offset. That is the byte-offset and bit of that byte. */ struct Bit { /** The byte offset. */ const unsigned int byte; /** The bit within the byte. */ const unsigned int bit; /** Implements a simple increment. */ inline Bit operator+ (unsigned int bits) const { unsigned int tmp = 8 * byte + (7-bit) + bits; return {tmp/8, (7 - (tmp % 8))}; } /** Implements a simple increment. */ inline Bit operator- (unsigned int bits) const { unsigned int tmp = 8 * byte + (7-bit) - bits; return {tmp/8, (7 - (tmp % 8))}; } }; }; public: /** Base class for Limits. */ struct Limit { /** Holds a range of values [min, max]. */ template struct Range { /// Lower bound. const T min; /// Upper bound. const T max; /// Limits the value to the range. inline T limit(const T &value) const { return std::min(max, std::max(min, value)); } /** Checks if value is in range. */ inline bool in(const T &value) const { return (value <= max) && (value >= min); } /** Maps a value from this range to the given range. */ inline T mapTo(const Range &other, const T &value) const { T myD = max-min, oD = other.max-other.min; return ((limit(value)-min)*oD)/myD + other.min; } }; }; protected: /** Hidden constructor. * @param ptr Specifies the pointer to the element within the codeplug. * @param size Specifies the size of the element in bytes. */ Element(uint8_t *ptr, size_t size); public: /** Copy constructor. */ Element(const Element &other); /** Destructor. */ virtual ~Element(); /** Copy assignment. */ Element &operator=(const Element &other); /** Returns @c true if the pointer is not null. */ virtual bool isValid() const; /** Abstract method to reset the element within the codeplug. Any device specific element * should implement this method. */ virtual void clear(); /** Fills memsets the entire element to the given value. */ bool fill(uint8_t value, unsigned offset=0, int size=-1); /** Reads a specific bit at the given byte-offset. */ bool getBit(const Offset::Bit &offset) const; /** Reads a specific bit at the given byte-offset. */ bool getBit(unsigned offset, unsigned bit) const; /** Sets a specific bit at the given byte-offset. */ void setBit(const Offset::Bit &offset, bool value=true); /** Sets a specific bit at the given byte-offset. */ void setBit(unsigned offset, unsigned bit, bool value=true); /** Clears a specific bit at the given byte-offset. */ void clearBit(unsigned offset, unsigned bit); /** Clears a specific bit. */ void clearBit(const Offset::Bit &offset); /** Reads a 2bit unsigned integer at the given bit-offset. */ uint8_t getUInt2(const Offset::Bit &offset) const; /** Reads a 2bit unsigned integer at the given byte- and bit-offset. */ uint8_t getUInt2(unsigned offset, unsigned bit) const; /** Stores a 2bit unsigned integer at the given bit-offset. */ void setUInt2(const Offset::Bit &offset, uint8_t value); /** Stores a 2bit unsigned integer at the given byte- and bit-offset. */ void setUInt2(unsigned offset, unsigned bit, uint8_t value); /** Reads a 3bit unsigned integer at the given bit-offset. */ uint8_t getUInt3(const Offset::Bit &offset) const; /** Reads a 3bit unsigned integer at the given byte- and bit-offset. */ uint8_t getUInt3(unsigned offset, unsigned bit) const; /** Stores a 3bit unsigned integer at the given bit-offset. */ void setUInt3(const Offset::Bit &offset, uint8_t value); /** Stores a 3bit unsigned integer at the given byte- and bit-offset. */ void setUInt3(unsigned offset, unsigned bit, uint8_t value); /** Reads a 4bit unsigned integer at the given bit-offset. */ uint8_t getUInt4(const Offset::Bit &offset) const; /** Reads a 4bit unsigned integer at the given byte- and bit-offset. */ uint8_t getUInt4(unsigned offset, unsigned bit) const; /** Stores a 4bit unsigned integer at the given bit-offset. */ void setUInt4(const Offset::Bit &offset, uint8_t value); /** Stores a 4bit unsigned integer at the given byte- and bit-offset. */ void setUInt4(unsigned offset, unsigned bit, uint8_t value); /** Reads a 5bit unsigned integer at the given byte- and bit-offset. */ uint8_t getUInt5(const Offset::Bit &offset) const; /** Reads a 5bit unsigned integer at the given byte- and bit-offset. */ uint8_t getUInt5(unsigned offset, unsigned bit) const; /** Stores a 5bit unsigned integer at the given byte- and bit-offset. */ void setUInt5(const Offset::Bit &offset, uint8_t value); /** Stores a 5bit unsigned integer at the given byte- and bit-offset. */ void setUInt5(unsigned offset, unsigned bit, uint8_t value); /** Reads a 6bit unsigned integer at the given byte- and bit-offset. */ uint8_t getUInt6(const Offset::Bit &offset) const; /** Reads a 6bit unsigned integer at the given byte- and bit-offset. */ uint8_t getUInt6(unsigned offset, unsigned bit) const; /** Stores a 6bit unsigned integer at the given byte- and bit-offset. */ void setUInt6(const Offset::Bit &offset, uint8_t value); /** Stores a 6bit unsigned integer at the given byte- and bit-offset. */ void setUInt6(unsigned offset, unsigned bit, uint8_t value); /** Reads a 8bit unsigned integer at the given byte- and bit-offset. */ uint8_t getUInt8(unsigned offset) const; /** Reads a 8bit unsigned integer at the given byte- and bit-offset. */ void setUInt8(unsigned offset, uint8_t value); /** Reads a 8bit signed integer at the given byte- and bit-offset. */ int8_t getInt8(unsigned offset) const; /** Reads a 8bit signed integer at the given byte- and bit-offset. */ void setInt8(unsigned offset, int8_t value); /** Reads a 16bit big-endian unsigned integer at the given byte-offset. */ uint16_t getUInt16_be(unsigned offset) const; /** Reads a 16bit little-endian unsigned integer at the given byte-offset. */ uint16_t getUInt16_le(unsigned offset) const; /** Stores a 16bit big-endian unsigned integer at the given byte-offset. */ void setUInt16_be(unsigned offset, uint16_t value); /** Stores a 16bit little-endian unsigned integer at the given byte-offset. */ void setUInt16_le(unsigned offset, uint16_t value); /** Reads a 24bit big-endian unsigned integer at the given byte-offset. */ uint32_t getUInt24_be(unsigned offset) const; /** Reads a 24bit little-endian unsigned integer at the given byte-offset. */ uint32_t getUInt24_le(unsigned offset) const; /** Stores a 24bit big-endian unsigned integer at the given byte-offset. */ void setUInt24_be(unsigned offset, uint32_t value); /** Stores a 24bit little-endian unsigned integer at the given byte-offset. */ void setUInt24_le(unsigned offset, uint32_t value); /** Reads a 32bit big-endian unsigned integer at the given byte-offset. */ uint32_t getUInt32_be(unsigned offset) const; /** Reads a 32bit little-endian unsigned integer at the given byte-offset. */ uint32_t getUInt32_le(unsigned offset) const; /** Stores a 32bit big-endian unsigned integer at the given byte-offset. */ void setUInt32_be(unsigned offset, uint32_t value); /** Stores a 32bit little-endian unsigned integer at the given byte-offset. */ void setUInt32_le(unsigned offset, uint32_t value); /** Reads a 64bit big-endian unsigned integer at the given byte-offset. */ uint64_t getUInt64_be(unsigned offset) const; /** Reads a 64bit little-endian unsigned integer at the given byte-offset. */ uint64_t getUInt64_le(unsigned offset) const; /** Stores a 64bit big-endian unsigned integer at the given byte-offset. */ void setUInt64_be(unsigned offset, uint64_t value); /** Stores a 64bit little-endian unsigned integer at the given byte-offset. */ void setUInt64_le(unsigned offset, uint64_t value); /** Reads a 2-digit (1-byte/8bit) BDC value in big-endian at the given byte-offset. */ uint8_t getBCD2(unsigned offset) const; /** Stores a 2-digit (1-byte/8bit) BDC value in big-endian at the given byte-offset. */ void setBCD2(unsigned offset, uint8_t value); /** Reads a 4-digit (2-byte/16bit) BDC value in big-endian at the given byte-offset. */ uint16_t getBCD4_be(unsigned offset) const; /** Stores a 4-digit (2-byte/16bit) BDC value in big-endian at the given byte-offset. */ void setBCD4_be(unsigned offset, uint16_t value); /** Reads a 4-digit (2-byte/16bit) BDC value in little-endian at the given byte-offset. */ uint16_t getBCD4_le(unsigned offset) const; /** Stores a 4-digit (1-byte/16bit) BDC value in little-endian at the given byte-offset. */ void setBCD4_le(unsigned offset, uint16_t value); /** Reads a 8-digit (4-byte/32bit) BDC value in big-endian at the given byte-offset. */ uint32_t getBCD8_be(unsigned offset) const; /** Stores a 8-digit (4-byte/32bit) BDC value in big-endian at the given byte-offset. */ void setBCD8_be(unsigned offset, uint32_t value); /** Reads a 8-digit (4-byte/32bit) BDC value in little-endian at the given byte-offset. */ uint32_t getBCD8_le(unsigned offset) const; /** Stores a 8-digit (4-byte/32bit) BDC value in little-endian at the given byte-offset. */ void setBCD8_le(unsigned offset, uint32_t value); /** Reads up to @c maxlen ASCII chars at the given byte-offset using @c eos as the string termination char. */ QString readASCII(unsigned offset, unsigned maxlen, uint8_t eos=0x00) const; /** Stores up to @c maxlen ASCII chars at the given byte-offset using @c eos as the string termination char. * The stored string gets padded with @c eos to @c maxlen. */ void writeASCII(unsigned offset, const QString &txt, unsigned maxlen, uint8_t eos=0x00); /** Reads up to @c maxlen unicode chars at the given byte-offset using @c eos as the string termination char. */ QString readUnicode(unsigned offset, unsigned maxlen, uint16_t eos=0x0000) const; /** Stores up to @c maxlen unicode chars at the given byte-offset using @c eos as the string termination char. * The stored string gets padded with @c eos to @c maxlen. */ void writeUnicode(unsigned offset, const QString &txt, unsigned maxlen, uint16_t eos=0x0000); protected: /** Holds the pointer to the element. */ uint8_t *_data; /** Holds the size of the element. */ size_t _size; }; /** Represents the base class for bitmaps in codeplugs. */ class BitmapElement: public Element { protected: /** Hidden constructor. */ BitmapElement(uint8_t *ptr, size_t size); public: /** Clears the bitmap, disables all channels. */ void clear(); /** Returns @c true if the given index is valid. */ virtual bool isEncoded(unsigned int idx) const ; /** Enables/disables the specified index. */ virtual void setEncoded(unsigned int idx, bool enable); /** Enables the first n elements. */ virtual void enableFirst(unsigned int n); }; /** Represents the base class for inverted bitmaps in codeplugs. */ class InvertedBitmapElement: public Element { protected: /** Hidden constructor. */ InvertedBitmapElement(uint8_t *ptr, size_t size); public: /** Clears the bitmap, disables all channels. */ void clear(); /** Returns @c true if the given index is valid. */ virtual bool isEncoded(unsigned int idx) const ; /** Enables/disables the specified index. */ virtual void setEncoded(unsigned int idx, bool enable); /** Enables the first n elements. */ virtual void enableFirst(unsigned int n); }; /** Base class for all codeplug contexts. * Each device specific codeplug may extend this class to allow for device specific elements to * be indexed in a separate index. By default tables for @c DigitalContact, @c RXGroupList, * @c Channel, @c Zone and @c ScanList are defined. For any other type, an additional table must * be defined first using @c addTable. * @since 0.9.0 */ class Context { public: /** Empty constructor. */ explicit Context(Config *config); /** Returns the reference to the config object. */ Config *config() const; /** Returns a reference to the satellite database. */ SatelliteDatabase *satellites() const; /** Resolves the given index for the specifies element type. * @returns @c nullptr if the index is not defined or the type is unknown. */ ConfigItem *obj(const QMetaObject *elementType, unsigned idx, bool exact=false); /** Returns the index for the given object. * @returns -1 if no index is associated with the object or its type is unknown. */ int index(ConfigItem *obj); /** Associates the given object with the given index. */ bool add(ConfigItem *obj, unsigned idx); /** Adds a table for the given type. */ bool addTable(const QMetaObject *obj); /** Returns @c true if a table is defined for the given type. */ bool hasTable(const QMetaObject *obj, bool exact=false) const; /** Deletes a table. */ bool remTable(const QMetaObject *obj, bool exact=false); /** Returns the object associated by the given index and type. */ template T* get(unsigned idx) { return this->obj(&(T::staticMetaObject), idx)->template as(); } /** Returns @c true, if the given index is defined for the specified type. */ template bool has(unsigned idx) { return nullptr != this->obj(&(T::staticMetaObject), idx)->template as(); } /** Returns the number of elements for the specified type. */ template unsigned int count(bool exact=true) { if (! hasTable(&T::staticMetaObject, exact)) return 0; return getTable(&T::staticMetaObject).indices.size(); } protected: /** Internal used table type to associate objects and indices. */ class Table { public: /** The index->object map. */ QHash objects; /** The object->index map. */ QHash indices; }; protected: /** Returns a reference to the table for the given type. */ Table &getTable(const QMetaObject *obj); protected: /** A weak reference to the config object. */ Config *_config; /** A weak reference to the satellite database. */ SatelliteDatabase *_satellites; /** Table of tables. */ QHash _tables; }; protected: /** Hidden default constructor. */ explicit Codeplug(QObject *parent=nullptr); public: /** Destructor. */ virtual ~Codeplug(); /** Indexes all elements of the codeplug. * This method must be implemented by any device or vendor specific codeplug to map config * objects to indices used within the binary codeplug to address each element (e.g., channels, * contacts etc.). */ virtual bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const = 0; /** Decodes a binary codeplug to the given abstract configuration @c config. * This must be implemented by the device-specific codeplug. */ virtual bool decode(Config *config, const ErrorStack &err=ErrorStack()) = 0; /** Returns a post-processed configuration of the decoded config. By default, the passed * config is returned. */ virtual bool postprocess(Config *config, const ErrorStack &err=ErrorStack()) const; /** Returns a prepared configuration for this particular radio. All unsupported features are * removed from the copy. The default implementation only copies the config. */ virtual Config *preprocess(Config *config, const ErrorStack &err=ErrorStack()) const; /** Encodes a given abstract configuration (@c config) to the device specific binary code-plug. * This must be implemented by the device-specific codeplug. */ virtual bool encode(Config *config, const Flags &flags=Flags(), const ErrorStack &err=ErrorStack()) = 0; }; #endif // CODEPLUG_HH ================================================ FILE: lib/commercial_extension.cc ================================================ #include "commercial_extension.hh" #include "encryptionextension.hh" /* ********************************************************************************************* * * Implementation of CommercialExtension * ********************************************************************************************* */ CommercialExtension::CommercialExtension(QObject *parent) : ConfigExtension(parent), _encryptionKeys(new EncryptionKeys(this)) { // pass... } ConfigItem * CommercialExtension::clone() const { CommercialExtension *ext = new CommercialExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } EncryptionKeys * CommercialExtension::encryptionKeys() const { return _encryptionKeys; } /* ********************************************************************************************* * * Implementation of CommercialChannelExtension * ********************************************************************************************* */ CommercialChannelExtension::CommercialChannelExtension(QObject *parent) : ConfigExtension(parent), _encryptionKey() { // pass... } ConfigItem * CommercialChannelExtension::clone() const { CommercialChannelExtension *ex = new CommercialChannelExtension(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } EncryptionKeyReference * CommercialChannelExtension::encryptionKeyRef() { return &_encryptionKey; } EncryptionKey * CommercialChannelExtension::encryptionKey() { return _encryptionKey.as(); } void CommercialChannelExtension::setEncryptionKey(EncryptionKey *key) { if (_encryptionKey.as() == key) return; _encryptionKey.set(key); emit modified(this); } ================================================ FILE: lib/commercial_extension.hh ================================================ #ifndef COMMERCIALEXTENSION_HH #define COMMERCIALEXTENSION_HH #include "configobject.hh" #include "configreference.hh" #include "encryptionextension.hh" /** Implements the generic extension for the codeplug to represent some commercial features of DMR. * @ingroup conf */ class CommercialExtension: public ConfigExtension { Q_OBJECT /** Read only property returning holding the list of encryption keys. */ Q_PROPERTY(EncryptionKeys* encryptionKeys READ encryptionKeys) public: /** Default constructor. */ Q_INVOKABLE explicit CommercialExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the list of encryption keys. */ EncryptionKeys *encryptionKeys() const; protected: /** Owns the instance of the encryption key list. */ EncryptionKeys *_encryptionKeys; }; /** Implements the generic extension for all channels configuring commercial features of DMR. * @ingroup conf */ class CommercialChannelExtension: public ConfigExtension { Q_OBJECT /** Holds a reference to the associated encryption key. */ Q_PROPERTY(EncryptionKeyReference* encryptionKey READ encryptionKeyRef) public: /** Empty constructor. */ Q_INVOKABLE explicit CommercialChannelExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the reference to the encryption key. */ EncryptionKeyReference *encryptionKeyRef(); /** Returns the referenced encryption key. */ EncryptionKey *encryptionKey(); /** References the given encryption key. */ void setEncryptionKey(EncryptionKey *key); protected: /** The actual reference to the the encryption key. */ EncryptionKeyReference _encryptionKey; }; #endif // COMMERCIALEXTENSION_HH ================================================ FILE: lib/config.cc ================================================ #include "config.hh" #include "config.h" #include "rxgrouplist.hh" #include "channel.hh" #include "csvreader.hh" #include "userdatabase.hh" #include "logger.hh" #include #include #include #include #include /* ********************************************************************************************* * * Implementation of Config * ********************************************************************************************* */ Config::Config(QObject *parent) : ConfigItem(parent), _modified(false), _settings(new RadioSettings(this)), _radioIDs(new RadioIDList(this)), _contacts(new ContactList(this)), _rxGroupLists(new RXGroupLists(this)), _channels(new ChannelList(this)), _zones(new ZoneList(this)), _scanlists(new ScanLists(this)), _gpsSystems(new PositionReportingSystems(this)), _roamingChannels(new RoamingChannelList(this)), _roamingZones(new RoamingZoneList(this)), _tytExtension(nullptr), _commercialExtension(new CommercialExtension(this)), _smsExtension(new SMSExtension(this)) { connect(_settings, SIGNAL(modified(ConfigItem*)), this, SLOT(onConfigModified())); connect(_radioIDs, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_radioIDs, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_radioIDs, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_contacts, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_contacts, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_contacts, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_rxGroupLists, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_rxGroupLists, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_rxGroupLists, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_channels, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_channels, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_channels, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_zones, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_zones, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_zones, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_scanlists, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_scanlists, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_scanlists, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_gpsSystems, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_gpsSystems, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_gpsSystems, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_roamingChannels, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_roamingChannels, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_roamingChannels, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_roamingZones, SIGNAL(elementAdded(int)), this, SLOT(onConfigModified())); connect(_roamingZones, SIGNAL(elementRemoved(int)), this, SLOT(onConfigModified())); connect(_roamingZones, SIGNAL(elementModified(int)), this, SLOT(onConfigModified())); connect(_commercialExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onConfigModified())); connect(_smsExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onConfigModified())); } bool Config::copy(const ConfigItem &other) { const Config *conf = other.as(); if ((nullptr==conf) || (! ConfigItem::copy(other))) return false; _settings->copy(*conf->settings()); _radioIDs->copy(*conf->radioIDs()); _contacts->copy(*conf->contacts()); _rxGroupLists->copy(*conf->rxGroupLists()); _channels->copy(*conf->channelList()); _zones->copy(*conf->zones()); _scanlists->copy(*conf->scanlists()); _gpsSystems->copy(*conf->posSystems()); _roamingChannels->copy(*conf->roamingChannels()); _roamingZones->copy(*conf->roamingZones()); return true; } ConfigItem * Config::clone() const { Config *conf = new Config(); if (! conf->copy(*this)) { conf->deleteLater(); return nullptr; } return conf; } bool Config::isModified() const { return _modified; } void Config::setModified(bool modified) { _modified = modified; emit this->modified(this); } bool Config::toYAML(QTextStream &stream, const ErrorStack &err) { ConfigItem::Context context; // Label all codeplug elements if (! this->label(context, err)) return false; // Serialize into YAML YAML::Node doc = serialize(context, err); if (doc.IsNull()) return false; // Print YAML YAML::Emitter emitter; emitter << YAML::BeginDoc << doc << YAML::EndDoc; stream << QString(emitter.c_str()); return true; } bool Config::populate(YAML::Node &node, const Context &context, const ErrorStack &err) { node["version"] = VERSION_STRING; if ((node["settings"]= _settings->serialize(context, err)).IsNull()) return false; if ((node["radioIDs"] = _radioIDs->serialize(context, err)).IsNull()) return false; if ((node["contacts"] = _contacts->serialize(context, err)).IsNull()) return false; if ((node["groupLists"] = _rxGroupLists->serialize(context, err)).IsNull()) return false; if ((node["channels"] = _channels->serialize(context, err)).IsNull()) return false; if ((node["zones"] = _zones->serialize(context, err)).IsNull()) return false; if (_scanlists->count()) { if ((node["scanLists"] = _scanlists->serialize(context, err)).IsNull()) return false; } if (_gpsSystems->count()) { if ((node["positioning"] = _gpsSystems->serialize(context, err)).IsNull()) return false; } if (_roamingChannels->count()) { if ((node["roamingChannels"] = _roamingChannels->serialize(context, err)).IsNull()) return false; } if (_roamingZones->count()) { if ((node["roamingZones"] = _roamingZones->serialize(context, err)).IsNull()) return false; } if (! ConfigItem::populate(node, context, err)) return false; return true; } RadioSettings * Config::settings() const { return _settings; } RadioIDList * Config::radioIDs() const { return _radioIDs; } ContactList * Config::contacts() const { return _contacts; } RXGroupLists * Config::rxGroupLists() const { return _rxGroupLists; } ChannelList * Config::channelList() const { return _channels; } ZoneList * Config::zones() const { return _zones; } ScanLists * Config::scanlists() const { return _scanlists; } PositionReportingSystems * Config::posSystems() const { return _gpsSystems; } RoamingChannelList * Config::roamingChannels() const { return _roamingChannels; } RoamingZoneList * Config::roamingZones() const { return _roamingZones; } bool Config::requiresRoaming() const { // Check is roaming should be enabled bool chHasRoaming = false; for (int i=0; icount(); i++) { const DMRChannel *digi = channelList()->channel(i)->as(); if (nullptr == digi) continue; if (nullptr != digi->roaming()) { chHasRoaming = true; break; } } return chHasRoaming; } bool Config::requiresGPS() const { // Check is GPS should be enabled bool chHasGPS = false; for (int i=0; icount(); i++) { Channel *ch = channelList()->channel(i); // For analog channels if APRS system is set or // for digital channels if any positioning system is set if ( (ch->is() && ch->as()->aprs()) || (ch->is() && ch->as()->aprs()) ) { chHasGPS = true; break; } } return chHasGPS; } void Config::clear() { ConfigItem::clear(); // Reset settings _settings->clear(); // Reset lists _radioIDs->clear(); _contacts->clear(); _rxGroupLists->clear(); _channels->clear(); _zones->clear(); _scanlists->clear(); _gpsSystems->clear(); _roamingChannels->clear(); _roamingZones->clear(); // Clear extensions commercialExtension()->clear(); smsExtension()->clear(); setTyTExtension(nullptr); onConfigModified(); } const Config * Config::config() const { return this; } CommercialExtension * Config::commercialExtension() const { return _commercialExtension; } SMSExtension * Config::smsExtension() const { return _smsExtension; } TyTConfigExtension * Config::tytExtension() const { return _tytExtension; } void Config::setTyTExtension(TyTConfigExtension *ext) { if (_tytExtension == ext) return; if (_tytExtension) _tytExtension->deleteLater(); _tytExtension = ext; if (_tytExtension) { _tytExtension->setParent(this); connect(_tytExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onConfigModified())); onConfigModified(); } } void Config::onConfigModified() { _modified = true; emit modified(this); } bool Config::readCSV(const QString &filename, QString &errorMessage) { QFile file(filename); if (! file.open(QIODevice::ReadOnly)) return false; QTextStream stream(&file); return readCSV(stream, errorMessage); } bool Config::readCSV(QTextStream &stream, QString &errorMessage) { return CSVReader::read(this, stream, errorMessage); } bool Config::readYAML(const QString &filename, const ErrorStack &err) { YAML::Node node; try { QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; errMsg(err) << "Cannot read YAML codeplug from file '" << filename << "'."; return false; } QByteArray content = file.readAll(); node = YAML::Load(content.constData()); } catch (const YAML::Exception &exc) { errMsg(err) << "Cannot read YAML codeplug from file '"<< filename << "': " << QString::fromStdString(exc.msg) << "."; return false; } if (! node) { errMsg(err) << "Cannot read YAML codeplug from file '" << filename << "'."; return false; } clear(); ConfigItem::Context context; if (! parse(node, context, err)) return false; if (! link(node, context, err)) return false; setModified(false); emit modified(this); return true; } bool Config::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node.IsMap()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot read configuration" << ": Element is not a map."; return false; } if (node["version"] && node["version"].IsScalar()) { ctx.setVersion(QString::fromStdString(node["version"].as())); logDebug() << "Using format version " << ctx.version() << "."; } else { logWarn() << "No version string set, assuming " << VERSION_STRING << "."; ctx.setVersion(VERSION_STRING); } if (node["settings"] && (! _settings->parse(node["settings"], ctx, err))) return false; if (node["radioIDs"] && (! _radioIDs->parse(node["radioIDs"], ctx, err))) return false; if (node["contacts"] && (! _contacts->parse(node["contacts"], ctx, err))) return false; if (node["groupLists"] && (! _rxGroupLists->parse(node["groupLists"], ctx, err))) return false; if (node["channels"] && (! _channels->parse(node["channels"], ctx, err))) return false; if (node["zones"] && (! _zones->parse(node["zones"], ctx, err))) return false; if (node["scanLists"] && (! _scanlists->parse(node["scanLists"], ctx, err))) return false; if (node["positioning"] && (! _gpsSystems->parse(node["positioning"], ctx, err))) return false; if (node["roamingChannels"] && (! _roamingChannels->parse(node["roamingChannels"], ctx, err))) return false; if (node["roamingZones"] && (! _roamingZones->parse(node["roamingZones"], ctx, err))) return false; /** @todo Implemented for backward compatibility with version 0.10.0, remove for 1.0.0.*/ else if (node["roaming"] && (! _roamingZones->parse(node["roaming"], ctx, err))) return false; // also parses extensions if (! ConfigItem::parse(node, ctx, err)) return false; return true; } bool Config::link(const YAML::Node &node, const Context &ctx, const ErrorStack &err) { // radio IDs must be linked before settings, as they may refer to the default DMR ID if (node["radioIDs"] && (! _radioIDs->link(node["radioIDs"], ctx, err))) return false; if (node["settings"]) if (!_settings->link(node["settings"], ctx, err)) return false; if (node["contacts"] && (! _contacts->link(node["contacts"], ctx, err))) return false; if (node["groupLists"] && (! _rxGroupLists->link(node["groupLists"], ctx, err))) return false; if (node["channels"] && (! _channels->link(node["channels"], ctx, err))) return false; if (node["zones"] && (! _zones->link(node["zones"], ctx, err))) return false; if (node["scanLists"] && (! _scanlists->link(node["scanLists"], ctx, err))) return false; if (node["positioning"] && (! _gpsSystems->link(node["positioning"], ctx, err))) return false; if (node["roamingZones"] && (! _roamingZones->link(node["roamingZones"], ctx, err))) return false; /** @todo Implemented for backward compatibility with version 0.10.0, remove for 1.0.0.*/ else if (node["roaming"] && (! _roamingZones->link(node["roaming"], ctx, err))) return false; // also links extensions if (! ConfigItem::link(node, ctx, err)) return false; return true; } ================================================ FILE: lib/config.h.in ================================================ #define VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define VERSION_MINOR @PROJECT_VERSION_MINOR@ #define VERSION_PATCH @PROJECT_VERSION_PATCH@ #define VERSION_STRING @PROJECT_VERSION_STRING@ #define LOCALE_DIRECTORY "@LOCALE_DIRECTORY@" ================================================ FILE: lib/config.hh ================================================ /** @defgroup conf Common codeplug configuration * This module collects all classes that represent the general configuration for all DMR codeplugs. * * To this end, it aims at covering the important features for ham radio applications. * All features that are more related to "professional" applications and are specific to each radio, * are implemented by so-called extensions, see @c ConfigExtension. The central class for the * abstract configuration is @c Config, this class represents a complete configuration a.k.a. * codeplug of a radio. It contains all the information being programmed into the radio irrespective * of the model and manufacturer. * * The entire configuration (abstract, device independent codeplug) consists of a tree of * @c ConfigItem instances. This class forms the base-class of all elements in the configuration * (excluding lists etc.). Each @c ConfigItem may have a set of properties. These properties are * used to implement the majority of the common functionality concerning the abstract codeplug. * These are * - Copying & cloning of elements of the configuration. * - Labeling of codeplug objects (everything that has an ID for cross referencing). * - Serialization into YAML (all properties are serialized into YAML automatically if not * prevented by marking the property as not @c SCRIPTABLE). * - Parsing of YAML codeplugs (automatic property parsing can be disabled on a per-property * basis by marking it as not @c SCRIPTABLE). * - Generic editing of the properties in the GUI. * . * To this end, the creation of codeplug extensions is pretty easy, as only the properties for the * extension must be defined. The rest is taken care of by the default implementation of the * @c ConfigItem::copy, @c ConfigItem::clone, @c ConfigItem::label, @c ConfigItem::serialize, * @c ConfigItem::parse and @c ConfigItem::link methods. It is not necessary to override any of * these methods if there is a one-to-one mapping between the property and its YAML representation. * * Frequently, however, it is necessary to represent a property in a different way in YAML. This * is usually true if a property may hold different specializations of a common type. For example * the channel list may hold analog and digital channels. In YAML, the type is specified explicitly * as an enclosing map. This structure is not a one-to-one representation of the actual property * (the channel list) to the YAML format. In these cases, the @c ConfigItem::parse and * @c ConfigItem::link method might be overridden to implement this. */ #ifndef CONFIG_HH #define CONFIG_HH #include #include "configobject.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "channel.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingchannel.hh" #include "roamingzone.hh" #include "radioid.hh" #include "radiosettings.hh" #include "commercial_extension.hh" #include "smsextension.hh" #include "tyt_extensions.hh" // Forward declaration class UserDatabase; /** The config class, representing the codeplug configuration. * * It contains the description of the contacts, channels, zones, etc. of the codeplug * configuration. * * @ingroup conf */ class Config : public ConfigItem { Q_OBJECT /** The global radio settings. */ Q_PROPERTY(RadioSettings* settings READ settings SCRIPTABLE false) /** The list of radio IDs. */ Q_PROPERTY(RadioIDList* radioIDs READ radioIDs SCRIPTABLE false) /** The list of contacts. */ Q_PROPERTY(ContactList* contacts READ contacts SCRIPTABLE false) /** The list of group lists. */ Q_PROPERTY(RXGroupLists* groupLists READ rxGroupLists SCRIPTABLE false) /** The list of channels. */ Q_PROPERTY(ChannelList* channels READ channelList SCRIPTABLE false) /** The list of zones. */ Q_PROPERTY(ZoneList* zones READ zones SCRIPTABLE false) /** The list of scan lists. */ Q_PROPERTY(ScanLists* scanLists READ scanlists SCRIPTABLE false) /** The list of positioning systems. */ Q_PROPERTY(PositionReportingSystems* positioning READ posSystems SCRIPTABLE false) /** The list of roaming channels. */ Q_PROPERTY(RoamingChannelList* roamingChannels READ roamingChannels SCRIPTABLE false) /** The list of roaming zones. */ Q_PROPERTY(RoamingZoneList* roamingZones READ roamingZones SCRIPTABLE false) /** Represents the config extension for encryption keys. */ Q_PROPERTY(CommercialExtension* commercial READ commercialExtension) /** Represents the extended SMS settings. */ Q_PROPERTY(SMSExtension *sms READ smsExtension) /** Represents the config extension for TyT devices. */ Q_PROPERTY(TyTConfigExtension* tytExtension READ tytExtension WRITE setTyTExtension) public: /** Constructs an empty configuration. */ Q_INVOKABLE explicit Config(QObject *parent = nullptr); bool copy(const ConfigItem &other); ConfigItem *clone() const; /** Returns @c true if the config was modified, @see modified. */ bool isModified() const; /** Sets the modified flag. */ void setModified(bool modified); /** Returns the radio wide settings. */ RadioSettings *settings() const; /** Returns the list of radio IDs. */ RadioIDList *radioIDs() const; /** Returns the list of contacts. */ ContactList *contacts() const; /** Returns the list of RX group lists. */ RXGroupLists *rxGroupLists() const; /** Returns the list of channels. */ ChannelList *channelList() const; /** Returns the list of zones. */ ZoneList *zones() const; /** Returns the list of scanlists. */ ScanLists *scanlists() const; /** Returns the list of positioning systems. */ PositionReportingSystems *posSystems() const; /** Returns the list of roaming channels. */ RoamingChannelList *roamingChannels() const; /** Returns the list of roaming zones. */ RoamingZoneList *roamingZones() const; /** Returns @c true if one of the digital channels has a roaming zone assigned. */ bool requiresRoaming() const; /** Returns @c true if one of the channels has a GPS or APRS system assigned. */ bool requiresGPS() const; /** Clears the complete configuration. */ void clear(); const Config *config() const; /** Returns the commercial extension. */ CommercialExtension *commercialExtension() const; /** Returns the SMS settings extension. */ SMSExtension *smsExtension() const; /** Returns the TyT settings extension. * If this extension is not set, returns @c nullptr. */ TyTConfigExtension *tytExtension() const; /** Sets the TyT settings extension. */ void setTyTExtension(TyTConfigExtension *ext); public: /** Imports a configuration from the given file. */ bool readCSV(const QString &filename, QString &errorMessage); /** Imports a configuration from the given text stream in text format. */ bool readCSV(QTextStream &stream, QString &errorMessage); /** Imports a configuration from the given YAML file. */ bool readYAML(const QString &filename, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); bool link(const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Serializes the configuration into the given stream as text. */ bool toYAML(QTextStream &stream, const ErrorStack &err=ErrorStack()); protected: bool populate(YAML::Node &node, const Context &context, const ErrorStack &err=ErrorStack()); protected slots: /** Iternal callback. */ void onConfigModified(); protected: /** If @c true, the configuration was modified. */ bool _modified; /** Radio wide settings. */ RadioSettings *_settings; /** The list of radio IDs. */ RadioIDList *_radioIDs; /** The list of contacts. */ ContactList *_contacts; /** The list of RX group lists. */ RXGroupLists *_rxGroupLists; /** The list of channels. */ ChannelList *_channels; /** The list of zones. */ ZoneList *_zones; /** The list of scan lists. */ ScanLists *_scanlists; /** The list of GPS Systems. */ PositionReportingSystems *_gpsSystems; /** The list of roaming channels. */ RoamingChannelList *_roamingChannels; /** The list of roaming zones. */ RoamingZoneList *_roamingZones; /** Owns the TyT settings extension. */ TyTConfigExtension *_tytExtension; /** Owns the commercial extension. */ CommercialExtension *_commercialExtension; /** Owns the SMS settings extension. */ SMSExtension *_smsExtension; }; #endif // CONFIG_HH ================================================ FILE: lib/configcopyvisitor.cc ================================================ #include "configcopyvisitor.hh" #include "configobject.hh" #include "configreference.hh" #include "channel.hh" #include "radioid.hh" #include "roamingzone.hh" #include "logger.hh" /* ********************************************************************************************* * * Implementation of ConfigCloneVisitor * ********************************************************************************************* */ ConfigCloneVisitor::ConfigCloneVisitor(QHash &map) : Visitor(), _map(map) { // pass... } bool ConfigCloneVisitor::processProperty(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err) { if (prop.isFlagType() || prop.isEnumType() || (QMetaType::Bool == prop.typeId()) || (QMetaType::Int == prop.typeId()) || (QMetaType::UInt == prop.typeId()) || (QMetaType::Double == prop.typeId()) || (QMetaType::QString == prop.typeId()) || (QMetaType::fromType() == prop.metaType()) || (QMetaType::fromType() == prop.metaType()) || (QMetaType::fromType() == prop.metaType()) || (QMetaType::fromType() == prop.metaType()) || (QMetaType::fromType() == prop.metaType())) { if ((! prop.isReadable()) && (!prop.isWritable())) { logDebug() << "Skip property " << prop.name() << " of item " << item->metaObject()->className() << ": Not readable or writable."; return true; } if (! Visitor::processProperty(item, prop, err)) return false; // Get clone ConfigItem *clone = qobject_cast(_stack.back()); if (nullptr == clone) { errMsg(err) << "Unexpected element on the stack. Found object of type " << _stack.back()->metaObject()->className() << ", expected ConfigItem."; return false; } // Find the property int pidx = clone->metaObject()->indexOfProperty(prop.name()); if (0 > pidx) { errMsg(err) << "Cannot set property " << prop.name() << " on element on stack."; return false; } // Set it if (! clone->metaObject()->property(pidx).write(clone, prop.read(item))) { errMsg(err) << "Cannot set property " << prop.name() << " on element on stack."; return false; } return true; } else if (propIsInstance(prop)) { // Get clone ConfigItem *clone = qobject_cast(_stack.back()); if (nullptr == clone) { errMsg(err) << "Unexpected element on the stack. Found object of type " << _stack.back()->metaObject()->className() << ", expected ConfigItem."; return false; } // Set reference to same object, will be replaced later. ConfigObjectReference *ref = prop.read(item).value(); ConfigObjectReference *cloneRef = prop.read(clone).value(); cloneRef->set(ref->as()); return true; } else if (propIsInstance(prop)) { if (nullptr == prop.read(item).value()) return true; // If writeable, simply traverse config item if (prop.isWritable()) { // Clone item in property recursively if(! this->processItem(prop.read(item).value(), err)) return false; // Get cloned item from stack ConfigItem *newItem = dynamic_cast(_stack.back()); _stack.pop_back(); // Also, get item from stack, who owns the clones item ConfigItem *clone = dynamic_cast(_stack.back()); int pidx = clone->metaObject()->indexOfProperty(prop.name()); // Find the property if (0 > pidx) { errMsg(err) << "Cannot set property " << prop.name() << " on element on stack."; return false; } // Set it if (! clone->metaObject()->property(pidx).write(clone, QVariant::fromValue(newItem))) { errMsg(err) << "Cannot set property " << prop.name() << " on element on stack."; return false; } return true; } // If property is not writeable, the parent element (top of stack), owns and creates the item // -> needs to be put on the stack ConfigItem *clone = dynamic_cast(_stack.back()); if (nullptr == clone) { errMsg(err) << "Unexpected element on the stack. Found object of type " << _stack.back()->metaObject()->className() << ", expected ConfigItem."; return false; } int pidx = clone->metaObject()->indexOfProperty(prop.name()); if (0 > pidx) { errMsg(err) << "Cannot read property " << prop.name() << " on element on stack."; return false; } QVariant value = clone->metaObject()->property(pidx).read(clone); if (!value.isValid()) { errMsg(err) << "Cannot read property " << prop.name() << " on element on stack."; return false; } _stack.push_back(value.value()); if (! Visitor::processItem(prop.read(item).value(), err)) { errMsg(err) << "Cannot process property " << prop.name() << "."; return false; } _stack.pop_back(); return true; } else if (ConfigObjectList *lst = prop.read(item).value()) { // Lists are always owned by the item. So dig up the list and put it on the stack ConfigItem *clone = dynamic_cast(_stack.back()); if (nullptr == clone) { errMsg(err) << "Unexpected element on the stack. Found object of type " << _stack.back()->metaObject()->className() << ", expected ConfigItem."; return false; } int pidx = clone->metaObject()->indexOfProperty(prop.name()); if (0 > pidx) { errMsg(err) << "Cannot read property " << prop.name() << " on element on stack."; return false; } QVariant value = clone->metaObject()->property(pidx).read(clone); if (!value.isValid()) { errMsg(err) << "Cannot read property " << prop.name() << " on element on stack."; return false; } _stack.push_back(value.value()); if (! processList(lst, err)) { errMsg(err) << "Cannot process object list in property " << prop.name() << "."; return false; } _stack.pop_back(); return true; } else if (ConfigObjectRefList *refs = prop.read(item).value()) { // Lists are always owned by the item. So dig up the parent and get the list ConfigItem *clone = dynamic_cast(_stack.back()); if (nullptr == clone) { errMsg(err) << "Unexpected element on the stack. Found object of type " << _stack.back()->metaObject()->className() << ", expected ConfigItem."; return false; } int pidx = clone->metaObject()->indexOfProperty(prop.name()); if (0 > pidx) { errMsg(err) << "Cannot read property " << prop.name() << " on element on stack."; return false; } QVariant value = clone->metaObject()->property(pidx).read(clone); if (!value.isValid()) { errMsg(err) << "Cannot read property " << prop.name() << " on element on stack."; return false; } _stack.push_back(value.value()); if (! processList(refs, err)) { errMsg(err) << "Cannot process reference list in property " << prop.name() << "."; return false; } _stack.pop_back(); return true; } else { if (! this->processUnknownType(item, prop, err)) { errMsg(err) << "While processing property '" << prop.name() << "' of '" << item->metaObject()->className() << "' of unknown type."; return false; } } return true; } bool ConfigCloneVisitor::processItem(ConfigItem *item, const ErrorStack &err) { // Call default constructor. QObject *obj = item->metaObject()->newInstance(); if (nullptr == obj) { errMsg(err) << "Cannot construct new instance for item of type " << item->metaObject()->className() << "."; return false; } // Put new item on stack _stack.push_back(obj); // If item is an object -> store in map if (item->is()) _map[item->as()] = dynamic_cast(obj); // then traverse by type ... if (item->is()) return processChannel(item->as(), err); // .. or use default. return Visitor::processItem(item, err); } bool ConfigCloneVisitor::processChannel(Channel *item, const ErrorStack &err) { if (! Visitor::processItem(item, err)) return false; if (item->defaultPower()) qobject_cast(_stack.back())->setDefaultPower(); return true; } bool ConfigCloneVisitor::processList(AbstractConfigObjectList *list, const ErrorStack &err) { if (ConfigObjectRefList *refs = dynamic_cast(list)) { ConfigObjectRefList *clone = dynamic_cast(_stack.back()); if (nullptr == clone) { errMsg(err) << "Cannot clone reference list, no ref list found on the stack. Got " << _stack.back()->metaObject()->className() << "."; return false; } // Just copy references, they will be replaced later for (int i=0; icount(); i++) { clone->add(refs->get(i)); } return true; } if (ConfigObjectList *olist = dynamic_cast(list)) { // First, get object list from stack ConfigObjectList *clone = dynamic_cast(_stack.back()); if (nullptr == clone) { errMsg(err) << "Cannot clone list, no list found on the stack. Got " << _stack.back()->metaObject()->className() << "."; return false; } // just traverse list if (! Visitor::processList(olist, err)) return false; // Take generated items from stack for (int i=0; icount(); i++) { clone->add(dynamic_cast(_stack.back()),0); _stack.pop_back(); } return true; } errMsg(err) << "Unexpected list type " << list->metaObject()->className() << "."; return false; } ConfigItem * ConfigCloneVisitor::takeResult(const ErrorStack &err) { if (1 != _stack.size()) { errMsg(err) << "Cannot take result, result stack does not contain one element, got " << _stack.size() << "."; return nullptr; } ConfigItem *item = dynamic_cast(_stack.back()); if (nullptr == item) { errMsg(err) << "Cannot take result, last element on stack is not a ConfigItem, got " << _stack.back()->metaObject()->className() << "."; return nullptr; } _stack.pop_back(); return item; } /* ********************************************************************************************* * * Implementation of FixReferencesVisitor * ********************************************************************************************* */ FixReferencesVisistor::FixReferencesVisistor(QHash &map, bool keepUnknown) : Visitor(), _map(map), _keepUnknown(keepUnknown) { // Populate with default singleton instances. map[SelectedChannel::get()] = SelectedChannel::get(); map[DefaultRadioID::get()] = DefaultRadioID::get(); map[DefaultRoamingZone::get()] = DefaultRoamingZone::get(); } bool FixReferencesVisistor::processProperty(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err) { // First, traverse normally if (! Visitor::processProperty(item, prop, err)) return false; if (ConfigObjectReference *ref = prop.read(item).value()) { if (ref->isNull()) return true; ConfigObject *obj = ref->as(); if ((! _keepUnknown) && (! _map.contains(obj))) { errMsg(err) << "Cannot fix reference to object '" << obj->name() << "' of type " << obj->metaObject()->className() << ": Not mapped/cloned yet."; return false; } if (_map.contains(obj)) ref->set(_map[obj]); return true; } return true; } bool FixReferencesVisistor::processList(AbstractConfigObjectList *list, const ErrorStack &err) { // First traverse list if (! Visitor::processList(list, err)) return false; if (ConfigObjectRefList *rlist = dynamic_cast(list)) { QSet del; // Resolve all references. for (int i=0; icount(); i++) { if ((! _keepUnknown) && (! _map.contains(rlist->get(i)))) { errMsg(err) << "Cannot fix reference to object '" << rlist->get(i)->name() << "' of type " << rlist->get(i)->metaObject()->className() << ": Not mapped/cloned yet."; return false; } // If the reference cannot be replaced (i.e., the replacement is already in the list): if (_map.contains(rlist->get(i)) && (0 > rlist->replace(_map[rlist->get(i)], i))) del.insert(rlist->get(i)); } // Now remove all references that could not be replaced foreach (ConfigObject *obj, del) { rlist->del(obj); } } return true; } /* ********************************************************************************************* * * Implementation of ConfigCopy * ********************************************************************************************* */ ConfigItem * ConfigCopy::copy(ConfigItem *original, const ErrorStack &err) { QHash map; ConfigCloneVisitor cloner(map); if (! cloner.processItem(original, err)) { errMsg(err) << "Cannot clone item of type " << original->metaObject()->className() << "."; return nullptr; } ConfigItem *clone = cloner.takeResult(); FixReferencesVisistor fixer(map, true); if (! fixer.processItem(clone, err)) { errMsg(err) << "Cannot fix references in item of type " << clone->metaObject()->className() << "."; delete clone; return nullptr; } return clone; } ================================================ FILE: lib/configcopyvisitor.hh ================================================ #ifndef CONFIGCOPYVISITOR_HH #define CONFIGCOPYVISITOR_HH #include "visitor.hh" class ConfigObject; class Channel; /** This visitor traverses the the given configuration and clones it. All references are still * pointing to the originals. * @ingroup conf */ class ConfigCloneVisitor : public Visitor { public: /** Constructor. * @param map Specifies the mapping table to be filled with the created clones. */ ConfigCloneVisitor(QHash &map); bool processProperty(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); bool processItem(ConfigItem *item, const ErrorStack &err=ErrorStack()); bool processList(AbstractConfigObjectList *list, const ErrorStack &err=ErrorStack()); /** Specialized handler for channels, must traverse the channel object. */ virtual bool processChannel(Channel *item, const ErrorStack &err=ErrorStack()); /** Extracts the cloned item. */ ConfigItem *takeResult(const ErrorStack &err=ErrorStack()); protected: /** Stack of the current object. */ QList _stack; /** Reference to the translation table original -> cloned object. */ QHash &_map; }; /** Replaces references using a specified map. * This can be considered a second step in copying an entire codeplug, first all objects are cloned * and in a second step, all references are fixed using this class. * @ingroup conf */ class FixReferencesVisistor: public Visitor { public: /** Constructor. */ FixReferencesVisistor(QHash &map, bool keepUnknown=false); bool processProperty(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); bool processList(AbstractConfigObjectList *list, const ErrorStack &err=ErrorStack()); protected: /** Reference to the translation table original -> cloned object. */ QHash &_map; /** If false, an unmapped reference is an error. */ bool _keepUnknown; }; /** Just a name space to hold the copy function. */ class ConfigCopy { public: /** Copies the given item. */ static ConfigItem *copy(ConfigItem *original, const ErrorStack &err=ErrorStack()); }; #endif // CONFIGCOPYVISITOR_HH ================================================ FILE: lib/configlabelingvisitor.cc ================================================ #include "configlabelingvisitor.hh" ConfigLabelingVisitor::ConfigLabelingVisitor(ConfigItem::Context &context) : Visitor(), _context(context) { // pass.. } bool ConfigLabelingVisitor::processItem(ConfigItem *item, const ErrorStack &err) { // First, check if item is an ConfigObject if (item->is()) { ConfigObject *obj = item->as(); QString prefix = obj->idPrefix(); // If no prefix is set -> skip. if (prefix.isEmpty()) return Visitor::processItem(item, err); // Find unused ID unsigned n=1; QString id=QString("%1%2").arg(prefix).arg(n); while (_context.contains(id)) id=QString("%1%2").arg(prefix).arg(++n); // Add to context if (! _context.add(id, obj)) { if (_context.contains(obj)) errMsg(err) << "Object already in context with id '" << _context.getId(obj) << "'."; errMsg(err) << "Cannot add element '" << id << "' to context."; return false; } } return Visitor::processItem(item, err); } ================================================ FILE: lib/configlabelingvisitor.hh ================================================ #ifndef CONFIGLABELINGVISITOR_HH #define CONFIGLABELINGVISITOR_HH #include "visitor.hh" #include "configobject.hh" /** A visitor to label the entire configuration. * That is, assigning unique labels to each @c ConfigObject within the configuration. * @ingroup conf */ class ConfigLabelingVisitor: protected Visitor { public: /** Use the static method @c label to label the configuration. */ ConfigLabelingVisitor(ConfigItem::Context &context); bool processItem(ConfigItem *item, const ErrorStack &err=ErrorStack()); /** Labels the configuration and stores the labels in the given context. */ static bool label(Config *config, ConfigItem::Context &context); protected: /** Holds a weak reference to the parser/serializer context. * That is, the id<->obj table. */ ConfigItem::Context &_context; }; #endif // CONFIGLABELINGVISITOR_HH ================================================ FILE: lib/configmergevisitor.cc ================================================ #include "configmergevisitor.hh" #include "configcopyvisitor.hh" #include "configobject.hh" #include "configreference.hh" #include "config.hh" #include "channel.hh" #include "logger.hh" /* ********************************************************************************************* * * Implementation of ConfigMergeVisitor * ********************************************************************************************* */ ConfigMergeVisitor::ConfigMergeVisitor(Config* destination, QHash& translation, ItemStrategy itemStrategy, SetStrategy setStrategy) : Visitor(), _destination(destination), _translation(translation), _itemStrategy(itemStrategy), _setStrategy(setStrategy) { // pass... } bool ConfigMergeVisitor::processItem(ConfigItem *item, const ErrorStack &err) { // Dispatch by type if (item->is()) return processRadioID(item->as(), err); else if (item->is()) return processContact(item->as(), err); else if (item->is()) return processGroupList(item->as(), err); else if (item->is()) return processChannel(item->as(), err); else if (item->is()) return processZone(item->as(), err); else if (item->is()) return processScanList(item->as(), err); else if (item->is()) return processPositioningSystem(item->as(), err); else if (item->is()) return processRoamingChannel(item->as(), err); else if (item->is()) return processRoamingZone(item->as(), err); return Visitor::processItem(item, err); } bool ConfigMergeVisitor::processRadioID(RadioID *item, const ErrorStack &err) { if (0 == _destination->radioIDs()->findItemsByName(item->name()).count()) return addObject(_destination->radioIDs(), nullptr, item, err); RadioID *present = _destination->radioIDs()->findItemsByName(item->name()) .first()->as(); if (ItemStrategy::Ignore == _itemStrategy) return ignoreObject(_destination->radioIDs(), present, item, err); if (ItemStrategy::Override == _itemStrategy) return replaceObject(_destination->radioIDs(), present, item, err); if (ItemStrategy::Duplicate == _itemStrategy) return duplicateObject(_destination->radioIDs(), present, item, err); return true; } bool ConfigMergeVisitor::processChannel(Channel *item, const ErrorStack &err) { if (0 == _destination->channelList()->findItemsByName(item->name()).count()) return addObject(_destination->channelList(), nullptr, item, err); Channel *present = _destination->channelList()->findItemsByName(item->name()) .first()->as(); if (ItemStrategy::Ignore == _itemStrategy) return ignoreObject(_destination->channelList(), present, item, err); if (ItemStrategy::Override == _itemStrategy) return replaceObject(_destination->channelList(), present, item, err); if (ItemStrategy::Duplicate == _itemStrategy) return duplicateObject(_destination->channelList(), present, item, err); return true; } bool ConfigMergeVisitor::processContact(Contact *item, const ErrorStack &err) { if (0 == _destination->contacts()->findItemsByName(item->name()).count()) return addObject(_destination->contacts(), nullptr, item, err); Contact *present = _destination->contacts()->findItemsByName(item->name()) .first()->as(); if (ItemStrategy::Ignore == _itemStrategy) return ignoreObject(_destination->contacts(), present, item, err); if (ItemStrategy::Override == _itemStrategy) return replaceObject(_destination->contacts(), present, item, err); if (ItemStrategy::Duplicate == _itemStrategy) return duplicateObject(_destination->contacts(), present, item, err); return true; } bool ConfigMergeVisitor::processPositioningSystem(PositionReportingSystem *item, const ErrorStack &err) { if (0 == _destination->posSystems()->findItemsByName(item->name()).count()) return addObject(_destination->posSystems(), nullptr, item, err); PositionReportingSystem *present = _destination->posSystems()->findItemsByName(item->name()) .first()->as(); if (ItemStrategy::Ignore == _itemStrategy) return ignoreObject(_destination->posSystems(), present, item, err); if (ItemStrategy::Override == _itemStrategy) return replaceObject(_destination->posSystems(), present, item, err); if (ItemStrategy::Duplicate == _itemStrategy) return duplicateObject(_destination->posSystems(), present, item, err); return true; } bool ConfigMergeVisitor::processRoamingChannel(RoamingChannel *item, const ErrorStack &err) { if (0 == _destination->roamingChannels()->findItemsByName(item->name()).count()) return addObject(_destination->roamingChannels(), nullptr, item, err); RoamingChannel *present = _destination->roamingChannels()->findItemsByName(item->name()) .first()->as(); if (ItemStrategy::Ignore == _itemStrategy) return ignoreObject(_destination->roamingChannels(), present, item, err); if (ItemStrategy::Override == _itemStrategy) return replaceObject(_destination->roamingChannels(), present, item, err); if (ItemStrategy::Duplicate == _itemStrategy) return duplicateObject(_destination->roamingChannels(), present, item, err); return true; } bool ConfigMergeVisitor::processGroupList(RXGroupList *item, const ErrorStack &err) { if (0 == _destination->rxGroupLists()->findItemsByName(item->name()).count()) return addObject(_destination->rxGroupLists(), nullptr, item, err); RXGroupList *present = _destination->rxGroupLists()->findItemsByName(item->name()) .first()->as(); if (SetStrategy::Ignore == _setStrategy) return ignoreObject(_destination->rxGroupLists(), present, item, err); if (SetStrategy::Override == _setStrategy) return replaceObject(_destination->rxGroupLists(), present, item, err); if (SetStrategy::Duplicate == _setStrategy) return duplicateObject(_destination->rxGroupLists(), present, item, err); if (SetStrategy::Merge == _setStrategy) return mergeList(present->contacts(), item->contacts(), err); return true; } bool ConfigMergeVisitor::processZone(Zone *item, const ErrorStack &err) { if (0 == _destination->zones()->findItemsByName(item->name()).count()) return addObject(_destination->zones(), nullptr, item, err); Zone *present = _destination->zones()->findItemsByName(item->name()) .first()->as(); if (SetStrategy::Ignore == _setStrategy) return ignoreObject(_destination->zones(), present, item, err); if (SetStrategy::Override == _setStrategy) return replaceObject(_destination->zones(), present, item, err); if (SetStrategy::Duplicate == _setStrategy) return duplicateObject(_destination->zones(), present, item, err); if (SetStrategy::Merge == _setStrategy) return mergeList(present->A(), item->A(), err) && mergeList(present->B(), item->B(), err); return true; } bool ConfigMergeVisitor::processScanList(ScanList *item, const ErrorStack &err) { if (0 == _destination->scanlists()->findItemsByName(item->name()).count()) return addObject(_destination->scanlists(), nullptr, item, err); ScanList *present = _destination->scanlists()->findItemsByName(item->name()) .first()->as(); if (SetStrategy::Ignore == _setStrategy) return ignoreObject(_destination->scanlists(), present, item, err); if (SetStrategy::Override == _setStrategy) return replaceObject(_destination->scanlists(), present, item, err); if (SetStrategy::Duplicate == _setStrategy) return duplicateObject(_destination->scanlists(), present, item, err); if (SetStrategy::Merge == _setStrategy) return mergeList(present->channels(), item->channels(), err); return true; } bool ConfigMergeVisitor::processRoamingZone(RoamingZone *item, const ErrorStack &err) { if (0 == _destination->roamingZones()->findItemsByName(item->name()).count()) return addObject(_destination->roamingZones(), nullptr, item, err); RoamingZone *present = _destination->roamingZones()->findItemsByName(item->name()) .first()->as(); if (SetStrategy::Ignore == _setStrategy) return ignoreObject(_destination->roamingZones(), present, item, err); if (SetStrategy::Override == _setStrategy) return replaceObject(_destination->roamingZones(), present, item, err); if (SetStrategy::Duplicate == _setStrategy) return duplicateObject(_destination->roamingZones(), present, item, err); if (SetStrategy::Merge == _setStrategy) return mergeList(present->channels(), item->channels(), err); return true; } bool ConfigMergeVisitor::addObject(AbstractConfigObjectList *list, ConfigObject *present, ConfigObject *merging, const ErrorStack &err) { Q_UNUSED(present) ConfigObject *newObject = ConfigCopy::copy(merging, err)->as(); if (nullptr == newObject) { errMsg(err) << "Cannot copy item '" << merging->name() << "'."; return false; } logDebug() << "Add object '" << merging->name() << "'."; list->add(newObject); _translation[merging] = newObject; return true; } bool ConfigMergeVisitor::ignoreObject(AbstractConfigObjectList *list, ConfigObject *present, ConfigObject *merging, const ErrorStack &err) { Q_UNUSED(list); Q_UNUSED(err) logDebug() << "Ignore object '" << merging->name() << "'."; _translation[merging] = present; return true; } bool ConfigMergeVisitor::replaceObject(AbstractConfigObjectList *list, ConfigObject *present, ConfigObject *merging, const ErrorStack &err) { ConfigObject *newObject = ConfigCopy::copy(merging, err)->as(); if (nullptr == newObject) { errMsg(err) << "Cannot copy item '" << merging->name() << "'."; return false; } logDebug() << "Replace object '" << present->name() << "'."; list->replace(newObject, list->indexOf(present)); _translation[merging] = newObject; _translation[present] = newObject; return true; } bool ConfigMergeVisitor::duplicateObject(AbstractConfigObjectList *list, ConfigObject *present, ConfigObject *merging, const ErrorStack &err) { Q_UNUSED(present) ConfigObject *newItem = ConfigCopy::copy(merging, err)->as(); if (nullptr == newItem) { errMsg(err) << "Cannot copy item '" << merging->name() << "'."; return false; } logDebug() << "Copy object '" << merging->name() << "'."; newItem->setName(newItem->name()+" (copy)"); list->add(newItem); _translation[merging] = newItem; return true; } bool ConfigMergeVisitor::mergeList(ConfigObjectRefList *present, ConfigObjectRefList *merging, const ErrorStack &err) { Q_UNUSED(err); for (int i=0; icount(); i++) { ConfigObject *mergingItem = merging->get(i); mergingItem = _translation.value(mergingItem, mergingItem)->as(); if (! present->has(mergingItem)) { present->add(mergingItem); } } return true; } /* ********************************************************************************************* * * Implementation of ConfigMerge * ********************************************************************************************* */ bool ConfigMerge::mergeInto(Config *destination, Config *source, ConfigMergeVisitor::ItemStrategy itemStrategy, ConfigMergeVisitor::SetStrategy setStrategy, const ErrorStack &err) { QHash referenceTable; ConfigMergeVisitor mergeVisitor(destination, referenceTable, itemStrategy, setStrategy); if (! mergeVisitor.process(source, err)) { errMsg(err) << "Cannot merge configurations."; return false; } FixReferencesVisistor linkVisitor(referenceTable, true); if (! linkVisitor.process(destination, err)) { errMsg(err) << "Cannot fix references in merged configuration."; return false; } return true; } Config * ConfigMerge::merge(Config *destination, Config *source, ConfigMergeVisitor::ItemStrategy itemStrategy, ConfigMergeVisitor::SetStrategy setStrategy, const ErrorStack &err) { Config *copy = ConfigCopy::copy(destination, err)->as(); if (nullptr == copy) { errMsg(err) << "Cannot merge configurations."; return nullptr; } if (! mergeInto(copy, source, itemStrategy, setStrategy, err)) { delete copy; return nullptr; } return copy; } ================================================ FILE: lib/configmergevisitor.hh ================================================ #ifndef CONFIGMERGEVISITOR_HH #define CONFIGMERGEVISITOR_HH #include "visitor.hh" class ConfigObject; class RadioID; class Channel; class Contact; class PositionReportingSystem; class RoamingChannel; class ConfigObjectRefList; class RXGroupList; class Zone; class ScanList; class RoamingZone; /** Implements a visitor to merge the visited config into a given config. * The destination configuration object is passed to the constructor. This allows to merge several * configurations into one. * @ingroup conf */ class ConfigMergeVisitor : public Visitor { public: /** Possible strategies to merge atomic items. That is, Channels, Contacts & PositioningSystems, * if there are two items with the same name. */ enum class ItemStrategy { Ignore, ///< Ignore the source item. Override, ///< Override the destination item. Duplicate ///< Add a new item with a modified name. }; /** Possible strategies to merge sets of references. That is, GroupLists, Zones & ScanLists, * if there are two sets with the same name. */ enum class SetStrategy { Ignore, ///< Ignore the source set. Override, ///< Override the destination item. Duplicate, ///< Add a new set with a modified name. Merge ///< Merge sets. }; public: /** Constructor of the merge visitor. * @param destination Specifies the destination configuration, the additional configurations are * merged into. * @param translation Specifies the translation table to fill of objects being replaced. * @param itemStrategy Specifies the item merge strategy. * @param setStrategy Specifies the set merge strategy. */ ConfigMergeVisitor(Config *destination, QHash &translation, ItemStrategy itemStrategy=ItemStrategy::Ignore, SetStrategy setStrategy=SetStrategy::Ignore); bool processItem(ConfigItem *item, const ErrorStack &err=ErrorStack()); protected: /** Handles a RadioID object of the source configuration. */ bool processRadioID(RadioID *item, const ErrorStack &err = ErrorStack()); /** Handles a Channel object of the source configuration. */ bool processChannel(Channel *item, const ErrorStack &err = ErrorStack()); /** Handles a Contact object of the source configuration. */ bool processContact(Contact *item, const ErrorStack &err = ErrorStack()); /** Handles a PositioningSystem object of the source configuration. */ bool processPositioningSystem(PositionReportingSystem *item, const ErrorStack &err = ErrorStack()); /** Handles a RoamingChannel object of the source configuration. */ bool processRoamingChannel(RoamingChannel *item, const ErrorStack &err = ErrorStack()); /** Handles a RXGroupList object of the source configuration. */ bool processGroupList(RXGroupList *item, const ErrorStack &err = ErrorStack()); /** Handles a Zone object of the source configuration. */ bool processZone(Zone *item, const ErrorStack &err = ErrorStack()); /** Handles a ScanList object of the source configuration. */ bool processScanList(ScanList *item, const ErrorStack &err = ErrorStack()); /** Handles a RoamingZone object of the source configuration. */ bool processRoamingZone(RoamingZone *item, const ErrorStack &err = ErrorStack()); /** Adds a copy of the @c merging object to the given list, containing the colliding @c present * object. Also updates the translation table to bend references to the merging object to that copy. */ bool addObject(AbstractConfigObjectList *list, ConfigObject *present, ConfigObject *merging, const ErrorStack &err = ErrorStack()); /** Does not add the @c merging object, but fixes the translation table to bend references to the merging object. */ bool ignoreObject(AbstractConfigObjectList *list, ConfigObject *present, ConfigObject *merging, const ErrorStack &err = ErrorStack()); /** Replaces the @c present object in the given list by a copy of @c merging object. Also fixes * the translation table to bend references to both the removed @c present object as well as the * @c merging object. */ bool replaceObject(AbstractConfigObjectList *list, ConfigObject *present, ConfigObject *merging, const ErrorStack &err = ErrorStack()); /** Adds a copy of the @c merging object to the given list. */ bool duplicateObject(AbstractConfigObjectList *list, ConfigObject *present, ConfigObject *merging, const ErrorStack &err = ErrorStack()); /** Merges two reference lists. That is, @c merging gets merged into the @c present list. */ bool mergeList(ConfigObjectRefList *present, ConfigObjectRefList *merging, const ErrorStack &err = ErrorStack()); protected: /** The destination configuration. */ Config *_destination; /** Translation table for fixing references. */ QHash &_translation; /** The item merge strategy. */ ItemStrategy _itemStrategy; /** The set merge strategy. */ SetStrategy _setStrategy; }; /** Just a namespace to provide merging functions. * @ingroup conf */ class ConfigMerge { public: /** Merges the given @c source into the given @c destination using the specified strategies to * handle conflicts. Here the @c destination codeplug gets modified, even on errors. */ static bool mergeInto(Config *destination, Config *source, ConfigMergeVisitor::ItemStrategy itemStrategy=ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy setStrategy=ConfigMergeVisitor::SetStrategy::Ignore, const ErrorStack &err = ErrorStack()); /** Merges the given @c source into a copy of the given @c destination, using the specified * strategies to handle conflicts. Here the @c destination codeplug does not get modified at * all. */ static Config *merge(Config *destination, Config *source, ConfigMergeVisitor::ItemStrategy itemStrategy=ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy setStrategy=ConfigMergeVisitor::SetStrategy::Ignore, const ErrorStack &err = ErrorStack()); }; #endif // CONFIGMERGEVISITOR_HH ================================================ FILE: lib/configobject.cc ================================================ #include "configobject.hh" #include "configreference.hh" #include "logger.hh" #include "frequency.hh" #include "interval.hh" #include "level.hh" #include "signaling.hh" #include "gnsssettings.hh" #include #include // Helper function to extract key names for a QMetaEnum inline QStringList enumKeys(const QMetaEnum &e) { QStringList lst; for (int i=0; imetaObject(); while (type) { if (typeNames.contains(type->className())) return true; type = type->superClass(); } return false; } /* ********************************************************************************************* * * Implementation of ConfigObject::Context * ********************************************************************************************* */ QHash>> ConfigObject::Context::_tags = QHash>>(); ConfigItem::Context::Context() : _version(), _objects(), _ids() { // pass... } ConfigItem::Context::~Context() { // pass... } const QString & ConfigItem::Context::version() const { return _version; } void ConfigItem::Context::setVersion(const QString &ver) { _version = ver; } bool ConfigItem::Context::contains(ConfigObject *obj) const { return _ids.contains(obj); } bool ConfigItem::Context::contains(const QString &id) const { return _objects.contains(id); } QString ConfigItem::Context::getId(ConfigObject *obj) const { return _ids.value(obj, ""); } ConfigObject * ConfigItem::Context::getObj(const QString &id) const { return _objects.value(id, nullptr); } bool ConfigItem::Context::add(const QString &id, ConfigObject *obj) { if (_objects.contains(id) || _ids.contains(obj)) return false; _objects.insert(id, obj); _ids.insert(obj, id); return true; } bool ConfigItem::Context::tagIsSet(const QString &className, const QString &property, const QString &tag) { QString qname = className+"::"+property; if (! _tags.contains(qname)) return false; for(auto pair: _tags[qname]) { if (pair.first == tag) return true; } return false; } bool ConfigItem::Context::hasTag(const QString &className, const QString &property, QVariant value) { QString qname = className+"::"+property; if (! _tags.contains(qname)) return false; for(auto pair: _tags[qname]) { if (QPartialOrdering::Equivalent == QVariant::compare(pair.second, value)) return true; } return false; } QVariant ConfigItem::Context::tagGetValue(const QString &className, const QString &property, const QString &tag) { QString qname = className+"::"+property; if (! _tags.contains(qname)) return QVariant(); for(auto pair: _tags[qname]) { if (pair.first == tag) return pair.second; } return QVariant(); } QString ConfigItem::Context::getTag(const QString &className, const QString &property, QVariant value) { QString qname = className+"::"+property; if (! _tags.contains(qname)) return QString(); for(auto pair: _tags[qname]) { if (QPartialOrdering::Equivalent == QVariant::compare(pair.second, value)) return pair.first; } return QString(); } void ConfigItem::Context::setTag(const QString &className, const QString &property, const QString &tag, QVariant value) { //logDebug() << "Register tag " << tag << " for " << property << " in " << className << "."; QString qname = className+"::"+property; if (! _tags.contains(qname)) { _tags[qname] = QList>(); _tags[qname].append({tag, value}); return; } for (auto it=_tags[qname].begin(); it != _tags[qname].end(); it++) { if (tag == it->first) { *it = {tag, value}; return; } } _tags[qname].append({tag, value}); } /* ********************************************************************************************* * * Implementation of ConfigItem * ********************************************************************************************* */ ConfigItem::ConfigItem(QObject *parent) : QObject(parent) { // pass... } bool ConfigItem::copy(const ConfigItem &other) { // check if other has the same type if (strcmp(other.metaObject()->className(), metaObject()->className())) { logError() << metaObject()->className() << " cannot copy from " << other.metaObject()->className(); return false; } // clear this instance this->clear(); // Iterate over all properties const QMetaObject *meta = metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { // This property QMetaProperty prop = meta->property(p); // the same property over at other QMetaProperty oprop = other.metaObject()->property(p); // Should never happen if (! prop.isValid()) { logWarn() << "Invalid property " << prop.name() << ". This should not happen."; continue; } // true if the property is a basic type bool isBasicType = ( prop.isEnumType() || prop.isFlagType() || (QMetaType::Bool==prop.typeId()) || (QMetaType::Int==prop.typeId()) || (QMetaType::UInt==prop.typeId()) || (QMetaType::Double==prop.typeId()) || (QMetaType::QString==prop.typeId()) || (QMetaType::fromType()==prop.metaType()) || (QMetaType::fromType()==prop.metaType()) || (QMetaType::fromType()==prop.metaType()) || (QMetaType::fromType()==prop.metaType()) || (QMetaType::fromType()==prop.metaType())); // If a basic type -> simply copy value if (isBasicType && prop.isWritable() && (prop.typeId()==oprop.typeId())) { if (! prop.write(this, oprop.read(&other))) { logError() << "Cannot set property '" << prop.name() << "' of " << this->metaObject()->className() << "."; return false; } } else if (ConfigObjectReference *ref = prop.read(this).value()) { if (! ref->copy(oprop.read(&other).value())) { logError() << "Cannot copy object reference '" << prop.name() << "' of " << this->metaObject()->className() << "."; return false; } } else if (ConfigObjectList *lst = prop.read(this).value()) { if (! lst->copy(*oprop.read(&other).value())) { logError() << "Cannot copy object list '" << prop.name() << "' of " << this->metaObject()->className() << "."; return false; } } else if (ConfigObjectRefList *lst = prop.read(this).value()) { if (! lst->copy(*oprop.read(&other).value())) { logError() << "Cannot copy reference list '" << prop.name() << "' of " << this->metaObject()->className() << "."; return false; } } else if (propIsInstance(prop)) { // If the item is owned by this item if (prop.isWritable()) { // If the owned item is writeable -> clone if set in other if (oprop.read(&other).isNull()) { if ((! prop.read(&other).isNull()) && (! prop.write(this, QVariant(prop.metaType())))) { logError() << "Cannot delete item '" << prop.name() << "' of " << this->metaObject()->className() << "."; return false; } } else { // Clone element form other item ConfigItem *cl = oprop.read(&other).value()->clone(); if (nullptr == cl) { logError() << "Cannot clone item '" << oprop.name() << "' of " << other.metaObject()->className() << "."; return false; } // Write clone if (! prop.write(this, QVariant(prop.metaType(), &cl))) { logError() << "Cannot replace item '" << prop.name() << "' of " << this->metaObject()->className() << "."; cl->deleteLater(); return false; } } } else if (! oprop.read(&other).isNull()) { // If the owned item is not writable (must be present) -> copy from other if set if (! prop.read(this).value()->copy(*oprop.read(&other).value())) { logError() << "Cannot copy fixed item '" << prop.name() << "' of " << this->metaObject()->className() << "."; return false; } } } } emit modified(this); return true; } int ConfigItem::compare(const ConfigItem &other) const { // Check if other has the same type if (strcmp(other.metaObject()->className(), metaObject()->className())) return strcmp(metaObject()->className(), other.metaObject()->className()); // Compare by properties const QMetaObject *meta = metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { // This property QMetaProperty prop = meta->property(p); // the same property over at other QMetaProperty oprop = other.metaObject()->property(p); // Should never happen if (! prop.isValid()) continue; // Handle comparison of basic types if (prop.isEnumType() || prop.isFlagType() || (QMetaType::Bool == prop.typeId()) || (QMetaType::Int == prop.typeId()) || (QMetaType::UInt == prop.typeId())) { int a=prop.read(this).toInt(), b=oprop.read(&other).toInt(); if (ab) return 1; continue; } if (QMetaType::Double == prop.typeId()) { double a=prop.read(this).toDouble(), b=oprop.read(&other).toDouble(); if (ab) return 1; continue; } if (QMetaType::QString == prop.typeId()) { int cmp = QString::compare(prop.read(this).toString(), oprop.read(&other).toString()); if (cmp) return cmp; continue; } if (QMetaType::fromType() == prop.metaType()) { Frequency a = prop.read(this).value(), b = oprop.read(&other).value(); if (a() == prop.metaType()) { Interval a = prop.read(this).value(), b = oprop.read(&other).value(); if (a() == prop.metaType()) { Level a = prop.read(this).value(), b = oprop.read(&other).value(); if (a()) { int cmp = ref->compare(*oprop.read(&other).value()); if (cmp) return cmp; continue; } if (ConfigObjectList *lst = prop.read(this).value()) { int cmp = lst->compare(*oprop.read(&other).value()); if (cmp) return cmp; continue; } if (propIsInstance(prop)) { ConfigObjectRefList *lst = prop.read(this).value(); int cmp = lst->compare(*oprop.read(&other).value()); if (cmp) return cmp; continue; } if (propIsInstance(prop)) { // If the owned item is writeable -> clone if set in other if (prop.read(this).isNull() && !oprop.read(&other).isNull()) return -1; if (!prop.read(this).isNull() && oprop.read(&other).isNull()) return 1; if (prop.read(this).isNull() && oprop.read(&other).isNull()) continue; int cmp = prop.read(this).value()->compare(*oprop.read(&other).value()); if (cmp) return cmp; continue; } } return 0; } bool ConfigItem::label(ConfigObject::Context &context, const ErrorStack &err) { // Label properties owning config objects, that is of type ConfigObject or ConfigObjectList const QMetaObject *meta = metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if (! prop.isValid()) continue; if (prop.read(this).value()) { ConfigObjectList *lst = prop.read(this).value(); if (! lst->label(context, err)) return false; } else if (prop.read(this).value()) { ConfigItem *obj = prop.read(this).value(); if (! obj->label(context, err)) return false; } } return true; } YAML::Node ConfigItem::serialize(const Context &context, const ErrorStack &err) { YAML::Node node; if (! populate(node, context, err)) return YAML::Node(); return node; } void ConfigItem::clear() { emit beginClear(); // Delete or clear all object owned by properties, that is ConfigObjectList and ConfigObject const QMetaObject *meta = metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if (! prop.isValid()) continue; if (propIsInstance(prop) && prop.isWritable()) { prop.write(this, QVariant(prop.metaType())); } else if (ConfigObjectList *lst = prop.read(this).value()) { lst->clear(); } } emit endClear(); } bool ConfigItem::populate(YAML::Node &node, const Context &context, const ErrorStack &err){ // Serialize all properties const QMetaObject *meta = metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if (! prop.isValid()) continue; if (! prop.isScriptable()) continue; // Handle tags: if (context.hasTag(prop.enclosingMetaObject()->className(), prop.name(), prop.read(this))) { YAML::Node tag(YAML::NodeType::Scalar); tag.SetTag(context.getTag(prop.enclosingMetaObject()->className(), prop.name(), prop.read(this)).toStdString()); node[prop.name()] = tag; continue; } // Dispatch by type if (prop.isFlagType()) { QMetaEnum e = prop.enumerator(); QStringList keys = QString::fromLatin1(e.valueToKeys(prop.read(this).toInt())).split("|"); YAML::Node list; for(auto key: keys) list.push_back(key.toStdString()); node[prop.name()] = list; } else if (prop.isEnumType()) { QMetaEnum e = prop.enumerator(); QVariant value = prop.read(this); const char *key = e.valueToKey(value.toInt()); if (nullptr == key) { errMsg(err) << "Cannot map value " << value.toUInt() << " to enum " << e.name() << ". Ignore attribute '" << prop.name() << "' but this points to an incompatibility in some codeplug. " << "Consider reporting it to https://github.com/hmatuschek/qdmr/issues."; continue; } node[prop.name()] = key; } else if (QMetaType::Bool == prop.typeId()) { node[prop.name()] = this->property(prop.name()).toBool(); } else if (QMetaType::Int == prop.typeId()) { node[prop.name()] = this->property(prop.name()).toInt(); } else if (QMetaType::UInt == prop.typeId()) { node[prop.name()] = this->property(prop.name()).toUInt(); } else if (QMetaType::Double == prop.typeId()) { node[prop.name()] = this->property(prop.name()).toDouble(); } else if (QMetaType::QString == prop.typeId()) { node[prop.name()] = this->property(prop.name()).toString().toStdString(); } else if (QMetaType::fromType() == prop.metaType()) { node[prop.name()] = this->property(prop.name()).value(); } else if (QMetaType::fromType() == prop.metaType()) { node[prop.name()] = this->property(prop.name()).value(); } else if (QMetaType::fromType() == prop.metaType()) { node[prop.name()] = this->property(prop.name()).value(); } else if (QMetaType::fromType() == prop.metaType()) { node[prop.name()] = this->property(prop.name()).value(); } else if (QMetaType::fromType() == prop.metaType()) { node[prop.name()] = this->property(prop.name()).value(); } else if (ConfigObjectReference *ref = prop.read(this).value()) { ConfigObject *obj = ref->as(); if (nullptr == obj) continue; if (context.hasTag(prop.enclosingMetaObject()->className(), prop.name(), QVariant::fromValue(obj))) { YAML::Node tag(YAML::NodeType::Scalar); tag.SetTag(context.getTag(prop.enclosingMetaObject()->className(), prop.name(), QVariant::fromValue(obj)).toStdString()); node[prop.name()] = tag; continue; } else if (! context.contains(obj)) { errMsg(err) << "Cannot reference object of type " << obj->metaObject()->className() << " object not labeled."; return false; } node[prop.name()] = context.getId(obj).toStdString(); } else if (ConfigObjectRefList *refs = prop.read(this).value()) { //logDebug() << "Serialize obj ref list w/ " << refs->count() << " elements." ; YAML::Node list = YAML::Node(YAML::NodeType::Sequence); list.SetStyle(YAML::EmitterStyle::Flow); for (int i=0; icount(); i++) { ConfigObject *obj = refs->get(i); if (! context.contains(obj)) { errMsg(err) << "Cannot reference object of type " << obj->metaObject()->className() << " object not labeled."; return false; } list.push_back(context.getId(obj).toStdString()); } node[prop.name()] = list; } else if (propIsInstance(prop)) { ConfigItem *obj = prop.read(this).value(); // Serialize config objects in-place. if (obj) node[prop.name()] = obj->serialize(context); } else if (ConfigObjectList *lst = prop.read(this).value()) { // Serialize config object lists in-place. node[prop.name()] = lst->serialize(context); } else { logDebug() << "Unhandled property " << prop.name() << " of unknown type " << prop.typeName() << "."; } } return true; } ConfigItem * ConfigItem::allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err) { Q_UNUSED(node); Q_UNUSED(ctx); if (QMetaType::UnknownType == prop.userType()) { errMsg(err) << "Cannot instantiate child of unregistered type " << prop.typeName() << "."; return nullptr; } QMetaType type(prop.userType()); if (! (QMetaType::PointerToQObject & type.flags())) { errMsg(err) << "Cannot instantiate child of non-qobject pointer type " << prop.typeName() << "."; return nullptr; } const QMetaObject *propType = type.metaObject(); /*logDebug() << "Try to instantiate child of type " << prop.typeName() << " for element " << prop.name() << " in " << this->metaObject()->className() << ".";*/ ConfigItem *item = qobject_cast( propType->newInstance(Q_ARG(QObject *,this))); if (! item) { errMsg(err) << "Could not instantiate " << propType->className() << "."; } return item; } bool ConfigItem::parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) { errMsg(err) << "YAML node is null!"; return false; } if (! node.IsMap()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse element: Expected object."; return false; } const QMetaObject *meta = this->metaObject(); for (int p=QObject::staticMetaObject.propertyOffset(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if ((! prop.isValid()) || (! prop.isScriptable())) continue; if (prop.isRequired() && !node[prop.name()]) { errMsg(err) << "Required property '" << prop.name() << "' not set!"; return false; } // Tags are handled during linking if (node[prop.name()] && (! node[prop.name()].IsNull()) && node[prop.name()].IsScalar() && (node[prop.name()].Scalar().empty()) && (! node[prop.name()].Tag().empty())) continue; if (prop.isFlagType()) { // If property is not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check enum key if (! node[prop.name()].IsSequence()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected list of enum key."; return false; } QMetaEnum e = prop.enumerator(); QByteArray keys; for (auto i: node[prop.name()]) { if (!keys.isEmpty()) keys.append("|"); keys.append(i.as()); } bool ok=true; int value = e.keysToValue(keys.constData(), &ok); if (! ok) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Unknown key-combination '" << keys << "' for enum '" << prop.name() << "'. Expected one of " << enumKeys(e).join(", ") << "."; return false; } // finally set property prop.write(this, value); } else if (prop.isEnumType()) { // If property is not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check enum key if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected enum key."; return false; } QMetaEnum e = prop.enumerator(); std::string key = node[prop.name()].as(); bool ok=true; int value = e.keyToValue(key.c_str(), &ok); if (! ok) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Unknown key '" << key.c_str() << "' for enum '" << prop.name() << "'. Expected one of " << enumKeys(e).join(", ") << "."; return false; } // finally set property prop.write(this, value); } else if (QMetaType::Bool == prop.typeId()) { // If property is not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected boolean value."; return false; } prop.write(this, node[prop.name()].as()); } else if (QMetaType::Int == prop.typeId()) { // If property is not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected integer value."; return false; } prop.write(this, node[prop.name()].as()); } else if (QMetaType::UInt == prop.typeId()) { // If property is not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected unsigned integer value."; return false; } prop.write(this, node[prop.name()].as()); } else if (QMetaType::Double == prop.typeId()) { // If property is not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected floating point value."; return false; } prop.write(this, node[prop.name()].as()); } else if (QMetaType::QString == prop.typeId()) { // If property is not set -> skip if ( (!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected string."; return false; } prop.write(this, QString::fromStdString(node[prop.name()].as())); } else if (QMetaType::fromType() == prop.metaType()) { // If property is not set -> skip if ((! node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected frequency."; return false; } Frequency f = node[prop.name()].as(); prop.write(this, QVariant::fromValue(f)); } else if (QMetaType::fromType() == prop.metaType()) { // If property is not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected interval."; return false; } prop.write(this, QVariant::fromValue(node[prop.name()].as())); } else if (QMetaType::fromType() == prop.metaType()) { // If property is not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull() || (!prop.isWritable())) continue; // parse & check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected level."; return false; } prop.write(this, QVariant::fromValue(node[prop.name()].as())); } else if (QMetaType::fromType() == prop.metaType()) { // If property is not set -> skip if ((! node[prop.name()]) || (node[prop.name()].IsNull()) || (!prop.isWritable())) { prop.write(this, QVariant::fromValue(SelectiveCall())); continue; } // parse & check type if ((! node[prop.name()].IsMap()) || (1 != node[prop.name()].size())) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected selective call."; return false; } prop.write(this, QVariant::fromValue(node[prop.name()].as())); } else if (QMetaType::fromType() == prop.metaType()) { // If property is not set -> skip if ((! node[prop.name()]) || (node[prop.name()].IsNull()) || (!prop.isWritable())) { prop.write(this, QVariant::fromValue(QGeoCoordinate())); continue; } // parse & check type if ((! node[prop.name()].IsMap()) || (2 > node[prop.name()].size())) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected geo coordinate."; return false; } QGeoCoordinate value = node[prop.name()].as(QGeoCoordinate()); qDebug() << "Write to property" << prop.name() << "value" << value; prop.write(this, QVariant::fromValue(value)); } else if (prop.read(this).value()) { // references are linked later continue; } else if (prop.read(this).value()) { // reference lists are linked later continue; } else if (propIsInstance(prop)) { if ((!node[prop.name()]) || node[prop.name()].IsNull()) continue; // check type if (! node[prop.name()].IsMap()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse '" << prop.name() << "' of '" << meta->className() << "': Expected instance of '" << QMetaType(prop.typeId()).metaObject()->className() << "'."; return false; } // Get object ConfigItem *obj = prop.read(this).value(); // If not set and writable -> allocate and set if ((nullptr == obj) && prop.isWritable()) { if (nullptr == (obj = this->allocateChild(prop, node[prop.name()], ctx))) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot allocate " << prop.name() << " of " << meta->className() << "."; return false; } // Set property if (! prop.write(this, QVariant::fromValue(obj))) { if (nullptr == obj->parent()) obj->deleteLater(); errMsg(err) << "Cannot set writable property '" << prop.name() << "' in " << this->metaObject()->className() << "."; return false; } } // parse instance YAML::Node propNode = node[prop.name()]; if (obj && (! obj->parse(propNode, ctx, err))) { errMsg(err) << propNode.Mark().line << ":" << propNode.Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << "."; if (nullptr == obj->parent()) obj->deleteLater(); return false; } } else if (prop.read(this).value()) { if ((!node[prop.name()]) || node[prop.name()].IsNull()) continue; // check type if (! node[prop.name()].IsSequence()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot parse " << prop.name() << " of " << meta->className() << ": Expected instance of '" << QMetaType(prop.typeId()).metaObject()->className() << "'."; return false; } // Get list ConfigObjectList *lst = prop.read(this).value(); // If not set and writable -> allocate and set if ((nullptr == lst) && prop.isWritable()) { if (nullptr == (lst = this->allocateChild(prop, node[prop.name()], ctx)->as())) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot allocate list " << prop.name() << " of " << meta->className() << "."; return false; } // Set property if (! prop.write(this, QVariant::fromValue(lst))) { if (nullptr == lst->parent()) lst->deleteLater(); errMsg(err) << "Cannot set writable property '" << prop.name() << "' in " << this->metaObject()->className() << "."; return false; } } // Allocate elements ConfigObject *obj = nullptr; for (YAML::const_iterator it=node[prop.name()].begin(); it!=node[prop.name()].end(); it++) { // allocate element if (nullptr == (obj = lst->allocateChild(*it, ctx, err)->as())) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot allocate child of list."; return false; } // parse element if (obj && (! obj->parse(*it, ctx))) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot parse object of type " << obj->metaObject()->className(); if (nullptr == obj->parent()) obj->deleteLater(); return false; } // add element to list if (-1 == lst->add(obj)) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot add element '" << obj->name() << "' of type " << obj->metaObject()->className() << "' to list."; if (nullptr == obj->parent()) obj->deleteLater(); return false; } } } } return true; } bool ConfigItem::link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) const QMetaObject *meta = this->metaObject(); for (int p=QObject::staticMetaObject.propertyOffset(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if (! prop.isValid()) continue; if (! prop.isScriptable()) { //logDebug() << "Do not link property '" << prop.name() << "': Marked as not scriptable."; continue; } if (ConfigObjectReference *ref = prop.read(this).value()) { // If not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull()) continue; // check type if (! node[prop.name()].IsScalar()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Expected id."; return false; } // handle tags QString tag = QString::fromStdString(node[prop.name()].Tag()); if ((!node[prop.name()].Scalar().size()) && (!tag.isEmpty())) { if (! ref->set(ctx.tagGetValue(prop.enclosingMetaObject()->className(), prop.name(), tag).value())) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Unknown tag " << tag << "."; return false; } continue; } // set reference QString id = QString::fromStdString(node[prop.name()].as()); if (! ctx.contains(id)) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link reference to '" << id << "', element not defined."; return false; } if (! ref->set(ctx.getObj(id))) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Cannot set reference."; return false; } /*logDebug() << "Linked reference " << prop.name() << "='" << id << "' to " << ctx.getObj(id)->metaObject()->className() << " '" << ctx.getObj(id)->name() << "'.";*/ } else if (ConfigObjectRefList *lst = prop.read(this).value()) { // If not set -> skip if ((!node[prop.name()]) || node[prop.name()].IsNull()) continue; // check type if (! node[prop.name()].IsSequence()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Expected sequence."; return false; } for (YAML::const_iterator it=node[prop.name()].begin(); it!=node[prop.name()].end(); it++) { if (! it->IsScalar()) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Expected ID string."; return false; } // check for tags QString tag = QString::fromStdString(it->Tag()); if ((!it->Scalar().size()) && (!tag.isEmpty())) { if (0 > lst->add(ctx.tagGetValue(prop.enclosingMetaObject()->className(), prop.name(), tag).value())) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Cannot add reference for tag '" << tag << "'."; return false; } continue; } QString id = QString::fromStdString(it->as()); if (! ctx.contains(id)) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Reference '" << id << "' not defined."; return false; } if (0 > lst->add(ctx.getObj(id))) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Cannot add reference to '" << id << "' to list."; return false; } } } else if (ConfigItem *obj = prop.read(this).value()) { // If not set -> skip if ((! node[prop.name()]) || node[prop.name()].IsNull()) continue; // check type if (! node[prop.name()].IsMap()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Expected object."; return false; } if (! obj->link(node[prop.name()], ctx, err)) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << "."; return false; } } else if (ConfigObjectList *lst = prop.read(this).value()) { // If not set -> skip if ((! node[prop.name()]) || node[prop.name()].IsNull()) continue; // check type if (! node[prop.name()].IsSequence()) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Expected sequence."; return false; } if (! lst->link(node[prop.name()], ctx, err)) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << "."; return false; } } else { YAML::Node element = node[prop.name()]; // If not set, not scalar or not tagged -> skip if ((!element) || element.IsNull() || (! element.IsScalar()) || (! element.Scalar().empty()) || element.Tag().empty()) continue; // Handle tagged values QString tag = QString::fromStdString(node[prop.name()].Tag()); if (! prop.write(this, ctx.tagGetValue(prop.enclosingMetaObject()->className(), prop.name(), tag))) { errMsg(err) << node[prop.name()].Mark().line << ":" << node[prop.name()].Mark().column << ": Cannot link " << prop.name() << " of " << meta->className() << ": Unknown tag " << tag << "."; return false; } } } return true; } const Config * ConfigItem::config() const { if (nullptr == parent()) return nullptr; if (ConfigItem *item = qobject_cast(parent())) return item->config(); if (ConfigObjectList *lst = qobject_cast(parent())) return lst->config(); return nullptr; } void ConfigItem::findItemsOfTypes(const QStringList &typeNames, QSet &items) const { // Do not check yourself const QMetaObject *meta = metaObject(); // Visit all properties for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if (! prop.isValid()) continue; if (! prop.isReadable()) continue; if (ConfigItem *obj = prop.read(this).value()) { if (isInstanceOf(obj, typeNames)) items.insert(obj); obj->findItemsOfTypes(typeNames, items); } else if (ConfigObjectList *lst = prop.read(this).value()) { lst->findItemsOfTypes(typeNames, items); } } } bool ConfigItem::hasDescription() const { const QMetaObject *meta = metaObject(); return (0 <= meta->indexOfClassInfo("description")); } bool ConfigItem::hasLongDescription() const { const QMetaObject *meta = metaObject(); return (0 <= meta->indexOfClassInfo("longDescription")); } bool ConfigItem::hasDescription(const QMetaProperty &prop) const { if (! prop.isValid()) return false; QString infoName = QString("%1Description").arg(prop.name()); const QMetaObject *meta = metaObject(); return (0 <= meta->indexOfClassInfo(infoName.toLocal8Bit().constData())); } bool ConfigItem::hasLongDescription(const QMetaProperty &prop) const { if (! prop.isValid()) return false; QString infoName = QString("%1LongDescription").arg(prop.name()); const QMetaObject *meta = metaObject(); return (0 <= meta->indexOfClassInfo(infoName.toLocal8Bit().constData())); } QString ConfigItem::description() const { if (! hasDescription()) return metaObject()->className(); const QMetaObject *meta = metaObject(); return meta->classInfo(meta->indexOfClassInfo("description")).value(); } QString ConfigItem::longDescription() const { if (! hasLongDescription()) return QString(); const QMetaObject *meta = metaObject(); return meta->classInfo(meta->indexOfClassInfo("longDescription")).value(); } QString ConfigItem::description(const QMetaProperty &prop) const { if (! hasDescription(prop)) return QString(); QString infoName = QString("%1Description").arg(prop.name()); const QMetaObject *meta = metaObject(); return meta->classInfo(meta->indexOfClassInfo(infoName.toLocal8Bit().constData())).value(); } QString ConfigItem::longDescription(const QMetaProperty &prop) const { if (! hasLongDescription(prop)) return QString(); QString infoName = QString("%1LongDescription").arg(prop.name()); const QMetaObject *meta = metaObject(); return meta->classInfo(meta->indexOfClassInfo(infoName.toLocal8Bit().constData())).value(); } /* ********************************************************************************************* * * Implementation of ConfigObject * ********************************************************************************************* */ ConfigObject::ConfigObject(QObject *parent) : ConfigItem(parent), _name() { // pass... } ConfigObject::ConfigObject(const QString &name, QObject *parent) : ConfigItem(parent), _name(name) { // pass... } const QString & ConfigObject::name() const { return _name; } void ConfigObject::setName(const QString &name) { if (name.simplified().isEmpty() || (_name == name.simplified())) return; _name = name; emit modified(this); } QString ConfigObject::idPrefix() const { return findIdPrefix(this->metaObject()); } bool ConfigObject::label(ConfigObject::Context &context, const ErrorStack &err) { unsigned n=1; QString prefix = this->idPrefix(); QString id=QString("%1%2").arg(prefix).arg(n); while (context.contains(id)) { id=QString("%1%2").arg(prefix).arg(++n); } if (! context.add(id, this)) { if (context.contains(this)) errMsg(err) << "Object already in context with id '" << context.getId(this) << "'."; errMsg(err) << "Cannot add element '" << id << "' to context."; return false; } if (! ConfigItem::label(context, err)) return false; return true; } bool ConfigObject::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node["id"]) { logWarn() << node.Mark().line << ":" << node.Mark().column << ": No id specified for " << metaObject()->className() << ". Object cannot be referenced later."; } else { QString id = QString::fromStdString(node["id"].as()); if (! ctx.add(id, this)) { errMsg(err) << node["id"].Mark().line << ":" << node["id"].Mark().column << ": Cannot register ID '" << id << "'."; return false; } } return ConfigItem::parse(node, ctx, err); } bool ConfigObject::populate(YAML::Node &node, const Context &context, const ErrorStack &err) { if (context.contains(this)) node["id"] = context.getId(this).toStdString(); return ConfigItem::populate(node, context, err); } QString ConfigObject::findIdPrefix(const QMetaObject *meta) { for (int i=meta->classInfoOffset(); iclassInfoCount(); i++) { if (0 == strcmp("IdPrefix", meta->classInfo(i).name())) { return meta->classInfo(i).value(); } } if (meta->superClass()) return findIdPrefix(meta->superClass()); return ""; } /* ********************************************************************************************* * * Implementation of ConfigExtension * ********************************************************************************************* */ ConfigExtension::ConfigExtension(QObject *parent) : ConfigItem(parent) { // pass... } /* ********************************************************************************************* * * Implementation of AbstractConfigObjectList * ********************************************************************************************* */ AbstractConfigObjectList::AbstractConfigObjectList(const QMetaObject &elementType, QObject *parent) : QObject(parent), _elementTypes(), _items() { _elementTypes.append(elementType); } AbstractConfigObjectList::AbstractConfigObjectList(const std::initializer_list &elementTypes, QObject *parent) : QObject(parent), _elementTypes(elementTypes), _items() { // pass... } bool AbstractConfigObjectList::copy(const AbstractConfigObjectList &other) { this->clear(); _elementTypes = other._elementTypes; foreach (ConfigObject *item, other._items) add(item); return true; } int AbstractConfigObjectList::count() const { return _items.count(); } int AbstractConfigObjectList::indexOf(ConfigObject *obj) const { return _items.indexOf(obj); } void AbstractConfigObjectList::clear() { for (int i=(count()-1); i>=0; i--) { _items.pop_back(); emit elementRemoved(i); } } const Config * AbstractConfigObjectList::config() const { if (nullptr == parent()) return nullptr; if (ConfigItem *item = qobject_cast(parent())) return item->config(); return nullptr; } void AbstractConfigObjectList::findItemsOfTypes(const QStringList &typeNames, QSet &items) const { foreach (ConfigObject *obj, _items) { if (isInstanceOf(obj, typeNames)) items.insert(obj); obj->findItemsOfTypes(typeNames, items); } } QList AbstractConfigObjectList::findItemsByName(const QString name) const { QList items; foreach (ConfigObject *obj, _items) { if (obj->name() == name) items.append(obj); } return items; } bool AbstractConfigObjectList::has(ConfigObject *obj) const { return 0 <= indexOf(obj); } ConfigObject * AbstractConfigObjectList::get(int idx) const { return _items.value(idx, nullptr); } int AbstractConfigObjectList::add(ConfigObject *obj, int row, bool unique) { // Ignore nullptr if (nullptr == obj) return -1; // If already in list -> ignore if (unique && (0 <= indexOf(obj))) return -1; if (-1 == row) row = _items.size(); // Check type bool matchesType = false; foreach (const QMetaObject &type, _elementTypes) { if (obj->inherits(type.className())) { matchesType = true; break; } } if (! matchesType) { logError() << "Cannot add element of type " << obj->metaObject()->className() << " to list, expected instances of " << classNames().join(", "); return -1; } _items.insert(row, obj); // Otherwise connect to object connect(obj, SIGNAL(destroyed(QObject*)), this, SLOT(onElementDeleted(QObject*))); connect(obj, SIGNAL(modified(ConfigItem*)), this, SLOT(onElementModified(ConfigItem*))); emit elementAdded(row); return row; } int AbstractConfigObjectList::replace(ConfigObject *obj, int row, bool unique) { // Ignore nullptr if (nullptr == obj) return -1; // Check index if (row >= count()) return -1; // Check if self-replacement if (row == indexOf(obj)) return indexOf(obj); // If already in list -> ignore if (unique && (0 <= indexOf(obj))) return -1; // Check type bool matchesType = false; foreach (const QMetaObject &type, _elementTypes) { if (obj->inherits(type.className())) { matchesType = true; break; } } if (! matchesType) { logError() << "Cannot add element of type " << obj->metaObject()->className() << " to list, expected instances of " << classNames().join(", "); return -1; } // Remove present element ConfigObject *oldobj = _items.at(row); _items.remove(row, 1); emit elementRemoved(row); disconnect(oldobj, nullptr, this, nullptr); _items.insert(row, obj); // connect to object connect(obj, SIGNAL(destroyed(QObject*)), this, SLOT(onElementDeleted(QObject*))); connect(obj, SIGNAL(modified(ConfigItem*)), this, SLOT(onElementModified(ConfigItem*))); emit elementAdded(row); return row; } bool AbstractConfigObjectList::take(ConfigObject *obj) { // Ignore nullptr if (nullptr == obj) return false; int idx = indexOf(obj); if (0 > idx) return false; _items.remove(idx, 1); emit elementRemoved(idx); // Otherwise disconnect from disconnect(obj, nullptr, this, nullptr); return true; } bool AbstractConfigObjectList::del(ConfigObject *obj) { return take(obj); } bool AbstractConfigObjectList::moveUp(int row) { if ((row <= 0) || (row>=count())) return false; std::swap(_items[row-1], _items[row]); return true; } bool AbstractConfigObjectList::moveUp(int first, int last) { if ((first <= 0) || (last>=count())) return false; for (int row=first; row<=last; row++) std::swap(_items[row-1], _items[row]); return true; } bool AbstractConfigObjectList::moveDown(int row) { if ((row >= (count()-1)) || (0 > row)) return false; std::swap(_items[row+1], _items[row]); return true; } bool AbstractConfigObjectList::moveDown(int first, int last) { if ((last >= (count()-1)) || (0 > first)) return false; for (int row=last; row>=first; row--) std::swap(_items[row+1], _items[row]); return true; } bool AbstractConfigObjectList::move(int source, int count, int destination) { if ((0 == count) || (source == destination)) return true; if ((source+count)>_items.size()) return false; if (source > destination) { // move up for (int take=source, put=destination, i=0; i & AbstractConfigObjectList::elementTypes() const { return _elementTypes; } QStringList AbstractConfigObjectList::classNames() const { QStringList cls; foreach (const QMetaObject &type, _elementTypes) { cls.append(type.className()); } return cls; } void AbstractConfigObjectList::onElementModified(ConfigItem *obj) { int idx = indexOf(obj->as()); if (0 <= idx) emit elementModified(idx); } void AbstractConfigObjectList::onElementDeleted(QObject *obj) { // Use reinterpret cast here as the obj may already be destroyed and this all RTTI freed. // We just use the pointer address to remove the element here. int idx = indexOf(reinterpret_cast(obj)); if (0 <= idx) { _items.remove(idx); emit elementRemoved(idx); } } /* ********************************************************************************************* * * Implementation of ConfigObjectList * ********************************************************************************************* */ ConfigObjectList::ConfigObjectList(const QMetaObject &elementType, QObject *parent) : AbstractConfigObjectList(elementType, parent) { // pass... } ConfigObjectList::ConfigObjectList(const std::initializer_list &elementTypes, QObject *parent) : AbstractConfigObjectList(elementTypes, parent) { // pass... } bool ConfigObjectList::label(ConfigItem::Context &context, const ErrorStack &err) { foreach (ConfigItem *obj, _items) { if (! obj->label(context, err)) return false; } return true; } YAML::Node ConfigObjectList::serialize(const ConfigItem::Context &context, const ErrorStack &err) { YAML::Node list(YAML::NodeType::Sequence); foreach (ConfigItem *obj, _items) { YAML::Node node = obj->serialize(context, err); if (node.IsNull()) return node; list.push_back(node); } return list; } bool ConfigObjectList::parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) return false; if (!node.IsSequence()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse list: Expected list."; return false; } for (YAML::Node::const_iterator it=node.begin(); it!=node.end(); it++) { // Create object for node ConfigItem *element = allocateChild(*it, ctx); if ((nullptr == element) || (!element->is())) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot parse list."; return false; } if (! element->parse(*it, ctx, err)) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot parse list."; element->deleteLater(); return false; } if (0 > add(element->as())) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot add element to list."; element->deleteLater(); return false; } } return true; } bool ConfigObjectList::link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) return false; if (!node.IsSequence()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse list: Expected list."; return false; } int i = 0; YAML::Node::const_iterator it=node.begin(); for (; it!=node.end(); it++, i++) { // Create object for node ConfigItem *element = get(i); if (nullptr == element) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link list, " << i << "-th element not present."; return false; } if (! element->link(*it, ctx, err)) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link list."; return false; } } return true; } int ConfigObjectList::add(ConfigObject *obj, int row, bool unique) { if (0 <= (row = AbstractConfigObjectList::add(obj, row, unique))) obj->setParent(this); return row; } bool ConfigObjectList::take(ConfigObject *obj) { if (AbstractConfigObjectList::take(obj)) obj->setParent(nullptr); return true; } bool ConfigObjectList::del(ConfigObject *obj) { if (AbstractConfigObjectList::del(obj)) delete obj; return true; } void ConfigObjectList::clear() { QVector items = _items; AbstractConfigObjectList::clear(); for (int i=0; ideleteLater(); } bool ConfigObjectList::copy(const AbstractConfigObjectList &other) { clear(); _elementTypes = other.elementTypes(); for (int i=0; iclone()->as()); return true; } int ConfigObjectList::compare(const ConfigObjectList &other) const { if (count() < other.count()) return -1; if (count() > other.count()) return 1; for (int i=0; iget(i)->compare(*other.get(i)); if (cmp) return cmp; } return 0; } /* ********************************************************************************************* * * Implementation of ConfigObjectRefList * ********************************************************************************************* */ ConfigObjectRefList::ConfigObjectRefList(const QMetaObject &elementType, QObject *parent) : AbstractConfigObjectList(elementType, parent) { // pass... } ConfigObjectRefList::ConfigObjectRefList(const std::initializer_list &elementTypes, QObject *parent) : AbstractConfigObjectList(elementTypes, parent) { // pass... } bool ConfigObjectRefList::label(ConfigItem::Context &context, const ErrorStack &err) { Q_UNUSED(context); Q_UNUSED(err); // pass... return true; } YAML::Node ConfigObjectRefList::serialize(const ConfigItem::Context &context, const ErrorStack &err) { YAML::Node list(YAML::NodeType::Sequence); foreach (ConfigObject *obj, _items) { if (! context.contains(obj)) { errMsg(err) << "Cannot serialized ref list: Object '" << obj->name() << "' not in context!"; return YAML::Node(); } list.push_back(context.getId(obj).toStdString()); } return list; } int ConfigObjectRefList::compare(const ConfigObjectRefList &other) const { if (count() < other.count()) return -1; if (count() > other.count()) return 1; for (int i=0; icompare(*other.get(i)); if (cmp) return cmp; } return 0; } ================================================ FILE: lib/configobject.hh ================================================ #ifndef CONFIGOBJECT_HH #define CONFIGOBJECT_HH #include #include #include #include #include #include #include "errorstack.hh" // Forward declaration class Config; class ConfigObject; class ConfigExtension; /** Helper function to test property type. */ template bool propIsInstance(const QMetaProperty &prop) { if (QMetaType::UnknownType == prop.typeId()) return false; QMetaType type = prop.metaType(); if (! (QMetaType::PointerToQObject & type.flags())) return false; return type.metaObject()->inherits(&T::staticMetaObject); } /** Base class for all configuration objects (channels, zones, contacts, etc). * * @ingroup conf */ class ConfigItem : public QObject { Q_OBJECT public: /** Parse context for config objects. During serialization, each config object gets an * ID assigned. When reading the config, these IDs must be matched back to the corresponding * objects. This is done using this context. */ class Context { public: /** Empty constructor. */ Context(); /** Destructor. */ virtual ~Context(); /** Returns the read version string. */ const QString &version() const; /** Sets the version string. */ void setVersion(const QString &ver); /** Returns @c true, if the context contains the given object. */ virtual bool contains(ConfigObject *obj) const; /** Returns @c true, if the context contains the given ID. */ virtual bool contains(const QString &id) const; /** Returns ID of the given object. */ virtual QString getId(ConfigObject *obj) const; /** Returns the object for the given ID. */ virtual ConfigObject *getObj(const QString &id) const; /** Associates the given object with the given ID. */ virtual bool add(const QString &id, ConfigObject *); /** Returns @c true if the property of the class has the specified tag associated. */ static bool tagIsSet(const QString &className, const QString &property, const QString &tag); /** Returns @c true if the property of the class has the specified object as a tag associated. */ static bool hasTag(const QString &className, const QString &property, QVariant value); /** Returns the object associated with the tag for the property of the class. */ static QVariant tagGetValue(const QString &className, const QString &property, const QString &tag); /** Returns the tag associated with the object for the property of the class. */ static QString getTag(const QString &className, const QString &property, QVariant value); /** Associates the given object with the tag for the property of the given class. */ static void setTag(const QString &className, const QString &property, const QString &tag, QVariant value); protected: /** The version string. */ QString _version; /** ID->OBJ look-up table. */ QHash _objects; /** OBJ->ID look-up table. */ QHash _ids; /** Set of tag-value pairs for all properties. */ static QHash>> _tags; }; protected: /** Hidden constructor. * @param parent Specifies the QObject parent. */ explicit ConfigItem(QObject *parent = nullptr); public: /** Copies the given item into this one. * @returns @c true if copying was successful and false otherwise. The two items must be of the * same type (obviously). */ virtual bool copy(const ConfigItem &other); /** Clones this item. */ virtual ConfigItem *clone() const = 0; /** Compares the items. * * This method returns 0 if the two items are equivalent and -1, 1 otherwise. The established * order is somewhat arbitrary. * * @returns 0 if the two items are equivalent, -1 or 1 otherwise.*/ virtual int compare(const ConfigItem &other) const; public: /** Recursively labels the config object. * Does not assign a label if the @c idBase passed to the constructor is empty. */ virtual bool label(Context &context, const ErrorStack &err=ErrorStack()); /** Recursively serializes the configuration to YAML nodes. * The complete configuration must be labeled first. */ virtual YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); /** Allocates an instance for the given property on the given YAML node. * This is usually done automatically based on the meta-type of the property. To be able to * instantiate the item, its default constructor must be Q_INVOKABLE. */ virtual ConfigItem *allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack()); /** Parses the given YAML node, updates the given object and updates the given context (IDs). */ virtual bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the given object to the rest of the codeplug using the given context. */ virtual bool link(const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack()); /** Clears the config object. */ virtual void clear(); /** Returns the config, the item belongs to or @c nullptr if not part of a config. */ virtual const Config *config() const; /** Searches the config tree to find all instances of the given type names. */ virtual void findItemsOfTypes(const QStringList &typeNames, QSet &items) const; /** Returns @c true if this object is of class @c Object. */ template bool is() const { return nullptr != qobject_cast(this); } /** Casts this object to the given type. */ template const Object *as() const { return qobject_cast(this); } /** Casts this object to the given type. */ template Object *as() { return qobject_cast(this); } /** Returns @c true if there is a class info "description" for this instance. */ bool hasDescription() const; /** Returns @c true if there is a class info "longDescription" for this instance. */ bool hasLongDescription() const; /** Returns @c true if there is a class info "[PropertyName]Description" for the given property. */ bool hasDescription(const QMetaProperty &prop) const; /** Returns @c true if there is a class info "[PropertyName]LongDescription" for the given property. */ bool hasLongDescription(const QMetaProperty &prop) const; /** Returns the description of this instance if set by a class info. */ QString description() const; /** Returns the long description of this instance if set by a class info. */ QString longDescription() const; /** Returns the description of property if set by a class info. */ QString description(const QMetaProperty &prop) const; /** Returns the long description of property if set by a class info. */ QString longDescription(const QMetaProperty &prop) const; protected: /** Recursively serializes the configuration to YAML nodes. * The complete configuration must be labeled first. */ virtual bool populate(YAML::Node &node, const Context &context, const ErrorStack &err=ErrorStack()); signals: /** Gets emitted once the config object is modified. * The instance passed is the modified item, this event is passed up the config tree. */ void modified(ConfigItem *obj); /** Gets emitted before clearing the item. */ void beginClear(); /** Gets emitted after clearing the item. */ void endClear(); }; /** Base class of all labeled and named objects. * @ingroup config */ class ConfigObject: public ConfigItem { Q_OBJECT /** The name of the object. */ Q_PROPERTY(QString name READ name WRITE setName) /** Specifies the prefix for every ID assigned to every object during serialization. */ Q_CLASSINFO("IdPrefix", "obj") protected: /** Hidden constructor. * @param parent Specifies the QObject parent. */ ConfigObject(QObject *parent = nullptr); /** Hidden constructor. * @param name Name of the object. * @param parent Specifies the QObject parent. */ ConfigObject(const QString &name, QObject *parent = nullptr); public: /** Returns the name of the object. */ virtual const QString &name() const; /** Sets the name of the object. */ virtual void setName(const QString &name); public: /** Returns the ID prefix for this object. */ QString idPrefix() const; bool label(Context &context, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); protected: virtual bool populate(YAML::Node &node, const Context &context, const ErrorStack &err=ErrorStack()); /** Helper to find the @c IdPrefix class info in the class hierarchy. */ static QString findIdPrefix(const QMetaObject* meta); protected: /** Holds the name of the object. */ QString _name; }; /** Base class of all device/vendor specific configuration extensions. * This class already implements the serialization of all @c QMetaObject * properties. */ class ConfigExtension : public ConfigItem { Q_OBJECT protected: /** Hidden constructor. */ explicit ConfigExtension(QObject *parent=nullptr); }; /** Generic list class for config objects. * @ingroup config */ class AbstractConfigObjectList: public QObject { Q_OBJECT protected: /** Hidden constructor. */ explicit AbstractConfigObjectList(const QMetaObject &elementTypes=ConfigObject::staticMetaObject, QObject *parent = nullptr); /** Hidden constructor from initializer list. */ AbstractConfigObjectList(const std::initializer_list &elementTypes, QObject *parent=nullptr); public: /** Copies all elements from @c other to this list. */ virtual bool copy(const AbstractConfigObjectList &other); /** Recursively labels the config object. */ virtual bool label(ConfigItem::Context &context, const ErrorStack &err=ErrorStack()) = 0; /** Recursively serializes the configuration to YAML nodes. * The complete configuration must be labeled first. */ virtual YAML::Node serialize(const ConfigItem::Context &context, const ErrorStack &err=ErrorStack()) = 0; /** Returns the number of elements in the list. */ virtual int count() const; /** Returns the index of the given object within the list. */ virtual int indexOf(ConfigObject *obj) const; /** Clears the list. */ virtual void clear(); /** Returns the config object, this list belongs to. */ virtual const Config *config() const; /** Searches the config tree to find all instances of the given type names. */ virtual void findItemsOfTypes(const QStringList &typeNames, QSet &items) const; /** Searches the list for objects with the given name. */ virtual QList findItemsByName(const QString name) const; /** Returns @c true, if the list contains the given object. */ virtual bool has(ConfigObject *obj) const; /** Returns the list element at the given index or @c nullptr if out of bounds. */ virtual ConfigObject *get(int idx) const; /** Adds an element to the list. */ virtual int add(ConfigObject *obj, int row=-1, bool unique=true); /** Replaces an element in the list. */ virtual int replace(ConfigObject *obj, int row, bool unique=true); /** Removes an element from the list. */ virtual bool take(ConfigObject *obj); /** Removes an element from the list (and deletes it if owned). */ virtual bool del(ConfigObject *obj); /** Moves an object at index @c idx one step up. */ virtual bool moveUp(int idx); /** Moves objects at [first, last] one step up. */ virtual bool moveUp(int first, int last); /** Moves an object at index @c idx one step down. */ virtual bool moveDown(int idx); /** Moves objects [first, last] one step down. */ virtual bool moveDown(int first, int last); /** Moves the given source range to the destination index. * The destination index is given before the movement. That is, if elements 0 & 1 are moved to * indices 1 & 2, call @c move(0,2, 2) */ virtual bool move(int source, int count, int destination); /** Returns the element type for this list. */ const QList &elementTypes() const; /** Returns a list of all class names. */ QStringList classNames() const; signals: /** Gets emitted if an element was added to the list. */ void elementAdded(int idx); /** Gets emitted if one of the lists elements gets modified. */ void elementModified(int idx); /** Gets emitted if one of the lists elements gets deleted. */ void elementRemoved(int idx); private slots: /** Internal used callback to handle modified elements. */ void onElementModified(ConfigItem *obj); /** Internal used callback to handle deleted elements. */ void onElementDeleted(QObject *obj); protected: /** Holds the static QMetaObject of the element type. */ QList _elementTypes; /** Holds the list items. */ QVector _items; }; /** List class for config objects. * This list owns the config objects it contains. See @c ConfigObjectRefList for a list of weak * references. * @ingroup config */ class ConfigObjectList: public AbstractConfigObjectList { Q_OBJECT protected: /** Hidden constructor. */ explicit ConfigObjectList(const QMetaObject &elementTypes=ConfigItem::staticMetaObject, QObject *parent = nullptr); /** Hidden constructor from initializer list. */ ConfigObjectList(const std::initializer_list &elementTypes, QObject *parent=nullptr); public: int add(ConfigObject *obj, int row=-1, bool unique=true); bool take(ConfigObject *obj); bool del(ConfigObject *obj); void clear(); bool copy(const AbstractConfigObjectList &other); /** Compares the object lists. * * This method returns 0 if the two lists are equivalent and -1, 1 otherwise. The established * order is somewhat arbitrary. * * @returns 0 if the two lists are equivalent, -1 or 1 otherwise.*/ virtual int compare(const ConfigObjectList &other) const; /** Allocates a member objects for the given YAML node. */ virtual ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Parses the list from the YAML node. */ virtual bool parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the list from the given YAML node. */ virtual bool link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); bool label(ConfigItem::Context &context, const ErrorStack &err=ErrorStack()); YAML::Node serialize(const ConfigItem::Context &context, const ErrorStack &err=ErrorStack()); }; /** List class for config objects. * This list only references the config objects, see @c ConfigObjectList for a list that owns the * config objects. * @ingroup config */ class ConfigObjectRefList: public AbstractConfigObjectList { Q_OBJECT protected: /** Hidden constructor. */ explicit ConfigObjectRefList(const QMetaObject &elementTypes=ConfigObject::staticMetaObject, QObject *parent = nullptr); /** Hidden constructor from initializer list. */ ConfigObjectRefList(const std::initializer_list &elementTypes, QObject *parent=nullptr); public: bool label(ConfigItem::Context &context, const ErrorStack &err=ErrorStack()); YAML::Node serialize(const ConfigItem::Context &context, const ErrorStack &err=ErrorStack()); /** Compares the object ref lists. * * This method returns 0 if the two lists are equivalent and -1, 1 otherwise. The established * order is somewhat arbitrary. * * @returns 0 if the two lists are equivalent, -1 or 1 otherwise.*/ virtual int compare(const ConfigObjectRefList &other) const; }; #endif // CONFIGOBJECT_HH ================================================ FILE: lib/configreference.cc ================================================ #include "configreference.hh" #include "logger.hh" #include "contact.hh" #include "channel.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "radioid.hh" #include "rxgrouplist.hh" #include "roamingzone.hh" #include "zone.hh" #include "encryptionextension.hh" /* ********************************************************************************************* * * Implementation of ConfigObjectReference * ********************************************************************************************* */ ConfigObjectReference::ConfigObjectReference(const QMetaObject &elementType, QObject *parent) : QObject(parent), _elementTypes(), _object(nullptr) { _elementTypes.append(elementType.className()); } bool ConfigObjectReference::isNull() const { return nullptr == _object; } void ConfigObjectReference::clear() { if (_object) { disconnect(_object, SIGNAL(destroyed(QObject*)), this, SLOT(onReferenceDeleted(QObject*))); emit modified(); } _object = nullptr; } bool ConfigObjectReference::set(ConfigObject *object) { if (_object) disconnect(_object, SIGNAL(destroyed(QObject*)), this, SLOT(onReferenceDeleted(QObject*))); if (nullptr == object) { _object = nullptr; return true; } // Check type bool typeCheck = false; foreach (const QString &cname, _elementTypes) { if (object->inherits(cname.toLatin1().constData())) { typeCheck = true; break; } } if (! typeCheck) { logError() << "Cannot reference element of type " << object->metaObject()->className() << ", expected instance of " << _elementTypes.join(", "); return false; } _object = object; if (_object) connect(_object, &QObject::destroyed, this, &ConfigObjectReference::onReferenceDeleted, Qt::DirectConnection); emit modified(); return true; } bool ConfigObjectReference::copy(const ConfigObjectReference *ref) { clear(); if (nullptr == ref) return true; return set(ref->_object); } int ConfigObjectReference::compare(const ConfigObjectReference &other) const { if (isNull() && other.isNull()) return 0; if (!isNull() && other.isNull()) return 1; if (isNull() && !other.isNull()) return -1; return this->_object->compare(*other._object); } bool ConfigObjectReference::allow(const QMetaObject *elementType) { if (! _elementTypes.contains(elementType->className())) _elementTypes.append(elementType->className()); return true; } const QStringList & ConfigObjectReference::elementTypeNames() const { return _elementTypes; } void ConfigObjectReference::onReferenceDeleted(QObject *obj) { // Check if destroyed obj is referenced one. if (_object != reinterpret_cast(obj)) return; // If it is _object = nullptr; emit modified(); } /* ********************************************************************************************* * * Implementation of ContactReference * ********************************************************************************************* */ ContactReference::ContactReference(const QMetaObject &elementType, QObject *parent) : ConfigObjectReference(elementType, parent) { // pass... } ContactReference::ContactReference(QObject *parent) : ConfigObjectReference(Contact::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRContactReference * ********************************************************************************************* */ DMRContactReference::DMRContactReference(QObject *parent) : ContactReference(DMRContact::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRContactReference * ********************************************************************************************* */ M17ContactReference::M17ContactReference(QObject *parent) : ContactReference(M17Contact::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRContactRefList * ********************************************************************************************* */ DMRContactRefList::DMRContactRefList(QObject *parent) : ConfigObjectRefList(DMRContact::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of ChannelReference * ********************************************************************************************* */ ChannelReference::ChannelReference(const QMetaObject &elementType, QObject *parent) : ConfigObjectReference(elementType, parent) { // pass... } ChannelReference::ChannelReference(QObject *parent) : ConfigObjectReference(Channel::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRChannelReference * ********************************************************************************************* */ DMRChannelReference::DMRChannelReference(QObject *parent) : ChannelReference(DMRChannel::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of FMChannelReference * ********************************************************************************************* */ FMChannelReference::FMChannelReference(QObject *parent) : ChannelReference(FMChannel::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of ChannelRefList * ********************************************************************************************* */ ChannelRefList::ChannelRefList(const QMetaObject &elementType, QObject *parent) : ConfigObjectRefList(elementType, parent) { // pass... } ChannelRefList::ChannelRefList(QObject *parent) : ConfigObjectRefList(Channel::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRChannelRefList * ********************************************************************************************* */ DMRChannelRefList::DMRChannelRefList(QObject *parent) : ChannelRefList(DMRChannel::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of RoamingChannelRefList * ********************************************************************************************* */ RoamingChannelRefList::RoamingChannelRefList(QObject *parent) : ConfigObjectRefList({RoamingChannel::staticMetaObject}, parent) { // pass... } /* ********************************************************************************************* * * Implementation of ScanListReference * ********************************************************************************************* */ ScanListReference::ScanListReference(QObject *parent) : ConfigObjectReference(ScanList::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of PositioningSystemReference * ********************************************************************************************* */ PositioningSystemReference::PositioningSystemReference(const QMetaObject &elementType, QObject *parent) : ConfigObjectReference(elementType, parent) { // pass... } PositioningSystemReference::PositioningSystemReference(QObject *parent) : ConfigObjectReference(PositionReportingSystem::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of FMAPRSSystemReference * ********************************************************************************************* */ FMAPRSSystemReference::FMAPRSSystemReference(QObject *parent) : PositioningSystemReference(FMAPRSSystem::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRAPRSSystemReference * ********************************************************************************************* */ DMRAPRSSystemReference::DMRAPRSSystemReference(QObject *parent) : PositioningSystemReference(FMAPRSSystem::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of RadioIDReference * ********************************************************************************************* */ DMRRadioIDReference::DMRRadioIDReference(QObject *parent) : ConfigObjectReference(DMRRadioID::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of GroupListReference * ********************************************************************************************* */ GroupListReference::GroupListReference(QObject *parent) : ConfigObjectReference(RXGroupList::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of RoamingZoneReference * ********************************************************************************************* */ RoamingZoneReference::RoamingZoneReference(QObject *parent) : ConfigObjectReference(RoamingZone::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of EncryptionKeyReference * ********************************************************************************************* */ EncryptionKeyReference::EncryptionKeyReference(QObject *parent) : ConfigObjectReference(EncryptionKey::staticMetaObject, parent) { // pass... } /* ********************************************************************************************* * * Implementation of ZoneReference * ********************************************************************************************* */ ZoneReference::ZoneReference(QObject *parent) : ConfigObjectReference(Zone::staticMetaObject, parent) { // pass... } ================================================ FILE: lib/configreference.hh ================================================ #ifndef CONFIGREFERENCE_HH #define CONFIGREFERENCE_HH #include "configobject.hh" #include class Channel; class DMRChannel; class FMChannel; class ScanList; class EncryptionKey; /** Implements a reference to a config object. * This class is only used to implement the automatic generation/parsing of the YAML codeplug files. * @ingroup conf */ class ConfigObjectReference: public QObject { Q_OBJECT protected: /** Hidden constructor. */ ConfigObjectReference(const QMetaObject &elementType=ConfigObject::staticMetaObject, QObject *parent = nullptr); public: /** Returns @c true if the reference is null. * That is, if there is no object referenced. */ bool isNull() const; /** Resets the reference. * Same as @c set(nullptr). */ virtual void clear(); /** Sets the reference. * If set to @c nullptr, the reference gets cleared. */ virtual bool set(ConfigObject *object); /** Copies the reference from another reference. */ virtual bool copy(const ConfigObjectReference *ref); /** Adds a possible type to this reference. */ virtual bool allow(const QMetaObject *elementType); /** Returns the type names of allowed objects. */ const QStringList &elementTypeNames() const; /** Returns the reference as the specified type. */ template Type *as() const { if (nullptr == _object) return nullptr; return _object->as(); } /** Returns @c true if the reference is of the specified type. */ template bool is() const { if (nullptr == _object) return false; return _object->is(); } /** Compares the references. */ int compare(const ConfigObjectReference &other) const; signals: /** Gets emitted if the reference is changed. * This signal is not emitted if the referenced object is modified. */ void modified(); protected slots: /** Internal call back whenever the referenced object gets deleted. */ void onReferenceDeleted(QObject *obj); protected: /** Holds the static QMetaObject of the possible element types. */ QStringList _elementTypes; /** The reference to the object. */ ConfigObject *_object; }; /** Represents a reference to a contact. * This class is only used to automate the parsing and generation of the YAML codeplug file. * @ingroup config */ class ContactReference: public ConfigObjectReference { Q_OBJECT protected: /** Constructor. */ ContactReference(const QMetaObject &elementType, QObject *parent = nullptr); public: /** Constructor. */ explicit ContactReference(QObject *parent=nullptr); }; /** Represents a reference to a DMR contact. * @ingroup conf*/ class DMRContactReference: public ContactReference { Q_OBJECT public: /** Constructor. */ explicit DMRContactReference(QObject *parent=nullptr); }; /** Represents a reference to a M17 contact. * @ingroup conf*/ class M17ContactReference: public ContactReference { Q_OBJECT public: /** Constructor. */ explicit M17ContactReference(QObject *parent=nullptr); }; /** List of references to DMR contacts. */ class DMRContactRefList: public ConfigObjectRefList { Q_OBJECT public: /** Constructor. */ explicit DMRContactRefList(QObject *parent=nullptr); }; /** Represents a reference to a channel. * This class is only used to automate the parsing and generation of the YAML codeplug file. * @ingroup config */ class ChannelReference: public ConfigObjectReference { Q_OBJECT protected: /** Hidden constructor. */ ChannelReference(const QMetaObject &elementType, QObject *parent = nullptr); public: /** Constructor. */ explicit ChannelReference(QObject *parent=nullptr); }; /** Implements a reference to a DMR channel. * @ingroup conf */ class DMRChannelReference: public ChannelReference { Q_OBJECT public: /** Constructor. */ explicit DMRChannelReference(QObject *parent=nullptr); }; /** Implements a reference to a FM channel. * @ingroup conf */ class FMChannelReference: public ChannelReference { Q_OBJECT public: /** Constructor. */ explicit FMChannelReference(QObject *parent=nullptr); }; /** Represents a list of weak references to channels (analog and digital). * @ingroup config */ class ChannelRefList: public ConfigObjectRefList { Q_OBJECT protected: /** Hidden constructor. */ explicit ChannelRefList(const QMetaObject &elementTypes, QObject *parent = nullptr); public: /** Empty constructor. */ explicit ChannelRefList(QObject *parent=nullptr); }; /** Represents a list of references to some DMR channels. * @ingroup config */ class DMRChannelRefList: public ChannelRefList { Q_OBJECT public: /** Empty constructor. */ explicit DMRChannelRefList(QObject *parent=nullptr); }; /** Represents a list of references to some roaming channels. * @ingroup config */ class RoamingChannelRefList: public ConfigObjectRefList { Q_OBJECT public: /** Empty constructor. */ explicit RoamingChannelRefList(QObject *parent=nullptr); }; /** Implements a reference to a scan list. * @ingroup conf */ class ScanListReference: public ConfigObjectReference { Q_OBJECT public: /** Constructor. */ explicit ScanListReference(QObject *parent=nullptr); }; /** Implements a reference to a positioning system. * @ingroup conf */ class PositioningSystemReference: public ConfigObjectReference { Q_OBJECT protected: /** Hidden constructor. */ PositioningSystemReference(const QMetaObject &elementType, QObject *parent = nullptr); public: /** Constructor. */ explicit PositioningSystemReference(QObject *parent=nullptr); }; /** Implements a reference to an APRS system. * @ingroup conf */ class FMAPRSSystemReference: public PositioningSystemReference { Q_OBJECT public: /** Constructor. */ explicit FMAPRSSystemReference(QObject *parent=nullptr); }; /** Implements a reference to a GPS system. * @ingroup conf */ class DMRAPRSSystemReference: public PositioningSystemReference { Q_OBJECT public: /** Constructor. */ explicit DMRAPRSSystemReference(QObject *parent=nullptr); }; /** Implements a reference to a radio ID. * @ingroup conf */ class DMRRadioIDReference: public ConfigObjectReference { Q_OBJECT public: /** Constructor. */ explicit DMRRadioIDReference(QObject *parent=nullptr); }; /** Implements a reference to a group list. * @ingroup conf */ class GroupListReference: public ConfigObjectReference { Q_OBJECT public: /** Constructor. */ explicit GroupListReference(QObject *parent=nullptr); }; /** Implements a reference to a roaming zone. * @ingroup conf */ class RoamingZoneReference: public ConfigObjectReference { Q_OBJECT public: /** Constructor. */ explicit RoamingZoneReference(QObject *parent=nullptr); }; /** Implements a reference to an encryption key. * @ingroup conf */ class EncryptionKeyReference: public ConfigObjectReference { Q_OBJECT public: /** Constructor. */ explicit EncryptionKeyReference(QObject *parent=nullptr); }; /** Implements a reference to a zone. * @ingroup conf */ class ZoneReference: public ConfigObjectReference { Q_OBJECT public: /** Constructor. */ explicit ZoneReference(QObject *parent=nullptr); }; #endif // CONFIGREFERENCE_HH ================================================ FILE: lib/contact.cc ================================================ #include "contact.hh" #include "config.hh" #include "utils.hh" #include "logger.hh" #include "opengd77_extension.hh" #include /* ********************************************************************************************* * * Implementation of Contact * ********************************************************************************************* */ Contact::Contact(QObject *parent) : ConfigObject(parent), _ring(false) { // pass... } Contact::Contact(const QString &name, bool rxTone, QObject *parent) : ConfigObject(name, parent), _ring(rxTone) { // pass... } void Contact::clear() { ConfigObject::clear(); _ring = false; } bool Contact::ring() const { return _ring; } void Contact::setRing(bool enable) { _ring = enable; emit modified(this); } bool Contact::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse contact: Expected object with one child."; return false; } YAML::Node cnt = node.begin()->second; return ConfigObject::parse(cnt, ctx, err); } bool Contact::link(const YAML::Node &node, const Context &ctx, const ErrorStack &err) { return ConfigObject::link(node.begin()->second, ctx, err); } /* ********************************************************************************************* * * Implementation of AnalogContact * ********************************************************************************************* */ AnalogContact::AnalogContact(QObject *parent) : Contact(parent) { // pass... } AnalogContact::AnalogContact(const QString &name, bool ring, QObject *parent) : Contact(name, ring, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DTMFContact * ********************************************************************************************* */ DTMFContact::DTMFContact(QObject *parent) : AnalogContact(parent), _number() { // pass... } DTMFContact::DTMFContact(const QString &name, const QString &number, bool rxTone, QObject *parent) : AnalogContact(name, rxTone, parent), _number(number.simplified()) { // pass... } ConfigItem * DTMFContact::clone() const { DTMFContact *c = new DTMFContact(); if (! c->copy(*this)) { c->deleteLater(); return nullptr; } return c; } void DTMFContact::clear() { Contact::clear(); _number.clear(); } const QString & DTMFContact::number() const { return _number; } bool DTMFContact::setNumber(const QString &number) { if (! validDTMFNumber(number)) return false; _number = number.simplified(); emit modified(this); return true; } YAML::Node DTMFContact::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = AnalogContact::serialize(context, err); if (node.IsNull()) return node; node.SetStyle(YAML::EmitterStyle::Flow); YAML::Node type; type["dtmf"] = node; return type; } /* ********************************************************************************************* * * Implementation of DigitalContact * ********************************************************************************************* */ DigitalContact::DigitalContact(QObject *parent) : Contact(parent) { // pass... } DigitalContact::DigitalContact(const QString &name, bool ring, QObject *parent) : Contact(name, ring, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRContact * ********************************************************************************************* */ DMRContact::DMRContact(QObject *parent) : DigitalContact(parent), _type(PrivateCall), _number(0), _anytone(nullptr), _openGD77(nullptr) { // pass... } DMRContact::DMRContact(Type type, const QString &name, unsigned number, bool rxTone, QObject *parent) : DigitalContact(name, rxTone, parent), _type(type), _number(number), _anytone(nullptr), _openGD77(nullptr) { // pass... } ConfigItem * DMRContact::clone() const { DMRContact *c = new DMRContact(); if (! c->copy(*this)) { c->deleteLater(); return nullptr; } return c; } void DMRContact::clear() { DigitalContact::clear(); _type = PrivateCall; _number = 0; if (_openGD77) _openGD77->deleteLater(); _openGD77 = nullptr; if (_anytone) _anytone->deleteLater(); _anytone = nullptr; } DMRContact::Type DMRContact::type() const { return _type; } void DMRContact::setType(DMRContact::Type type) { _type = type; } unsigned DMRContact::number() const { return _number; } bool DMRContact::setNumber(unsigned number) { _number = number; emit modified(this); return true; } YAML::Node DMRContact::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = DigitalContact::serialize(context, err); if (node.IsNull()) return node; node.SetStyle(YAML::EmitterStyle::Flow); YAML::Node type; type["dmr"] = node; return type; } OpenGD77ContactExtension * DMRContact::openGD77ContactExtension() const { return _openGD77; } void DMRContact::setOpenGD77ContactExtension(OpenGD77ContactExtension *ext) { if (_openGD77) _openGD77->deleteLater(); _openGD77 = ext; if (_openGD77) { _openGD77->setParent(this); connect(_openGD77, &OpenGD77ContactExtension::modified, [this](ConfigItem*){ emit modified(this); }); } } AnytoneContactExtension * DMRContact::anytoneExtension() const { return _anytone; } void DMRContact::setAnytoneExtension(AnytoneContactExtension *ext) { if (_anytone) _anytone->deleteLater(); _anytone = ext; if (_anytone) { _anytone->setParent(this); connect(_anytone, &OpenGD77ContactExtension::modified, [this](ConfigItem*){ emit modified(this); }); } } /* ********************************************************************************************* * * Implementation of M17Contact * ********************************************************************************************* */ M17Contact::M17Contact(QObject *parent) : DigitalContact(parent), _isBroadcast(false), _call() { // pass... } M17Contact::M17Contact(const QString &name, bool ring, const QString &call, QObject *parent) : DigitalContact(name, ring, parent), _isBroadcast(false), _call() { _call = normalizeCall(call); } ConfigItem * M17Contact::clone() const { M17Contact *c = new M17Contact(); if (! c->copy(*this)) { c->deleteLater(); return nullptr; } return c; } void M17Contact::clear() { DigitalContact::clear(); _isBroadcast = false; _call.clear(); } const QString & M17Contact::call() const { return _call; } void M17Contact::setCall(const QString &call) { _call = normalizeCall(call); } bool M17Contact::isBroadcast() const { return _isBroadcast; } void M17Contact::setBroadcast(bool enable) { _isBroadcast = enable; } YAML::Node M17Contact::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = DigitalContact::serialize(context, err); if (node.IsNull()) return node; node.SetStyle(YAML::EmitterStyle::Flow); YAML::Node type; type["m17"] = node; return type; } QString M17Contact::normalizeCall(const QString call) { QString tmp = call.toUpper(); tmp.remove(QRegularExpression("[^A-Z0-9/\\.\\-]")); return tmp.mid(0, 9); } /* ********************************************************************************************* * * Implementation of ContactList * ********************************************************************************************* */ ContactList::ContactList(QObject *parent) : ConfigObjectList(Contact::staticMetaObject, parent) { // pass... } int ContactList::add(ConfigObject *obj, int row, bool unique) { if ((nullptr == obj) || (! obj->is())) return -1; return ConfigObjectList::add(obj, row, unique); } Contact * ContactList::contact(int idx) const { if ((0>idx) || (idx >= count())) return nullptr; return _items[idx]->as(); } ConfigItem * ContactList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) return nullptr; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create contact: Expected object with one child."; return nullptr; } QString type = QString::fromStdString(node.begin()->first.as()); if ("dmr" == type) { return new DMRContact(); } else if ("m17" == type) { return new M17Contact(); } else if ("dtmf" == type) { return new DTMFContact(); } errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create contact: Unknown type '" << type << "'."; return nullptr; } ================================================ FILE: lib/contact.hh ================================================ #ifndef CONTACT_HH #define CONTACT_HH #include "configobject.hh" #include #include #include "anytone_extension.hh" #include "opengd77_extension.hh" class Config; /** Represents the base-class for all contact types, analog (DTMF) or digital (DMR, M17). * * @ingroup conf */ class Contact: public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "cont") /** If @c true and supported by radio, ring on call from this contact. */ Q_PROPERTY(bool ring READ ring WRITE setRing) protected: /** Default constructor. */ explicit Contact(QObject *parent=nullptr); /** Hidden constructor. * @param name Specifies the name of the contact. * @param ring Specifies whether a ring-tone for this contact is used. * @param parent Specifies the QObject parent. */ Contact(const QString &name, bool ring=true, QObject *parent=nullptr); public: void clear(); public: /** Returns @c true if the ring-tone is enabled for this contact. */ bool ring() const; /** Enables/disables the ring-tone for this contact. */ void setRing(bool enable); /** Typecheck contact. * For example, @c contact->is() returns @c true if @c contact is a * @c DigitalContact. */ template bool is() const { return 0 != dynamic_cast(this); } /** Typecast contact. */ template T *as() { return dynamic_cast(this); } /** Typecast contact. */ template const T *as() const { return dynamic_cast(this); } public: bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); bool link(const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Ringtone enabled? */ bool _ring; }; /** Base class for all analog contacts. * @ingroup conf */ class AnalogContact: public Contact { Q_OBJECT protected: /** Hidden constructor. */ explicit AnalogContact(QObject *parent=nullptr); /** Constructor. */ AnalogContact(const QString &name, bool rxTone, QObject *parent=nullptr); }; /** Represents an analog contact, that is a DTMF number. * @ingroup conf */ class DTMFContact: public AnalogContact { Q_OBJECT /** The contact number. */ Q_PROPERTY(QString number READ number WRITE setNumber) public: /** Default constructor. */ Q_INVOKABLE explicit DTMFContact(QObject *parent=nullptr); /** Constructs a DTMF (analog) contact. * @param name Specifies the contact name. * @param number Specifies the DTMF number (0-9,A,B,C,D,*,#). * @param ring Specifies whether the ring-tone is enabled for this contact. * @param parent Specifies the QObject parent. */ DTMFContact(const QString &name, const QString &number, bool ring=false, QObject *parent=nullptr); ConfigItem *clone() const; void clear(); /** Returns the DTMF number of this contact. * The number must consist of 0-9, a-f, * or #. */ const QString &number() const; /** (Re-)Sets the DTMF number of this contact. */ bool setNumber(const QString &number); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); protected: /** The DTMF number. */ QString _number; }; /** Base class for all digital contacts. * @ingroup conf */ class DigitalContact: public Contact { Q_OBJECT protected: /** Hidden constructor. */ explicit DigitalContact(QObject *parent=nullptr); /** Hidden constructor. */ DigitalContact(const QString &name, bool ring, QObject *parent=nullptr); }; /** Represents a digital contact, that is a DMR number. * @ingroup conf */ class DMRContact: public DigitalContact { Q_OBJECT /** The type of the contact. */ Q_PROPERTY(Type type READ type WRITE setType) /** The number of the contact. */ Q_PROPERTY(unsigned number READ number WRITE setNumber) /** The AnyTone extension to the digital contact. */ Q_PROPERTY(AnytoneContactExtension* anytone READ anytoneExtension WRITE setAnytoneExtension) /** The OpenGD77 extension to the digital contact. */ Q_PROPERTY(OpenGD77ContactExtension* openGD77 READ openGD77ContactExtension WRITE setOpenGD77ContactExtension) public: /** Possible call types for a contact. */ typedef enum { PrivateCall, ///< A private call. GroupCall, ///< A group call. AllCall ///< An all-call. } Type; Q_ENUM(Type) public: /** Default constructor. */ Q_INVOKABLE explicit DMRContact(QObject *parent=nullptr); /** Constructs a DMR (digital) contact. * @param type Specifies the call type (private, group, all-call). * @param name Specifies the contact name. * @param number Specifies the DMR number for this contact. * @param ring Specifies whether the ring-tone is enabled for this contact. * @param parent Specifies the QObject parent. */ DMRContact(Type type, const QString &name, unsigned number, bool ring=false, QObject *parent=nullptr); ConfigItem *clone() const; void clear(); /** Returns the call-type. */ Type type() const; /** (Re-)Sets the call-type. */ void setType(Type type); /** Returns the DMR number. */ unsigned number() const; /** (Re-)Sets the DMR number of the contact. */ bool setNumber(unsigned number); /** Returns the OpenGD77 extension, or @c nullptr if not set. */ OpenGD77ContactExtension *openGD77ContactExtension() const; /** Sets the OpenGD77 extension. */ void setOpenGD77ContactExtension(OpenGD77ContactExtension *ext); /** Returns the AnyTone extension, or @c nullptr if not set. */ AnytoneContactExtension *anytoneExtension() const; /** Sets the AnyTone extension. */ void setAnytoneExtension(AnytoneContactExtension *ext); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); protected: /** The call type. */ Type _type; /** The DMR number of the contact. */ unsigned _number; /** Owns the AnytoneContactextension to the digital contacts. */ AnytoneContactExtension *_anytone; /** Owns the OpenGD77 extensions to the digital contacts. */ OpenGD77ContactExtension *_openGD77; }; /** Represents an M17 contact. * @ingroup conf */ class M17Contact: public DigitalContact { Q_OBJECT /** The call of the contact. */ Q_PROPERTY(QString call READ call WRITE setCall) /** If @c true, the contact is the M17 broadcast destination. */ Q_PROPERTY(bool isBroadcast READ isBroadcast WRITE setBroadcast) public: /** Empty constructor. */ explicit M17Contact(QObject *parent=nullptr); /** Constructor. * @param name Specifies the name of the contact. Can be descriptive. * @param ring Specifies if a call from this contact rings a bell. * @param call Specifies the call/ID of the contact. * @param parent QObject parent, takes the ownership of this object. */ M17Contact(const QString &name, bool ring, const QString &call, QObject *parent=nullptr); ConfigItem *clone() const; void clear(); /** Returns the call/ID of the contact. */ const QString &call() const; /** Sets the call/ID of the contact. */ void setCall(const QString &call); /** Retunrs @c true, if the contact is the M17 broadcast contact. If set, the call has no effect. */ bool isBroadcast() const; /** Sets/Resets the broadcast flag. */ void setBroadcast(bool enable); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); /** Helper function to normalize a call. */ static QString normalizeCall(const QString call); protected: /** If true, the contact is the M17 broadcast contact. The call is ignored then. */ bool _isBroadcast; /** Holds the normalized call/ID of the contact. */ QString _call; }; /** Represents the list of contacts within the abstract radio configuration. * * A special feature of this list, is that DTMF and digital contacts can be accessed by their own * unique index although they are held within this single list. Most radios manage digital and * DTMF contacts in separate lists, hence a means to iterate over and get indices of digital and * DTMF contacts only is needed. * * This class implements the @c QAbstractTableModel, such that the list can be shown with the * @c QTableView widget. * * @ingroup conf */ class ContactList: public ConfigObjectList { Q_OBJECT public: /** Constructs an empty contact list. */ explicit ContactList(QObject *parent=nullptr); int add(ConfigObject *obj, int row=-1, bool unique=true); /** Returns the number of digital contacts. */ [[deprecated("Use indexing instead.")]] int digitalCount() const; /** Returns the number of DTMF contacts. */ [[deprecated("Use indexing instead.")]] int dtmfCount() const; /** Returns the contact at index @c idx. */ Contact *contact(int idx) const; /** Returns the digital contact at index @c idx among digital contacts. */ [[deprecated("Use indexing instead.")]] DMRContact *digitalContact(int idx) const; /** Returns the DTMF contact at index @c idx among DTMF contacts. */ [[deprecated("Use indexing instead.")]] DTMFContact *dtmfContact(int idx) const; public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // CONTACT_HH ================================================ FILE: lib/crc32.cc ================================================ #include "crc32.hh" static const uint32_t _crc_table[256] = { /* CRC polynomial 0xedb88320 */ 0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924, 0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f, 0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236, 0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9, 0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d }; CRC32::CRC32() : _crc(0xFFFFFFFF) { // pass... } void CRC32::update(uint8_t c) { _crc = ( _crc_table[(_crc ^ c) & 0xFF] ^ (_crc >> 8) ); } void CRC32::update(const uint8_t *buf, size_t n) { for (size_t i=0; i> 8) ); } void CRC32::update(const QByteArray &buf) { update((const uint8_t *)buf.constData(), buf.size()); } ================================================ FILE: lib/crc32.hh ================================================ #ifndef CRC32_HH #define CRC32_HH #include /** Implements the CRC32 checksum as used in DFU files. * * @ingroup util */ class CRC32 { public: /** Default constructor. */ CRC32(); /** Update CRC with given byte. */ void update(uint8_t c); /** Update CRC with given data. */ void update(const uint8_t *c, size_t n); /** Update CRC with given data. */ void update(const QByteArray &data); /** Returns the current CRC. */ inline uint32_t get() { return _crc; } protected: /** Current CRC. */ uint32_t _crc; }; #endif // CRC32_HH ================================================ FILE: lib/csvreader.cc ================================================ #include "csvreader.hh" #include "config.hh" #include "utils.hh" #include "logger.hh" #include #include QVector< QPair > CSVLexer::_pattern = { { QRegularExpression("^n([0-9]{3})"), CSVLexer::Token::T_DCS_N }, { QRegularExpression("^i([0-9]{3})"), CSVLexer::Token::T_DCS_I }, { QRegularExpression("^([a-zA-Z0-9]{1,6}-[0-9]{1,2})"), CSVLexer::Token::T_APRSCALL }, { QRegularExpression("^([a-zA-Z_][a-zA-Z0-9_]*)"), CSVLexer::Token::T_KEYWORD }, { QRegularExpression("^\"([^\"\r\n]*)\""), CSVLexer::Token::T_STRING }, { QRegularExpression("^([+-]?[0-9]+(\\.[0-9]*)?)"), CSVLexer::Token::T_NUMBER }, { QRegularExpression("^(:)"), CSVLexer::Token::T_COLON }, { QRegularExpression("^(-)"), CSVLexer::Token::T_NOT_SET }, { QRegularExpression("^(\\+)"), CSVLexer::Token::T_ENABLED }, { QRegularExpression("^(,)"), CSVLexer::Token::T_COMMA }, { QRegularExpression("^([ \t]+)"), CSVLexer::Token::T_WHITESPACE }, { QRegularExpression("^(\r?\n)"), CSVLexer::Token::T_NEWLINE}, { QRegularExpression("^(#[^\n\r]*)"), CSVLexer::Token::T_COMMENT}, }; /* ********************************************************************************************* * * Implementation of CSVLexer * ********************************************************************************************* */ CSVLexer::CSVLexer(QTextStream &stream, QObject *parent) : QObject(parent), _errorMessage(), _stream(stream), _stack(), _currentLine() { _stream.seek(0); _stack.reserve(10); _stack.push_back({0, 1, 1}); _currentLine = _stream.readLine(); } const QString & CSVLexer::errorMessage() const { return _errorMessage; } CSVLexer::Token CSVLexer::next() { Token token = lex(); while ((Token::T_WHITESPACE == token.type) || (Token::T_COMMENT == token.type)) token = lex(); return token; } CSVLexer::Token CSVLexer::lex() { if (_currentLine.isEmpty() && _stream.atEnd()) { return {Token::T_END_OF_STREAM, "", _stack.back().line, _stack.back().column }; } else if (_currentLine.isEmpty()) { Token token = {Token::T_NEWLINE, "", _stack.back().line, _stack.back().column }; _stack.back().offset = _stream.pos(); _currentLine = _stream.readLine(); _stack.back().line++; _stack.back().column = 1; return token; } foreach (auto pattern, _pattern) { auto match = pattern.first.match(_currentLine); if (0 == match.capturedStart()) { Token token = {pattern.second, match.captured(1), _stack.back().line, _stack.back().column}; _stack.back().offset += match.capturedLength(); _stack.back().column += token.value.size(); _currentLine = _currentLine.mid(match.capturedLength()); return token; } } _errorMessage = tr("Lexer error %1,%2: Unexpected char '%3'.").arg(_stack.back().line) .arg(_stack.back().column).arg(_currentLine.at(0)); return {Token::T_ERROR, _errorMessage, _stack.back().line, _stack.back().column}; } void CSVLexer::push() { _stack.push_back(_stack.back()); } void CSVLexer::pop() { if (_stack.size() < 2) return; _stack.pop_back(); _stream.seek(_stack.back().offset); _currentLine = QString(); } /* ********************************************************************************************* * * Implementation of CSVHandler * ********************************************************************************************* */ CSVHandler::CSVHandler(QObject *parent) : QObject(parent) { // pass... } CSVHandler::~CSVHandler() { // pass... } bool CSVHandler::handleRadioId(const QList &ids, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(ids); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage) return true; } bool CSVHandler::handleRadioName(const QString &name, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(name); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleIntroLine1(const QString &text, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(text); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleIntroLine2(const QString &text, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(text); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleMicLevel(unsigned level, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(level); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleSpeech(bool speech, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(speech); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleDTMFContact(qint64 idx, const QString &name, const QString &num, bool rxTone, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(num); Q_UNUSED(rxTone); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleDigitalContact(qint64 idx, const QString &name, DMRContact::Type type, qint64 id, bool rxTone, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(type); Q_UNUSED(id); Q_UNUSED(rxTone); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleGroupList(qint64 idx, const QString &name, const QList &contacts, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(contacts); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleDigitalChannel(qint64 idx, const QString &name, double rx, double tx, Channel::Power power, qint64 scan, qint64 tot, bool ro, DMRChannel::Admit admit, qint64 color, DMRChannel::TimeSlot slot, qint64 gl, qint64 contact, qint64 gps, qint64 roam, qint64 radioID, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(rx); Q_UNUSED(tx); Q_UNUSED(power); Q_UNUSED(scan); Q_UNUSED(tot); Q_UNUSED(ro); Q_UNUSED(admit); Q_UNUSED(color); Q_UNUSED(slot); Q_UNUSED(gl); Q_UNUSED(contact); Q_UNUSED(gps); Q_UNUSED(roam); Q_UNUSED(radioID); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleAnalogChannel(qint64 idx, const QString &name, double rx, double tx, Channel::Power power, qint64 scan, qint64 aprs, qint64 tot, bool ro, FMChannel::Admit admit, qint64 squelch, const SelectiveCall &rxTone, const SelectiveCall &txTone, FMChannel::Bandwidth bw, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(rx); Q_UNUSED(tx); Q_UNUSED(power); Q_UNUSED(scan); Q_UNUSED(aprs); Q_UNUSED(tot); Q_UNUSED(ro); Q_UNUSED(admit); Q_UNUSED(squelch); Q_UNUSED(rxTone); Q_UNUSED(txTone); Q_UNUSED(bw); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleZone(qint64 idx, const QString &name, bool a, const QList &channels, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(a); Q_UNUSED(channels); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleGPSSystem( qint64 idx, const QString &name, qint64 contactIdx, qint64 period, qint64 revertChannelIdx, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(contactIdx); Q_UNUSED(period); Q_UNUSED(revertChannelIdx); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleAPRSSystem(qint64 idx, const QString &name, qint64 channelIdx, qint64 period, const QString &src, unsigned srcSSID, const QString &dest, unsigned destSSID, const QString &path, const QString &icon, const QString &message, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(channelIdx); Q_UNUSED(period); Q_UNUSED(src); Q_UNUSED(srcSSID); Q_UNUSED(dest); Q_UNUSED(destSSID); Q_UNUSED(path); Q_UNUSED(icon); Q_UNUSED(message); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleScanList(qint64 idx, const QString &name, qint64 pch1, qint64 pch2, qint64 txch, const QList &channels, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(pch1); Q_UNUSED(pch2); Q_UNUSED(txch); Q_UNUSED(channels); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } bool CSVHandler::handleRoamingZone(qint64 idx, const QString &name, const QList &channels, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(idx); Q_UNUSED(name); Q_UNUSED(channels); Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); return true; } /* ********************************************************************************************* * * Implementation of CSVParser * ********************************************************************************************* */ CSVParser::CSVParser(CSVHandler *handler, QObject *parent) : QObject(parent), _errorMessage(), _handler(handler) { // pass... } const QString & CSVParser::errorMessage() const { return _errorMessage; } bool CSVParser::parse(QTextStream &stream) { if (! stream.seek(0)) return false; CSVLexer lexer(stream); for (CSVLexer::Token token=lexer.next(); CSVLexer::Token::T_END_OF_STREAM != token.type; token = lexer.next()) { if (CSVLexer::Token::T_NEWLINE == token.type) continue; if ((CSVLexer::Token::T_KEYWORD == token.type) && ("id" == token.value.toLower())) { if (! _parse_radio_id(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("name" == token.value.toLower())) { if (! _parse_radio_name(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("introline1" == token.value.toLower())) { if (! _parse_introline1(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("introline2" == token.value.toLower())) { if (! _parse_introline2(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("miclevel" == token.value.toLower())) { if (! _parse_mic_level(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("speech" == token.value.toLower())) { if (! _parse_speech(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("userdb" == token.value.toLower())) { if (! _parse_userdb(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("contact" == token.value.toLower())) { if (! _parse_contacts(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("grouplist" == token.value.toLower())) { if (! _parse_rx_groups(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("digital" == token.value.toLower())) { if (! _parse_digital_channels(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("analog" == token.value.toLower())) { if (! _parse_analog_channels(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("zone" == token.value.toLower())) { if (! _parse_zones(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("gps" == token.value.toLower())) { if (! _parse_gps_systems(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("aprs" == token.value.toLower())) { if (! _parse_aprs_systems(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("scanlist" == token.value.toLower())) { if (! _parse_scanlists(lexer)) return false; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("roaming" == token.value.toLower())) { if (! _parse_roaming_zones(lexer)) return false; } else if (CSVLexer::Token::T_ERROR == token.type) { _errorMessage = QString("Lexer error @ %1,%2 '%3': %4").arg(token.line).arg(token.column) .arg(token.value).arg(lexer.errorMessage()); return false; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4'.").arg(token.line) .arg(token.column).arg(token.type).arg(token.value); return false; } } return true; } bool CSVParser::_parse_radio_id(CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); if (CSVLexer::Token::T_COLON != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected ':'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QList ids; token = lexer.next(); while (CSVLexer::Token::T_NUMBER == token.type) { ids.append(token.value.toInt()); token = lexer.next(); if (CSVLexer::Token::T_COMMA == token.type) { token = lexer.next(); continue; } } if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } if (0 == ids.size()) { _errorMessage = QString("Parse error @ %1,%2: At least one radio ID must be specified.") .arg(token.line).arg(token.column); return false; } return _handler->handleRadioId(ids, token.line, token.column, _errorMessage); } bool CSVParser::_parse_radio_name(CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); if (CSVLexer::Token::T_COLON != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected ':'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; qint64 line=token.line, column=token.column; token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleRadioName(name, line, column, _errorMessage); } bool CSVParser::_parse_introline1(CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); if (CSVLexer::Token::T_COLON != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected ':'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString text = token.value; qint64 line=token.line, column=token.column; token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleIntroLine1(text, line, column, _errorMessage); } bool CSVParser::_parse_introline2(CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); if (CSVLexer::Token::T_COLON != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected ':'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString text = token.value; qint64 line=token.line, column=token.column; token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleIntroLine2(text, line, column, _errorMessage); } bool CSVParser::_parse_mic_level(CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); if (CSVLexer::Token::T_COLON != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected ':'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_NUMBER != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 level = token.value.toInt(); qint64 line=token.line, column=token.column; token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleMicLevel(level, line, column, _errorMessage); } bool CSVParser::_parse_speech(CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); if (CSVLexer::Token::T_COLON != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected ':'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_KEYWORD != token.type) || (("on" != token.value.toLower()) && ("off" != token.value.toLower()))) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'On' or 'Off'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 line=token.line, column=token.column; bool speech = ("on" == token.value.toLower()); token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleSpeech(speech, line, column, _errorMessage); } bool CSVParser::_parse_userdb(CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); if (CSVLexer::Token::T_COLON != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected ':'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_KEYWORD != token.type) || (("on" != token.value.toLower()) && ("off" != token.value.toLower()))) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'On' or 'Off'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 line=token.line, column=token.column; token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } logWarn() << line << "," << column << ": The 'UserDB' setting is obsolete. " << "It will be removed in future releases. Just delete this line."; // Ignore user DB setting return true; } bool CSVParser::_parse_contacts(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); for (; CSVLexer::Token::T_KEYWORD==token.type; token=lexer.next()) { // skip } if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_contact(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_contact(qint64 idx, CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; token = lexer.next(); if (CSVLexer::Token::T_KEYWORD != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected keyword.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool dtmf = false; DMRContact::Type type = DMRContact::PrivateCall; if ("group" == token.value.toLower()) { type = DMRContact::GroupCall; } else if ("private" == token.value.toLower()) { type = DMRContact::PrivateCall; } else if ("all" == token.value.toLower()) { type = DMRContact::AllCall; } else if ("dtmf" == token.value.toLower()) { dtmf = true; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'Group', 'Private', 'All' or 'DTMF'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ( (!dtmf) && (CSVLexer::Token::T_NUMBER != token.type) ) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } else if (dtmf && (CSVLexer::Token::T_NUMBER != token.type) && (CSVLexer::Token::T_STRING != token.type)) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString dtmf_num = token.value; if (dtmf && (! validDTMFNumber(dtmf_num))) { _errorMessage = QString("Parse error @ %1,%2: Invalid DTMF number '%3'.") .arg(token.line).arg(token.column).arg(token.value); } qint64 id = token.value.toInt(); bool rxToneEnabled; token = lexer.next(); if (CSVLexer::Token::T_NOT_SET == token.type) rxToneEnabled = false; else if (CSVLexer::Token::T_ENABLED == token.type) rxToneEnabled = true; else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected '+' or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } if (dtmf) return _handler->handleDTMFContact(idx, name, dtmf_num, rxToneEnabled, line, column, _errorMessage); return _handler->handleDigitalContact(idx, name, type, id, rxToneEnabled, line, column, _errorMessage); } bool CSVParser::_parse_rx_groups(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); while (CSVLexer::Token::T_KEYWORD==token.type) token = lexer.next(); if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_rx_group(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_rx_group(qint64 idx, CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; QList lst; token = lexer.next(); while (CSVLexer::Token::T_NUMBER == token.type) { lst.append(token.value.toInt()); token = lexer.next(); if (CSVLexer::Token::T_COMMA == token.type) token = lexer.next(); } if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleGroupList(idx, name, lst, line, column, _errorMessage); } bool CSVParser::_parse_digital_channels(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); for (; CSVLexer::Token::T_KEYWORD==token.type; token=lexer.next()) { // skip } if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_digital_channel(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_digital_channel(qint64 idx, CSVLexer &lexer) { bool ok=false; CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; token = lexer.next(); if (CSVLexer::Token::T_NUMBER != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } double rx = token.value.toDouble(&ok); if (! ok) { _errorMessage = QString("Parse error @ %1,%2: Cannot convert '%3' to double.") .arg(token.line).arg(token.column).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_NUMBER != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } double tx = token.value.toDouble(&ok); if (! ok) { _errorMessage = QString("Parse error @ %1,%2: Cannot convert '%3' to double.") .arg(token.line).arg(token.column).arg(token.value); return false; } if (token.value.startsWith('+') || token.value.startsWith('-')) tx = rx + tx; token = lexer.next(); if (CSVLexer::Token::T_KEYWORD != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected keyword.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } Channel::Power pwr; if ("max" == token.value.toLower()) { pwr = Channel::Power::Max; } else if ("high" == token.value.toLower()) { pwr = Channel::Power::High; } else if ("mid" == token.value.toLower()) { pwr = Channel::Power::Mid; } else if ("low" == token.value.toLower()) { pwr = Channel::Power::Low; } else if ("min" == token.value.toLower()) { pwr = Channel::Power::Min; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'High' or 'Low'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); qint64 scanlist; if (CSVLexer::Token::T_NOT_SET == token.type) { scanlist = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { scanlist = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_NUMBER != token.type) && (CSVLexer::Token::T_NOT_SET != token.type)) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 tot = (CSVLexer::Token::T_NOT_SET == token.type) ? 0 : token.value.toInt(); bool rxOnly; token = lexer.next(); if (CSVLexer::Token::T_NOT_SET == token.type) rxOnly = false; else if (CSVLexer::Token::T_ENABLED == token.type) rxOnly = true; else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected '+' or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); DMRChannel::Admit admit; if (CSVLexer::Token::T_NOT_SET == token.type) { admit = DMRChannel::Admit::Always; } else if (CSVLexer::Token::T_KEYWORD == token.type) { if ("free" == token.value.toLower()) admit = DMRChannel::Admit::Free; else if ("color" == token.value.toLower()) admit = DMRChannel::Admit::ColorCode; else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'Free' or 'Color'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'Free', 'Color' or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_NUMBER != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 color = token.value.toInt(); token = lexer.next(); DMRChannel::TimeSlot slot; if (CSVLexer::Token::T_NUMBER == token.type) { if (1 == token.value.toInt()) { slot = DMRChannel::TimeSlot::TS1; } else if (2 == token.value.toInt()) { slot = DMRChannel::TimeSlot::TS2; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected '1' or '2'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); qint64 rxGroupList; if (CSVLexer::Token::T_NOT_SET == token.type) { rxGroupList = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { rxGroupList = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); qint64 txContact; if (CSVLexer::Token::T_NOT_SET == token.type) { txContact = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { txContact = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); qint64 gps; if (CSVLexer::Token::T_NOT_SET == token.type) { gps = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { gps = token.value.toInt(&ok); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); qint64 roam = -1, radioID = -1; if (CSVLexer::Token::T_NOT_SET == token.type) { roam = -1; } else if (CSVLexer::Token::T_ENABLED == token.type) { roam = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { roam = token.value.toInt(&ok); } else if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) { // This entry is optional for backward compatibility. goto done; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number, '-' or '+'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_NOT_SET == token.type) { radioID = -1; } else if (CSVLexer::Token::T_NUMBER == token.type) { radioID = token.value.toInt(&ok); } else if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) { // This entry is optional for backward compatibility. goto done; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } done: return _handler->handleDigitalChannel(idx, name, rx, tx, pwr, scanlist, tot, rxOnly, admit, color, slot, rxGroupList, txContact, gps, roam, radioID, line, column, _errorMessage); } bool CSVParser::_parse_analog_channels(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); for (; CSVLexer::Token::T_KEYWORD==token.type; token=lexer.next()) { // skip } if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_analog_channel(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_analog_channel(qint64 idx, CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; token = lexer.next(); if (CSVLexer::Token::T_NUMBER != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } double rx = token.value.toDouble(); token = lexer.next(); if (CSVLexer::Token::T_NUMBER != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } double tx = token.value.toDouble(); if (token.value.startsWith('+') || token.value.startsWith('-')) tx = rx + tx; token = lexer.next(); if (CSVLexer::Token::T_KEYWORD != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected keyword.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } Channel::Power pwr; if ("max" == token.value.toLower()) { pwr = Channel::Power::Max; } else if ("high" == token.value.toLower()) { pwr = Channel::Power::High; } else if ("mid" == token.value.toLower()) { pwr = Channel::Power::Mid; } else if ("low" == token.value.toLower()) { pwr = Channel::Power::Low; } else if ("min" == token.value.toLower()) { pwr = Channel::Power::Min; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'High' or 'Low'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); qint64 scanlist; if (CSVLexer::Token::T_NOT_SET == token.type) { scanlist = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { scanlist = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_NUMBER != token.type) && (CSVLexer::Token::T_NOT_SET != token.type)) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 tot = (CSVLexer::Token::T_NOT_SET == token.type) ? 0 : token.value.toInt(); bool rxOnly; token = lexer.next(); if (CSVLexer::Token::T_NOT_SET == token.type) rxOnly = false; else if (CSVLexer::Token::T_ENABLED == token.type) rxOnly = true; else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected '+' or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); FMChannel::Admit admit; if (CSVLexer::Token::T_NOT_SET == token.type) { admit = FMChannel::Admit::Always; } else if (CSVLexer::Token::T_KEYWORD == token.type) { if ("free" == token.value.toLower()) admit = FMChannel::Admit::Free; else if ("tone" == token.value.toLower()) admit = FMChannel::Admit::Tone; else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'Free', 'Tone'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'Free', 'Tone' or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_NUMBER != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 squelch = token.value.toInt(); token = lexer.next(); SelectiveCall rxTone; if (CSVLexer::Token::T_NOT_SET == token.type) { rxTone = SelectiveCall(); } else if (CSVLexer::Token::T_NUMBER == token.type) { rxTone = SelectiveCall(token.value.toFloat()); } else if (CSVLexer::Token::T_DCS_N == token.type) { rxTone = SelectiveCall(token.value.toUInt(), false); } else if (CSVLexer::Token::T_DCS_I == token.type) { rxTone = SelectiveCall(token.value.toUInt(), true); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); SelectiveCall txTone; if (CSVLexer::Token::T_NOT_SET == token.type) { txTone = SelectiveCall(); } else if (CSVLexer::Token::T_NUMBER == token.type) { txTone = SelectiveCall(token.value.toFloat()); } else if (CSVLexer::Token::T_DCS_N == token.type) { txTone = SelectiveCall(token.value.toUInt(), false); } else if (CSVLexer::Token::T_DCS_I == token.type) { txTone = SelectiveCall(token.value.toUInt(), true); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if (CSVLexer::Token::T_NUMBER != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } FMChannel::Bandwidth bw; if (25 == token.value.toFloat()) { bw = FMChannel::Bandwidth::Wide; } else if (12.5 == token.value.toFloat()) { bw = FMChannel::Bandwidth::Narrow; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected '12.5' or '25'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); qint64 aprs = 0; if (CSVLexer::Token::T_NOT_SET == token.type) { aprs = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { aprs = token.value.toInt(); } else if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) { // This entry is optional for backward compatibility. goto done; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } done: return _handler->handleAnalogChannel(idx, name, rx, tx, pwr, scanlist, aprs, tot, rxOnly, admit, squelch, rxTone, txTone, bw, line, column, _errorMessage); } bool CSVParser::_parse_zones(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); for (; CSVLexer::Token::T_KEYWORD==token.type; token=lexer.next()) { // skip } if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_zone(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_zone(qint64 idx, CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; token = lexer.next(); if ((CSVLexer::Token::T_KEYWORD != token.type) || (("a" != token.value.toLower()) && ("b" != token.value.toLower())) ) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected 'A or 'B'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool a = ("a" == token.value.toLower()); QList lst; token = lexer.next(); while (CSVLexer::Token::T_NUMBER == token.type) { lst.append(token.value.toInt()); token = lexer.next(); if (CSVLexer::Token::T_COMMA == token.type) token = lexer.next(); } if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleZone(idx, name, a, lst, line, column, _errorMessage); } bool CSVParser::_parse_gps_systems(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); for (; CSVLexer::Token::T_KEYWORD==token.type; token=lexer.next()) { // skip } if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_gps_system(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_gps_system(qint64 id, CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; qint64 contact; token = lexer.next(); if (CSVLexer::Token::T_NUMBER == token.type) { contact = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 period; token = lexer.next(); if (CSVLexer::Token::T_NUMBER == token.type) { period = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 chan; token = lexer.next(); if (CSVLexer::Token::T_NOT_SET == token.type) { chan = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { chan = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleGPSSystem(id, name, contact, period, chan, line, column, _errorMessage); } bool CSVParser::_parse_aprs_systems(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); for (; CSVLexer::Token::T_KEYWORD==token.type; token=lexer.next()) { // skip } if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_aprs_system(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_aprs_system(qint64 id, CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; qint64 channel; token = lexer.next(); if (CSVLexer::Token::T_NUMBER == token.type) { channel = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 period; token = lexer.next(); if (CSVLexer::Token::T_NUMBER == token.type) { period = token.value.toInt(); } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); QString src=""; unsigned srcSSID=0; if (CSVLexer::Token::T_APRSCALL == token.type) { QStringList lst = token.value.split('-'); src = lst.first(); srcSSID = lst.last().toInt(); } else if ((CSVLexer::Token::T_KEYWORD == token.type) && (token.value.size()<=6)) { src = token.value; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected APRS call.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); QString dest=""; unsigned destSSID=0; if (CSVLexer::Token::T_APRSCALL == token.type) { QStringList lst = token.value.split('-'); dest = lst.first(); destSSID = lst.last().toInt(); } else if ((CSVLexer::Token::T_KEYWORD == token.type) && (token.value.size()<=6)) { dest = token.value; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected APRS call.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); QString path=""; if (CSVLexer::Token::T_NOT_SET == token.type) { // pass.. } else if (CSVLexer::Token::T_STRING == token.type) { path = token.value; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); QString iconname; if ((CSVLexer::Token::T_KEYWORD == token.type) || (CSVLexer::Token::T_STRING == token.type)) { iconname = token.value; } else if (CSVLexer::Token::T_NOT_SET == token.type) { iconname = ""; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected keyword or string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); QString message; if (CSVLexer::Token::T_STRING == token.type) { message = token.value; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleAPRSSystem(id, name, channel, period, src, srcSSID, dest, destSSID, path, iconname, message, line, column, _errorMessage); } bool CSVParser::_parse_scanlists(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); for (; CSVLexer::Token::T_KEYWORD==token.type; token=lexer.next()) { // skip } if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_scanlist(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_scanlist(qint64 idx, CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; qint64 pch1; token = lexer.next(); if (CSVLexer::Token::T_NOT_SET == token.type) { pch1 = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { pch1 = token.value.toInt() + 1; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("sel" == token.value.toLower())) { pch1 = -1; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 pch2; token = lexer.next(); if (CSVLexer::Token::T_NOT_SET == token.type) { pch2 = 0; } else if (CSVLexer::Token::T_NUMBER == token.type) { pch2 = token.value.toInt() + 1; } else if ((CSVLexer::Token::T_KEYWORD == token.type) && ("sel" == token.value.toLower())) { pch2 = -1; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } qint64 txch; token = lexer.next(); if (CSVLexer::Token::T_NOT_SET == token.type) { txch = 0; } else if (CSVLexer::Token::T_KEYWORD == token.type) { if ("sel" == token.value.toLower()) { txch = -1; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number, 'Sel' or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } } else if (CSVLexer::Token::T_NUMBER == token.type) { txch = token.value.toInt() + 1; } else { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected number, 'Sel' or '-'.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QList lst; token = lexer.next(); while ((CSVLexer::Token::T_NUMBER == token.type) || (CSVLexer::Token::T_KEYWORD == token.type)) { if (CSVLexer::Token::T_NUMBER == token.type) { lst.append(token.value.toInt()); } else if (CSVLexer::Token::T_KEYWORD == token.type) { if ("sel" == token.value.toLower()) lst.append(-1); else { _errorMessage = QString("Parse error @ %1,%2: Unexpected keyword '%3' expected number or 'Sel'.") .arg(token.line).arg(token.column).arg(token.value); return false; } } token = lexer.next(); if (CSVLexer::Token::T_COMMA == token.type) token = lexer.next(); } if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleScanList(idx, name, pch1, pch2, txch, lst, line, column, _errorMessage); } bool CSVParser::_parse_roaming_zones(CSVLexer &lexer) { // skip rest of header CSVLexer::Token token = lexer.next(); for (; CSVLexer::Token::T_KEYWORD==token.type; token=lexer.next()) { // skip } if (CSVLexer::Token::T_NEWLINE != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } token = lexer.next(); for (; CSVLexer::Token::T_NUMBER == token.type; token=lexer.next()) { qint64 idx = token.value.toInt(); if (! _parse_roaming_zone(idx, lexer)) return false; } if ((CSVLexer::Token::T_NEWLINE == token.type) || (CSVLexer::Token::T_END_OF_STREAM == token.type)) return true; _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } bool CSVParser::_parse_roaming_zone(qint64 idx, CSVLexer &lexer) { CSVLexer::Token token = lexer.next(); qint64 line=token.line, column=token.column; if (CSVLexer::Token::T_STRING != token.type) { _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected string.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } QString name = token.value; QList lst; token = lexer.next(); while (CSVLexer::Token::T_NUMBER == token.type) { lst.append(token.value.toInt()); token = lexer.next(); if (CSVLexer::Token::T_COMMA == token.type) token = lexer.next(); } if ((CSVLexer::Token::T_NEWLINE != token.type) && (CSVLexer::Token::T_END_OF_STREAM != token.type)){ _errorMessage = QString("Parse error @ %1,%2: Unexpected token %3 '%4' expected newline/EOS.") .arg(token.line).arg(token.column).arg(token.type).arg(token.value); return false; } return _handler->handleRoamingZone(idx, name, lst, line, column, _errorMessage); } /* ********************************************************************************************* * * Implementation of CSVReader * ********************************************************************************************* */ CSVReader::CSVReader(Config *config, QObject *parent) : CSVHandler(parent), _link(false), _config(config) { // pass... } bool CSVReader::read(Config *config, QTextStream &stream, QString &errorMessage) { // Seek to start-of-stream if (! stream.seek(0)) { errorMessage = "Cannot read CSV codeplug: Cannot seek within text stream. Abort import." ; return false; } config->clear(); CSVReader reader(config); CSVParser parser(&reader); if (! parser.parse(stream)) { errorMessage = parser.errorMessage(); errorMessage.append(tr("\nThe generic code-plug format might be changed with a new release of qdmr." "\nVisit https://github.com/hmatuschek/qdmr/releases for further information.")); return false; } reader._link = true; if (! parser.parse(stream)) { errorMessage = parser.errorMessage(); return false; } return true; } bool CSVReader::handleRadioId(const QList &ids, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); if (_link) return true; logDebug() << "Got " << ids.count() << " IDs..."; for (int i=0; iradioIDs()->add(id); _radioIDs[i+1] = id; } return true; } bool CSVReader::handleRadioName(const QString &name, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); if (_link && (!_config->settings()->defaultIdRef()->isNull())) { _config->settings()->defaultIdRef()->as()->setName(name); } return true; } bool CSVReader::handleIntroLine1(const QString &text, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); if (_link) { _config->settings()->boot()->setMessage1(text); } return true; } bool CSVReader::handleIntroLine2(const QString &text, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); if (_link) { _config->settings()->boot()->setMessage2(text); } return true; } bool CSVReader::handleMicLevel(unsigned level, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); if (_link) { _config->settings()->audio()->setMicGain(Level::fromValue(level)); } return true; } bool CSVReader::handleSpeech(bool speech, qint64 line, qint64 column, QString &errorMessage) { Q_UNUSED(line); Q_UNUSED(column); Q_UNUSED(errorMessage); if (_link) { _config->settings()->audio()->enableSpeechSynthesis(speech); } return true; } bool CSVReader::handleDTMFContact(qint64 idx, const QString &name, const QString &num, bool rxTone, qint64 line, qint64 column, QString &errorMessage) { if (_link) { // pass... return true; } if (_digital_contacts.contains(idx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create contact '%3' with index %4, index already taken.") .arg(line).arg(column).arg(name).arg(idx); return false; } DTMFContact *contact = new DTMFContact(name, num, rxTone); //_contacts[idx] = contact; _config->contacts()->add(contact); return true; } bool CSVReader::handleDigitalContact(qint64 idx, const QString &name, DMRContact::Type type, qint64 id, bool rxTone, qint64 line, qint64 column, QString &errorMessage) { if (_link) { // pass... return true; } if (_digital_contacts.contains(idx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create contact '%3' with index %4, index already taken.") .arg(line).arg(column).arg(name).arg(idx); return false; } DMRContact *contact = new DMRContact(type, name, id, rxTone); _digital_contacts[idx] = contact; _config->contacts()->add(contact); return true; } bool CSVReader::handleGroupList(qint64 idx, const QString &name, const QList &contacts, qint64 line, qint64 column, QString &errorMessage) { if (_link) { foreach(qint64 i, contacts) { // Check contacts if (! _digital_contacts.contains(i)) { errorMessage = QString("Parse error @ %1,%2: Cannot create contact list '%3' with index %4, unknown index.") .arg(line).arg(column).arg(name).arg(i); return false; } // link contacts _rxgroups[idx]->addContact(_digital_contacts[i]); } // done return true; } // check index if (_rxgroups.contains(idx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create RX-group list '%3' with index %4, index already taken.") .arg(line).arg(column).arg(name).arg(idx); return false; } // Create group list RXGroupList *lst = new RXGroupList(name); _config->rxGroupLists()->add(lst); _rxgroups[idx] = lst; return true; } bool CSVReader::handleDigitalChannel(qint64 idx, const QString &name, double rx, double tx, Channel::Power power, qint64 scan, qint64 tot, bool ro, DMRChannel::Admit admit, qint64 color, DMRChannel::TimeSlot slot, qint64 gl, qint64 contact, qint64 gps, qint64 roam, qint64 radioID, qint64 line, qint64 column, QString &errorMessage) { if (_link) { // Check RX Grouplist if (0 < gl) { if (! _rxgroups.contains(gl)) { errorMessage = QString("Parse error @ %1,%2: Cannot link digital channel '%3', unknown RX-group list %4.") .arg(line).arg(column).arg(name).arg(gl); return false; } _channels[idx]->as()->setGroupList(_rxgroups[gl]); } // Check TX Contact if (0 < contact) { if (! _digital_contacts.contains(contact)) { errorMessage = QString("Parse error @ %1,%2: Cannot link digital channel '%3', unknown contact index %4.") .arg(line).arg(column).arg(name).arg(contact); return false; } _channels[idx]->as()->setContact(_digital_contacts[contact]); } // Check scanlist if (0 < scan) { if (! _scanlists.contains(scan)) { errorMessage = QString("Parse error @ %1,%2: Cannot link digital channel '%3', unknown scan-list index %4.") .arg(line).arg(column).arg(name).arg(scan); return false; } _channels[idx]->as()->setScanList(_scanlists[scan]); } // Check GPS System if (0 < gps) { if (! _posSystems.contains(gps)) { errorMessage = QString("Parse error @ %1,%2: Cannot link digital channel '%3', unknown GPS system index %4.") .arg(line).arg(column).arg(name).arg(gps); return false; } _channels[idx]->as()->setAPRS(_posSystems[gps]); } // Check roaming zone if (0 == roam) { // index 0 mean default zone -> just set it _channels[idx]->as()->setRoaming(DefaultRoamingZone::get()); } else if (0 < roam) { // positive index means reference to roaming specific roaming zone if (! _roamingZones.contains(roam)) { errorMessage = QString("Parse error @ %1,%2: Cannot link digital channel '%3', unknown roaming zone index %4.") .arg(line).arg(column).arg(name).arg(roam); return false; } _channels[idx]->as()->setRoaming(_roamingZones[roam]); } // check radio ID if (-1 == radioID) { _channels[idx]->as()->setRadioId(DefaultRadioID::get()); } else if ((0 < radioID) && (_radioIDs.contains(radioID))) { _channels[idx]->as()->setRadioId(_radioIDs[radioID]); } else { errorMessage = QString("Parse error @ %1,%2: Cannot link digital channel '%3', unknown radio ID index %4.") .arg(line).arg(column).arg(name).arg(radioID); return false; } return true; } // check index if (_channels.contains(idx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create digital channel '%3' with index %4, index already taken.") .arg(line).arg(column).arg(name).arg(idx); return false; } DMRChannel *chan = new DMRChannel(); chan->setName(name); chan->setRXFrequency(Frequency::fromMHz(rx)); chan->setTXFrequency(Frequency::fromMHz(tx)); chan->setPower(power); chan->setTimeout(Interval::fromSeconds(tot)); chan->setRXOnly(ro); chan->setAdmit(admit); chan->setColorCode(color); chan->setTimeSlot(slot); _config->channelList()->add(chan); _channels[idx] = chan; return true; } bool CSVReader::handleAnalogChannel(qint64 idx, const QString &name, double rx, double tx, Channel::Power power, qint64 scan, qint64 aprs, qint64 tot, bool ro, FMChannel::Admit admit, qint64 squelch, const SelectiveCall &rxTone, const SelectiveCall &txTone, FMChannel::Bandwidth bw, qint64 line, qint64 column, QString &errorMessage) { if (_link) { // Check scanlist if (0 < scan) { if (! _scanlists.contains(scan)) { errorMessage = QString("Parse error @ %1,%2: Cannot link analog channel '%3', unknown scan-list index %4.") .arg(line).arg(column).arg(name).arg(scan); return false; } _channels[idx]->as()->setScanList(_scanlists[scan]); } // Check APRS system if (0 < aprs) { if (! _posSystems.contains(aprs)) { errorMessage = QString("Parse error @ %1,%2: Cannot link analog channel '%3', unknown APRS system index %4.") .arg(line).arg(column).arg(name).arg(aprs); return false; } if (! _posSystems[aprs]->is()) { errorMessage = QString("Parse error @ %1,%2: Cannot link analog channel '%3', positioning system %4 ('%5') is not an APRS system!.") .arg(line).arg(column).arg(name).arg(aprs).arg(_posSystems[aprs]->name()); return false; } _channels[idx]->as()->setAPRS(_posSystems[aprs]->as()); } return true; } // check index if (_channels.contains(idx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create analog channel '%3' with index %4, index already taken.") .arg(line).arg(column).arg(name).arg(idx); return false; } FMChannel *chan = new FMChannel(); chan->setName(name); chan->setRXFrequency(Frequency::fromMHz(rx)); chan->setTXFrequency(Frequency::fromMHz(tx)); chan->setPower(power); chan->setTimeout(Interval::fromSeconds(tot)); chan->setRXOnly(ro); chan->setAdmit(admit); chan->setSquelch(Level::fromValue(squelch)); chan->setRXTone(rxTone); chan->setTXTone(txTone); chan->setBandwidth(bw); _config->channelList()->add(chan); _channels[idx] = chan; return true; } bool CSVReader::handleZone(qint64 idx, const QString &name, bool a, const QList &channels, qint64 line, qint64 column, QString &errorMessage) { if (_link) { foreach(qint64 i, channels) { // Check channels if (! _channels.contains(i)) { errorMessage = QString("Parse error @ %1,%2: Cannot create zone '%3', unknown channel index %4.") .arg(line).arg(column).arg(name).arg(i); return false; } // link channels if (a) _zones[idx]->A()->add(_channels[i]); else _zones[idx]->B()->add(_channels[i]); } // done return true; } // check index if (! _zones.contains(idx)) { Zone *zone = new Zone(name); _zones[idx] = zone; _config->zones()->add(zone); } return true; } bool CSVReader::handleGPSSystem( qint64 idx, const QString &name, qint64 contactIdx, qint64 period, qint64 revertChannelIdx, qint64 line, qint64 column, QString &errorMessage) { if (_link) { DMRAPRSSystem *gps = _posSystems[idx]->as(); // Check contact ID if (! _digital_contacts.contains(contactIdx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create GPS system '%3', unknown destination contact ID %4.") .arg(line).arg(column).arg(name).arg(contactIdx); logError() << errorMessage; return false; } gps->setContact(_digital_contacts[contactIdx]); if (revertChannelIdx) { if (! _channels.contains(revertChannelIdx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create GPS system '%3', unknown revert channel ID %4.") .arg(line).arg(column).arg(name).arg(revertChannelIdx); return false; } if (! _channels[revertChannelIdx]->is()) { errorMessage = QString("Parse error @ %1,%2: Cannot create GPS system '%3', revert channel %4 is not a digital channel.") .arg(line).arg(column).arg(name).arg(revertChannelIdx); return false; } gps->setRevertChannel(_channels[revertChannelIdx]->as()); } return true; } // check index if (_posSystems.contains(idx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create GPS system '%3' with index %4, index already taken.") .arg(line).arg(column).arg(name).arg(idx); return false; } DMRAPRSSystem *gps = new DMRAPRSSystem(name, nullptr, nullptr, Interval::fromSeconds(period)); _posSystems[idx] = gps; _config->posSystems()->add(gps); return true; } bool CSVReader::handleAPRSSystem( qint64 idx, const QString &name, qint64 channelIdx, qint64 period, const QString &src, unsigned srcSSID, const QString &dest, unsigned destSSID, const QString &path, const QString &iconname, const QString &message, qint64 line, qint64 column, QString &errorMessage) { if (_link) { FMAPRSSystem *aprs = _posSystems[idx]->as(); if (! _channels.contains(channelIdx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create APRS system '%3', unknown channel ID %4.") .arg(line).arg(column).arg(name).arg(channelIdx); return false; } if (! _channels[channelIdx]->is()) { errorMessage = QString("Parse error @ %1,%2: Cannot create APRS system '%3', transmit channel %4 is not an analog channel.") .arg(line).arg(column).arg(name).arg(channelIdx); return false; } aprs->setRevertChannel(_channels[channelIdx]->as()); return true; } // check index if (_posSystems.contains(idx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create GPS system '%3' with index %4, index already taken.") .arg(line).arg(column).arg(name).arg(idx); return false; } FMAPRSSystem::Icon icon = name2aprsicon(iconname); FMAPRSSystem *aprs = new FMAPRSSystem(name, nullptr, dest, destSSID, src, srcSSID, path, icon, message, Interval::fromSeconds(period)); _posSystems[idx] = aprs; _config->posSystems()->add(aprs); return true; } bool CSVReader::handleScanList(qint64 idx, const QString &name, qint64 pch1, qint64 pch2, qint64 txch, const QList &channels, qint64 line, qint64 column, QString &errorMessage) { if (_link) { // Check PriChan 1 if ((pch1>0) && (! _channels.contains(pch1))) { errorMessage = QString("Parse error @ %1,%2: Cannot create scanlist '%3', unknown priority channel 1 index %4.") .arg(line).arg(column).arg(name).arg(pch1); return false; } if (pch1 < 0) { _scanlists[idx]->setPrimaryChannel(SelectedChannel::get()); } else if (pch2 > 0) { _scanlists[idx]->setPrimaryChannel(_channels[pch1-1]); } // Check PriChan 2 if ((pch2>0) && (! _channels.contains(pch2))) { errorMessage = QString("Parse error @ %1,%2: Cannot create scanlist '%3', unknown priority channel 2 index %4.") .arg(line).arg(column).arg(name).arg(pch2); return false; } if (pch2 < 0) { _scanlists[idx]->setSecondaryChannel(SelectedChannel::get()); } else if (pch2 > 0){ _scanlists[idx]->setSecondaryChannel(_channels[pch2-1]); } // Check Tx channel if ((txch>0) && (! _channels.contains(txch-1))) { errorMessage = QString("Parse error @ %1,%2: Cannot create scanlist '%3', unknown TX channel index %4.") .arg(line).arg(column).arg(name).arg(txch-1); return false; } if (txch<0) { _scanlists[idx]->setRevertChannel(SelectedChannel::get()); } else if (txch>0) { _scanlists[idx]->setRevertChannel(_channels[txch-2]); } // Check channels foreach(qint64 i, channels) { if ((i>=0) && (!_channels.contains(i))) { errorMessage = QString("Parse error @ %1,%2: Cannot create scanlist '%3', unknown channel index %4.") .arg(line).arg(column).arg(name).arg(i); return false; } // link channels if (i<0) _scanlists[idx]->addChannel(SelectedChannel::get()); else _scanlists[idx]->addChannel(_channels[i]); } // done return true; } // check index if (_scanlists.contains(idx)) { errorMessage = QString("Parse error @ %1,%2: Cannot create scan list '%3' with index %4, index already taken.") .arg(line).arg(column).arg(name).arg(idx); return false; } ScanList *lst = new ScanList(name); _scanlists[idx] = lst; _config->scanlists()->add(lst); return true; } bool CSVReader::handleRoamingZone(qint64 idx, const QString &name, const QList &channels, qint64 line, qint64 column, QString &errorMessage) { if (_link) { foreach(qint64 i, channels) { // Check channels if (! _channels.contains(i)) { errorMessage = QString("Parse error @ %1,%2: Cannot create zone '%3', unknown channel index %4.") .arg(line).arg(column).arg(name).arg(i); return false; } else if (_channels[i]->is()) { errorMessage = QString("Parse error @ %1,%2: Cannot add channel '%3' (idx %4) " "to roaming zone, only digital channels can be used.") .arg(line).arg(column).arg(name).arg(i); return false; } RoamingChannel *rch = RoamingChannel::fromDMRChannel(_channels[i]->as()); _config->roamingChannels()->add(rch); _roamingZones[idx]->addChannel(rch); } // done return true; } // check index if (! _roamingZones.contains(idx)) { RoamingZone *zone = new RoamingZone(name); _roamingZones[idx] = zone; _config->roamingZones()->add(zone); } return true; } ================================================ FILE: lib/csvreader.hh ================================================ #ifndef CSVREADER_HH #define CSVREADER_HH #include #include #include #include #include "channel.hh" #include "contact.hh" class Config; class DMRContact; class RXGroupList; class Zone; class PositionReportingSystem; class ScanList; class RoamingZone; /** The lexer class divides a text stream into tokens. */ class CSVLexer: public QObject { Q_OBJECT public: /** The token. */ struct Token { public: /** Possible token types. */ enum TokenType { T_KEYWORD, ///< A Keyword/Identifier. T_APRSCALL, ///< A APRS call of form CALL-SSID. T_STRING, ///< A quoted string. T_NUMBER, ///< An integer or floating point number. T_DCS_N, ///< A normal DCS code number. T_DCS_I, ///< An inverted DCS code number. T_COLON, ///< A colon. T_NOT_SET, ///< A dash, being used as "not-set". T_ENABLED, ///< A plus sign, being used as "enabled" T_COMMA, ///< A comma T_WHITESPACE, ///< Any whitespace character excluding newline. T_NEWLINE, ///< A new line. T_COMMENT, ///< A comment starts with # end ends at the line-end. T_END_OF_STREAM, ///< Indicates the end-of-input. T_ERROR ///< Indicates a lexer error. }; /// The token type. TokenType type; /// The token value. QString value; /// Line number. qint64 line; /// Column number. qint64 column; }; /// Current state of lexer. struct State { /// The current stream offset. qint64 offset; /// The current line count. qint64 line; /// The current column number. qint64 column; }; public: /** Constructs a lexer for the given stream. */ CSVLexer(QTextStream &stream, QObject *parent=nullptr); /** Saves the current lexer state. */ void push(); /** Restores the last lexer state. */ void pop(); /** Reads the next token from the stream. */ Token next(); /** Returns the last error message. */ const QString &errorMessage() const; protected: /** Internal used function to get the next token. Also returns ignored tokens like whitespace * and comment. */ Token lex(); protected: /// The error message. QString _errorMessage; /// The text stream to read from. QTextStream &_stream; /// The stack of saved lexer states QVector _stack; /// The current line count QString _currentLine; /// The list of patterns to match static QVector< QPair > _pattern; }; /** Basic parse-handler interface. * * That is, a set of callbacks getting called by the parser on the occurrence of a particular * statement in the config file. * @ingroup conf */ class CSVHandler: public QObject { Q_OBJECT protected: /** Hidden constructor. */ explicit CSVHandler(QObject *parent=nullptr); public: /** Destructor. */ virtual ~CSVHandler(); /** Gets called once the DMR IDs has been parsed. */ virtual bool handleRadioId(const QList &ids, qint64 line, qint64 column, QString &errorMessage); /** Gets called once the radio name has been parsed. */ virtual bool handleRadioName(const QString &name, qint64 line, qint64 column, QString &errorMessage); /** Gets called once the first intro line has been parsed. */ virtual bool handleIntroLine1(const QString &text, qint64 line, qint64 column, QString &errorMessage); /** Gets called once the second intro line has been parsed. */ virtual bool handleIntroLine2(const QString &text, qint64 line, qint64 column, QString &errorMessage); /** Gets called once the MIC level has been parsed. */ virtual bool handleMicLevel(unsigned level, qint64 line, qint64 column, QString &errorMessage); /** Gets called once the Speech flag has been parsed. */ virtual bool handleSpeech(bool speech, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a DTMF contact has been parsed. */ virtual bool handleDTMFContact(qint64 idx, const QString &name, const QString &num, bool rxTone, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a digital contact has been parsed. */ virtual bool handleDigitalContact(qint64 idx, const QString &name, DMRContact::Type type, qint64 id, bool rxTone, qint64 line, qint64 column, QString &errorMessage); /** Gets called once an RX group list has been parsed. */ virtual bool handleGroupList(qint64 idx, const QString &name, const QList &contacts, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a digital channel has been parsed. */ virtual bool handleDigitalChannel(qint64 idx, const QString &name, double rx, double tx, Channel::Power power, qint64 scan, qint64 tot, bool ro, DMRChannel::Admit admit, qint64 color, DMRChannel::TimeSlot slot, qint64 gl, qint64 contact, qint64 gps, qint64 roam, qint64 radioID, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a analog channel has been parsed. */ virtual bool handleAnalogChannel(qint64 idx, const QString &name, double rx, double tx, Channel::Power power, qint64 scan, qint64 aprs, qint64 tot, bool ro, FMChannel::Admit admit, qint64 squelch, const SelectiveCall &rxTone, const SelectiveCall &txTone, FMChannel::Bandwidth bw, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a zone list has been parsed. */ virtual bool handleZone(qint64 idx, const QString &name, bool a, const QList &channels, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a GPS system has been parsed. */ virtual bool handleGPSSystem(qint64 idx, const QString &name, qint64 contactIdx, qint64 period, qint64 revertChannelIdx, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a APRS system has been parsed. */ virtual bool handleAPRSSystem(qint64 idx, const QString &name, qint64 channelIdx, qint64 period, const QString &src, unsigned srcSSID, const QString &dest, unsigned destSSID, const QString &path, const QString &icon, const QString &message, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a scan list has been parsed. */ virtual bool handleScanList(qint64 idx, const QString &name, qint64 pch1, qint64 pch2, qint64 txch, const QList &channels, qint64 line, qint64 column, QString &errorMessage); /** Gets called once a roaming zone list has been parsed. */ virtual bool handleRoamingZone(qint64 idx, const QString &name, const QList &channels, qint64 line, qint64 column, QString &errorMessage); }; /** The actual config file parser. * * This class parses the config file and calls the associated callback functions of a handler * instance that is responsible to assemble the final @c Config instance. */ class CSVParser: public QObject { Q_OBJECT public: /** Constructs a parser using the given handler instance. */ explicit CSVParser(CSVHandler *handler, QObject *parent=nullptr); /** Parses the given text stream. */ bool parse(QTextStream &stream); /** Returns the current error message, for example if @c parse returns @c false. */ const QString &errorMessage() const; protected: /** Internal function to parse DMR IDs. */ bool _parse_radio_id(CSVLexer &lexer); /** Internal function to parse radio names. */ bool _parse_radio_name(CSVLexer &lexer); /** Internal function to parse intro line 1. */ bool _parse_introline1(CSVLexer &lexer); /** Internal function to parse intro line 2. */ bool _parse_introline2(CSVLexer &lexer); /** Internal function to parse MIC level. */ bool _parse_mic_level(CSVLexer &lexer); /** Internal function to parse Speech flag. */ bool _parse_speech(CSVLexer &lexer); /** Internal function to parse UserDB flag. */ bool _parse_userdb(CSVLexer &lexer); /** Internal function to parse a digital contact list. */ bool _parse_contacts(CSVLexer &lexer); /** Internal function to parse digital contact. */ bool _parse_contact(qint64 id, CSVLexer &lexer); /** Internal function to parse an RX group list. */ bool _parse_rx_groups(CSVLexer &lexer); /** Internal function to parse an RX group. */ bool _parse_rx_group(qint64 id, CSVLexer &lexer); /** Internal function to parse a digital channel list. */ bool _parse_digital_channels(CSVLexer &lexer); /** Internal function to parse a digital channel. */ bool _parse_digital_channel(qint64 id, CSVLexer &lexer); /** Internal function to parse an analog channel list. */ bool _parse_analog_channels(CSVLexer &lexer); /** Internal function to parse an analog channel. */ bool _parse_analog_channel(qint64 id, CSVLexer &lexer); /** Internal function to parse a zone list. */ bool _parse_zones(CSVLexer &lexer); /** Internal function to parse a zone. */ bool _parse_zone(qint64 id, CSVLexer &lexer); /** Internal function to parse a GPS system list. */ bool _parse_gps_systems(CSVLexer &lexer); /** Internal function to parse a GPS system. */ bool _parse_gps_system(qint64 id, CSVLexer &lexer); /** Internal function to parse a APRS system list. */ bool _parse_aprs_systems(CSVLexer &lexer); /** Internal function to parse a APRS system. */ bool _parse_aprs_system(qint64 id, CSVLexer &lexer); /** Internal function to parse a scanlist list. */ bool _parse_scanlists(CSVLexer &lexer); /** Internal function to parse a scanlist. */ bool _parse_scanlist(qint64 id, CSVLexer &lexer); /** Internal function to parse a roaming zone list. */ bool _parse_roaming_zones(CSVLexer &lexer); /** Internal function to parse a zone. */ bool _parse_roaming_zone(qint64 id, CSVLexer &lexer); protected: /** Holds the current error message. */ QString _errorMessage; /** The handler instance. */ CSVHandler *_handler; }; /** Implements the text-file codeplug reader. * @ingroup conf */ class CSVReader : public CSVHandler { Q_OBJECT protected: /** Hidden constructor. Consider using the static @c read method. */ CSVReader(Config *config, QObject *parent=nullptr); public: /** Reads a config file from @c stream and stores the read configuration into @c config. * @returns @c true on success. */ static bool read(Config *config, QTextStream &stream, QString &errorMessage); virtual bool handleRadioId(const QList &ids, qint64 line, qint64 column, QString &errorMessage); virtual bool handleRadioName(const QString &name, qint64 line, qint64 column, QString &errorMessage); virtual bool handleIntroLine1(const QString &text, qint64 line, qint64 column, QString &errorMessage); virtual bool handleIntroLine2(const QString &text, qint64 line, qint64 column, QString &errorMessage); virtual bool handleMicLevel(unsigned level, qint64 line, qint64 column, QString &errorMessage); virtual bool handleSpeech(bool speech, qint64 line, qint64 column, QString &errorMessage); virtual bool handleDTMFContact(qint64 idx, const QString &name, const QString &num, bool rxTone, qint64 line, qint64 column, QString &errorMessage); virtual bool handleDigitalContact( qint64 idx, const QString &name, DMRContact::Type type, qint64 id, bool rxTone, qint64 line, qint64 column, QString &errorMessage); virtual bool handleGroupList(qint64 idx, const QString &name, const QList &contacts, qint64 line, qint64 column, QString &errorMessage); virtual bool handleDigitalChannel( qint64 idx, const QString &name, double rx, double tx, Channel::Power power, qint64 scan, qint64 tot, bool ro, DMRChannel::Admit admit, qint64 color, DMRChannel::TimeSlot slot, qint64 gl, qint64 contact, qint64 gps, qint64 roam, qint64 radioID, qint64 line, qint64 column, QString &errorMessage); virtual bool handleAnalogChannel(qint64 idx, const QString &name, double rx, double tx, Channel::Power power, qint64 scan, qint64 aprs, qint64 tot, bool ro, FMChannel::Admit admit, qint64 squelch, const SelectiveCall &rxTone, const SelectiveCall &txTone, FMChannel::Bandwidth bw, qint64 line, qint64 column, QString &errorMessage); virtual bool handleZone(qint64 idx, const QString &name, bool a, const QList &channels, qint64 line, qint64 column, QString &errorMessage); virtual bool handleGPSSystem(qint64 idx, const QString &name, qint64 contactIdx, qint64 period, qint64 revertChannelIdx, qint64 line, qint64 column, QString &errorMessage); virtual bool handleAPRSSystem(qint64 idx, const QString &name, qint64 channelIdx, qint64 period, const QString &src, unsigned srcSSID, const QString &dest, unsigned destSSID, const QString &path, const QString &icon, const QString &message, qint64 line, qint64 column, QString &errorMessage); virtual bool handleScanList(qint64 idx, const QString &name, qint64 pch1, qint64 pch2, qint64 txch, const QList &channels, qint64 line, qint64 column, QString &errorMessage); virtual bool handleRoamingZone(qint64 idx, const QString &name, const QList &channels, qint64 line, qint64 column, QString &errorMessage); protected: /** If @c true, the reader is in "link" mode. */ bool _link; /** The configuration to read. */ Config *_config; /** Index <-> Channel map. */ QMap _channels; /** Index <-> Digital contact map. */ QMap _digital_contacts; /** Index <-> RX group list map. */ QMap _rxgroups; /** Index <-> Zone map. */ QMap _zones; /** Index <-> GPS System map. */ QMap _posSystems; /** Index <-> Scan list map. */ QMap _scanlists; /** Index <-> RoamingZone map */ QMap _roamingZones; /** Index <-> radio ID map */ QMap _radioIDs; }; #endif // CSVREADER_HH ================================================ FILE: lib/d168uv.cc ================================================ #include "d168uv.hh" #include "logger.hh" #include "d168uv_limits.hh" #include "d168uv_codeplug.hh" #include "d878uv2_callsigndb.hh" D168UV::D168UV(AnytoneInterface *device, QObject *parent) : AnytoneRadio("Anytone AT-D168UV", device, parent), _limits(nullptr) { _codeplug = new D168UVCodeplug(this); _codeplug->clear(); _callsigns = new D878UV2CallsignDB(this); _satellites = new AnytoneSatelliteConfig(this); // Get device info and determine supported TX frequency bands AnytoneInterface::RadioVariant info; if (_dev) _dev->getInfo(info); switch (info.bands) { case 0x00: case 0x01: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x02: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x03: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x04: _limits = new D168UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(438.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(438.)} }, info.version, this); break; case 0x05: _limits = new D168UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(437.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(437.)} }, info.version, this); break; case 0x06: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x07: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x08: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x09: _limits = new D168UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, info.version, this); break; case 0x0a: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x0b: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x0c: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(490.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(490.)} }, info.version, this); break; case 0x0d: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x0e: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x0f: _limits = new D168UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x10: _limits = new D168UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(147.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(147.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x11: _limits = new D168UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)} }, info.version, this); break; default: logInfo() << "Unknown band-code" << QString::number(int(info.bands), 16) << ": Do not check frequency range."; _limits = new D168UVLimits({}, {}, info.version, this); break; } } const RadioLimits & D168UV::limits() const { return *_limits; } RadioInfo D168UV::defaultRadioInfo() { return RadioInfo( RadioInfo::D168UV, "d168uv", "AT-D168UV", "AnyTone", {AnytoneSTM32Interface::interfaceInfo()}); } ================================================ FILE: lib/d168uv.hh ================================================ /** @defgroup d168uv Anytone AT-D168UV * Device specific classes for Anytone AT-D168UVII. * * @ingroup anytone */ #ifndef __D168UV2_HH__ #define __D168UV2_HH__ #include "anytone_radio.hh" #include "anytone_interface.hh" //#include "d878uv2_callsigndb.hh" /** Implements an interface to Anytone AT-D168UVII VHF/UHF 5W DMR (Tier I & II) radios. * * @ingroup d128uv */ class D168UV: public AnytoneRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit D168UV(AnytoneInterface *device=nullptr, QObject *parent=nullptr); const RadioLimits &limits() const; /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: /** The limits for the radio. */ RadioLimits *_limits; }; #endif // __D878UV2_HH__ ================================================ FILE: lib/d168uv_codeplug.cc ================================================ #include "d168uv_codeplug.hh" #include "config.hh" #include #include /* ******************************************************************************************** * * Implementation of D168UVCodeplug::ChannelElement * ******************************************************************************************** */ D168UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : D878UVCodeplug::ChannelElement(ptr) { // pass... } bool D168UVCodeplug::ChannelElement::dmrCRCDisabled() const { return getBit(Offset::disableDMRCRC()); } void D168UVCodeplug::ChannelElement::disableDMRCRC(bool disable) { setBit(Offset::disableDMRCRC(), disable); } Channel * D168UVCodeplug::ChannelElement::toChannelObj(Context &ctx) const { auto ch = D878UVCodeplug::ChannelElement::toChannelObj(ctx); if (nullptr == ch) return ch; if (ch->is()) { auto ext = ch->as()->anytoneChannelExtension(); if (nullptr == ext) ch->as()->setAnytoneChannelExtension( ext = new AnytoneDMRChannelExtension()); ext->enableCRC(! dmrCRCDisabled()); } return ch; } bool D168UVCodeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx) { if (! D878UVCodeplug::ChannelElement::fromChannelObj(c, ctx)) return false; if (c->is() && c->as()->anytoneChannelExtension()) { auto ext = c->as()->anytoneChannelExtension(); disableDMRCRC(! ext->crcEnabled()); } return true; } /* ******************************************************************************************** * * Implementation of D168UVCodeplug::GeneralSettingsElement::TimeZone * ******************************************************************************************** */ QVector D168UVCodeplug::GeneralSettingsElement::TimeZone::_timeZones = { QTimeZone(-43200), QTimeZone(-39600), QTimeZone(-36000), QTimeZone(-32400), QTimeZone(-28800), QTimeZone(-25200), QTimeZone(-21600), QTimeZone(-18000), QTimeZone(-14400), QTimeZone(-12600), QTimeZone(-10800), QTimeZone(- 7200), QTimeZone(- 3600), QTimeZone( 0), QTimeZone( 3600), QTimeZone( 7200), QTimeZone( 10800), QTimeZone( 12600), QTimeZone( 14400), QTimeZone( 16200), QTimeZone( 18000), QTimeZone( 19800), QTimeZone( 20700), QTimeZone( 21600), QTimeZone( 25200), QTimeZone( 28600), QTimeZone( 30600), QTimeZone( 32400), QTimeZone( 36000), QTimeZone( 39600), QTimeZone( 43200), QTimeZone( 46800) }; QTimeZone D168UVCodeplug::GeneralSettingsElement::TimeZone::decode(uint8_t code) { if (code >= _timeZones.size()) return _timeZones.back(); return _timeZones.at(code); } uint8_t D168UVCodeplug::GeneralSettingsElement::TimeZone::encode(const QTimeZone &zone) { if (! _timeZones.contains(zone)) return 13; //<- UTC return _timeZones.indexOf(zone); } /* ******************************************************************************************** * * Implementation of D168UVCodeplug::GeneralSettingsElement * ******************************************************************************************** */ D168UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : D878UVCodeplug::GeneralSettingsElement(ptr, GeneralSettingsElement::size()) { // pass... } QTimeZone D168UVCodeplug::GeneralSettingsElement::gpsTimeZone() const { return TimeZone::decode(getUInt8(Offset::gpsTimeZone())); } void D168UVCodeplug::GeneralSettingsElement::setGPSTimeZone(const QTimeZone &zone) { setUInt8(Offset::gpsTimeZone(), TimeZone::encode(zone)); // <- Set to UTC } /* ******************************************************************************************** * * Implementation of D168UVCodeplug::ExtendedSettingsElement * ******************************************************************************************** */ D168UVCodeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr) : D878UVCodeplug::ExtendedSettingsElement(ptr, size()) { // pass... } bool D168UVCodeplug::ExtendedSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! D878UVCodeplug::ExtendedSettingsElement::fromConfig(flags, ctx, err)) return false; setUInt8(Offset::talkerAliasType(), (uint8_t)TalkerAliasType::RadioName); return true; } /* ******************************************************************************************** * * Implementation of D168UVCodeplug::FixedLocationNamesElement * ******************************************************************************************** */ D168UVCodeplug::FixedLocationNamesElement::FixedLocationNamesElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void D168UVCodeplug::FixedLocationNamesElement::clear() { memset(_data, 0x00, size()); } QString D168UVCodeplug::FixedLocationNamesElement::name(unsigned int n) const { if (n >= Limit::nameCount()) return {}; return readASCII(Offset::names() + n*Offset::betweenNames(), Limit::nameLength(), 0x00); } void D168UVCodeplug::FixedLocationNamesElement::setName(unsigned int n, const QString &name) { if (n >= Limit::nameCount()) return; writeASCII(Offset::names() + n*Offset::betweenNames(), name, Limit::nameLength(), 0x00); } /* ******************************************************************************************** * * Implementation of D168UVCodeplug * ******************************************************************************************** */ D168UVCodeplug::D168UVCodeplug(const QString &label, QObject *parent) : D878UVCodeplug(label, parent) { // pass... } D168UVCodeplug::D168UVCodeplug(QObject *parent) : D878UVCodeplug("AnyTone AT-D168UV Codeplug", parent) { // pass... } void D168UVCodeplug::allocateUpdated() { D878UVCodeplug::allocateUpdated(); // Fixed location names, not yet touched by dmrconf image(0).addElement(Offset::fixedLocationNames(), FixedLocationNamesElement::size()); // allocate unknown segments image(0).addElement(Offset::unknown_25c2000(), 0x0020); image(0).addElement(Offset::unknown_25c2c80(), 0x0020); } bool D168UVCodeplug::encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // Encode channels for (unsigned int i=0; i(); i++) { // enable channel uint16_t bank = i/Limit::channelsPerBank(), idx = i%Limit::channelsPerBank(); uint32_t addr = Offset::channelBanks() + bank*Offset::betweenChannelBanks() + idx*ChannelElement::size(); ChannelElement ch(data(addr)); ch.fromChannelObj(ctx.get(i), ctx); ChannelExtensionElement ext(data(addr + Offset::toChannelExtension())); ext.fromChannelObj(ctx.get(i), ctx); } return true; } bool D168UVCodeplug::createChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create channels ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); for (uint16_t i=0; ichannelList()->add(obj); ctx.add(obj, i); ext.updateChannelObj(obj, ctx); } } return true; } bool D168UVCodeplug::linkChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); // Link channel objects for (uint16_t i=0; i(i)) { ch.linkChannelObj(ctx.get(i), ctx); ext.linkChannelObj(ctx.get(i), ctx); } } return true; } bool D168UVCodeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GeneralSettingsElement(data(Offset::settings())).fromConfig(flags, ctx, err); DMRAPRSMessageElement(data(Offset::dmrAPRSMessage())).fromConfig(flags, ctx); ExtendedSettingsElement(data(Offset::settingsExtension())).fromConfig(flags, ctx); return true; } bool D168UVCodeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GeneralSettingsElement(data(Offset::settings())).updateConfig(ctx, err); DMRAPRSMessageElement(data(Offset::dmrAPRSMessage())).updateConfig(ctx); ExtendedSettingsElement(data(Offset::settingsExtension())).updateConfig(ctx, err); return true; } bool D168UVCodeplug::linkGeneralSettings(Context &ctx, const ErrorStack &err) { if (! GeneralSettingsElement(data(Offset::settings())).linkSettings(ctx.config()->settings(), ctx, err)) { errMsg(err) << "Cannot link general settings extension."; return false; } if (! ExtendedSettingsElement(data(Offset::settingsExtension())).linkConfig(ctx, err)) { errMsg(err) << "Cannot link general settings extension."; return false; } return true; } ================================================ FILE: lib/d168uv_codeplug.hh ================================================ #ifndef D168UV_CODEPLUG_HH #define D168UV_CODEPLUG_HH #include #include "d878uv_codeplug.hh" #include "d878uv_codeplug.hh" #include "d878uv_codeplug.hh" #include "d878uv_codeplug.hh" class Channel; class DMRContact; class Zone; class RXGroupList; class ScanList; class DMRAPRSSystem; /** Represents the device specific binary codeplug for Anytone AT-D878UVII radios. * * This class only implements the difference to the AT-D878UV codeplug. In fact there is only a * difference in the address of the contact ID<->Index map. * * @ingroup d878uv2 */ class D168UVCodeplug : public D878UVCodeplug { Q_OBJECT public: /** Impleemnts a channel for the AnyTone AT-D168UV. */ class ChannelElement: public D878UVCodeplug::ChannelElement { public: /** Constructor. */ ChannelElement(uint8_t *ptr); /** Returns @c true if the CRC check for DMR channels is disabled. */ virtual bool dmrCRCDisabled() const; /** Disables DMR CRC check. */ virtual void disableDMRCRC(bool disable); /** Constructs a Channel object from this element. */ Channel *toChannelObj(Context &ctx) const override; /** Encodes the given channel object. */ bool fromChannelObj(const Channel *c, Context &ctx) override; protected: /** Some internal offsets. */ struct Offset: D878UVCodeplug::ChannelElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr Bit disableDMRCRC() { return {34, 3}; } /// @endcond }; }; /** Represents the general config of the radio within the D168UV binary codeplug. * This covers the CPS version 1.07. */ class GeneralSettingsElement: public D878UVCodeplug::GeneralSettingsElement { protected: /** Device specific time zones. */ struct TimeZone { public: /** Encodes time zone. */ static uint8_t encode(const QTimeZone& zone); /** Decodes time zone. */ static QTimeZone decode(uint8_t code); protected: /** Vector of possible time-zones. */ static QVector _timeZones; }; public: /** Constructor. */ GeneralSettingsElement(uint8_t *ptr); QTimeZone gpsTimeZone() const override; void setGPSTimeZone(const QTimeZone &zone) override; }; /** Represents the extended settings within the D168UV codeplug. * This covers the CPS version 1.07. */ class ExtendedSettingsElement: public D878UVCodeplug::ExtendedSettingsElement { protected: /** Encoding of possible talker-alias types. That is the text being sent as talker alias.*/ enum class TalkerAliasType { RadioName = 0, CustomText = 1 }; public: /** Constructor. */ ExtendedSettingsElement(uint8_t *ptr); // Size changed static constexpr unsigned int size() { return 0x100; } bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) override; protected: struct Offset: D878UVCodeplug::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int talkerAliasType() { return 0x0001; } /// @endcond }; }; /** Represents a set of up to 8 fixed location names. The locations are stored in the * APRSSettingsElement. */ class FixedLocationNamesElement: public Element { public: /** Constructor. */ explicit FixedLocationNamesElement(uint8_t *ptr); /** Size of the element. */ static constexpr unsigned int size() { return 0x0090; } void clear() override; /** Returns the n-th name. */ virtual QString name(unsigned int n) const; /** Sets the n-th name. */ virtual void setName(unsigned int n, const QString &name); public: /** Some limits. */ struct Limit: public D878UVCodeplug::Limit { /// Number of names. static constexpr unsigned int nameCount() { return 8; } static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets. */ struct Offset: D878UVCodeplug::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int names() { return 0x0003; } static constexpr unsigned int betweenNames() { return Limit::nameLength() + 1; } /// @endcond }; }; protected: /** Hidden constructor. */ explicit D168UVCodeplug(const QString &label, QObject *parent = nullptr); public: /** Empty constructor. */ explicit D168UVCodeplug(QObject *parent = nullptr); protected: void allocateUpdated() override; bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err) override; bool createChannels(Context &ctx, const ErrorStack &err) override; bool linkChannels(Context &ctx, const ErrorStack &err) override; bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) override; bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) override; bool linkGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) override; protected: /** Internal used offsets within the codeplug. */ struct Offset: public D878UVCodeplug::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int fixedLocationNames() { return 0x02504800; } static constexpr unsigned int unknown_25c2000() { return 0x025c2000; } static constexpr unsigned int unknown_25c2c80() { return 0x025c2c80; } /// @endcond }; }; #endif // D168UV_CODEPLUG_HH ================================================ FILE: lib/d168uv_limits.cc ================================================ #include "d168uv_limits.hh" #include "d878uv2_callsigndb.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingzone.hh" D168UVLimits::D168UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent) : AnytoneLimits(hardwareRevision, "V100", true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = D878UV2CallsignDB::Limit::entries(); // Define limits for satellite config _hasSatelliteConfig = true; _satelliteConfigImplemented = true; _numSatellites = 25; /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 250, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, 0, 128, new RadioLimitObject { { "name", new RadioLimitString(1, 15, RadioLimitString::ASCII) }, { "number", new RadioLimitString(1, 14, RadioLimitString::DTMF) } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1, 64) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 4000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRef(FMAPRSSystem::staticMetaObject)}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false)}, {"aprs", new RadioLimitObjRef(PositionReportingSystem::staticMetaObject, true)}, {"roaming", new RadioLimitObjRef(RoamingZone::staticMetaObject, true) }, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 250, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } })); /* Handle positioning systems. */ add("positioning", new RadioLimitList{ { DMRAPRSSystem::staticMetaObject, 0, 8, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, 1, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, FMChannel::staticMetaObject}, false) }, { "icon", new RadioLimitEnum{} }, { "message", new RadioLimitString(0, 60, RadioLimitString::ASCII) } ///@todo extend APRSSystem to expose other settings as properties. }} } ); /* Handle roaming zones. */ add("roaming", new RadioLimitList(RoamingZone::staticMetaObject, 0, 64, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "channels", new RadioLimitRefList(0, 64, DMRChannel::staticMetaObject) } } ) ); } ================================================ FILE: lib/d168uv_limits.hh ================================================ #ifndef D168UVLIMITS_HH #define D168UVLIMITS_HH #include "anytone_limits.hh" /** Implements the limits for the AnyTone AT-D168UV. * @ingroup d878uv2 */ class D168UVLimits: public AnytoneLimits { Q_OBJECT public: /** Constructor. */ D168UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent=nullptr); }; #endif // D878UVLIMITS_HH ================================================ FILE: lib/d168uv_satelliteconfig.cc ================================================ #include "d168uv_satelliteconfig.hh" #include "satellitedatabase.hh" #include "logger.hh" /* ********************************************************************************************* * * Implementation of D168UVSatelliteConfig * ********************************************************************************************* */ D168UVSatelliteConfig::D168UVSatelliteConfig(QObject *parent) : AnytoneSatelliteConfig{parent} { // pass... } AnytoneSatelliteConfig::SatelliteElement D168UVSatelliteConfig::satellite(unsigned int idx) { return SatelliteElement(data(Offset::satellites() + idx*Offset::betweenSatellites())); } bool D168UVSatelliteConfig::encode(SatelliteDatabase *db, const ErrorStack &err) { unsigned int numSat = std::min(Limit::satellites(), db->count()); image(0).addElement(Offset::satellites(), numSat*SatelliteElement::size()); for (unsigned int i=0; igetAt(i).name() << "' at index " << i << "."; if (! satellite(i).encode(db->getAt(i), err)) { errMsg(err) << "Cannot encode satellite '" << db->getAt(i).name() << "at index " << i << "."; return false; } } return true; } ================================================ FILE: lib/d168uv_satelliteconfig.hh ================================================ #ifndef D168UV_SATELLITECONFIG_HH #define D168UV_SATELLITECONFIG_HH #include "anytone_satelliteconfig.hh" /** Implements the satellite configuration for the AnyTone AT-D168UV. * @ingroup d168uv */ class D168UVSatelliteConfig : public AnytoneSatelliteConfig { Q_OBJECT public: /** Default constructor. */ explicit D168UVSatelliteConfig(QObject *parent = nullptr); /** Returns the satellite element at the specified index. */ SatelliteElement satellite(unsigned int idx); bool encode(SatelliteDatabase *db, const ErrorStack &err) override; /** Some limits for the satellite config. */ struct Limit: AnytoneSatelliteConfig::Limit { /// Number of satellites. static constexpr unsigned int satellites() { return 25; } }; protected: /** Some internal offsets. */ struct Offset: AnytoneSatelliteConfig::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int satellites() { return 0x2d40000; } /// @endcond }; }; #endif //D168UV_SATELLITECONFIG_HH ================================================ FILE: lib/d578uv.cc ================================================ #include "userdatabase.hh" #include "d578uv.hh" #include "config.hh" #include "logger.hh" #include "d578uv_limits.hh" #include "d578uv_codeplug.hh" // uses same callsign db as 878 #include "d878uv2_callsigndb.hh" #define RBSIZE 16 #define WBSIZE 16 D578UV::D578UV(AnytoneInterface *device, QObject *parent) : AnytoneRadio("Anytone AT-D578UV", device, parent), _limits(nullptr) { _codeplug = new D578UVCodeplug(this); _codeplug->clear(); _callsigns = new D878UV2CallsignDB(this); // Get device info and determine supported TX frequency bands AnytoneInterface::RadioVariant info; if (_dev) _dev->getInfo(info); switch (info.bands) { case 0x00: case 0x01: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x02: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x03: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x04: _limits = new D578UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(438.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(438.)} }, info.version, this); break; case 0x05: _limits = new D578UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x06: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x07: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x08: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x09: _limits = new D578UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, info.version, this); break; case 0x0a: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x0b: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x0c: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(490.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(490.)} }, info.version, this); break; case 0x0d: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} },info.version, this); break; case 0x0e: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x0f: _limits = new D578UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x10: _limits = new D578UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(147.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(147.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x11: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)} }, info.version, this); break; case 0x12: _limits = new D578UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(222.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, info.version, this); break; default: logInfo() << "Unknown band-code 0x" << QString::number(int(info.bands), 16) << ": Do not perform a frequency range check."; _limits = new D578UVLimits({}, {}, info.version, this); break; } } const RadioLimits & D578UV::limits() const { return *_limits; } RadioInfo D578UV::defaultRadioInfo() { return RadioInfo( RadioInfo::D578UV, "d578uv", "AT-D578UV", "AnyTone", { AnytoneGD32Interface::interfaceInfo(), AnytoneSTM32Interface::interfaceInfo() }, { RadioInfo(RadioInfo::D578UV, "d578uv2", "AT-D578UVII", "AnyTone", { AnytoneGD32Interface::interfaceInfo(), AnytoneSTM32Interface::interfaceInfo() })}); } ================================================ FILE: lib/d578uv.hh ================================================ /** @defgroup d578uv Anytone AT-D578UV * Device specific classes for Anytone AT-D578UV. * * \image html d578uv.jpg "AT-D578UV" width=200px * \image latex d578uv.jpg "AT-D578UV" width=200px * * @ingroup anytone */ #ifndef __D578UV_HH__ #define __D578UV_HH__ #include "anytone_radio.hh" #include "anytone_interface.hh" #include "d878uv2_callsigndb.hh" /** Implements an interface to Anytone AT-D578UV VHF/UHF 50W DMR (Tier I & II) radios. * * @ingroup d578uv */ class D578UV: public AnytoneRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit D578UV(AnytoneInterface *device=nullptr, QObject *parent=nullptr); const RadioLimits &limits() const; /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: /** Holds the limits for the radio. */ RadioLimits *_limits; }; #endif // __D878UV_HH__ ================================================ FILE: lib/d578uv_codeplug.cc ================================================ #include "gpssystem.hh" #include "d578uv_codeplug.hh" #include "config.hh" #include "utils.hh" #include "channel.hh" #include "config.h" #include "intermediaterepresentation.hh" #include #include /* ******************************************************************************************** * * Implementation of D578UVCodeplug::ChannelElement * ******************************************************************************************** */ D578UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, unsigned size) : D878UVCodeplug::ChannelElement(ptr, size) { // pass... } D578UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : D878UVCodeplug::ChannelElement(ptr) { // pass... } bool D578UVCodeplug::ChannelElement::bluetoothEnabled() const { return getBit(Offset::bluetooth()); } void D578UVCodeplug::ChannelElement::enableBluetooth(bool enable) { setBit(Offset::bluetooth(), enable); } bool D578UVCodeplug::ChannelElement::roamingEnabled() const { // inverted! return !getBit(Offset::roaming()); } void D578UVCodeplug::ChannelElement::enableRoaming(bool enable) { // inverted! setBit(Offset::roaming(), !enable); } D578UVCodeplug::ChannelElement::InterruptPriority D578UVCodeplug::ChannelElement::interruptPriority() const { return (InterruptPriority)getUInt2(Offset::interruptPriority()); } void D578UVCodeplug::ChannelElement::setInterruptPriority(InterruptPriority pri) { setUInt2(Offset::interruptPriority(), (unsigned) pri); } bool D578UVCodeplug::ChannelElement::noiseReductionEnabled() const { return getBit(Offset::noiseReduction()); } void D578UVCodeplug::ChannelElement::enableNoiseReduction(bool enable) { setBit(Offset::noiseReduction(), enable); } bool D578UVCodeplug::ChannelElement::multipleKeyEncryption() const { return getBit(Offset::multipleKeyEncryption()); } void D578UVCodeplug::ChannelElement::enableMultipleKeyEncryption(bool enable) { setBit(Offset::multipleKeyEncryption(), enable); } bool D578UVCodeplug::ChannelElement::randomKey() const { return getBit(Offset::randomKey()); } void D578UVCodeplug::ChannelElement::enableRandomKey(bool enable) { setBit(Offset::randomKey(), enable); } bool D578UVCodeplug::ChannelElement::sms() const { return !getBit(Offset::sms()); } void D578UVCodeplug::ChannelElement::enableSMS(bool enable) { setBit(Offset::sms(), !enable); } bool D578UVCodeplug::ChannelElement::dataACK() const { // inverted! return !getBit(Offset::dataACK()); } void D578UVCodeplug::ChannelElement::enableDataACK(bool enable) { // inverted! setBit(Offset::dataACK(), !enable); } bool D578UVCodeplug::ChannelElement::autoScan() const { return getBit(Offset::autoScan()); } void D578UVCodeplug::ChannelElement::enableAutoScan(bool enable) { setBit(Offset::autoScan(), enable); } bool D578UVCodeplug::ChannelElement::sendTalkerAlias() const { return getBit(Offset::talkerAlias()); } void D578UVCodeplug::ChannelElement::enableSendTalkerAlias(bool enable) { setBit(Offset::talkerAlias(), enable); } D878UVCodeplug::ChannelElement::AdvancedEncryptionType D578UVCodeplug::ChannelElement::advancedEncryptionType() const { return getBit(Offset::dmrEncryptionType()) ? AdvancedEncryptionType::ARC4 : AdvancedEncryptionType::AES; } void D578UVCodeplug::ChannelElement::setEncryptionType(AdvancedEncryptionType type) { setBit(Offset::dmrEncryptionType(), AdvancedEncryptionType::ARC4 == type); } bool D578UVCodeplug::ChannelElement::analogScamblerEnabled() const { return FMScramblerFrequency::Off != (FMScramblerFrequency)getUInt8(Offset::fmScrambler()); } Frequency D578UVCodeplug::ChannelElement::analogScramblerFrequency() const { switch ((FMScramblerFrequency)getUInt8(Offset::fmScrambler())) { case FMScramblerFrequency::Off: return Frequency(); case FMScramblerFrequency::Hz3300: return Frequency::fromHz(3300); case FMScramblerFrequency::Hz3200: return Frequency::fromHz(3200); case FMScramblerFrequency::Hz3100: return Frequency::fromHz(3100); case FMScramblerFrequency::Hz3000: return Frequency::fromHz(3000); case FMScramblerFrequency::Hz2900: return Frequency::fromHz(2900); case FMScramblerFrequency::Hz2800: return Frequency::fromHz(2800); case FMScramblerFrequency::Hz2700: return Frequency::fromHz(2700); case FMScramblerFrequency::Hz2600: return Frequency::fromHz(2600); case FMScramblerFrequency::Hz2500: return Frequency::fromHz(2500); case FMScramblerFrequency::Hz4095: return Frequency::fromHz(4095); case FMScramblerFrequency::Hz3458: return Frequency::fromHz(3458); case FMScramblerFrequency::Custom: return Frequency::fromHz(100*getUInt8(Offset::customScrambler())+1500); } return Frequency(); } void D578UVCodeplug::ChannelElement::setAnalogScamberFrequency(Frequency f) { if (4095 == f.inHz()) { setUInt8(Offset::fmScrambler(), (unsigned)FMScramblerFrequency::Hz4095); setUInt8(Offset::customScrambler(), 0); } else if (3458 == f.inHz()) { setUInt8(Offset::fmScrambler(), (unsigned)FMScramblerFrequency::Hz3458); setUInt8(Offset::customScrambler(), 0); } else if ((f.inHz() >= 1500) && (f.inHz() <= 4100)) { setUInt8(Offset::fmScrambler(), (unsigned)FMScramblerFrequency::Custom); setUInt8(Offset::customScrambler(), (f.inHz()-1500)/100); } else { clearAnalogScambler(); } } void D578UVCodeplug::ChannelElement::clearAnalogScambler() { setUInt8(Offset::fmScrambler(), (unsigned)FMScramblerFrequency::Off); setUInt8(Offset::customScrambler(), 0); } unsigned int D578UVCodeplug::ChannelElement::fmAPRSFrequencyIndex() const { return getUInt8(Offset::fmAPRSFrequencyIndex()); } void D578UVCodeplug::ChannelElement::setFMAPRSFrequencyIndex(unsigned int idx) { setUInt8(Offset::fmAPRSFrequencyIndex(), std::min(7U, idx)); } bool D578UVCodeplug::ChannelElement::hasARC4EncryptionKeyIndex() const { return 0 != getUInt8(Offset::arc4KeyIndex()); } unsigned D578UVCodeplug::ChannelElement::arc4EncryptionKeyIndex() const { return getUInt8(Offset::arc4KeyIndex()) - 1; } void D578UVCodeplug::ChannelElement::setARC4EncryptionKeyIndex(unsigned idx) { setUInt8(Offset::arc4KeyIndex(), idx+1); } void D578UVCodeplug::ChannelElement::clearARC4EncryptionKeyIndex() { setUInt8(Offset::arc4KeyIndex(), 0); } Channel * D578UVCodeplug::ChannelElement::toChannelObj(Context &ctx) const { Channel *ch = D878UVCodeplug::ChannelElement::toChannelObj(ctx); if (nullptr == ch) return nullptr; // Apply extensions if (FMChannel *fch = ch->as()) { if (AnytoneFMChannelExtension *ext = fch->anytoneChannelExtension()) { // Common settings ext->enableHandsFree(bluetoothEnabled()); // FM specific settings ext->setScramblerFrequency(analogScamblerEnabled() ? analogScramblerFrequency() : Frequency()); } } else if (DMRChannel *dch = ch->as()) { if (AnytoneDMRChannelExtension *ext = dch->anytoneChannelExtension()) { // Common settings ext->enableHandsFree(bluetoothEnabled()); // DMR specific extensions } } // Done. return ch; } bool D578UVCodeplug::ChannelElement::fromChannelObj(const Channel *ch, Context &ctx) { if (! D878UVCodeplug::ChannelElement::fromChannelObj(ch, ctx)) return false; // Apply extensions if (const FMChannel *fch = ch->as()) { if (AnytoneFMChannelExtension *ext = fch->anytoneChannelExtension()) { // Common settings enableBluetooth(ext->handsFree()); // FM specific settings if (ext->scramblerFrequency().isZero()) clearAnalogScambler(); else setAnalogScamberFrequency(ext->scramblerFrequency()); } } else if (const DMRChannel *dch = ch->as()) { if (AnytoneDMRChannelExtension *ext = dch->anytoneChannelExtension()) { // Common settings enableBluetooth(ext->handsFree()); // DMR specific extensions } } return true; } /* ******************************************************************************************** * * Implementation of D578UVCodeplug::ChannelExtensionElement * ******************************************************************************************** */ D578UVCodeplug::ChannelExtensionElement::ChannelExtensionElement(uint8_t *data, unsigned size) : Element(data, size) { // pass... } D578UVCodeplug::ChannelExtensionElement::ChannelExtensionElement(uint8_t *data) : Element(data, size()) { // pass... } void D578UVCodeplug::ChannelExtensionElement::clear() { Element::clear(); std::memset(_data, 0, size()); } unsigned int D578UVCodeplug::ChannelExtensionElement::fiveToneIdIndexBOT() const { return getUInt8(Offset::fiveToneIdIndexBOT()); } void D578UVCodeplug::ChannelExtensionElement::setFiveToneIdIndexBOT(unsigned int idx) { setUInt8(Offset::fiveToneIdIndexBOT(), idx); } unsigned int D578UVCodeplug::ChannelExtensionElement::fiveToneIdIndexEOT() const { return getUInt8(Offset::fiveToneIdIndexEOT()); } void D578UVCodeplug::ChannelExtensionElement::setFiveToneIdIndexEOT(unsigned int idx) { setUInt8(Offset::fiveToneIdIndexEOT(), idx); } /* ******************************************************************************************** * * Implementation of D578UVCodeplug::GeneralSettingsElement::KeyFunction * ******************************************************************************************** */ uint8_t D578UVCodeplug::KeyFunction::encode(AnytoneKeySettingsExtension::KeyFunction func) { switch (func) { case AnytoneKeySettingsExtension::KeyFunction::Off: return (uint8_t)KeyFunction::Off; case AnytoneKeySettingsExtension::KeyFunction::Voltage: return (uint8_t)KeyFunction::Voltage; case AnytoneKeySettingsExtension::KeyFunction::Power: return (uint8_t)KeyFunction::Power; case AnytoneKeySettingsExtension::KeyFunction::Repeater: return (uint8_t)KeyFunction::Repeater; case AnytoneKeySettingsExtension::KeyFunction::Reverse: return (uint8_t)KeyFunction::Reverse; case AnytoneKeySettingsExtension::KeyFunction::Encryption: return (uint8_t)KeyFunction::Encryption; case AnytoneKeySettingsExtension::KeyFunction::Call: return (uint8_t)KeyFunction::Call; case AnytoneKeySettingsExtension::KeyFunction::ToggleVFO: return (uint8_t)KeyFunction::ToggleVFO; case AnytoneKeySettingsExtension::KeyFunction::Scan: return (uint8_t)KeyFunction::Scan; case AnytoneKeySettingsExtension::KeyFunction::WFM: return (uint8_t)KeyFunction::WFM; case AnytoneKeySettingsExtension::KeyFunction::Alarm: return (uint8_t)KeyFunction::Alarm; case AnytoneKeySettingsExtension::KeyFunction::RecordSwitch: return (uint8_t)KeyFunction::RecordSwitch; case AnytoneKeySettingsExtension::KeyFunction::Record: return (uint8_t)KeyFunction::Record; case AnytoneKeySettingsExtension::KeyFunction::SMS: return (uint8_t)KeyFunction::SMS; case AnytoneKeySettingsExtension::KeyFunction::Dial: return (uint8_t)KeyFunction::Dial; case AnytoneKeySettingsExtension::KeyFunction::GPSInformation: return (uint8_t)KeyFunction::GPSInformation; case AnytoneKeySettingsExtension::KeyFunction::Monitor: return (uint8_t)KeyFunction::Monitor; case AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel: return (uint8_t)KeyFunction::ToggleMainChannel; case AnytoneKeySettingsExtension::KeyFunction::HotKey1: return (uint8_t)KeyFunction::HotKey1; case AnytoneKeySettingsExtension::KeyFunction::HotKey2: return (uint8_t)KeyFunction::HotKey2; case AnytoneKeySettingsExtension::KeyFunction::HotKey3: return (uint8_t)KeyFunction::HotKey3; case AnytoneKeySettingsExtension::KeyFunction::HotKey4: return (uint8_t)KeyFunction::HotKey4; case AnytoneKeySettingsExtension::KeyFunction::HotKey5: return (uint8_t)KeyFunction::HotKey5; case AnytoneKeySettingsExtension::KeyFunction::HotKey6: return (uint8_t)KeyFunction::HotKey6; case AnytoneKeySettingsExtension::KeyFunction::WorkAlone: return (uint8_t)KeyFunction::WorkAlone; case AnytoneKeySettingsExtension::KeyFunction::SkipChannel: return (uint8_t)KeyFunction::SkipChannel; case AnytoneKeySettingsExtension::KeyFunction::DMRMonitor: return (uint8_t)KeyFunction::DMRMonitor; case AnytoneKeySettingsExtension::KeyFunction::SubChannel: return (uint8_t)KeyFunction::SubChannel; case AnytoneKeySettingsExtension::KeyFunction::PriorityZone: return (uint8_t)KeyFunction::PriorityZone; case AnytoneKeySettingsExtension::KeyFunction::VFOScan: return (uint8_t)KeyFunction::VFOScan; case AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality: return (uint8_t)KeyFunction::MICSoundQuality; case AnytoneKeySettingsExtension::KeyFunction::LastCallReply: return (uint8_t)KeyFunction::LastCallReply; case AnytoneKeySettingsExtension::KeyFunction::ChannelType: return (uint8_t)KeyFunction::ChannelType; case AnytoneKeySettingsExtension::KeyFunction::Ranging: return (uint8_t)KeyFunction::Ranging; case AnytoneKeySettingsExtension::KeyFunction::Roaming: return (uint8_t)KeyFunction::Roaming; case AnytoneKeySettingsExtension::KeyFunction::ChannelRanging: return (uint8_t)KeyFunction::ChannelRanging; case AnytoneKeySettingsExtension::KeyFunction::MaxVolume: return (uint8_t)KeyFunction::MaxVolume; case AnytoneKeySettingsExtension::KeyFunction::Slot: return (uint8_t)KeyFunction::Slot; case AnytoneKeySettingsExtension::KeyFunction::APRSTypeSwitch: return (uint8_t)KeyFunction::APRSTypeSwitch; case AnytoneKeySettingsExtension::KeyFunction::Zone: return (uint8_t)KeyFunction::Zone; case AnytoneKeySettingsExtension::KeyFunction::MuteA: return (uint8_t)KeyFunction::MuteA; case AnytoneKeySettingsExtension::KeyFunction::MuteB: return (uint8_t)KeyFunction::MuteB; case AnytoneKeySettingsExtension::KeyFunction::RoamingSet: return (uint8_t)KeyFunction::RoamingSet; case AnytoneKeySettingsExtension::KeyFunction::APRSSet: return (uint8_t)KeyFunction::APRSSet; case AnytoneKeySettingsExtension::KeyFunction::ZoneUp: return (uint8_t)KeyFunction::ZoneUp; case AnytoneKeySettingsExtension::KeyFunction::ZoneDown: return (uint8_t)KeyFunction::ZoneDown; case AnytoneKeySettingsExtension::KeyFunction::Exit: return (uint8_t)KeyFunction::Exit; case AnytoneKeySettingsExtension::KeyFunction::Menu: return (uint8_t)KeyFunction::Menu; case AnytoneKeySettingsExtension::KeyFunction::XBandRepeater: return (uint8_t)KeyFunction::XBandRepeater; case AnytoneKeySettingsExtension::KeyFunction::Speaker: return (uint8_t)KeyFunction::Speaker; case AnytoneKeySettingsExtension::KeyFunction::ChannelName: return (uint8_t)KeyFunction::ChannelName; case AnytoneKeySettingsExtension::KeyFunction::Bluetooth: return (uint8_t)KeyFunction::Bluetooth; case AnytoneKeySettingsExtension::KeyFunction::GPS: return (uint8_t)KeyFunction::GPS; case AnytoneKeySettingsExtension::KeyFunction::CDTScan: return (uint8_t)KeyFunction::CDTScan; case AnytoneKeySettingsExtension::KeyFunction::TBSTSend: return (uint8_t)KeyFunction::TBSTSend; case AnytoneKeySettingsExtension::KeyFunction::APRSSend: return (uint8_t)KeyFunction::APRSSend; case AnytoneKeySettingsExtension::KeyFunction::APRSInfo: return (uint8_t)KeyFunction::APRSInfo; case AnytoneKeySettingsExtension::KeyFunction::GPSRoaming: return (uint8_t)KeyFunction::GPSRoaming; case AnytoneKeySettingsExtension::KeyFunction::Squelch: return (uint8_t)KeyFunction::Squelch; case AnytoneKeySettingsExtension::KeyFunction::NoiseReductionTX: return (uint8_t)KeyFunction::NoiseReductionTX; default: return (uint8_t)KeyFunction::Off; } } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::KeyFunction::decode(uint8_t code) { switch ((KeyFunctionCode)code) { case KeyFunction::Off: return AnytoneKeySettingsExtension::KeyFunction::Off; case KeyFunction::Voltage: return AnytoneKeySettingsExtension::KeyFunction::Voltage; case KeyFunction::Power: return AnytoneKeySettingsExtension::KeyFunction::Power; case KeyFunction::Repeater: return AnytoneKeySettingsExtension::KeyFunction::Repeater; case KeyFunction::Reverse: return AnytoneKeySettingsExtension::KeyFunction::Reverse; case KeyFunction::Encryption: return AnytoneKeySettingsExtension::KeyFunction::Encryption; case KeyFunction::Call: return AnytoneKeySettingsExtension::KeyFunction::Call; case KeyFunction::ToggleVFO: return AnytoneKeySettingsExtension::KeyFunction::ToggleVFO; case KeyFunction::Scan: return AnytoneKeySettingsExtension::KeyFunction::Scan; case KeyFunction::WFM: return AnytoneKeySettingsExtension::KeyFunction::WFM; case KeyFunction::Alarm: return AnytoneKeySettingsExtension::KeyFunction::Alarm; case KeyFunction::RecordSwitch: return AnytoneKeySettingsExtension::KeyFunction::RecordSwitch; case KeyFunction::Record: return AnytoneKeySettingsExtension::KeyFunction::Record; case KeyFunction::SMS: return AnytoneKeySettingsExtension::KeyFunction::SMS; case KeyFunction::Dial: return AnytoneKeySettingsExtension::KeyFunction::Dial; case KeyFunction::GPSInformation: return AnytoneKeySettingsExtension::KeyFunction::GPSInformation; case KeyFunction::Monitor: return AnytoneKeySettingsExtension::KeyFunction::Monitor; case KeyFunction::ToggleMainChannel: return AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel; case KeyFunction::HotKey1: return AnytoneKeySettingsExtension::KeyFunction::HotKey1; case KeyFunction::HotKey2: return AnytoneKeySettingsExtension::KeyFunction::HotKey2; case KeyFunction::HotKey3: return AnytoneKeySettingsExtension::KeyFunction::HotKey3; case KeyFunction::HotKey4: return AnytoneKeySettingsExtension::KeyFunction::HotKey4; case KeyFunction::HotKey5: return AnytoneKeySettingsExtension::KeyFunction::HotKey5; case KeyFunction::HotKey6: return AnytoneKeySettingsExtension::KeyFunction::HotKey6; case KeyFunction::WorkAlone: return AnytoneKeySettingsExtension::KeyFunction::WorkAlone; case KeyFunction::SkipChannel: return AnytoneKeySettingsExtension::KeyFunction::SkipChannel; case KeyFunction::DMRMonitor: return AnytoneKeySettingsExtension::KeyFunction::DMRMonitor; case KeyFunction::SubChannel: return AnytoneKeySettingsExtension::KeyFunction::SubChannel; case KeyFunction::PriorityZone: return AnytoneKeySettingsExtension::KeyFunction::PriorityZone; case KeyFunction::VFOScan: return AnytoneKeySettingsExtension::KeyFunction::VFOScan; case KeyFunction::MICSoundQuality: return AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality; case KeyFunction::LastCallReply: return AnytoneKeySettingsExtension::KeyFunction::LastCallReply; case KeyFunction::ChannelType: return AnytoneKeySettingsExtension::KeyFunction::ChannelType; case KeyFunction::Ranging: return AnytoneKeySettingsExtension::KeyFunction::Ranging; case KeyFunction::Roaming: return AnytoneKeySettingsExtension::KeyFunction::Roaming; case KeyFunction::ChannelRanging: return AnytoneKeySettingsExtension::KeyFunction::ChannelRanging; case KeyFunction::MaxVolume: return AnytoneKeySettingsExtension::KeyFunction::MaxVolume; case KeyFunction::Slot: return AnytoneKeySettingsExtension::KeyFunction::Slot; case KeyFunction::APRSTypeSwitch: return AnytoneKeySettingsExtension::KeyFunction::APRSTypeSwitch; case KeyFunction::Zone: return AnytoneKeySettingsExtension::KeyFunction::Zone; case KeyFunction::MuteA: return AnytoneKeySettingsExtension::KeyFunction::MuteA; case KeyFunction::MuteB: return AnytoneKeySettingsExtension::KeyFunction::MuteB; case KeyFunction::RoamingSet: return AnytoneKeySettingsExtension::KeyFunction::RoamingSet; case KeyFunction::APRSSet: return AnytoneKeySettingsExtension::KeyFunction::APRSSet; case KeyFunction::ZoneUp: return AnytoneKeySettingsExtension::KeyFunction::ZoneUp; case KeyFunction::ZoneDown: return AnytoneKeySettingsExtension::KeyFunction::ZoneDown; case KeyFunction::Exit: return AnytoneKeySettingsExtension::KeyFunction::Exit; case KeyFunction::Menu: return AnytoneKeySettingsExtension::KeyFunction::Menu; case KeyFunction::XBandRepeater: return AnytoneKeySettingsExtension::KeyFunction::XBandRepeater; case KeyFunction::Speaker: return AnytoneKeySettingsExtension::KeyFunction::Speaker; case KeyFunction::ChannelName: return AnytoneKeySettingsExtension::KeyFunction::ChannelName; case KeyFunction::Bluetooth: return AnytoneKeySettingsExtension::KeyFunction::Bluetooth; case KeyFunction::GPS: return AnytoneKeySettingsExtension::KeyFunction::GPS; case KeyFunction::CDTScan: return AnytoneKeySettingsExtension::KeyFunction::CDTScan; case KeyFunction::TBSTSend: return AnytoneKeySettingsExtension::KeyFunction::TBSTSend; case KeyFunction::APRSSend: return AnytoneKeySettingsExtension::KeyFunction::APRSSend; case KeyFunction::APRSInfo: return AnytoneKeySettingsExtension::KeyFunction::APRSInfo; case KeyFunction::GPSRoaming: return AnytoneKeySettingsExtension::KeyFunction::GPSRoaming; case KeyFunction::Squelch: return AnytoneKeySettingsExtension::KeyFunction::Squelch; case KeyFunction::NoiseReductionTX: return AnytoneKeySettingsExtension::KeyFunction::NoiseReductionTX; default: return AnytoneKeySettingsExtension::KeyFunction::Off; } } /* ******************************************************************************************** * * Implementation of D578UVCodeplug::GeneralSettingsElement::TimeZone * ******************************************************************************************** */ QVector D578UVCodeplug::GeneralSettingsElement::TimeZone::_timeZones = { QTimeZone(-43200), QTimeZone(-39600), QTimeZone(-36000), QTimeZone(-32400), QTimeZone(-28800), QTimeZone(-25200), QTimeZone(-21600), QTimeZone(-18000), QTimeZone(-14400), QTimeZone(-12600), QTimeZone(-10800), QTimeZone(- 7200), QTimeZone(- 3600), QTimeZone( 0), QTimeZone( 3600), QTimeZone( 7200), QTimeZone( 10800), QTimeZone( 12600), QTimeZone( 14400), QTimeZone( 16200), QTimeZone( 18000), QTimeZone( 19800), QTimeZone( 20700), QTimeZone( 21600), QTimeZone( 25200), QTimeZone( 28800), QTimeZone( 30600), QTimeZone( 32400), QTimeZone( 36000), QTimeZone( 39600), QTimeZone( 43200), QTimeZone( 46800) }; QTimeZone D578UVCodeplug::GeneralSettingsElement::TimeZone::decode(uint8_t code) { if (code >= _timeZones.size()) return _timeZones.back(); return _timeZones.at(code); } uint8_t D578UVCodeplug::GeneralSettingsElement::TimeZone::encode(const QTimeZone &zone) { if (! _timeZones.contains(zone)) return 13; //<- UTC return _timeZones.indexOf(zone); } /* ******************************************************************************************** * * Implementation of D578UVCodeplug::GeneralSettingsElement * ******************************************************************************************** */ D578UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, unsigned size) : AnytoneCodeplug::GeneralSettingsElement(ptr, size) { // pass... } D578UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : AnytoneCodeplug::GeneralSettingsElement(ptr, GeneralSettingsElement::size()) { // pass... } bool D578UVCodeplug::GeneralSettingsElement::keyToneEnabled() const { return 0x00 != getUInt8(Offset::enableKeyTone()); } void D578UVCodeplug::GeneralSettingsElement::enableKeyTone(bool enable) { setUInt8(Offset::enableKeyTone(), enable ? 0x01 : 0x00); } Interval D578UVCodeplug::GeneralSettingsElement::transmitTimeout() const { return Interval::fromSeconds((unsigned)getUInt8(Offset::transmitTimeout())*30); } void D578UVCodeplug::GeneralSettingsElement::setTransmitTimeout(const Interval &tot) { setUInt8(Offset::transmitTimeout(), tot.seconds()/30); } AnytoneDisplaySettingsExtension::Language D578UVCodeplug::GeneralSettingsElement::language() const { return (AnytoneDisplaySettingsExtension::Language)getUInt8(Offset::language()); } void D578UVCodeplug::GeneralSettingsElement::setLanguage(AnytoneDisplaySettingsExtension::Language lang) { setUInt8(Offset::language(), (unsigned)lang); } Frequency D578UVCodeplug::GeneralSettingsElement::vfoStepSize() const { switch ((VFOStepSize)getUInt8(Offset::vfoStepSize())) { case VFOStepSize::Hz2500: return Frequency::fromHz(2500); case VFOStepSize::Hz5000: return Frequency::fromHz(5000); case VFOStepSize::Hz6250: return Frequency::fromHz(6250); case VFOStepSize::Hz8330: return Frequency::fromHz(8330); case VFOStepSize::kHz10: return Frequency::fromkHz(10); case VFOStepSize::Hz12500: return Frequency::fromHz(12500); case VFOStepSize::kHz20: return Frequency::fromkHz(20); case VFOStepSize::kHz25: return Frequency::fromkHz(25); case VFOStepSize::kHz30: return Frequency::fromkHz(30); case VFOStepSize::kHz50: return Frequency::fromkHz(50); } return Frequency::fromHz(2500); } void D578UVCodeplug::GeneralSettingsElement::setVFOStepSize(const Frequency &f) { VFOStepSize stepSize = VFOStepSize::Hz2500; if (f.inHz() <= 2500) stepSize = VFOStepSize::Hz2500; else if (f.inHz() <= 5000) stepSize = VFOStepSize::Hz5000; else if (f.inHz() <= 6250) stepSize = VFOStepSize::Hz6250; else if (f.inHz() <= 8330) stepSize = VFOStepSize::Hz8330; else if (f.inkHz() <= 10) stepSize = VFOStepSize::kHz10; else if (f.inHz() <= 12500) stepSize = VFOStepSize::Hz12500; else if (f.inkHz() <= 20) stepSize = VFOStepSize::kHz20; else if (f.inkHz() <= 25) stepSize = VFOStepSize::kHz25; else if (f.inkHz() <= 30) stepSize = VFOStepSize::kHz30; else if (f.inkHz() <= 50) stepSize = VFOStepSize::kHz50; setUInt8(Offset::vfoStepSize(), (unsigned) stepSize); } AnytoneSettingsExtension::VFOScanType D578UVCodeplug::GeneralSettingsElement::vfoScanType() const { return (AnytoneSettingsExtension::VFOScanType)getUInt8(Offset::vfoScanType()); } void D578UVCodeplug::GeneralSettingsElement::setVFOScanType(AnytoneSettingsExtension::VFOScanType type) { setUInt8(Offset::vfoScanType(), (unsigned int) type); } Level D578UVCodeplug::GeneralSettingsElement::dmrMicGain() const { return Level::fromValue(getUInt8(Offset::dmrMicGain()), Limit::micGain()); } void D578UVCodeplug::GeneralSettingsElement::setDMRMicGain(Level gain) { setUInt8(Offset::dmrMicGain(), gain.mapTo(Limit::micGain())); } bool D578UVCodeplug::GeneralSettingsElement::vfoModeA() const { return getUInt8(Offset::vfoModeA()); } void D578UVCodeplug::GeneralSettingsElement::enableVFOModeA(bool enable) { setUInt8(Offset::vfoModeA(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::vfoModeB() const { return getUInt8(Offset::vfoModeB()); } void D578UVCodeplug::GeneralSettingsElement::enableVFOModeB(bool enable) { setUInt8(Offset::vfoModeB(), (enable ? 0x01 : 0x00)); } AnytoneSettingsExtension::STEType D578UVCodeplug::GeneralSettingsElement::steType() const { return (AnytoneSettingsExtension::STEType)getUInt8(Offset::steType()); } void D578UVCodeplug::GeneralSettingsElement::setSTEType(AnytoneSettingsExtension::STEType type) { setUInt8(Offset::steType(), (unsigned)type); } double D578UVCodeplug::GeneralSettingsElement::steFrequency() const { switch ((STEFrequency)getUInt8(Offset::steFrequency())) { case STEFrequency::Off: return 0; case STEFrequency::Hz55_2: return 55.2; case STEFrequency::Hz259_2: return 259.2; } return 0; } void D578UVCodeplug::GeneralSettingsElement::setSTEFrequency(double freq) { if (0 >= freq) { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Off); } else if (100 > freq) { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Hz55_2); } else { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Hz259_2); } } Interval D578UVCodeplug::GeneralSettingsElement::groupCallHangTime() const { return Interval::fromSeconds(getUInt8(Offset::groupCallHangTime())); } void D578UVCodeplug::GeneralSettingsElement::setGroupCallHangTime(Interval intv) { setUInt8(Offset::groupCallHangTime(), intv.seconds()); } Interval D578UVCodeplug::GeneralSettingsElement::privateCallHangTime() const { return Interval::fromSeconds(getUInt8(Offset::privateCallHangTime())); } void D578UVCodeplug::GeneralSettingsElement::setPrivateCallHangTime(Interval intv) { setUInt8(Offset::privateCallHangTime(), intv.seconds()); } Interval D578UVCodeplug::GeneralSettingsElement::wakeHeadPeriod() const { return Interval::fromMilliseconds(((unsigned)getUInt8(Offset::wakeHeadPeriod()))*20); } void D578UVCodeplug::GeneralSettingsElement::setWakeHeadPeriod(Interval intv) { setUInt8(Offset::wakeHeadPeriod(), intv.milliseconds()/20); } unsigned D578UVCodeplug::GeneralSettingsElement::wfmChannelIndex() const { return getUInt8(Offset::wfmChannelIndex()); } void D578UVCodeplug::GeneralSettingsElement::setWFMChannelIndex(unsigned idx) { setUInt8(Offset::wfmChannelIndex(), idx); } bool D578UVCodeplug::GeneralSettingsElement::wfmVFOEnabled() const { return getUInt8(Offset::wfmVFOEnabled()); } void D578UVCodeplug::GeneralSettingsElement::enableWFMVFO(bool enable) { setUInt8(Offset::wfmVFOEnabled(), (enable ? 0x01 : 0x00)); } unsigned D578UVCodeplug::GeneralSettingsElement::memoryZoneA() const { return getUInt8(Offset::memZoneA()); } void D578UVCodeplug::GeneralSettingsElement::setMemoryZoneA(unsigned zone) { setUInt8(Offset::memZoneA(), zone); } unsigned D578UVCodeplug::GeneralSettingsElement::memoryZoneB() const { return getUInt8(Offset::memZoneB()); } void D578UVCodeplug::GeneralSettingsElement::setMemoryZoneB(unsigned zone) { setUInt8(Offset::memZoneB(), zone); } bool D578UVCodeplug::GeneralSettingsElement::wfmEnabled() const { return 0x00 != getUInt8(Offset::wfmEnable()); } void D578UVCodeplug::GeneralSettingsElement::enableWFM(bool enable) { setUInt8(Offset::wfmEnable(), enable ? 0x01 : 0x00); } bool D578UVCodeplug::GeneralSettingsElement::recording() const { return getUInt8(Offset::enableRecoding()); } void D578UVCodeplug::GeneralSettingsElement::enableRecording(bool enable) { setUInt8(Offset::enableRecoding(), (enable ? 0x01 : 0x00)); } unsigned D578UVCodeplug::GeneralSettingsElement::brightness() const { return (getUInt8(Offset::displayBrightness())*10)/4; } void D578UVCodeplug::GeneralSettingsElement::setBrightness(unsigned level) { setUInt8(Offset::displayBrightness(), (level*4)/10); } bool D578UVCodeplug::GeneralSettingsElement::gps() const { return getUInt8(Offset::gpsEnable()); } void D578UVCodeplug::GeneralSettingsElement::enableGPS(bool enable) { setUInt8(Offset::gpsEnable(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::smsAlert() const { return getUInt8(Offset::smsAlert()); } void D578UVCodeplug::GeneralSettingsElement::enableSMSAlert(bool enable) { setUInt8(Offset::smsAlert(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::wfmMonitor() const { return getUInt8(Offset::wfmMonitor()); } void D578UVCodeplug::GeneralSettingsElement::enableWFMMonitor(bool enable) { setUInt8(Offset::wfmMonitor(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::activeChannelB() const { return getUInt8(Offset::activeChannelB()); } void D578UVCodeplug::GeneralSettingsElement::enableActiveChannelB(bool enable) { setUInt8(Offset::activeChannelB(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::subChannel() const { return getUInt8(Offset::subChannel()); } void D578UVCodeplug::GeneralSettingsElement::enableSubChannel(bool enable) { setUInt8(Offset::subChannel(), (enable ? 0x01 : 0x00)); } Frequency D578UVCodeplug::GeneralSettingsElement::tbstFrequency() const { switch ((TBSTFrequency)getUInt8(Offset::tbstFrequency())) { case TBSTFrequency::Hz1000: return Frequency::fromHz(1000); case TBSTFrequency::Hz1450: return Frequency::fromHz(1450); case TBSTFrequency::Hz1750: return Frequency::fromHz(1750); case TBSTFrequency::Hz2100: return Frequency::fromHz(2100); } return Frequency::fromHz(1750); } void D578UVCodeplug::GeneralSettingsElement::setTBSTFrequency(Frequency freq) { if (1000 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1000); } else if (1450 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1450); } else if (1750 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1750); } else if (2100 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz2100); } else { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1750); } } bool D578UVCodeplug::GeneralSettingsElement::callAlert() const { return getUInt8(Offset::callAlert()); } void D578UVCodeplug::GeneralSettingsElement::enableCallAlert(bool enable) { setUInt8(Offset::callAlert(), (enable ? 0x01 : 0x00)); } QTimeZone D578UVCodeplug::GeneralSettingsElement::gpsTimeZone() const { return TimeZone::decode(getUInt8(Offset::gpsTimeZone())); } void D578UVCodeplug::GeneralSettingsElement::setGPSTimeZone(const QTimeZone &zone) { setUInt8(Offset::gpsTimeZone(), TimeZone::encode(zone)); } bool D578UVCodeplug::GeneralSettingsElement::dmrTalkPermit() const { return getBit(Offset::talkPermit(), 0); } void D578UVCodeplug::GeneralSettingsElement::enableDMRTalkPermit(bool enable) { return setBit(Offset::talkPermit(), 0, enable); } bool D578UVCodeplug::GeneralSettingsElement::fmTalkPermit() const { return getBit(Offset::talkPermit(), 1); } void D578UVCodeplug::GeneralSettingsElement::enableFMTalkPermit(bool enable) { return setBit(Offset::talkPermit(), 1, enable); } bool D578UVCodeplug::GeneralSettingsElement::dmrResetTone() const { return getUInt8(Offset::dmrResetTone()); } void D578UVCodeplug::GeneralSettingsElement::enableDMRResetTone(bool enable) { return setUInt8(Offset::dmrResetTone(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::idleChannelTone() const { return getUInt8(Offset::idleChannelTone()); } void D578UVCodeplug::GeneralSettingsElement::enableIdleChannelTone(bool enable) { return setUInt8(Offset::idleChannelTone(), (enable ? 0x01 : 0x00)); } Interval D578UVCodeplug::GeneralSettingsElement::menuExitTime() const { return Interval::fromSeconds(5 + 5*((unsigned) getUInt8(Offset::menuExitTime()))); } void D578UVCodeplug::GeneralSettingsElement::setMenuExitTime(Interval intv) { setUInt8(Offset::menuExitTime(), (std::max(5ULL, intv.seconds())-5)/5); } bool D578UVCodeplug::GeneralSettingsElement::filterOwnID() const { return getUInt8(Offset::filterOwnID()); } void D578UVCodeplug::GeneralSettingsElement::enableFilterOwnID(bool enable) { setUInt8(Offset::filterOwnID(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::startupTone() const { return getUInt8(Offset::startupTone()); } void D578UVCodeplug::GeneralSettingsElement::enableStartupTone(bool enable) { return setUInt8(Offset::startupTone(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::callEndPrompt() const { return getUInt8(Offset::callEndPrompt()); } void D578UVCodeplug::GeneralSettingsElement::enableCallEndPrompt(bool enable) { return setUInt8(Offset::callEndPrompt(), (enable ? 0x01 : 0x00)); } Level D578UVCodeplug::GeneralSettingsElement::maxSpeakerVolume() const { return Level::fromValue(getUInt8(Offset::maxSpeakerVolume()), Limit::volume()); } void D578UVCodeplug::GeneralSettingsElement::setMaxSpeakerVolume(Level level) { setUInt8(Offset::maxSpeakerVolume(), level.mapTo(Limit::volume())); } bool D578UVCodeplug::GeneralSettingsElement::remoteStunKill() const { return getUInt8(Offset::remoteStunKill()); } void D578UVCodeplug::GeneralSettingsElement::enableRemoteStunKill(bool enable) { setUInt8(Offset::remoteStunKill(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::remoteMonitor() const { return getUInt8(Offset::remoteMonitor()); } void D578UVCodeplug::GeneralSettingsElement::enableRemoteMonitor(bool enable) { setUInt8(Offset::remoteMonitor(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::getGPSPosition() const { return getUInt8(Offset::getGPSPosition()); } void D578UVCodeplug::GeneralSettingsElement::enableGetGPSPosition(bool enable) { return setUInt8(Offset::getGPSPosition(), (enable ? 0x01 : 0x00)); } Interval D578UVCodeplug::GeneralSettingsElement::longPressDuration() const { return Interval::fromSeconds(((unsigned)getUInt8(Offset::longPressDuration()))+1); } void D578UVCodeplug::GeneralSettingsElement::setLongPressDuration(Interval ms) { setUInt8(Offset::longPressDuration(), std::max(1ULL,ms.seconds())-1); } bool D578UVCodeplug::GeneralSettingsElement::volumeChangePrompt() const { return getUInt8(Offset::volumeChangePrompt()); } void D578UVCodeplug::GeneralSettingsElement::enableVolumeChangePrompt(bool enable) { setUInt8(Offset::volumeChangePrompt(), (enable ? 0x01 : 0x00)); } AnytoneAutoRepeaterSettingsExtension::Direction D578UVCodeplug::GeneralSettingsElement::autoRepeaterDirectionA() const { return (AnytoneAutoRepeaterSettingsExtension::Direction) getUInt8(Offset::autoRepeaterDirA()); } void D578UVCodeplug::GeneralSettingsElement::setAutoRepeaterDirectionA(AnytoneAutoRepeaterSettingsExtension::Direction dir) { setUInt8(Offset::autoRepeaterDirA(), (unsigned)dir); } AnytoneDMRSettingsExtension::SlotMatch D578UVCodeplug::GeneralSettingsElement::monitorSlotMatch() const { return (AnytoneDMRSettingsExtension::SlotMatch)getUInt8(Offset::monSlotMatch()); } void D578UVCodeplug::GeneralSettingsElement::setMonitorSlotMatch(AnytoneDMRSettingsExtension::SlotMatch match) { setUInt8(Offset::monSlotMatch(), (unsigned)match); } bool D578UVCodeplug::GeneralSettingsElement::monitorColorCodeMatch() const { return getUInt8(Offset::monColorCodeMatch()); } void D578UVCodeplug::GeneralSettingsElement::enableMonitorColorCodeMatch(bool enable) { setUInt8(Offset::monColorCodeMatch(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::monitorIDMatch() const { return getUInt8(Offset::monIDMatch()); } void D578UVCodeplug::GeneralSettingsElement::enableMonitorIDMatch(bool enable) { setUInt8(Offset::monIDMatch(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::monitorTimeSlotHold() const { return getUInt8(Offset::monTimeSlotHold()); } void D578UVCodeplug::GeneralSettingsElement::enableMonitorTimeSlotHold(bool enable) { setUInt8(Offset::monTimeSlotHold(), (enable ? 0x01 : 0x00)); } AnytoneDisplaySettingsExtension::LastCallerDisplayMode D578UVCodeplug::GeneralSettingsElement::lastCallerDisplayMode() const { return (AnytoneDisplaySettingsExtension::LastCallerDisplayMode)getUInt8(Offset::lastCallerDisplay()); } void D578UVCodeplug::GeneralSettingsElement::setLastCallerDisplayMode(AnytoneDisplaySettingsExtension::LastCallerDisplayMode mode) { setUInt8(Offset::lastCallerDisplay(), (unsigned)mode); } unsigned D578UVCodeplug::GeneralSettingsElement::fmCallHold() const { return getUInt8(Offset::fmCallHold()); } void D578UVCodeplug::GeneralSettingsElement::setFMCallHold(unsigned sec) { setUInt8(Offset::fmCallHold(), sec); } bool D578UVCodeplug::GeneralSettingsElement::displayClock() const { return getUInt8(Offset::showClock()); } void D578UVCodeplug::GeneralSettingsElement::enableDisplayClock(bool enable) { setUInt8(Offset::showClock(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::gpsMessageEnabled() const { return getUInt8(Offset::enableGPSMessage()); } void D578UVCodeplug::GeneralSettingsElement::enableGPSMessage(bool enable) { setUInt8(Offset::enableGPSMessage(), (enable ? 0x01 : 0x00)); } bool D578UVCodeplug::GeneralSettingsElement::enhanceAudio() const { return getUInt8(Offset::enhanceAudio()); } void D578UVCodeplug::GeneralSettingsElement::enableEnhancedAudio(bool enable) { setUInt8(Offset::enhanceAudio(), (enable ? 0x01 : 0x00)); } Frequency D578UVCodeplug::GeneralSettingsElement::minVFOScanFrequencyUHF() const { return Frequency::fromHz(((unsigned)getUInt32_le(Offset::minVFOScanUHF()))*10); } void D578UVCodeplug::GeneralSettingsElement::setMinVFOScanFrequencyUHF(Frequency freq) { setUInt32_le(Offset::minVFOScanUHF(), freq.inHz()/10); } Frequency D578UVCodeplug::GeneralSettingsElement::maxVFOScanFrequencyUHF() const { return Frequency::fromHz(((unsigned)getUInt32_le(Offset::maxVFOScanUHF()))*10); } void D578UVCodeplug::GeneralSettingsElement::setMaxVFOScanFrequencyUHF(Frequency freq) { setUInt32_le(Offset::maxVFOScanUHF(), freq.inHz()/10); } Frequency D578UVCodeplug::GeneralSettingsElement::minVFOScanFrequencyVHF() const { return Frequency::fromHz(((unsigned)getUInt32_le(Offset::minVFOScanVHF()))*10); } void D578UVCodeplug::GeneralSettingsElement::setMinVFOScanFrequencyVHF(Frequency freq) { setUInt32_le(Offset::minVFOScanVHF(), freq.inHz()/10); } Frequency D578UVCodeplug::GeneralSettingsElement::maxVFOScanFrequencyVHF() const { return Frequency::fromHz(((unsigned)getUInt32_le(Offset::maxVFOScanVHF()))*10); } void D578UVCodeplug::GeneralSettingsElement::setMaxVFOScanFrequencyVHF(Frequency freq) { setUInt32_le(Offset::maxVFOScanVHF(), freq.inHz()/10); } bool D578UVCodeplug::GeneralSettingsElement::hasAutoRepeaterOffsetFrequencyIndexUHF() const { return 0xff != autoRepeaterOffsetFrequencyIndexUHF(); } unsigned D578UVCodeplug::GeneralSettingsElement::autoRepeaterOffsetFrequencyIndexUHF() const { return getUInt8(Offset::autoRepOffsetUHF()); } void D578UVCodeplug::GeneralSettingsElement::setAutoRepeaterOffsetFrequenyIndexUHF(unsigned idx) { setUInt8(Offset::autoRepOffsetUHF(), idx); } void D578UVCodeplug::GeneralSettingsElement::clearAutoRepeaterOffsetFrequencyIndexUHF() { setAutoRepeaterOffsetFrequenyIndexUHF(0xff); } bool D578UVCodeplug::GeneralSettingsElement::hasAutoRepeaterOffsetFrequencyIndexVHF() const { return 0xff != autoRepeaterOffsetFrequencyIndexVHF(); } unsigned D578UVCodeplug::GeneralSettingsElement::autoRepeaterOffsetFrequencyIndexVHF() const { return getUInt8(Offset::autoRepOffsetVHF()); } void D578UVCodeplug::GeneralSettingsElement::setAutoRepeaterOffsetFrequenyIndexVHF(unsigned idx) { setUInt8(Offset::autoRepOffsetVHF(), idx); } void D578UVCodeplug::GeneralSettingsElement::clearAutoRepeaterOffsetFrequencyIndexVHF() { setAutoRepeaterOffsetFrequenyIndexVHF(0xff); } Frequency D578UVCodeplug::GeneralSettingsElement::autoRepeaterMinFrequencyVHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMinVHF())*10); } void D578UVCodeplug::GeneralSettingsElement::setAutoRepeaterMinFrequencyVHF(Frequency freq) { setUInt32_le(Offset::autoRepMinVHF(), freq.inHz()/10); } Frequency D578UVCodeplug::GeneralSettingsElement::autoRepeaterMaxFrequencyVHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMaxVHF())*10); } void D578UVCodeplug::GeneralSettingsElement::setAutoRepeaterMaxFrequencyVHF(Frequency freq) { setUInt32_le(Offset::autoRepMaxVHF(), freq.inHz()/10); } Frequency D578UVCodeplug::GeneralSettingsElement::autoRepeaterMinFrequencyUHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMinUHF())*10); } void D578UVCodeplug::GeneralSettingsElement::setAutoRepeaterMinFrequencyUHF(Frequency freq) { setUInt32_le(Offset::autoRepMinUHF(), freq.inHz()/10); } Frequency D578UVCodeplug::GeneralSettingsElement::autoRepeaterMaxFrequencyUHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMaxUHF())*10); } void D578UVCodeplug::GeneralSettingsElement::setAutoRepeaterMaxFrequencyUHF(Frequency freq) { setUInt32_le(Offset::autoRepMaxUHF(), freq.inHz()/10); } void D578UVCodeplug::GeneralSettingsElement::callToneMelody(Melody &melody) const { QVector> tones; tones.reserve(5); for (int i=0; i<5; i++) { double freq = getUInt16_le(Offset::callToneTones()+2*i); unsigned int duration = getUInt16_le(Offset::callToneDurations()+2*i); if (duration) tones.append({freq, duration}); } melody.infer(tones); } void D578UVCodeplug::GeneralSettingsElement::setCallToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; i> tones; tones.reserve(5); for (int i=0; i<5; i++) { double frequency = getUInt16_le(Offset::idleToneTones()+2*i); unsigned int duration = getUInt16_le(Offset::idleToneDurations()+2*i); if (duration) tones.append({frequency, duration}); } melody.infer(tones); } void D578UVCodeplug::GeneralSettingsElement::setIdleToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; i> tones; tones.reserve(5); for (int i=0; i<5; i++) { double frequency = getUInt16_le(Offset::resetToneTones()+2*i); unsigned int duration = getUInt16_le(Offset::resetToneDurations()+2*i); if (duration) tones.append({frequency, duration}); } melody.infer(tones); } void D578UVCodeplug::GeneralSettingsElement::setResetToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; i= delay.milliseconds()) { setUInt8(Offset::btRXDelay(), 0); } else { unsigned int millis = std::min(5500ULL, std::max(500ULL, delay.milliseconds())); setUInt8(Offset::btRXDelay(), (millis-500)/500); } } bool D578UVCodeplug::GeneralSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::GeneralSettingsElement::fromConfig(flags, ctx, err)) return false; // Encode tone settings enableIdleChannelTone( ctx.config()->settings()->tone()->channelIdle().testFlag(Channel::Type::DMR)); // Set transmit timeout setTransmitTimeout(ctx.config()->settings()->tot()); // Set measurement system based on system locale (0x00==Metric) enableGPSUnitsImperial(GNSSSettings::Units::Archaic == ctx.config()->settings()->gnss()->units()); // DMR settings setGroupCallHangTime(ctx.config()->settings()->dmr()->groupCallHangTime()); setPrivateCallHangTime(ctx.config()->settings()->dmr()->privateCallHangTime()); setSMSFormat(ctx.config()->smsExtension()->format()); // Handle D578UV specific settings AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { // <- if extension is not set setPriorityZoneAIndex(0xff); setPriorityZoneBIndex(0xff); return true; } // Encode boot settings if (ext->bootSettings()->priorityZoneA()->isNull()) setPriorityZoneAIndex(0xff); else setPriorityZoneAIndex(ctx.index(ext->bootSettings()->priorityZoneA()->as())); if (ext->bootSettings()->priorityZoneB()->isNull()) setPriorityZoneBIndex(0xff); else setPriorityZoneBIndex(ctx.index(ext->bootSettings()->priorityZoneB()->as())); if (! ext->roamingSettings()->defaultZone()->isNull()) setDefaultRoamingZoneIndex(ctx.index(ext->roamingSettings()->defaultZone()->as())); // Encode key settings enableKnobLock(ext->keySettings()->knobLockEnabled()); enableKeypadLock(ext->keySettings()->keypadLockEnabled()); enableSidekeysLock(ext->keySettings()->sideKeysLockEnabled()); enableKeyLockForced(ext->keySettings()->forcedKeyLockEnabled()); setFuncKey3Short(ext->keySettings()->funcKey3Short()); setFuncKey3Long(ext->keySettings()->funcKey3Long()); setFuncKey4Short(ext->keySettings()->funcKey4Short()); setFuncKey4Long(ext->keySettings()->funcKey4Long()); setFuncKey5Short(ext->keySettings()->funcKey5Short()); setFuncKey5Long(ext->keySettings()->funcKey5Long()); setFuncKey6Short(ext->keySettings()->funcKey6Short()); setFuncKey6Long(ext->keySettings()->funcKey6Long()); setFuncKeyDShort(ext->keySettings()->funcKeyDShort()); setFuncKeyDLong(ext->keySettings()->funcKeyDLong()); // Encode display settings setCallDisplayColor(ext->displaySettings()->callColor()); setLanguage(ext->displaySettings()->language()); enableDisplayChannelNumber(ext->displaySettings()->showChannelNumberEnabled()); enableShowCurrentContact(ext->displaySettings()->showContact()); setStandbyTextColor(ext->displaySettings()->standbyTextColor()); enableShowLastHeard(ext->displaySettings()->showLastHeardEnabled()); setChannelNameColor(ext->displaySettings()->channelNameColor()); enableShowCurrentContact(ext->displaySettings()->showContact()); setTXBacklightDuration(ext->displaySettings()->backlightDurationTX()); // Encode menu settings enableSeparateDisplay(ext->menuSettings()->separatorEnabled()); // Encode auto-repeater settings setAutoRepeaterDirectionB(ext->autoRepeaterSettings()->directionB()); setAutoRepeaterMinFrequencyVHF(ext->autoRepeaterSettings()->vhfMin()); setAutoRepeaterMaxFrequencyVHF(ext->autoRepeaterSettings()->vhfMax()); setAutoRepeaterMinFrequencyUHF(ext->autoRepeaterSettings()->uhfMin()); setAutoRepeaterMaxFrequencyUHF(ext->autoRepeaterSettings()->uhfMax()); // Encode DMR settings setWakeHeadPeriod(ext->dmrSettings()->wakeHeadPeriod()); enableFilterOwnID(ext->dmrSettings()->filterOwnIDEnabled()); setMonitorSlotMatch(ext->dmrSettings()->monitorSlotMatch()); enableMonitorColorCodeMatch(ext->dmrSettings()->monitorColorCodeMatchEnabled()); enableMonitorIDMatch(ext->dmrSettings()->monitorIDMatchEnabled()); enableMonitorTimeSlotHold(ext->dmrSettings()->monitorTimeSlotHoldEnabled()); // Encode GPS settings setGPSTimeZone(ext->gpsSettings()->timeZone()); enableGPSMessage(ext->gpsSettings()->positionReportingEnabled()); setGPSUpdatePeriod(ext->gpsSettings()->updatePeriod()); // Encode ranging/roaming settings. setAutoRoamPeriod(ext->roamingSettings()->autoRoamPeriod()); setAutoRoamDelay(ext->roamingSettings()->autoRoamDelay()); enableRepeaterRangeCheck(ext->roamingSettings()->repeaterRangeCheckEnabled()); setRepeaterRangeCheckInterval(ext->roamingSettings()->repeaterCheckInterval()); setRepeaterRangeCheckCount(ext->roamingSettings()->repeaterRangeCheckCount()); setRoamingStartCondition(ext->roamingSettings()->roamingStartCondition()); enableRepeaterCheckNotification(ext->roamingSettings()->notificationEnabled()); setRepeaterCheckNumNotifications(ext->roamingSettings()->notificationCount()); // Encode other settings enableKeepLastCaller(ext->keepLastCallerEnabled()); setSTEType(ext->steType()); setSTEFrequency(ext->steFrequency()); setTBSTFrequency(ext->tbstFrequency()); return true; } bool D578UVCodeplug::GeneralSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::GeneralSettingsElement::updateConfig(ctx, err)) return false; ctx.config()->settings()->setTOT(transmitTimeout()); // decode tone settings ctx.config()->settings()->tone()->setChannelIdle( idleChannelTone() ? Channel::Type::DMR : Channel::Type::None ); // decode GNSS settings ctx.config()->settings()->gnss()->setUnits( this->gpsUnitsImperial() ? GNSSSettings::Units::Archaic : GNSSSettings::Units::Metric); // decode DMR settings ctx.config()->settings()->dmr()->setGroupCallHangTime(this->groupCallHangTime()); ctx.config()->settings()->dmr()->setPrivateCallHangTime(this->privateCallHangTime()); ctx.config()->smsExtension()->setFormat(this->smsFormat()); // Handle D578UV specific extension AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Decode key settings ext->keySettings()->enableKnobLock(this->knobLock()); ext->keySettings()->enableKeypadLock(this->keypadLock()); ext->keySettings()->enableSideKeysLock(this->sidekeysLock()); ext->keySettings()->enableForcedKeyLock(this->keyLockForced()); ext->keySettings()->setFuncKey3Short(funcKey3Short()); ext->keySettings()->setFuncKey3Long(funcKey3Long()); ext->keySettings()->setFuncKey4Short(funcKey4Short()); ext->keySettings()->setFuncKey4Long(funcKey4Long()); ext->keySettings()->setFuncKey5Short(funcKey5Short()); ext->keySettings()->setFuncKey5Long(funcKey5Long()); ext->keySettings()->setFuncKey6Short(funcKey6Short()); ext->keySettings()->setFuncKey6Long(funcKey6Long()); ext->keySettings()->setFuncKeyDShort(funcKeyDShort()); ext->keySettings()->setFuncKeyDLong(funcKeyDLong()); // Decode display settings ext->displaySettings()->setCallColor(this->callDisplayColor()); ext->displaySettings()->setLanguage(this->language()); ext->displaySettings()->enableShowChannelNumber(this->displayChannelNumber()); ext->displaySettings()->enableShowContact(this->showCurrentContact()); ext->displaySettings()->setStandbyTextColor(this->standbyTextColor()); ext->displaySettings()->enableShowLastHeard(this->showLastHeard()); ext->displaySettings()->setChannelNameColor(this->channelNameColor()); ext->displaySettings()->enableShowContact(this->showCurrentContact()); ext->displaySettings()->setBacklightDurationTX(this->txBacklightDuration()); // Decode menu settings ext->menuSettings()->enableSeparator(this->separateDisplay()); // Decode auto-repeater settings ext->autoRepeaterSettings()->setDirectionB(autoRepeaterDirectionB()); ext->autoRepeaterSettings()->setVHFMin(this->autoRepeaterMinFrequencyVHF()); ext->autoRepeaterSettings()->setVHFMax(this->autoRepeaterMaxFrequencyVHF()); ext->autoRepeaterSettings()->setUHFMin(this->autoRepeaterMinFrequencyUHF()); ext->autoRepeaterSettings()->setUHFMax(this->autoRepeaterMaxFrequencyUHF()); // Encode dmr settings ext->dmrSettings()->setWakeHeadPeriod(this->wakeHeadPeriod()); ext->dmrSettings()->enableFilterOwnID(this->filterOwnID()); ext->dmrSettings()->setMonitorSlotMatch(this->monitorSlotMatch()); ext->dmrSettings()->enableMonitorColorCodeMatch(this->monitorColorCodeMatch()); ext->dmrSettings()->enableMonitorIDMatch(this->monitorIDMatch()); ext->dmrSettings()->enableMonitorTimeSlotHold(this->monitorTimeSlotHold()); // Encode GPS settings ext->gpsSettings()->setTimeZone(this->gpsTimeZone()); ext->gpsSettings()->enablePositionReporting(this->gpsMessageEnabled()); ext->gpsSettings()->setUpdatePeriod(this->gpsUpdatePeriod()); // Encode ranging/roaming settings ext->roamingSettings()->setAutoRoamPeriod(this->autoRoamPeriod()); ext->roamingSettings()->setAutoRoamDelay(this->autoRoamDelay()); ext->roamingSettings()->enableRepeaterRangeCheck(this->repeaterRangeCheck()); ext->roamingSettings()->setRepeaterCheckInterval(this->repeaterRangeCheckInterval()); ext->roamingSettings()->setRepeaterRangeCheckCount(this->repeaterRangeCheckCount()); ext->roamingSettings()->setRoamingStartCondition(this->roamingStartCondition()); ext->roamingSettings()->enableNotification(this->repeaterCheckNotification()); ext->roamingSettings()->setNotificationCount(this->repeaterCheckNumNotifications()); // Decode other settings ext->enableKeepLastCaller(this->keepLastCaller()); ext->setSTEType(this->steType()); ext->setSTEFrequency(this->steFrequency()); ext->setTBSTFrequency(this->tbstFrequency()); return true; } bool D578UVCodeplug::GeneralSettingsElement::linkSettings(RadioSettings *settings, Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::GeneralSettingsElement::linkSettings(settings, ctx, err)) return false; AnytoneSettingsExtension *ext = settings->anytoneExtension(); if (0xff != priorityZoneAIndex()) { if (! ctx.has(priorityZoneAIndex())) { errMsg(err) << "Cannot link priority zone A index " << priorityZoneAIndex() << ": Zone with that index not defined."; return false; } ext->bootSettings()->priorityZoneA()->set(ctx.get(priorityZoneAIndex())); } if (0xff != priorityZoneBIndex()) { if (! ctx.has(priorityZoneBIndex())) { errMsg(err) << "Cannot link priority zone B index " << priorityZoneBIndex() << ": Zone with that index not defined."; return false; } ext->bootSettings()->priorityZoneB()->set(ctx.get(priorityZoneBIndex())); } if (ctx.has(defaultRoamingZoneIndex())) { ext->roamingSettings()->defaultZone()->set(ctx.get(this->defaultRoamingZoneIndex())); } return true; } /* ******************************************************************************************** * * Implementation of D578UVCodeplug::ExtendedSettingsElement * ******************************************************************************************** */ D578UVCodeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr, unsigned int size) : AnytoneCodeplug::ExtendedSettingsElement(ptr, size) { // pass... } D578UVCodeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr) : AnytoneCodeplug::ExtendedSettingsElement(ptr, size()) { // pass... } void D578UVCodeplug::ExtendedSettingsElement::clear() { AnytoneCodeplug::ExtendedSettingsElement::clear(); } AnytoneDMRSettingsExtension::TalkerAliasSource D578UVCodeplug::ExtendedSettingsElement::talkerAliasSource() const { return (AnytoneDMRSettingsExtension::TalkerAliasSource)getUInt8(Offset::talkerAliasDisplay()); } void D578UVCodeplug::ExtendedSettingsElement::setTalkerAliasSource(AnytoneDMRSettingsExtension::TalkerAliasSource mode) { setUInt8(Offset::talkerAliasDisplay(), (unsigned)mode); } DMRSettings::TalkerAliasEncoding D578UVCodeplug::ExtendedSettingsElement::talkerAliasEncoding() const { switch ((TalkerAliasEncoding)getUInt8(Offset::talkerAliasEncoding())) { case TalkerAliasEncoding::ISO7: return DMRSettings::TalkerAliasEncoding::Iso7; case TalkerAliasEncoding::ISO8: return DMRSettings::TalkerAliasEncoding::Iso8; case TalkerAliasEncoding::Unicode: return DMRSettings::TalkerAliasEncoding::Unicode; } return DMRSettings::TalkerAliasEncoding::Iso8; } void D578UVCodeplug::ExtendedSettingsElement::setTalkerAliasEncoding(DMRSettings::TalkerAliasEncoding enc) { switch (enc) { case DMRSettings::TalkerAliasEncoding::Iso7: setUInt8(Offset::talkerAliasEncoding(), (unsigned)TalkerAliasEncoding::ISO7); break; case DMRSettings::TalkerAliasEncoding::Iso8: setUInt8(Offset::talkerAliasEncoding(), (unsigned)TalkerAliasEncoding::ISO8); break; case DMRSettings::TalkerAliasEncoding::Unicode: setUInt8(Offset::talkerAliasEncoding(), (unsigned)TalkerAliasEncoding::Unicode); break; } } bool D578UVCodeplug::ExtendedSettingsElement::weatherAlarmEnabled() const { return 0 != getUInt8(Offset::weatherAlarm()); } void D578UVCodeplug::ExtendedSettingsElement::enableWeatherAlarm(bool enabled) { setUInt8(Offset::weatherAlarm(), enabled ? 0x01 : 0x00); } bool D578UVCodeplug::ExtendedSettingsElement::repeaterEnabled() const { return 0 != getUInt8(Offset::repeater()); } void D578UVCodeplug::ExtendedSettingsElement::enableRepeater(bool enabled) { setUInt8(Offset::repeater(), enabled ? 0x01 : 0x00); } AnytoneAudioSettingsExtension::Speaker D578UVCodeplug::ExtendedSettingsElement::speaker() const { switch ((Speaker) getUInt8(Offset::speakers())) { case Speaker::Microphone: return AnytoneAudioSettingsExtension::Speaker::Handset; case Speaker::Radio: return AnytoneAudioSettingsExtension::Speaker::Radio; case Speaker::Both: return AnytoneAudioSettingsExtension::Speaker::Both; } return AnytoneAudioSettingsExtension::Speaker::Radio; } void D578UVCodeplug::ExtendedSettingsElement::setSpeaker(AnytoneAudioSettingsExtension::Speaker speaker) { switch (speaker) { case AnytoneAudioSettingsExtension::Speaker::Handset: setUInt8(Offset::speakers(), (unsigned)Speaker::Microphone); break; case AnytoneAudioSettingsExtension::Speaker::Radio: setUInt8(Offset::speakers(), (unsigned)Speaker::Radio); break; case AnytoneAudioSettingsExtension::Speaker::Both: setUInt8(Offset::speakers(), (unsigned)Speaker::Both); break; } } AnytoneAudioSettingsExtension::HandsetSpeakerSource D578UVCodeplug::ExtendedSettingsElement::micSpeakerSource() const { switch ((SpeakerSource) getUInt8(Offset::micSpeakerSource())) { case SpeakerSource::MainChannel: return AnytoneAudioSettingsExtension::HandsetSpeakerSource::MainChannel; case SpeakerSource::SubChannel: return AnytoneAudioSettingsExtension::HandsetSpeakerSource::SubChannel; } return AnytoneAudioSettingsExtension::HandsetSpeakerSource::MainChannel; } void D578UVCodeplug::ExtendedSettingsElement::setMicSpeakerSource(AnytoneAudioSettingsExtension::HandsetSpeakerSource source) { switch (source) { case AnytoneAudioSettingsExtension::HandsetSpeakerSource::MainChannel: setUInt8(Offset::micSpeakerSource(), (unsigned) SpeakerSource::MainChannel); break; case AnytoneAudioSettingsExtension::HandsetSpeakerSource::SubChannel: setUInt8(Offset::micSpeakerSource(), (unsigned) SpeakerSource::SubChannel); break; } } GNSSSettings::Systems D578UVCodeplug::ExtendedSettingsElement::gnss() const { switch ((GPSMode)getUInt8(Offset::gpsMode())) { case GPSMode::GPS: return GNSSSettings::System::GPS; case GPSMode::Beidou: return GNSSSettings::System::Beidou; case GPSMode::GPS_Beidou: return GNSSSettings::System::GPS | GNSSSettings::System::Beidou; } return GNSSSettings::System::GPS; } void D578UVCodeplug::ExtendedSettingsElement::setGNSS(GNSSSettings::Systems mode) { if (mode.testFlag(GNSSSettings::System::GPS)) setUInt8(Offset::gpsMode(), (unsigned)GPSMode::GPS); if (mode.testFlag(GNSSSettings::System::Beidou)) setUInt8(Offset::gpsMode(), (unsigned)GPSMode::Beidou); if (mode.testFlags(GNSSSettings::System::GPS|GNSSSettings::System::Beidou)) setUInt8(Offset::gpsMode(), (unsigned)GPSMode::GPS_Beidou); } bool D578UVCodeplug::ExtendedSettingsElement::bluetoothPTTLatch() const { return getUInt8(Offset::btPTTLatch()); } void D578UVCodeplug::ExtendedSettingsElement::enableBluetoothPTTLatch(bool enable) { setUInt8(Offset::btPTTLatch(), enable ? 0x01 : 0x00); } bool D578UVCodeplug::ExtendedSettingsElement::infiniteBluetoothPTTSleepDelay() const { return bluetoothPTTSleepDelay().isNull(); } Interval D578UVCodeplug::ExtendedSettingsElement::bluetoothPTTSleepDelay() const { return Interval::fromMinutes(getUInt8(Offset::btPTTSleepDelay())); } void D578UVCodeplug::ExtendedSettingsElement::setBluetoothPTTSleepDelay(Interval delay) { unsigned int t = std::min(Limit::maxBluetoothPTTSleepDelay(), (unsigned int)delay.minutes()); setUInt8(Offset::btPTTSleepDelay(), t); } void D578UVCodeplug::ExtendedSettingsElement::setInfiniteBluetoothPTTSleepDelay() { setBluetoothPTTSleepDelay(Interval::fromMinutes(0)); } AnytoneSettingsExtension::FanControl D578UVCodeplug::ExtendedSettingsElement::fanControl() const { switch ((FanControl) getUInt8(Offset::fanControl())) { case FanControl::PTT: return AnytoneSettingsExtension::FanControl::PTT; case FanControl::Temperature: return AnytoneSettingsExtension::FanControl::Temperature; case FanControl::Both: return AnytoneSettingsExtension::FanControl::Both; } return AnytoneSettingsExtension::FanControl::PTT; } void D578UVCodeplug::ExtendedSettingsElement::setFanControl(AnytoneSettingsExtension::FanControl ctrl) { switch (ctrl) { case AnytoneSettingsExtension::FanControl::PTT: setUInt8(Offset::fanControl(), (unsigned) FanControl::PTT); break; case AnytoneSettingsExtension::FanControl::Temperature: setUInt8(Offset::fanControl(), (unsigned) FanControl::Temperature); break; case AnytoneSettingsExtension::FanControl::Both: setUInt8(Offset::fanControl(), (unsigned) FanControl::Both); break; } } unsigned int D578UVCodeplug::ExtendedSettingsElement::weatherChannelIndex() const { return getUInt8(Offset::weatherChannelIndex()); } void D578UVCodeplug::ExtendedSettingsElement::setWeatherChannelIndex(unsigned int idx) { setUInt8(Offset::weatherChannelIndex(), std::min(Limit::maxWeatherChannelIndex(), idx)); } bool D578UVCodeplug::ExtendedSettingsElement::infiniteManDialGroupCallHangTime() const { return manDialGroupCallHangTime().isNull(); } Interval D578UVCodeplug::ExtendedSettingsElement::manDialGroupCallHangTime() const { unsigned int t = getUInt8(Offset::manGrpCallHangTime())+1; if (t<=30) return Interval::fromSeconds(t); else if (31==t) return Interval::fromMinutes(30); return Interval(); } void D578UVCodeplug::ExtendedSettingsElement::setManDialGroupCallHangTime(Interval dur) { unsigned int t = dur.seconds(); if (t > 30) t = 31; // = 30min else if (0 == t) t = 32; // = infinite setUInt8(Offset::manGrpCallHangTime(), t-1); } void D578UVCodeplug::ExtendedSettingsElement::setManDialGroupCallHangTimeInfinite() { setManDialGroupCallHangTime(Interval::fromSeconds(0)); } bool D578UVCodeplug::ExtendedSettingsElement::infiniteManDialPrivateCallHangTime() const { return manDialPrivateCallHangTime().isNull(); } Interval D578UVCodeplug::ExtendedSettingsElement::manDialPrivateCallHangTime() const { unsigned int t = getUInt8(Offset::manPrivCallHangTime())+1; if (t<=30) return Interval::fromSeconds(t); else if (31==t) return Interval::fromMinutes(30); return Interval(); } void D578UVCodeplug::ExtendedSettingsElement::setManDialPrivateCallHangTime(Interval dur) { unsigned int t = dur.seconds(); if (t > 30) t = 31; // = 30min else if (0 == t) t = 32; // = infinite setUInt8(Offset::manPrivCallHangTime(), t-1); } void D578UVCodeplug::ExtendedSettingsElement::setManDialPrivateCallHangTimeInfinite() { setManDialPrivateCallHangTime(Interval::fromSeconds(0)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::chKnobShortPressFunction() const { return KeyFunction::decode(getUInt8(Offset::chKnobShortPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setChKnobShortPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::chKnobShortPressFunction(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::chKnobLongPressFunction() const { return KeyFunction::decode(getUInt8(Offset::chKnobLongPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setChKnobLongPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::chKnobLongPressFunction(), KeyFunction::encode(func)); } AnytoneDisplaySettingsExtension::Color D578UVCodeplug::ExtendedSettingsElement::channelBNameColor() const { return NameColor::decode(getUInt8(Offset::channelBNameColor())); } void D578UVCodeplug::ExtendedSettingsElement::setChannelBNameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::channelBNameColor(), NameColor::encode(color)); } AnytoneDMRSettingsExtension::EncryptionType D578UVCodeplug::ExtendedSettingsElement::encryption() const { return (AnytoneDMRSettingsExtension::EncryptionType)getUInt8(Offset::encryptionType()); } void D578UVCodeplug::ExtendedSettingsElement::setEncryption(AnytoneDMRSettingsExtension::EncryptionType mode) { setUInt8(Offset::encryptionType(), (unsigned int)mode); } bool D578UVCodeplug::ExtendedSettingsElement::professionalMode() const { return 1 == getUInt8(Offset::uiMode()); } void D578UVCodeplug::ExtendedSettingsElement::enableProfessionalMode(bool enable) { setUInt8(Offset::uiMode(), enable ? 0x01 : 0x00); } Interval D578UVCodeplug::ExtendedSettingsElement::steDuration() const { return Interval::fromMilliseconds((getUInt8(Offset::steDuration())+1)*10); } void D578UVCodeplug::ExtendedSettingsElement::setSTEDuration(Interval dur) { unsigned int t = std::max(10U, std::min(1000U, (unsigned int)dur.milliseconds())); setUInt8(Offset::steDuration(), t/10-1); } AnytoneAudioSettingsExtension::HandsetType D578UVCodeplug::ExtendedSettingsElement::micType() const { switch ((MicType)getUInt8(Offset::micType())) { case MicType::AnyTone: return AnytoneAudioSettingsExtension::HandsetType::Anytone; case MicType::Generic: return AnytoneAudioSettingsExtension::HandsetType::Generic; } return AnytoneAudioSettingsExtension::HandsetType::Anytone; } void D578UVCodeplug::ExtendedSettingsElement::setMicType(AnytoneAudioSettingsExtension::HandsetType type) { switch (type) { case AnytoneAudioSettingsExtension::HandsetType::Anytone: setUInt8(Offset::micType(), (unsigned) MicType::AnyTone); break; case AnytoneAudioSettingsExtension::HandsetType::Generic: setUInt8(Offset::micType(), (unsigned) MicType::Generic); break; } } AnytoneDisplaySettingsExtension::Color D578UVCodeplug::ExtendedSettingsElement::zoneANameColor() const { return NameColor::decode(getUInt8(Offset::zoneANameColor())); } void D578UVCodeplug::ExtendedSettingsElement::setZoneANameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::zoneANameColor(), NameColor::encode(color)); } AnytoneDisplaySettingsExtension::Color D578UVCodeplug::ExtendedSettingsElement::zoneBNameColor() const { return NameColor::decode(getUInt8(Offset::zoneBNameColor())); } void D578UVCodeplug::ExtendedSettingsElement::setZoneBNameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::zoneBNameColor(), NameColor::encode(color)); } bool D578UVCodeplug::ExtendedSettingsElement::resetAutoShutdownOnCall() const { return 0x00 != getUInt8(Offset::autoShutdownMode()); } void D578UVCodeplug::ExtendedSettingsElement::enableResetAutoShutdownOnCall(bool enable) { setUInt8(Offset::autoShutdownMode(), enable ? 0x01 : 0x00); } bool D578UVCodeplug::ExtendedSettingsElement::showColorCode() const { return getBit(Offset::displayColorCode()); } void D578UVCodeplug::ExtendedSettingsElement::enableShowColorCode(bool enable) { setBit(Offset::displayColorCode(), enable); } bool D578UVCodeplug::ExtendedSettingsElement::showTimeSlot() const { return getBit(Offset::displayTimeSlot()); } void D578UVCodeplug::ExtendedSettingsElement::enableShowTimeSlot(bool enable) { setBit(Offset::displayTimeSlot(), enable); } bool D578UVCodeplug::ExtendedSettingsElement::showChannelType() const { return getBit(Offset::displayChannelType()); } void D578UVCodeplug::ExtendedSettingsElement::enableShowChannelType(bool enable) { setBit(Offset::displayChannelType(), enable); } bool D578UVCodeplug::ExtendedSettingsElement::fmIdleTone() const { return 0x01 == getUInt8(Offset::fmIdleTone()); } void D578UVCodeplug::ExtendedSettingsElement::enableFMIdleTone(bool enable) { setUInt8(Offset::fmIdleTone(), enable ? 0x01 : 0x00); } AnytoneDisplaySettingsExtension::DateFormat D578UVCodeplug::ExtendedSettingsElement::dateFormat() const { return (AnytoneDisplaySettingsExtension::DateFormat)getUInt8(Offset::dateFormat()); } void D578UVCodeplug::ExtendedSettingsElement::setDateFormat(AnytoneDisplaySettingsExtension::DateFormat format) { setUInt8(Offset::dateFormat(), (unsigned int)format); } Level D578UVCodeplug::ExtendedSettingsElement::fmMicGain() const { return Level::fromValue(getUInt8(Offset::analogMicGain())+1, Limit::micGain()); } void D578UVCodeplug::ExtendedSettingsElement::setFMMicGain(Level gain) { setUInt8(Offset::analogMicGain(), gain.mapTo(Limit::micGain())-1); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK1ShortPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK1ShortPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK1ShortPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK1ShortPressFunction(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK2ShortPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK2ShortPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK2ShortPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK2ShortPressFunction(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK3ShortPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK3ShortPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK3ShortPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK3ShortPressFunction(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK1LongPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK1LongPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK1LongPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK1LongPressFunction(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK2LongPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK2LongPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK2LongPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK2LongPressFunction(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK3LongPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK3LongPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK3LongPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK3LongPressFunction(), KeyFunction::encode(func)); } unsigned int D578UVCodeplug::ExtendedSettingsElement::btHandsetMicGain() const { return 2*(getUInt8(Offset::btHSMicGain())+1); } void D578UVCodeplug::ExtendedSettingsElement::setBtHandsetMicGain(unsigned int gain) { gain = std::min(10U, std::max(1U, gain)); setUInt8(Offset::btHSMicGain(), (gain-1)/2); } Interval D578UVCodeplug::ExtendedSettingsElement::btHandsetBacklightDuration() const { unsigned int val = getUInt8(Offset::btHSBacklightDuration()); if ((1 <= val) && (5 >= val)) return Interval::fromSeconds(5 * val); else if ((6 <= val) && (10 >= val)) return Interval::fromMinutes(val-5); else if (11 == val) return Interval::fromMinutes(15); else if (12 == val) return Interval::fromMinutes(35); else if (13 == val) return Interval::fromMinutes(45); else if (14 == val) return Interval::fromMinutes(60); return Interval::infinity(); } void D578UVCodeplug::ExtendedSettingsElement::setBtHandsetBacklightDuration(Interval dur) { if (dur.seconds() < 5) setUInt8(Offset::btHSBacklightDuration(), 1); else if (dur.seconds() < 30) setUInt8(Offset::btHSBacklightDuration(), dur.seconds()/5); else if (dur.minutes() < 1) setUInt8(Offset::btHSBacklightDuration(), 6); else if (dur.minutes() < 10) setUInt8(Offset::btHSBacklightDuration(), 5+dur.minutes()); else if (dur.minutes() <= 15) setUInt8(Offset::btHSBacklightDuration(), 11); else if (dur.minutes() <= 35) setUInt8(Offset::btHSBacklightDuration(), 12); else if (dur.minutes() <= 45) setUInt8(Offset::btHSBacklightDuration(), 13); else if (dur.minutes() <= 60) setUInt8(Offset::btHSBacklightDuration(), 14); else setUInt8(Offset::btHSBacklightDuration(), 0); } AnytoneKeySettingsExtension::UpDownKeyFunction D578UVCodeplug::ExtendedSettingsElement::micUpDownKeyFunction() const { switch ((UpDownKeyFunction)getUInt8(Offset::upDownKeyFunction())) { case UpDownKeyFunction::Channel: return AnytoneKeySettingsExtension::UpDownKeyFunction::Channel; case UpDownKeyFunction::Volume: return AnytoneKeySettingsExtension::UpDownKeyFunction::Volume; } return AnytoneKeySettingsExtension::UpDownKeyFunction::Channel; } void D578UVCodeplug::ExtendedSettingsElement::setMicUpDownKeyFunction(AnytoneKeySettingsExtension::UpDownKeyFunction func) { switch (func) { case AnytoneKeySettingsExtension::UpDownKeyFunction::Channel: setUInt8(Offset::upDownKeyFunction(), (unsigned)UpDownKeyFunction::Channel); break; case AnytoneKeySettingsExtension::UpDownKeyFunction::Volume: setUInt8(Offset::upDownKeyFunction(), (unsigned)UpDownKeyFunction::Volume); break; } } bool D578UVCodeplug::ExtendedSettingsElement::totNotification() const { return 0x00 != getUInt8(Offset::totNotification()); } void D578UVCodeplug::ExtendedSettingsElement::enableTOTNotification(bool enable) { setUInt8(Offset::totNotification(), enable ? 0x01 : 0x00); } bool D578UVCodeplug::ExtendedSettingsElement::gpsRoaming() const { return 0x00 != getUInt8(Offset::gpsRoaming()); } void D578UVCodeplug::ExtendedSettingsElement::enableGPSRoaming(bool enable) { setUInt8(Offset::gpsRoaming(), enable ? 0x01 : 0x00); } AnytoneRepeaterSettingsExtension::ColorCode D578UVCodeplug::ExtendedSettingsElement::repColorCodeMatch() const { switch ((RepeaterColorCodeMatch)getUInt8(Offset::repeaterColorCode())) { case RepeaterColorCodeMatch::None: return AnytoneRepeaterSettingsExtension::ColorCode::Ignored; case RepeaterColorCodeMatch::VFO_A: return AnytoneRepeaterSettingsExtension::ColorCode::VFOA; case RepeaterColorCodeMatch::VFO_B: return AnytoneRepeaterSettingsExtension::ColorCode::VFOB; } return AnytoneRepeaterSettingsExtension::ColorCode::Ignored; } void D578UVCodeplug::ExtendedSettingsElement::setRepColorCodeMatch(AnytoneRepeaterSettingsExtension::ColorCode mode) { switch (mode) { case AnytoneRepeaterSettingsExtension::ColorCode::Ignored: setUInt8(Offset::repeaterColorCode(), (unsigned) RepeaterColorCodeMatch::None); break; case AnytoneRepeaterSettingsExtension::ColorCode::VFOA: setUInt8(Offset::repeaterColorCode(), (unsigned) RepeaterColorCodeMatch::VFO_A); break; case AnytoneRepeaterSettingsExtension::ColorCode::VFOB: setUInt8(Offset::repeaterColorCode(), (unsigned) RepeaterColorCodeMatch::VFO_B); break; } } AnytoneRepeaterSettingsExtension::TimeSlot D578UVCodeplug::ExtendedSettingsElement::repTimeSlotAMatch() const { switch ((RepeaterTimeSlotMatch)getUInt8(Offset::repeaterATimeslot())) { case RepeaterTimeSlotMatch::Any: return AnytoneRepeaterSettingsExtension::TimeSlot::Any; case RepeaterTimeSlotMatch::RX1_TX2: return AnytoneRepeaterSettingsExtension::TimeSlot::TS1; case RepeaterTimeSlotMatch::RX2_TX1: return AnytoneRepeaterSettingsExtension::TimeSlot::TS2; } return AnytoneRepeaterSettingsExtension::TimeSlot::Any; } void D578UVCodeplug::ExtendedSettingsElement::setRepTimeSlotAMatch(AnytoneRepeaterSettingsExtension::TimeSlot mode) { switch (mode) { case AnytoneRepeaterSettingsExtension::TimeSlot::TS1: setUInt8(Offset::repeaterATimeslot(), (unsigned) RepeaterTimeSlotMatch::RX1_TX2); break; case AnytoneRepeaterSettingsExtension::TimeSlot::TS2: setUInt8(Offset::repeaterATimeslot(), (unsigned) RepeaterTimeSlotMatch::RX2_TX1); break; case AnytoneRepeaterSettingsExtension::TimeSlot::Any: default: setUInt8(Offset::repeaterATimeslot(), (unsigned) RepeaterTimeSlotMatch::Any); break; } } AnytoneRepeaterSettingsExtension::TimeSlot D578UVCodeplug::ExtendedSettingsElement::repTimeSlotBMatch() const { switch ((RepeaterTimeSlotMatch)getUInt8(Offset::repeaterBTimeslot())) { case RepeaterTimeSlotMatch::Any: return AnytoneRepeaterSettingsExtension::TimeSlot::Any; case RepeaterTimeSlotMatch::RX1_TX2: return AnytoneRepeaterSettingsExtension::TimeSlot::TS1; case RepeaterTimeSlotMatch::RX2_TX1: return AnytoneRepeaterSettingsExtension::TimeSlot::TS2; } return AnytoneRepeaterSettingsExtension::TimeSlot::Any; } void D578UVCodeplug::ExtendedSettingsElement::setRepTimeSlotBMatch(AnytoneRepeaterSettingsExtension::TimeSlot mode) { switch (mode) { case AnytoneRepeaterSettingsExtension::TimeSlot::TS1: setUInt8(Offset::repeaterBTimeslot(), (unsigned) RepeaterTimeSlotMatch::RX1_TX2); break; case AnytoneRepeaterSettingsExtension::TimeSlot::TS2: setUInt8(Offset::repeaterBTimeslot(), (unsigned) RepeaterTimeSlotMatch::RX2_TX1); break; case AnytoneRepeaterSettingsExtension::TimeSlot::Any: default: setUInt8(Offset::repeaterBTimeslot(), (unsigned) RepeaterTimeSlotMatch::Any); break; } } unsigned int D578UVCodeplug::ExtendedSettingsElement::btHandsetSquelch() const { if (0 == getUInt8(Offset::btHSRxNoiseReduction())) return 0; return (getUInt8(Offset::btHSRxNoiseReduction())*10)/9; } void D578UVCodeplug::ExtendedSettingsElement::setBtHandsetSquelch(unsigned int level) { level = std::min(10U, level); if (1 >= level) setUInt8(Offset::btHSRxNoiseReduction(), level); else setUInt8(Offset::btHSRxNoiseReduction(), (level*9)/10); } bool D578UVCodeplug::ExtendedSettingsElement::btHandsetAutoPowerOffEnabled() const { return 0x01 == getUInt8(Offset::btHSShutDown()); } void D578UVCodeplug::ExtendedSettingsElement::enableBtHandsetAutoPowerOff(bool enable) { setUInt8(Offset::btHSShutDown(), enable ? 0x01 : 0x00); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK1VeryLongPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK1VeryLongPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK1VeryLongPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK1VeryLongPressFunction(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK2VeryLongPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK2VeryLongPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK2VeryLongPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK2VeryLongPressFunction(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D578UVCodeplug::ExtendedSettingsElement::btSK3VeryLongPressFunction() const { return KeyFunction::decode(getUInt8(Offset::btSK3VeryLongPressFunction())); } void D578UVCodeplug::ExtendedSettingsElement::setBtSK3VeryLongPressFunction(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::btSK3VeryLongPressFunction(), KeyFunction::encode(func)); } unsigned int D578UVCodeplug::ExtendedSettingsElement::btHandsetTxNoiseRedLevel() const { if (0 == getUInt8(Offset::btHSTxNoiseReduction())) return 0; return (getUInt8(Offset::btHSTxNoiseReduction())*10)/9; } void D578UVCodeplug::ExtendedSettingsElement::setBtHandsetTxNoiseRedLevel(unsigned int level) { level = std::min(10U, level); if (1 >= level) setUInt8(Offset::btHSTxNoiseReduction(), level); else setUInt8(Offset::btHSTxNoiseReduction(), (level*9)/10); } unsigned int D578UVCodeplug::ExtendedSettingsElement::btHandsetVOXLevel() const { return ((getUInt8(Offset::btHSVOXLevel())+1)*10)/9; } void D578UVCodeplug::ExtendedSettingsElement::setBtHandsetVOXLevel(unsigned int level) { level = std::max(1U, std::min(10U, level)); setUInt8(Offset::btHSVOXLevel(), ((level-1)*8)/9); } Interval D578UVCodeplug::ExtendedSettingsElement::btHandsetVOXDelay() const { return Interval::fromMilliseconds((unsigned int)(1+getUInt8(Offset::btHSVOXDelay()))*500); } void D578UVCodeplug::ExtendedSettingsElement::setBtHandsetVOXDelay(Interval delay) { auto ms = std::max(500ull, delay.milliseconds()); setUInt8(Offset::btHSVOXDelay(), (ms/500)-1); } unsigned int D578UVCodeplug::ExtendedSettingsElement::btHandsetVolumeA() const { unsigned int val = getUInt8(Offset::btHSVolumeA()); if (0 == val) return 0; return 1+((val-1)*9)/31; } void D578UVCodeplug::ExtendedSettingsElement::setBtHandsetVolumeA(unsigned int vol) { setUInt8(Offset::btHSVolumeA(), (std::min(10u, vol)*32)/10); } unsigned int D578UVCodeplug::ExtendedSettingsElement::btHandsetVolumeB() const { unsigned int val = getUInt8(Offset::btHSVolumeB()); if (0 == val) return 0; return 1+((val-1)*9)/31; } void D578UVCodeplug::ExtendedSettingsElement::setBtHandsetVolumeB(unsigned int vol) { setUInt8(Offset::btHSVolumeB(), (std::min(10u, vol)*32)/10); } void D578UVCodeplug::ExtendedSettingsElement::callEndToneMelody(Melody &melody) const { QVector> tones; tones.reserve(5); for (int i=0; i<5; i++) { double freq = getUInt16_le(Offset::callEndTones()+2*i); unsigned int duration = getUInt16_le(Offset::callEndDurations()+2*i); if (duration) tones.append({freq, duration}); } melody.infer(tones); } void D578UVCodeplug::ExtendedSettingsElement::setCallEndToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; i> tones; tones.reserve(5); for (int i=0; i<5; i++) { double freq = getUInt16_le(Offset::allCallTones()+2*i); unsigned int duration = getUInt16_le(Offset::allCallDurations()+2*i); if (duration) tones.append({freq, duration}); } melody.infer(tones); } void D578UVCodeplug::ExtendedSettingsElement::setAllCallToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; iclear(); if (! AnytoneCodeplug::ExtendedSettingsElement::fromConfig(flags, ctx, err)) return false; // Encode tone settings enableFMIdleTone(ctx.config()->settings()->tone()->channelIdle().testFlag(Channel::Type::FM)); setCallEndToneMelody(*ctx.config()->settings()->tone()->callEndMelody()); // Encode GPS settings setGNSS(ctx.config()->settings()->gnss()->systems()); // Encode audio settings if (ctx.config()->settings()->audio()->fmMicGainEnabled()) setFMMicGain(ctx.config()->settings()->audio()->fmMicGain()); else setFMMicGain(ctx.config()->settings()->audio()->micGain()); setTalkerAliasEncoding(ctx.config()->settings()->dmr()->talkerAliasEncoding()); if (nullptr == ctx.config()->settings()->anytoneExtension()) return true; // Get extension AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); // Encode DMR settings setTalkerAliasSource(ext->dmrSettings()->talkerAliasSource()); // Some general settings setSTEDuration(ext->steDuration()); enableProfessionalMode(ext->proModeEnabled()); setFanControl(ext->fan()); // Key settings setChKnobShortPressFunction(ext->keySettings()->funcKnobShort()); setChKnobLongPressFunction(ext->keySettings()->funcKnobLong()); setMicUpDownKeyFunction(ext->keySettings()->upDownKeyFunction()); // Power save settings enableResetAutoShutdownOnCall(ext->powerSaveSettings()->resetAutoShutdownOnCall()); // Encode tone settings enableTOTNotification(ext->toneSettings()->totNotification()); // Encode audio settings setSpeaker(ext->audioSettings()->speaker()); setMicSpeakerSource(ext->audioSettings()->handsetSpeaker()); setMicType(ext->audioSettings()->handsetType()); // Encode DMR settings setManDialGroupCallHangTime(ext->dmrSettings()->manualGroupCallHangTime()); setManDialPrivateCallHangTime(ext->dmrSettings()->manualPrivateCallHangTime()); setEncryption(ext->dmrSettings()->encryption()); // Encode display settings enableShowColorCode(ext->displaySettings()->showColorCode()); enableShowTimeSlot(ext->displaySettings()->showTimeSlot()); enableShowChannelType(ext->displaySettings()->showChannelType()); setDateFormat(ext->displaySettings()->dateFormat()); // Encode roaming settings enableGPSRoaming(ext->roamingSettings()->gpsRoaming()); // Encode bluetooth settings enableBluetoothPTTLatch(ext->bluetoothSettings()->pttLatch()); setBluetoothPTTSleepDelay(ext->bluetoothSettings()->pttSleepTimer()); // Encode BT handset settings enableBtHandsetAutoPowerOff(ext->bluetoothSettings()->handset()->shutdownEnabled()); setBtHandsetBacklightDuration(ext->bluetoothSettings()->handset()->backlight()); setBtHandsetVolumeA(ext->bluetoothSettings()->handset()->volumeLevelA()); setBtHandsetVolumeB(ext->bluetoothSettings()->handset()->volumeLevelB()); setBtHandsetMicGain(ext->bluetoothSettings()->handset()->micGain()); setBtHandsetSquelch(ext->bluetoothSettings()->handset()->squelch()); setBtHandsetTxNoiseRedLevel(ext->bluetoothSettings()->handset()->txNoiseReduction()); setBtHandsetVOXLevel(ext->bluetoothSettings()->handset()->voxLevel()); setBtHandsetVOXDelay(ext->bluetoothSettings()->handset()->voxDelay()); setBtSK1ShortPressFunction(ext->bluetoothSettings()->handset()->funcKeyAShort()); setBtSK2ShortPressFunction(ext->bluetoothSettings()->handset()->funcKeyBShort()); setBtSK3ShortPressFunction(ext->bluetoothSettings()->handset()->funcKeyCShort()); setBtSK1LongPressFunction(ext->bluetoothSettings()->handset()->funcKeyALong()); setBtSK2LongPressFunction(ext->bluetoothSettings()->handset()->funcKeyBLong()); setBtSK3LongPressFunction(ext->bluetoothSettings()->handset()->funcKeyCLong()); setBtSK1VeryLongPressFunction(ext->bluetoothSettings()->handset()->funcKeyAVeryLong()); setBtSK2VeryLongPressFunction(ext->bluetoothSettings()->handset()->funcKeyBVeryLong()); setBtSK3VeryLongPressFunction(ext->bluetoothSettings()->handset()->funcKeyCVeryLong()); // Encode repeater settings enableRepeater(ext->repeaterSettings()->enabled()); setRepColorCodeMatch(ext->repeaterSettings()->colorCode()); setRepTimeSlotAMatch(ext->repeaterSettings()->timeSlot()); setRepTimeSlotBMatch(ext->repeaterSettings()->secTimeSlot()); return true; } bool D578UVCodeplug::ExtendedSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); if (! AnytoneCodeplug::ExtendedSettingsElement::updateConfig(ctx, err)) return false; // Store GPS settings ctx.config()->settings()->gnss()->setSystems(this->gnss()); // Store audio settings if (ctx.config()->settings()->audio()->micGain() == fmMicGain()) ctx.config()->settings()->audio()->disableFMMicGain(); else ctx.config()->settings()->audio()->setFMMicGain(fmMicGain()); // Store tone settings ctx.config()->settings()->tone()->setChannelIdle( ctx.config()->settings()->tone()->channelIdle() | (fmIdleTone() ? Channel::Type::FM : Channel::Type::None)); this->callEndToneMelody(*ctx.config()->settings()->tone()->callEndMelody()); // Store DMR settings ctx.config()->config()->settings()->dmr()->setTalkerAliasEncoding(talkerAliasEncoding()); // Get or add extension if not present AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Store DMR settings ext->dmrSettings()->setTalkerAliasSource(talkerAliasSource()); // Some general settings ext->setSTEDuration(this->steDuration()); ext->enableProMode(this->professionalMode()); ext->setFan(this->fanControl()); // Some key settings ext->keySettings()->setFuncKnobShort(chKnobShortPressFunction()); ext->keySettings()->setFuncKnobLong(chKnobLongPressFunction()); ext->keySettings()->setUpDownKeyFunction(micUpDownKeyFunction()); // Some power-save settings ext->powerSaveSettings()->enableResetAutoShutdownOnCall(this->resetAutoShutdownOnCall()); // Store tone settings ext->toneSettings()->enableTOTNotification(this->totNotification()); // Store FM mic gain separately, if different ext->audioSettings()->setSpeaker(speaker()); ext->audioSettings()->setHandsetSpeaker(micSpeakerSource()); ext->audioSettings()->setHandsetType(micType()); // Store display settings ext->displaySettings()->enableShowColorCode(this->showColorCode()); ext->displaySettings()->enableShowTimeSlot(this->showTimeSlot()); ext->displaySettings()->enableShowChannelType(this->showChannelType()); ext->displaySettings()->setDateFormat(this->dateFormat()); // Store some DMR settings ext->dmrSettings()->setManualGroupCallHangTime(this->manDialGroupCallHangTime()); ext->dmrSettings()->setManualPrivateCallHangTime(this->manDialPrivateCallHangTime()); ext->dmrSettings()->setEncryption(this->encryption()); // Store roaming settings ext->roamingSettings()->enableGPSRoaming(this->gpsRoaming()); // Store bluetooth settings ext->bluetoothSettings()->enablePTTLatch(this->bluetoothPTTLatch()); ext->bluetoothSettings()->setPTTSleepTimer(this->bluetoothPTTSleepDelay()); // Store BT handset settings ext->bluetoothSettings()->handset()->enableShutdown(this->btHandsetAutoPowerOffEnabled()); ext->bluetoothSettings()->handset()->setBacklight(this->btHandsetBacklightDuration()); ext->bluetoothSettings()->handset()->setVolumeLevelA(this->btHandsetVolumeA()); ext->bluetoothSettings()->handset()->setVolumeLevelB(this->btHandsetVolumeB()); ext->bluetoothSettings()->handset()->setMicGain(this->btHandsetMicGain()); ext->bluetoothSettings()->handset()->setSquelch(this->btHandsetSquelch()); ext->bluetoothSettings()->handset()->setTxNoiseReduction(this->btHandsetTxNoiseRedLevel()); ext->bluetoothSettings()->handset()->setVoxLevel(this->btHandsetVOXLevel()); ext->bluetoothSettings()->handset()->setVoxDelay(this->btHandsetVOXDelay()); ext->bluetoothSettings()->handset()->setFuncKeyAShort(this->btSK1ShortPressFunction()); ext->bluetoothSettings()->handset()->setFuncKeyBShort(this->btSK2ShortPressFunction()); ext->bluetoothSettings()->handset()->setFuncKeyCShort(this->btSK3ShortPressFunction()); ext->bluetoothSettings()->handset()->setFuncKeyALong(this->btSK1LongPressFunction()); ext->bluetoothSettings()->handset()->setFuncKeyBLong(this->btSK2LongPressFunction()); ext->bluetoothSettings()->handset()->setFuncKeyCLong(this->btSK3LongPressFunction()); ext->bluetoothSettings()->handset()->setFuncKeyAVeryLong(this->btSK1VeryLongPressFunction()); ext->bluetoothSettings()->handset()->setFuncKeyBVeryLong(this->btSK2VeryLongPressFunction()); ext->bluetoothSettings()->handset()->setFuncKeyCVeryLong(this->btSK3VeryLongPressFunction()); return true; } /* ******************************************************************************************** * * Implementation of D578UVCodeplug::HotKeySettingsElement * ******************************************************************************************** */ D578UVCodeplug::HotKeySettingsElement::HotKeySettingsElement(uint8_t *ptr, size_t size) : AnytoneCodeplug::HotKeySettingsElement(ptr, size) { // pass... } D578UVCodeplug::HotKeySettingsElement::HotKeySettingsElement(uint8_t *ptr) : AnytoneCodeplug::HotKeySettingsElement(ptr, HotKeySettingsElement::size()) { // pass... } uint8_t * D578UVCodeplug::HotKeySettingsElement::hotKeySetting(unsigned int n) const { if (n >= Limit::numEntries()) return nullptr; return _data + Offset::hotKeySettings() + n*Offset::betweenHotKeySettings(); } /* ******************************************************************************************** * * Implementation of D578UVCodeplug::AirBandChannelElement * ******************************************************************************************** */ D578UVCodeplug::AirBandChannelElement::AirBandChannelElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } D578UVCodeplug::AirBandChannelElement::AirBandChannelElement(uint8_t *ptr) : Element(ptr, AirBandChannelElement::size()) { // pass... } void D578UVCodeplug::AirBandChannelElement::clear() { memset(_data, 0, _size); } Frequency D578UVCodeplug::AirBandChannelElement::frequency() const { return Frequency::fromHz(((unsigned long long)getBCD8_be(Offset::frequency()))*10); } void D578UVCodeplug::AirBandChannelElement::setFrequency(Frequency freq) { setBCD8_be(Offset::frequency(), freq.inHz()/10); } QString D578UVCodeplug::AirBandChannelElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void D578UVCodeplug::AirBandChannelElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } bool D578UVCodeplug::AirBandChannelElement::encode(AMChannel *am, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); if (nullptr == am) return false; setName(am->name()); setFrequency(am->rxFrequency()); return true; } AMChannel * D578UVCodeplug::AirBandChannelElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); auto am = new AMChannel(); am->setName(name()); am->setRXFrequency(frequency()); return am; } /* ******************************************************************************************** * * Implementation of D578UVCodeplug::AirBandBitmapElement * ******************************************************************************************** */ D578UVCodeplug::AirBandBitmapElement::AirBandBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } D578UVCodeplug::AirBandBitmapElement::AirBandBitmapElement(uint8_t *ptr) : BitmapElement(ptr, AirBandBitmapElement::size()) { // pass... } /* ******************************************************************************************** * * Implementation of D578UVCodeplug * ******************************************************************************************** */ D578UVCodeplug::D578UVCodeplug(const QString &label, QObject *parent) : D878UVCodeplug(label, parent) { // pass... } D578UVCodeplug::D578UVCodeplug(QObject *parent) : D878UVCodeplug("AnyTone AT-D578UV Codeplug", parent) { // pass... } bool D578UVCodeplug::allocateBitmaps() { if (! D878UVCodeplug::allocateBitmaps()) return false; image(0).addElement(Offset::airBandChannelBitmap(), AirBandBitmapElement::size()); image(0).addElement(Offset::airBandScanBitmap(), AirBandBitmapElement::size()); return true; } void D578UVCodeplug::setBitmaps(Context &ctx) { D878UVCodeplug::setBitmaps(ctx); AirBandBitmapElement air_band_bitmap(data(Offset::airBandChannelBitmap())), air_scan_bitmap(data(Offset::airBandScanBitmap())); unsigned int num_am_channels = std::min(Limit::airBandChannels(), ctx.count()); air_band_bitmap.clear(); air_band_bitmap.enableFirst(num_am_channels); air_scan_bitmap.clear(); air_scan_bitmap.enableFirst(num_am_channels); } void D578UVCodeplug::allocateForEncoding() { // First allocate everything common D878UVCodeplug::allocateForEncoding(); this->allocateAirBandChannels(); } void D578UVCodeplug::allocateForDecoding() { // First allocate everything common D878UVCodeplug::allocateForDecoding(); this->allocateAirBandChannels(); } void D578UVCodeplug::allocateHotKeySettings() { image(0).addElement(Offset::hotKeySettings(), HotKeySettingsElement::size()); } Config * D578UVCodeplug::preprocess(Config *config, const ErrorStack &err) const { // Apply base preprocessing auto intermediate = Codeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot apply preprocessing for D578UV."; return nullptr; } // Split A/B zones into two. ZoneSplitVisitor splitter; if (! splitter.process(intermediate, err)) { errMsg(err) << "Split multi-VFO zones."; delete intermediate; return nullptr; } // Keep 16bit DMR, 128 bit AES and 256 bit AES keys. EncryptionKeyFilterVisitor filter( { EncryptionKeyFilterVisitor::Filter(BasicEncryptionKey::staticMetaObject, 16, 16), EncryptionKeyFilterVisitor::Filter(AESEncryptionKey::staticMetaObject, 128, 128), EncryptionKeyFilterVisitor::Filter(AESEncryptionKey::staticMetaObject, 256, 256),}); if (! filter.process(intermediate, err)) { errMsg(err) << "Cannot remove unsupported exncryption."; delete intermediate; return nullptr; } return intermediate; } bool D578UVCodeplug:: encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! D878UVCodeplug::encodeElements(flags, ctx, err)) { errMsg(err) << "Cannot encode elements for D578UV."; return false; } if (! encodeAirBandChannels(flags, ctx, err)) { errMsg(err) << "Cannot encode AM channels."; return false; } return true; } bool D578UVCodeplug::createElements(Context &ctx, const ErrorStack &err) { if (! D878UVCodeplug::createElements(ctx, err)) { errMsg(err) << "Cannot create elements for D578UV."; return false; } if (! createAirBandChannels(ctx, err)) { errMsg(err) << "Cannot create AM channels."; return false; } return true; } bool D578UVCodeplug::encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // Encode channels for (unsigned int i=0; i(); i++) { // enable channel uint16_t bank = i/Limit::channelsPerBank(), idx = i%Limit::channelsPerBank(); uint32_t addr = Offset::channelBanks() + bank*Offset::betweenChannelBanks() + idx*ChannelElement::size(); ChannelElement ch(data(addr)); ch.fromChannelObj(ctx.get(i), ctx); ChannelExtensionElement ext(data(addr + Offset::toChannelExtension())); ext.clear(); } return true; } bool D578UVCodeplug::createChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); // Create channels for (uint16_t i=0; ichannelList()->add(obj); ctx.add(obj, i); } } return true; } bool D578UVCodeplug::linkChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); // Link channel objects for (uint16_t i=0; i(i)) ch.linkChannelObj(ctx.get(i), ctx); } return true; } void D578UVCodeplug::allocateContacts() { /* Allocate contacts */ ContactBitmapElement contact_bitmap(data(Offset::contactBitmap())); unsigned contactCount=0; for (uint16_t i=0; i contacts; // Encode contacts and also collect id<->index map for (unsigned int i=0; i(); i++) { uint32_t bank_addr = Offset::contactBanks() + (i/Limit::contactsPerBank())*Offset::betweenContactBanks(); uint32_t addr = bank_addr + (i%Limit::contactsPerBank())*ContactElement::size(); ContactElement con(data(addr)); DMRContact *contact = ctx.get(i); if(! con.fromContactObj(contact, ctx)) return false; ((uint32_t *)data(Offset::contactIndex()))[i] = qToLittleEndian(i); contacts.append(contact); } // encode index map for contacts std::sort(contacts.begin(), contacts.end(), [](DMRContact *a, DMRContact *b) { return a->number() < b->number(); }); for (int i=0; inumber(), (DMRContact::GroupCall==contacts[i]->type())); el.setIndex(ctx.index(contacts[i])); } return true; } void D578UVCodeplug::allocateGeneralSettings() { // override allocation of general settings for D878UV code-plug. General settings are larger! image(0).addElement(Offset::settings(), GeneralSettingsElement::size()); image(0).addElement(Offset::gpsMessages(), DMRAPRSMessageElement::size()); image(0).addElement(Offset::settingsExtension(), ExtendedSettingsElement::size()); } bool D578UVCodeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GeneralSettingsElement(data(Offset::settings())).fromConfig(flags, ctx, err); DMRAPRSMessageElement(data(Offset::gpsMessages())).fromConfig(flags, ctx); ExtendedSettingsElement(data(Offset::settingsExtension())).fromConfig(flags, ctx); return true; } bool D578UVCodeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GeneralSettingsElement(data(Offset::settings())).updateConfig(ctx, err); DMRAPRSMessageElement(data(Offset::gpsMessages())).updateConfig(ctx); ExtendedSettingsElement(data(Offset::settingsExtension())).updateConfig(ctx); return true; } bool D578UVCodeplug::linkGeneralSettings(Context &ctx, const ErrorStack &err) { if (! GeneralSettingsElement(data(Offset::settings())).linkSettings(ctx.config()->settings(), ctx, err)) { errMsg(err) << "Cannot link general settings extension."; return false; } if (! ExtendedSettingsElement(data(Offset::settingsExtension())).linkConfig(ctx, err)) { errMsg(err) << "Cannot link general settings extension."; return false; } return true; } bool D578UVCodeplug::allocateAirBandChannels() { // Allocate valid air-band channels AirBandBitmapElement bitmap(data(Offset::airBandChannelBitmap())); for (unsigned int i=0; i(i)) { errMsg(err) << "Cannot resolve AM channel at index " << i << ". Not defined!"; return false; } AirBandChannelElement ch(data(Offset::airBandChannels() + i*AirBandChannelElement::size())); if (! ch.encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode AM channel at index " << i << "."; return false; } } return true; } bool D578UVCodeplug::createAirBandChannels(Context &ctx, const ErrorStack &err) { AirBandBitmapElement air_band_bitmap(data(Offset::airBandChannelBitmap())); for (unsigned int i=0; ichannelList()->add(am); ctx.add(am, i); } return true; } ================================================ FILE: lib/d578uv_codeplug.hh ================================================ #ifndef D578UV_CODEPLUG_HH #define D578UV_CODEPLUG_HH #include #include "d878uv_codeplug.hh" class Channel; class DMRContact; class Zone; class RXGroupList; class ScanList; class DMRAPRSSystem; /** Represents the device specific binary codeplug for Anytone AT-D578UV radios. * * Matches firmware/CPS version 1.21. * * @ingroup d578uv */ class D578UVCodeplug : public D878UVCodeplug { Q_OBJECT protected: /** Device specific key function encoding and decoding. */ struct KeyFunction { public: /** Encodes key function. */ static uint8_t encode(AnytoneKeySettingsExtension::KeyFunction tone); /** Decodes key function. */ static AnytoneKeySettingsExtension::KeyFunction decode(uint8_t code); protected: /** Encoded key functions. */ typedef enum { Off = 0x00, Voltage = 0x01, Power = 0x02, Repeater = 0x03, Reverse = 0x04, Encryption = 0x05, Call = 0x06, ToggleVFO = 0x07, Scan = 0x08, WFM = 0x09, Alarm = 0x0a, RecordSwitch = 0x0b, Record = 0x0c, SMS = 0x0d, Dial = 0x0e, GPSInformation=0x0f, Monitor = 0x10, ToggleMainChannel = 0x11, HotKey1 = 0x12, HotKey2 = 0x13, HotKey3 = 0x14, HotKey4 = 0x15, HotKey5 = 0x16, HotKey6 = 0x17, WorkAlone = 0x18, SkipChannel = 0x19, DMRMonitor = 0x1a, SubChannel = 0x1b, PriorityZone = 0x1c, VFOScan = 0x1d, MICSoundQuality = 0x1e, LastCallReply = 0x1f, ChannelType = 0x20, Ranging=0x21, Roaming = 0x22, ChannelRanging = 0x23, MaxVolume = 0x24, Slot = 0x25, APRSTypeSwitch=0x26, Zone = 0x27, MuteA = 0x28, MuteB = 0x29, RoamingSet = 0x2a, APRSSet = 0x2b, ZoneUp = 0x2c, ZoneDown = 0x2d, Exit = 0x2e, Menu = 0x2f, XBandRepeater = 0x30, Speaker = 0x31, ChannelName = 0x32, Bluetooth = 0x33, GPS = 0x34, CDTScan = 0x35, TBSTSend = 0x36, APRSSend = 0x37, APRSInfo = 0x38, GPSRoaming = 0x39, Squelch=0x3a, NoiseReductionTX=0x3b } KeyFunctionCode; }; public: /** Represents the actual channel encoded within the binary code-plug. * Matches firmware/CPS version 1.21. */ class ChannelElement: public D878UVCodeplug::ChannelElement { public: /** Possible FM scrambler carrier frequencies. */ enum class FMScramblerFrequency { Off = 0, Hz3300 = 1, Hz3200 = 2, Hz3100 = 3, Hz3000 = 4, Hz2900 = 5, Hz2800 = 6, Hz2700 = 7, Hz2600 = 8, Hz2500 = 9, Hz4095 = 10, Hz3458 = 11, Custom = 12 }; /** Possible interrupt priorities. */ enum class InterruptPriority { None = 0, Low = 1, High = 2 }; protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ChannelElement(uint8_t *ptr); /** Returns @c true if bluetooth hands-free is enabled. */ virtual bool bluetoothEnabled() const; /** Enables/disables hands-free. */ virtual void enableBluetooth(bool enable); bool roamingEnabled() const override; void enableRoaming(bool enable) override; /** Returns the interrupt priority. */ virtual InterruptPriority interruptPriority() const; /** Sets the interrupt priority. */ virtual void setInterruptPriority(InterruptPriority pri); /** Returns @c true if noise reduction is enabled. */ virtual bool noiseReductionEnabled() const; /** Enables/disables noise reduction. */ virtual void enableNoiseReduction(bool enable); bool multipleKeyEncryption() const override; void enableMultipleKeyEncryption(bool enable) override; bool randomKey() const override; void enableRandomKey(bool enable) override; bool sms() const override; void enableSMS(bool enable) override; bool dataACK() const override; void enableDataACK(bool enable) override; bool autoScan() const override; void enableAutoScan(bool enable) override; bool sendTalkerAlias() const override; void enableSendTalkerAlias(bool enable) override; AdvancedEncryptionType advancedEncryptionType() const override; void setEncryptionType(AdvancedEncryptionType type) override; /** Returns @c true if the analog scambler is enabled. */ virtual bool analogScamblerEnabled() const; /** If enabled, returns the analog scrambler frequency. */ virtual Frequency analogScramblerFrequency() const; /** Sets the analog scambler frequency and enables the scrambler. */ virtual void setAnalogScamberFrequency(Frequency f); /** Disables the scambler*/ virtual void clearAnalogScambler(); unsigned int fmAPRSFrequencyIndex() const override; void setFMAPRSFrequencyIndex(unsigned int idx) override; bool hasARC4EncryptionKeyIndex() const override; unsigned int arc4EncryptionKeyIndex() const override; void setARC4EncryptionKeyIndex(unsigned int index) override; void clearARC4EncryptionKeyIndex() override; Channel *toChannelObj(Context &ctx) const override; bool fromChannelObj(const Channel *ch, Context &ctx) override; protected: /** Internal offsets within the channel element. */ struct Offset: public D878UVCodeplug::ChannelElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr Bit bluetooth() { return {0x0034, 2}; } static constexpr Bit noiseReduction() { return {0x0034, 3}; } static constexpr Bit interruptPriority() { return {0x0034, 4}; } static constexpr Bit roaming() { return {0x0034, 6}; } static constexpr unsigned int fmScrambler() { return 0x003a; } static constexpr unsigned int customScrambler() { return 0x003b; } static constexpr Bit multipleKeyEncryption() { return {0x003d, 0}; } static constexpr Bit randomKey() { return {0x003d, 1}; } static constexpr Bit sms() { return {0x003d, 2}; } static constexpr Bit dataACK() { return {0x003d, 3}; } static constexpr Bit autoScan() { return {0x003d, 4}; } static constexpr Bit talkerAlias() { return {0x003d, 5}; } static constexpr Bit advancedEncryptionType() { return {0x003d, 6}; } static constexpr unsigned int fmAPRSFrequencyIndex() { return 0x003e; } static constexpr unsigned int arc4KeyIndex() { return 0x003f; } // Deleted static constexpr Bit muteFMAPRS() { return {0x0000, 0}; } static constexpr Bit ctcssPhaseReversal() { return {0x0000, 0}; } /// @endcond }; }; /** Represents the a channel extension element within the binary codeplug. * Matches firmware/CPS version 1.21. */ class ChannelExtensionElement: public Element { protected: /** Hidden constructor. */ ChannelExtensionElement(uint8_t *ptr, unsigned size); public: /** Constructor from data. */ ChannelExtensionElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0040; } void clear() override; /** Returns the index of the 5-tone ID send at the start of the transmission. */ virtual unsigned int fiveToneIdIndexBOT() const; /** Sets the index of the 5-tone ID send at the start of the transmission. */ virtual void setFiveToneIdIndexBOT(unsigned int idx); /** Returns the index of the 5-tone ID send at the end of the transmission. */ virtual unsigned int fiveToneIdIndexEOT() const; /** Sets the index of the 5-tone ID send at the end of the transmission. */ virtual void setFiveToneIdIndexEOT(unsigned int idx); protected: /** Internal offsets within element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int fiveToneIdIndexBOT() { return 0x0000; } static constexpr unsigned int fiveToneIdIndexEOT() { return 0x0001; } /// @endcond }; }; /** Represents the general config of the radio within the D578UV binary codeplug. * This covers the CPS version 1.21. */ class GeneralSettingsElement: public AnytoneCodeplug::GeneralSettingsElement { protected: /** Device specific time zones. */ struct TimeZone { public: /** Encodes time zone. */ static uint8_t encode(const QTimeZone& zone); /** Decodes time zone. */ static QTimeZone decode(uint8_t code); protected: /** Vector of possible time-zones. */ static QVector _timeZones; }; /** TBST (open repeater) frequencies. */ enum class TBSTFrequency { Hz1000 = 0, Hz1450 = 1, Hz1750 = 2, Hz2100 = 3 }; /** All possible STE (squelch tail eliminate) frequencies. */ enum class STEFrequency { Off = 0, Hz55_2 = 1, Hz259_2 = 2 }; /** Encoded VFO step sizes. */ enum class VFOStepSize { Hz2500 = 0, Hz5000 = 1, Hz6250 = 2, Hz8330 = 3, kHz10 = 4, Hz12500 = 5, kHz20 = 6, kHz25 = 7, kHz30 = 8, kHz50=9 }; /** Possible SMS formats. */ enum class SMSFormat { Motorola = 0, Hytera = 1, DMR = 2 }; protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ GeneralSettingsElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x00f0; } bool keyToneEnabled() const override; void enableKeyTone(bool enable) override; /** Returns the transmit timeout in seconds. */ virtual Interval transmitTimeout() const; /** Sets the transmit timeout in seconds. */ virtual void setTransmitTimeout(const Interval &tot); /** Returns the UI language. */ virtual AnytoneDisplaySettingsExtension::Language language() const; /** Sets the UI language. */ virtual void setLanguage(AnytoneDisplaySettingsExtension::Language lang); /** Returns the VFO step size. */ virtual Frequency vfoStepSize() const; /** Sets the VFO step size. */ virtual void setVFOStepSize(const Frequency &f); AnytoneSettingsExtension::VFOScanType vfoScanType() const override; void setVFOScanType(AnytoneSettingsExtension::VFOScanType type) override; Level dmrMicGain() const override; void setDMRMicGain(Level gain) override; bool vfoModeA() const override; void enableVFOModeA(bool enable) override; bool vfoModeB() const override; void enableVFOModeB(bool enable) override; /** Returns the STE (squelch tail eliminate) type. */ virtual AnytoneSettingsExtension::STEType steType() const; /** Sets the STE (squelch tail eliminate) type. */ virtual void setSTEType(AnytoneSettingsExtension::STEType type); /** Returns the STE (squelch tail eliminate) frequency setting in Hz. * A value of 0 disables the STE. Possible values are 55.2 and 259.2 Hz. */ virtual double steFrequency() const; /** Sets the STE (squelch tail eliminate) frequency setting. * A value of 0 disables the STE. Possible values are 55.2 and 259.2 Hz. */ virtual void setSTEFrequency(double freq); /** Returns the group call hang time in seconds. */ virtual Interval groupCallHangTime() const; /** Sets the group call hang time in seconds. */ virtual void setGroupCallHangTime(Interval sec); /** Returns the private call hang time in seconds. */ virtual Interval privateCallHangTime() const; /** Sets the private call hang time in seconds. */ virtual void setPrivateCallHangTime(Interval sec); /** Returns the wake head-period in ms. */ virtual Interval wakeHeadPeriod() const; /** Sets the wake head-period in ms. */ virtual void setWakeHeadPeriod(Interval ms); /** Returns the wide-FM (broadcast) channel index. */ virtual unsigned wfmChannelIndex() const; /** Sets the wide-FM (broadcast) channel index. */ virtual void setWFMChannelIndex(unsigned idx); /** Returns @c true if the WFM RX is in VFO mode. */ virtual bool wfmVFOEnabled() const; /** Enables/disables VFO mode for WFM RX. */ virtual void enableWFMVFO(bool enable); unsigned memoryZoneA() const override; void setMemoryZoneA(unsigned zone) override; unsigned memoryZoneB() const override; void setMemoryZoneB(unsigned zone) override; /** Returns @c true, if the WFM/Airband receiver is enabled. */ virtual bool wfmEnabled() const; /** Enables/disables WFM/Airband receiver. */ virtual void enableWFM(bool enable); bool recording() const override; void enableRecording(bool enable) override; unsigned brightness() const override; void setBrightness(unsigned level) override; bool gps() const override; void enableGPS(bool enable) override; bool smsAlert() const override; void enableSMSAlert(bool enable) override; /** Returns @c true if WFM monitor is enabled. */ virtual bool wfmMonitor() const; /** Enables/disables WFM monitor. */ virtual void enableWFMMonitor(bool enable); bool activeChannelB() const override; void enableActiveChannelB(bool enable) override; bool subChannel() const override; void enableSubChannel(bool enable) override; /** Returns the TBST frequency. */ virtual Frequency tbstFrequency() const; /** Sets the TBST frequency. */ virtual void setTBSTFrequency(Frequency freq); bool callAlert() const override; void enableCallAlert(bool enable) override; QTimeZone gpsTimeZone() const override; void setGPSTimeZone(const QTimeZone &zone) override; bool dmrTalkPermit() const override; void enableDMRTalkPermit(bool enable) override; bool fmTalkPermit() const override; void enableFMTalkPermit(bool enable) override; bool dmrResetTone() const override; void enableDMRResetTone(bool enable) override; bool idleChannelTone() const override; void enableIdleChannelTone(bool enable) override; Interval menuExitTime() const override; void setMenuExitTime(Interval intv) override; /** Returns @c true if the own ID is filtered in call lists. */ virtual bool filterOwnID() const; /** Enables/disables filter of own ID in call lists. */ virtual void enableFilterOwnID(bool enable); bool startupTone() const override; void enableStartupTone(bool enable) override; bool callEndPrompt() const override; void enableCallEndPrompt(bool enable) override; Level maxSpeakerVolume() const override; void setMaxSpeakerVolume(Level level) override; /** Returns @c true remote stun/kill is enabled. */ virtual bool remoteStunKill() const; /** Enables/disables remote stun/kill. */ virtual void enableRemoteStunKill(bool enable); /** Returns @c true remote monitor is enabled. */ virtual bool remoteMonitor() const; /** Enables/disables remote monitor. */ virtual void enableRemoteMonitor(bool enable); bool getGPSPosition() const override; void enableGetGPSPosition(bool enable) override; Interval longPressDuration() const override; void setLongPressDuration(Interval ms) override; bool volumeChangePrompt() const override; void enableVolumeChangePrompt(bool enable) override; AnytoneAutoRepeaterSettingsExtension::Direction autoRepeaterDirectionA() const override; void setAutoRepeaterDirectionA(AnytoneAutoRepeaterSettingsExtension::Direction dir) override; /** Returns the monitor slot match. */ virtual AnytoneDMRSettingsExtension::SlotMatch monitorSlotMatch() const; /** Sets the monitor slot match. */ virtual void setMonitorSlotMatch(AnytoneDMRSettingsExtension::SlotMatch match); /** Returns @c true if the monitor matches color code. */ virtual bool monitorColorCodeMatch() const; /** Enables/disables monitor color code match. */ virtual void enableMonitorColorCodeMatch(bool enable); /** Returns @c true if the monitor matches ID. */ virtual bool monitorIDMatch() const; /** Enables/disables monitor ID match. */ virtual void enableMonitorIDMatch(bool enable); /** Returns @c true if the monitor holds the time slot. */ virtual bool monitorTimeSlotHold() const; /** Enables/disables monitor time slot hold. */ virtual void enableMonitorTimeSlotHold(bool enable); AnytoneDisplaySettingsExtension::LastCallerDisplayMode lastCallerDisplayMode() const override; void setLastCallerDisplayMode(AnytoneDisplaySettingsExtension::LastCallerDisplayMode mode) override; /** Returns the analog call hold in seconds. */ virtual unsigned fmCallHold() const; /** Sets the analog call hold in seconds. */ virtual void setFMCallHold(unsigned sec); bool displayClock() const override; void enableDisplayClock(bool enable) override; /** Returns @c true if the GPS range reporting is enabled. */ virtual bool gpsMessageEnabled() const; /** Enables/disables GPS range reporting. */ virtual void enableGPSMessage(bool enable); bool enhanceAudio() const override; void enableEnhancedAudio(bool enable) override; Frequency minVFOScanFrequencyUHF() const override; void setMinVFOScanFrequencyUHF(Frequency hz) override; Frequency maxVFOScanFrequencyUHF() const override; void setMaxVFOScanFrequencyUHF(Frequency hz) override; Frequency minVFOScanFrequencyVHF() const override; void setMinVFOScanFrequencyVHF(Frequency hz) override; Frequency maxVFOScanFrequencyVHF() const override; void setMaxVFOScanFrequencyVHF(Frequency hz) override; bool hasAutoRepeaterOffsetFrequencyIndexUHF() const override; unsigned autoRepeaterOffsetFrequencyIndexUHF() const override; void setAutoRepeaterOffsetFrequenyIndexUHF(unsigned idx) override; void clearAutoRepeaterOffsetFrequencyIndexUHF() override; bool hasAutoRepeaterOffsetFrequencyIndexVHF() const override; unsigned autoRepeaterOffsetFrequencyIndexVHF() const override; void setAutoRepeaterOffsetFrequenyIndexVHF(unsigned idx) override; void clearAutoRepeaterOffsetFrequencyIndexVHF() override; Frequency autoRepeaterMinFrequencyVHF() const override; void setAutoRepeaterMinFrequencyVHF(Frequency Hz) override; Frequency autoRepeaterMaxFrequencyVHF() const override; void setAutoRepeaterMaxFrequencyVHF(Frequency Hz) override; Frequency autoRepeaterMinFrequencyUHF() const override; void setAutoRepeaterMinFrequencyUHF(Frequency Hz) override; Frequency autoRepeaterMaxFrequencyUHF() const override; void setAutoRepeaterMaxFrequencyUHF(Frequency Hz) override; void callToneMelody(Melody &melody) const override; void setCallToneMelody(const Melody &melody) override; void idleToneMelody(Melody &melody) const override; void setIdleToneMelody(const Melody &melody) override; void resetToneMelody(Melody &melody) const override; void setResetToneMelody(const Melody &melody) override; /** Returns the priority Zone A index. */ virtual unsigned priorityZoneAIndex() const; /** Sets the priority zone A index. */ virtual void setPriorityZoneAIndex(unsigned idx); /** Returns the priority Zone B index. */ virtual unsigned priorityZoneBIndex() const; /** Sets the priority zone B index. */ virtual void setPriorityZoneBIndex(unsigned idx); bool displayCall() const override; void enableDisplayCall(bool enable) override; /** Returns @c true if bluetooth is enabled. */ virtual bool bluetooth() const; /** Enables/disables bluetooth. */ virtual void enableBluetooth(bool enable); /** Returns @c true if the internal mic is additionally active when BT is active. */ virtual bool btAndInternalMic() const; /** Enables/disables the internal mic when BT is active. */ virtual void enableBTAndInternalMic(bool enable); /** Returns @c true if the internal speaker is additionally active when BT is active. */ virtual bool btAndInternalSpeaker() const; /** Enables/disables the internal speaker when BT is active. */ virtual void enableBTAndInternalSpeaker(bool enable); /** Returns @c true if the plug-in record tone is enabled. */ virtual bool pluginRecTone() const; /** Enables/disables the plug-in record tone. */ virtual void enablePluginRecTone(bool enable); /** Returns the GPS ranging interval in seconds. */ virtual Interval gpsUpdatePeriod() const; /** Sets the GPS ranging interval in seconds. */ virtual void setGPSUpdatePeriod(Interval sec); /** Returns the bluetooth microphone gain [1,10]. */ virtual unsigned int btMicGain() const; /** Sets the bluetooth microphone gain [1,10]. */ virtual void setBTMicGain(unsigned int gain); /** Returns the bluetooth speaker gain [1,10]. */ virtual unsigned int btSpeakerGain() const; /** Sets the bluetooth speaker gain [1,10]. */ virtual void setBTSpeakerGain(unsigned int gain); /** Returns @c true if the channel number is displayed. */ virtual bool displayChannelNumber() const; /** Enables/disables display of channel number. */ virtual void enableDisplayChannelNumber(bool enable); bool showCurrentContact() const override; void enableShowCurrentContact(bool enable) override; /** Returns the auto roaming period in minutes. */ virtual Interval autoRoamPeriod() const; /** Sets the auto roaming period in minutes. */ virtual void setAutoRoamPeriod(Interval min); AnytoneDisplaySettingsExtension::Color callDisplayColor() const override; void setCallDisplayColor(AnytoneDisplaySettingsExtension::Color color) override; bool gpsUnitsImperial() const override; void enableGPSUnitsImperial(bool enable) override; bool knobLock() const override; void enableKnobLock(bool enable) override; bool keypadLock() const override; void enableKeypadLock(bool enable) override; bool sidekeysLock() const override; void enableSidekeysLock(bool enable) override; bool keyLockForced() const override; void enableKeyLockForced(bool enable) override; /** Returns the auto-roam delay in seconds. */ virtual Interval autoRoamDelay() const; /** Sets the auto-roam delay in seconds. */ virtual void setAutoRoamDelay(Interval sec); /** Returns the standby text color. */ virtual AnytoneDisplaySettingsExtension::Color standbyTextColor() const; /** Sets the standby text color. */ virtual void setStandbyTextColor(AnytoneDisplaySettingsExtension::Color color); /** Returns the standby image color. */ virtual AnytoneDisplaySettingsExtension::Color standbyBackgroundColor() const; /** Sets the standby image color. */ virtual void setStandbyBackgroundColor(AnytoneDisplaySettingsExtension::Color color); bool showLastHeard() const override; void enableShowLastHeard(bool enable) override; /** Returns the SMS format. */ virtual SMSExtension::Format smsFormat() const; /** Sets the SMS format. */ virtual void setSMSFormat(SMSExtension::Format fmt); AnytoneAutoRepeaterSettingsExtension::Direction autoRepeaterDirectionB() const override; void setAutoRepeaterDirectionB(AnytoneAutoRepeaterSettingsExtension::Direction dir) override; /** If enabled, the FM ID is sent together with selected contact. */ virtual bool fmSendIDAndContact() const; /** Enables/disables sending contact with FM ID. */ virtual void enableFMSendIDAndContact(bool enable); bool defaultChannel() const override; void enableDefaultChannel(bool enable) override; unsigned defaultZoneIndexA() const override; void setDefaultZoneIndexA(unsigned idx) override; unsigned defaultZoneIndexB() const override; void setDefaultZoneIndexB(unsigned idx) override; bool defaultChannelAIsVFO() const override; unsigned defaultChannelAIndex() const override; void setDefaultChannelAIndex(unsigned idx) override; void setDefaultChannelAToVFO() override; bool defaultChannelBIsVFO() const override; unsigned defaultChannelBIndex() const override; void setDefaultChannelBIndex(unsigned idx) override; void setDefaultChannelBToVFO() override; /** Returns the default roaming zone index. */ virtual unsigned defaultRoamingZoneIndex() const; /** Sets the default roaming zone index. */ virtual void setDefaultRoamingZoneIndex(unsigned idx); /** Returns @c true if repeater range check is enabled. */ virtual bool repeaterRangeCheck() const; /** Enables/disables repeater range check. */ virtual void enableRepeaterRangeCheck(bool enable); /** Returns the repeater range check period in seconds. */ virtual Interval repeaterRangeCheckInterval() const; /** Sets the repeater range check interval in seconds. */ virtual void setRepeaterRangeCheckInterval(Interval sec); /** Returns the number of repeater range checks. */ virtual unsigned repeaterRangeCheckCount() const; /** Sets the number of repeater range checks. */ virtual void setRepeaterRangeCheckCount(unsigned n); /** Returns the roaming start condition. */ virtual AnytoneRoamingSettingsExtension::RoamStart roamingStartCondition() const; /** Sets the roaming start condition. */ virtual void setRoamingStartCondition(AnytoneRoamingSettingsExtension::RoamStart cond); /** Returns @c true if the "separate display" is enabled. */ virtual bool separateDisplay() const; /** Enables/disables "separate display. */ virtual void enableSeparateDisplay(bool enable); bool keepLastCaller() const override; void enableKeepLastCaller(bool enable) override; /** Returns the channel name color. */ virtual AnytoneDisplaySettingsExtension::Color channelNameColor() const; /** Sets the channel name color. */ virtual void setChannelNameColor(AnytoneDisplaySettingsExtension::Color color); /** Returns @c true if repeater check notification is enabled. */ virtual bool repeaterCheckNotification() const; /** Enables/disables repeater check notification. */ virtual void enableRepeaterCheckNotification(bool enable); /** Returns @c true if roaming is enabled. */ virtual bool roaming() const; /** Enables/disables repeater check notification. */ virtual void enableRoaming(bool enable); AnytoneKeySettingsExtension::KeyFunction funcKey1Short() const override; void setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey2Short() const override; void setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func) override; /** Returns the function for programmable function key 3 short press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey3Short() const; /** Sets the function for programmable function key 3 short press. */ virtual void setFuncKey3Short(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the function for programmable function key 4 short press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey4Short() const; /** Sets the function for programmable function key 4 short press. */ virtual void setFuncKey4Short(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the function for programmable function key 5 short press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey5Short() const; /** Sets the function for programmable function key 5 short press. */ virtual void setFuncKey5Short(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the function for programmable function key 6 short press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey6Short() const; /** Sets the function for programmable function key 6 short press. */ virtual void setFuncKey6Short(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyAShort() const override; void setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyBShort() const override; void setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyCShort() const override; void setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) override; /** Returns the function for programmable function key D short press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKeyDShort() const; /** Sets the function for programmable function key D short press. */ virtual void setFuncKeyDShort(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey1Long() const override; void setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey2Long() const override; void setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func) override; /** Returns the function for programmable function key 3 long press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey3Long() const; /** Sets the function for programmable function key 3 long press. */ virtual void setFuncKey3Long(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the function for programmable function key 4 long press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey4Long() const; /** Sets the function for programmable function key 4 long press. */ virtual void setFuncKey4Long(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the function for programmable function key 5 long press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey5Long() const; /** Sets the function for programmable function key 5 long press. */ virtual void setFuncKey5Long(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the function for programmable function key 6 long press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKey6Long() const; /** Sets the function for programmable function key 6 long press. */ virtual void setFuncKey6Long(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyALong() const override; void setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyBLong() const override; void setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyCLong() const override; void setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) override; /** Returns the function for programmable function key D long press. */ virtual AnytoneKeySettingsExtension::KeyFunction funcKeyDLong() const; /** Sets the function for programmable function key D long press. */ virtual void setFuncKeyDLong(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the number of repeater check notifications. */ virtual unsigned repeaterCheckNumNotifications() const; /** Sets the number of repeater check notifications. */ virtual void setRepeaterCheckNumNotifications(unsigned num); /** Returns the backlight duration during TX in seconds. */ virtual Interval txBacklightDuration() const; /** Sets the backlight duration during TX in seconds. */ virtual void setTXBacklightDuration(Interval sec); /** Returns @c true, if the bluetooth hold time is enabled. */ virtual bool btHoldTimeEnabled() const; /** Returns @c true, if the bluetooth hold time is infinite. */ virtual bool btHoldTimeInfinite() const; /** Returns the bluetooth hold time. */ virtual Interval btHoldTime() const; /** Sets the Bluetooth hold time (1-120s). */ virtual void setBTHoldTime(Interval interval); /** Sets the Bluetooth hold time to infinite. */ virtual void setBTHoldTimeInfinite(); /** Sets the Bluetooth hold time to infinite. */ virtual void disableBTHoldTime(); /** Returns the bluetooth RX delay in ms. */ virtual Interval btRXDelay() const; /** Sets the bluetooth RX delay in ms. */ virtual void setBTRXDelay(Interval delay); bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) override; bool updateConfig(Context &ctx, const ErrorStack &err) override; bool linkSettings(RadioSettings *settings, Context &ctx, const ErrorStack &err) override; public: /** Some limits for the settings. */ struct Limit: D878UVCodeplug::GeneralSettingsElement::Limit { // pass... }; protected: /** Some internal offsets. */ struct Offset: AnytoneCodeplug::GeneralSettingsElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int enableKeyTone() { return 0x0000; } static constexpr unsigned int transmitTimeout() { return 0x0004; } static constexpr unsigned int language() { return 0x0005; } static constexpr unsigned int vfoStepSize() { return 0x0008; } static constexpr unsigned int vfoScanType() { return 0x000b; } static constexpr unsigned int dmrMicGain() { return 0x000c; } static constexpr unsigned int vfoModeA() { return 0x000d; } static constexpr unsigned int vfoModeB() { return 0x000e; } static constexpr unsigned int steType() { return 0x000f; } static constexpr unsigned int steFrequency() { return 0x0010; } static constexpr unsigned int groupCallHangTime() { return 0x0011; } static constexpr unsigned int privateCallHangTime() { return 0x0012; } static constexpr unsigned int preWaveDelay() { return 0x0013; } static constexpr unsigned int wakeHeadPeriod() { return 0x0014; } static constexpr unsigned int wfmChannelIndex() { return 0x0015; } static constexpr unsigned int wfmVFOEnabled() { return 0x0016; } static constexpr unsigned int memZoneA() { return 0x0017; } static constexpr unsigned int memZoneB() { return 0x0018; } static constexpr unsigned int wfmEnable() { return 0x0019; } static constexpr unsigned int enableRecoding() { return 0x001a; } static constexpr unsigned int displayBrightness() { return 0x001d; } static constexpr unsigned int gpsEnable() { return 0x001f; } static constexpr unsigned int smsAlert() { return 0x0020; } static constexpr unsigned int wfmMonitor() { return 0x0022; } static constexpr unsigned int activeChannelB() { return 0x0023; } static constexpr unsigned int subChannel() { return 0x0024; } static constexpr unsigned int tbstFrequency() { return 0x0025; } static constexpr unsigned int callAlert() { return 0x0026; } static constexpr unsigned int gpsTimeZone() { return 0x0027; } static constexpr unsigned int talkPermit() { return 0x0028; } static constexpr unsigned int dmrResetTone() { return 0x0029; } static constexpr unsigned int idleChannelTone() { return 0x002a; } static constexpr unsigned int menuExitTime() { return 0x002b; } static constexpr unsigned int filterOwnID() { return 0x002c; } static constexpr unsigned int startupTone() { return 0x002d; } static constexpr unsigned int callEndPrompt() { return 0x002e; } static constexpr unsigned int maxSpeakerVolume() { return 0x002f; } static constexpr unsigned int remoteStunKill() { return 0x0030; } static constexpr unsigned int remoteMonitor() { return 0x0031; } static constexpr unsigned int getGPSPosition() { return 0x0032; } static constexpr unsigned int longPressDuration() { return 0x0033; } static constexpr unsigned int volumeChangePrompt() { return 0x0034; } static constexpr unsigned int autoRepeaterDirA() { return 0x0035; } static constexpr unsigned int monSlotMatch() { return 0x0036; } static constexpr unsigned int monColorCodeMatch() { return 0x0037; } static constexpr unsigned int monIDMatch() { return 0x0038; } static constexpr unsigned int monTimeSlotHold() { return 0x0039; } static constexpr unsigned int lastCallerDisplay() { return 0x003a; } static constexpr unsigned int fmCallHold() { return 0x003c; } static constexpr unsigned int showClock() { return 0x003d; } static constexpr unsigned int enableGPSMessage() { return 0x003e; } static constexpr unsigned int enhanceAudio() { return 0x003f; } static constexpr unsigned int minVFOScanUHF() { return 0x0040; } static constexpr unsigned int maxVFOScanUHF() { return 0x0044; } static constexpr unsigned int minVFOScanVHF() { return 0x0048; } static constexpr unsigned int maxVFOScanVHF() { return 0x004c; } static constexpr unsigned int autoRepMinVHF() { return 0x0050; } static constexpr unsigned int autoRepMaxVHF() { return 0x0054; } static constexpr unsigned int autoRepMinUHF() { return 0x0058; } static constexpr unsigned int autoRepMaxUHF() { return 0x005c; } static constexpr unsigned int callToneTones() { return 0x0060; } static constexpr unsigned int callToneDurations() { return 0x006a; } static constexpr unsigned int idleToneTones() { return 0x0074; } static constexpr unsigned int idleToneDurations() { return 0x007e; } static constexpr unsigned int resetToneTones() { return 0x0088; } static constexpr unsigned int resetToneDurations() { return 0x0092; } static constexpr unsigned int autoRepOffsetUHF() { return 0x009c; } static constexpr unsigned int autoRepOffsetVHF() { return 0x009d; } static constexpr unsigned int priorityZoneA() { return 0x009f; } static constexpr unsigned int priorityZoneB() { return 0x00a0; } static constexpr unsigned int callDisplayMode() { return 0x00a2; } static constexpr unsigned int bluetooth() { return 0x00a4; } static constexpr unsigned int btAndInternalMic() { return 0x00a5; } static constexpr unsigned int btAndInternalSpeaker(){ return 0x00a6; } static constexpr unsigned int pluginRecTone() { return 0x00a7; } static constexpr unsigned int gpsRangingInterval() { return 0x00a8; } static constexpr unsigned int btMicGain() { return 0x00a9; } static constexpr unsigned int btSpeakerGain() { return 0x00aa; } static constexpr unsigned int showChannelNumber() { return 0x00ab; } static constexpr unsigned int showCurrentContact() { return 0x00ac; } static constexpr unsigned int autoRoamPeriod() { return 0x00ad; } static constexpr unsigned int callColor() { return 0x00ae; } static constexpr unsigned int gpsUnits() { return 0x00af; } static constexpr Bit knobLock() { return {0x00b0, 0}; } static constexpr Bit keypadLock() { return {0x00b0, 1}; } static constexpr Bit sideKeyLock() { return {0x00b0, 3}; } static constexpr Bit forceKeyLock() { return {0x00b0, 4}; } static constexpr unsigned int autoRoamDelay() { return 0x00b1; } static constexpr unsigned int standbyTextColor() { return 0x00b2; } static constexpr unsigned int standbyBackground() { return 0x00b3; } static constexpr unsigned int showLastHeard() { return 0x00b4; } static constexpr unsigned int smsFormat() { return 0x00b5; } static constexpr unsigned int autoRepeaterDirB() { return 0x00b6; } static constexpr unsigned int fmSendIDAndContact() { return 0x00b7; } static constexpr unsigned int defaultChannels() { return 0x00b8; } static constexpr unsigned int defaultZoneA() { return 0x00b9; } static constexpr unsigned int defaultZoneB() { return 0x00ba; } static constexpr unsigned int defaultChannelA() { return 0x00bb; } static constexpr unsigned int defaultChannelB() { return 0x00bc; } static constexpr unsigned int defaultRoamingZone() { return 0x00bd; } static constexpr unsigned int repRangeCheck() { return 0x00be; } static constexpr unsigned int rangeCheckInterval() { return 0x00bf; } static constexpr unsigned int rangeCheckCount() { return 0x00c0; } static constexpr unsigned int roamStartCondition() { return 0x00c1; } static constexpr unsigned int displaySeparator() { return 0x00c3; } static constexpr unsigned int keepLastCaller() { return 0x00c4; } static constexpr unsigned int channelNameColor() { return 0x00c5; } static constexpr unsigned int repCheckNotify() { return 0x00c6; } static constexpr unsigned int txBacklightDuration() { return 0x00c7; } static constexpr unsigned int roaming() { return 0x00c8; } static constexpr unsigned int progFuncKey1Short() { return 0x00c9; } static constexpr unsigned int progFuncKey2Short() { return 0x00ca; } static constexpr unsigned int progFuncKey3Short() { return 0x00cb; } static constexpr unsigned int progFuncKey4Short() { return 0x00cc; } static constexpr unsigned int progFuncKey5Short() { return 0x00cd; } static constexpr unsigned int progFuncKey6Short() { return 0x00ce; } static constexpr unsigned int progFuncKeyAShort() { return 0x00cf; } static constexpr unsigned int progFuncKeyBShort() { return 0x00d0; } static constexpr unsigned int progFuncKeyCShort() { return 0x00d1; } static constexpr unsigned int progFuncKeyDShort() { return 0x00d2; } static constexpr unsigned int progFuncKey1Long() { return 0x00d3; } static constexpr unsigned int progFuncKey2Long() { return 0x00d4; } static constexpr unsigned int progFuncKey3Long() { return 0x00d5; } static constexpr unsigned int progFuncKey4Long() { return 0x00d6; } static constexpr unsigned int progFuncKey5Long() { return 0x00d7; } static constexpr unsigned int progFuncKey6Long() { return 0x00d8; } static constexpr unsigned int progFuncKeyALong() { return 0x00d9; } static constexpr unsigned int progFuncKeyBLong() { return 0x00da; } static constexpr unsigned int progFuncKeyCLong() { return 0x00db; } static constexpr unsigned int progFuncKeyDLong() { return 0x00dc; } static constexpr unsigned int repCheckNumNotify() { return 0x00de; } static constexpr unsigned int btHoldTime() { return 0x00e1; } static constexpr unsigned int btRXDelay() { return 0x00e2; } /// @endcond }; }; /** General settings extension element for the D578UV. */ class ExtendedSettingsElement: public AnytoneCodeplug::ExtendedSettingsElement { protected: /* Encoding of possible speakers. */ enum class Speaker { Microphone=0, Radio=1, Both=2 }; /** Encoding of microphone-speaker source. */ enum class SpeakerSource { MainChannel = 0, SubChannel = 1 }; /** Encoding of possible GPS modes. */ enum class GPSMode { GPS = 0, Beidou=1, GPS_Beidou=2 }; /** Encoding of possible fan-control settings. */ enum class FanControl { PTT=0, Temperature=1, Both=2 }; /** Possible mic types. */ enum class MicType { AnyTone = 0, Generic = 1 }; /** Encoding of up/down key functions. */ enum class UpDownKeyFunction { Channel = 0, Volume = 1 }; /** Encoding of repeater color code match. */ enum class RepeaterColorCodeMatch { None = 0, VFO_A = 1, VFO_B = 2 }; /** Encoding of repeater timeslot match. */ enum class RepeaterTimeSlotMatch { Any = 0, RX1_TX2 = 1, RX2_TX1 = 2 }; /** Talker alias encoding. */ enum class TalkerAliasEncoding { ISO8 = 0, ISO7 = 1, Unicode = 2, }; protected: /** Hidden Constructor. */ ExtendedSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ExtendedSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00000200; } /** Resets the settings. */ void clear(); /** Returns the talker alias source. */ virtual AnytoneDMRSettingsExtension::TalkerAliasSource talkerAliasSource() const; /** Sets the talker alias source. */ virtual void setTalkerAliasSource(AnytoneDMRSettingsExtension::TalkerAliasSource mode); /** Returns the talker alias encoding. */ virtual DMRSettings::TalkerAliasEncoding talkerAliasEncoding() const; /** Sets the talker alias encoding. */ virtual void setTalkerAliasEncoding(DMRSettings::TalkerAliasEncoding encoding); /** Returns @c true if the weather alarm is enabled. */ virtual bool weatherAlarmEnabled() const; /** Enables/disables the weather alarm. */ virtual void enableWeatherAlarm(bool enable); /** Returns @c true if the repeater function is enabled. */ virtual bool repeaterEnabled() const; /** Enables/disables the repeater function. */ virtual void enableRepeater(bool enable); /** Returns the speaker setting. */ virtual AnytoneAudioSettingsExtension::Speaker speaker() const; /** Sets the speaker setting. */ virtual void setSpeaker(AnytoneAudioSettingsExtension::Speaker speaker); /** Returns the microphone-speaker source. */ virtual AnytoneAudioSettingsExtension::HandsetSpeakerSource micSpeakerSource() const; /** Sets the microphone-speaker source. */ virtual void setMicSpeakerSource(AnytoneAudioSettingsExtension::HandsetSpeakerSource source); /** Returns the GPS mode. */ virtual GNSSSettings::Systems gnss() const; /** Sets the GPS mode. */ virtual void setGNSS(GNSSSettings::Systems mode); /** Returns @c true if the BT PTT latch is enabled. */ virtual bool bluetoothPTTLatch() const; /** Enables/disables bluetooth PTT latch. */ virtual void enableBluetoothPTTLatch(bool enable); /** Returns @c true if the bluetooth PTT sleep delay is disabled (infinite). */ virtual bool infiniteBluetoothPTTSleepDelay() const; /** Returns the bluetooth PTT sleep delay in minutes, 0=off. */ virtual Interval bluetoothPTTSleepDelay() const; /** Sets the bluetooth PTT sleep delay in minutes. */ virtual void setBluetoothPTTSleepDelay(Interval delay); /** Sets the bluetooth PTT sleep delay to infinite/disabled. */ virtual void setInfiniteBluetoothPTTSleepDelay(); /** Returns the fan-control setting. */ virtual AnytoneSettingsExtension::FanControl fanControl() const; /** Sets the fan-control setting. */ virtual void setFanControl(AnytoneSettingsExtension::FanControl ctrl); /** Returns the weather channel index. */ virtual unsigned int weatherChannelIndex() const; /** Sets the weather channel index. */ virtual void setWeatherChannelIndex(unsigned int idx); /** Returns @c true if the manual dialed group call hang time is infinite. */ virtual bool infiniteManDialGroupCallHangTime() const; /** Returns the manual dial group call hang time. */ virtual Interval manDialGroupCallHangTime() const; /** Sets the manual dial group call hang time. */ virtual void setManDialGroupCallHangTime(Interval dur); /** Sets the manual dial group call hang time to infinite. */ virtual void setManDialGroupCallHangTimeInfinite(); /** Returns @c true if the manual dialed private call hang time is infinite. */ virtual bool infiniteManDialPrivateCallHangTime() const; /** Returns the manual dial private call hang time. */ virtual Interval manDialPrivateCallHangTime() const; /** Sets the manual dial private call hang time. */ virtual void setManDialPrivateCallHangTime(Interval dur); /** Sets the manual dial private call hang time to infinite. */ virtual void setManDialPrivateCallHangTimeInfinite(); /** Returns the short-press function for the channel knob. */ virtual AnytoneKeySettingsExtension::KeyFunction chKnobShortPressFunction() const; /** Sets the channel knob short-press function. */ virtual void setChKnobShortPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the long-press function for the channel knob. */ virtual AnytoneKeySettingsExtension::KeyFunction chKnobLongPressFunction() const; /** Sets the channel knob long-press function. */ virtual void setChKnobLongPressFunction(AnytoneKeySettingsExtension::KeyFunction func); AnytoneDisplaySettingsExtension::Color channelBNameColor() const; void setChannelBNameColor(AnytoneDisplaySettingsExtension::Color color); /** Returns the encryption mode. */ virtual AnytoneDMRSettingsExtension::EncryptionType encryption() const; /** Sets the encryption mode. */ virtual void setEncryption(AnytoneDMRSettingsExtension::EncryptionType mode); /** Returns @c true if the professional mode is enabled. */ virtual bool professionalMode() const; /** Enables/disables professional mode. */ virtual void enableProfessionalMode(bool enable); /** Returns the STE (squelch tail elimination) duration. */ virtual Interval steDuration() const; /** Sets the STE (squelch tail elimination) duration. */ virtual void setSTEDuration(Interval dur); /** Returns the microphone type. */ virtual AnytoneAudioSettingsExtension::HandsetType micType() const; /** Sets the microphone type. */ virtual void setMicType(AnytoneAudioSettingsExtension::HandsetType type); AnytoneDisplaySettingsExtension::Color zoneANameColor() const; void setZoneANameColor(AnytoneDisplaySettingsExtension::Color color); AnytoneDisplaySettingsExtension::Color zoneBNameColor() const; void setZoneBNameColor(AnytoneDisplaySettingsExtension::Color color); /** Returns @c true if the auto-shutdown timer is reset on a call. */ virtual bool resetAutoShutdownOnCall() const; /** Enables/disables reset on call of the auto-shutdown timer. */ virtual void enableResetAutoShutdownOnCall(bool enable); /** Returns @c true if the color code is shown. */ virtual bool showColorCode() const; /** Enables/disables display of color code. */ virtual void enableShowColorCode(bool enable); /** Returns @c true if the time slot is shown. */ virtual bool showTimeSlot() const; /** Enables/disables display of time slot. */ virtual void enableShowTimeSlot(bool enable); /** Returns @c true if the channel type is shown. */ virtual bool showChannelType() const; /** Enables/disables display of channel type. */ virtual void enableShowChannelType(bool enable); /** Returns @c true if the FM idle channel tone is enabled. */ virtual bool fmIdleTone() const; /** Enables/disables FM idle channel tone. */ virtual void enableFMIdleTone(bool enable); /** Returns the date format. */ virtual AnytoneDisplaySettingsExtension::DateFormat dateFormat() const; /** Sets the date format. */ virtual void setDateFormat(AnytoneDisplaySettingsExtension::DateFormat format); /** Returns the FM Mic gain [1,10]. */ virtual Level fmMicGain() const; /** Sets the analog mic gain [1,10]. */ virtual void setFMMicGain(Level gain); /** Returns the short-press function for SK1 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK1ShortPressFunction() const; /** Sets the SK1 short-press function of the BT handset. */ virtual void setBtSK1ShortPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the short-press function for SK2 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK2ShortPressFunction() const; /** Sets the SK2 short-press function of the BT handset. */ virtual void setBtSK2ShortPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the short-press function for SK3 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK3ShortPressFunction() const; /** Sets the SK3 short-press function of the BT handset. */ virtual void setBtSK3ShortPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the lone-press function for SK1 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK1LongPressFunction() const; /** Sets the SK1 long-press function of the BT handset. */ virtual void setBtSK1LongPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the long-press function for SK2 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK2LongPressFunction() const; /** Sets the SK2 long-press function of the BT handset. */ virtual void setBtSK2LongPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the long-press function for SK3 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK3LongPressFunction() const; /** Sets the SK3 long-press function of the BT handset. */ virtual void setBtSK3LongPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the BT handset mic gain [1-10]. */ virtual unsigned int btHandsetMicGain() const; /** Sets the BT handset mic gain. */ virtual void setBtHandsetMicGain(unsigned int gain); /** Returns the bluetooth handset backlight duration. */ virtual Interval btHandsetBacklightDuration() const; /** Sets the bluetooth handset backlight duration. */ virtual void setBtHandsetBacklightDuration(Interval delay); /** Returns the function of up/down keys on microphone. */ virtual AnytoneKeySettingsExtension::UpDownKeyFunction micUpDownKeyFunction() const; /** Sets the microphone up/down key function. */ virtual void setMicUpDownKeyFunction(AnytoneKeySettingsExtension::UpDownKeyFunction func); /** Returns @c true if the transmit timeout notification is enabled. */ virtual bool totNotification() const; /** Enables/disables transmit timeout notification. */ virtual void enableTOTNotification(bool enable); /** Returns @c true if the GPS roaming is enabled. */ virtual bool gpsRoaming() const; /** Enables/disables GPS roaming. */ virtual void enableGPSRoaming(bool enable); /** Returns the repeater colorcode match mode. */ virtual AnytoneRepeaterSettingsExtension::ColorCode repColorCodeMatch() const; /** Sets the repeater colorcode match mode. */ virtual void setRepColorCodeMatch(AnytoneRepeaterSettingsExtension::ColorCode mode); /** Returns the repeater timeslots for VFO A. */ virtual AnytoneRepeaterSettingsExtension::TimeSlot repTimeSlotAMatch() const; /** Sets the repeater timeslots for VFO A. */ virtual void setRepTimeSlotAMatch(AnytoneRepeaterSettingsExtension::TimeSlot mode); /** Returns the repeater timeslots for VFO B. */ virtual AnytoneRepeaterSettingsExtension::TimeSlot repTimeSlotBMatch() const; /** Sets the repeater timeslots for VFO B. */ virtual void setRepTimeSlotBMatch(AnytoneRepeaterSettingsExtension::TimeSlot mode); /** Returns the BT handset squelch level [0, 1-10]. */ virtual unsigned int btHandsetSquelch() const; /** Sets the BT handset squelch level [0, 1-10]. */ virtual void setBtHandsetSquelch(unsigned int level); /** If @c true, the BT handset to shut off automatically, if the device powers down. */ virtual bool btHandsetAutoPowerOffEnabled() const; /** Enables/disables the BT handset to shut off automatically, if the device powers down. */ virtual void enableBtHandsetAutoPowerOff(bool enable); /** Returns the very-long-press function for SK1 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK1VeryLongPressFunction() const; /** Sets the SK1 very-long-press function of the BT handset. */ virtual void setBtSK1VeryLongPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the very-long-press function for SK2 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK2VeryLongPressFunction() const; /** Sets the SK2 very-long-press function of the BT handset. */ virtual void setBtSK2VeryLongPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the very-long-press function for SK3 of the BT handset. */ virtual AnytoneKeySettingsExtension::KeyFunction btSK3VeryLongPressFunction() const; /** Sets the SK3 very-long-press function of the BT handset. */ virtual void setBtSK3VeryLongPressFunction(AnytoneKeySettingsExtension::KeyFunction func); /** Returns the BT handset TX noise reduction level [0,1-10]. */ virtual unsigned int btHandsetTxNoiseRedLevel() const; /** Sets the BT handset TX noise reduction level [0,1-10]. */ virtual void setBtHandsetTxNoiseRedLevel(unsigned int level); /** Returns the BT handset VOX level [0,1-10]. */ virtual unsigned int btHandsetVOXLevel() const; /** Sets the BT handset VOX level [0,1-10]. */ virtual void setBtHandsetVOXLevel(unsigned int level); /** Returns the VOX delay for the BT handset. */ virtual Interval btHandsetVOXDelay() const; /** Sets the VOX delay for the BT handset. */ virtual void setBtHandsetVOXDelay(Interval delay); /** Returns the BT handset volume for VFO A [0,1,10]. */ virtual unsigned int btHandsetVolumeA() const; /** Sets the BF handset volume for VFO A [0,1,10]. */ virtual void setBtHandsetVolumeA(unsigned int vol); /** Returns the BT handset volume for VFO B [0,1,10]. */ virtual unsigned int btHandsetVolumeB() const; /** Sets the BF handset volume for VFO B [0,1,10]. */ virtual void setBtHandsetVolumeB(unsigned int vol); /** Returns the call-end tone melody. */ virtual void callEndToneMelody(Melody &melody) const; /** Sets the call-end tone melody. */ virtual void setCallEndToneMelody(const Melody &melody); /** Returns the all-call tone melody. */ virtual void allCallToneMelody(Melody &melody) const; /** Sets the all-call tone melody. */ virtual void setAllCallToneMelody(const Melody &melody); /** Encodes the settings from the config. */ virtual bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Update config from settings. */ virtual bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the settings. */ struct Limit: AnytoneCodeplug::ExtendedSettingsElement::Limit { static constexpr unsigned int maxBluetoothPTTSleepDelay() { return 4; } ///< Maximum delay in minutes. static constexpr unsigned int maxWeatherChannelIndex() { return 9; } ///< Maximum weather channel index. static constexpr Range micGain() { return {1,5}; } ///< Valid range for mic gain settings. }; protected: /** Internal used offset within the element. */ struct Offset : public AnytoneCodeplug::ExtendedSettingsElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int talkerAliasDisplay() { return 0x001e; } static constexpr unsigned int talkerAliasEncoding() { return 0x001f; } static constexpr unsigned int weatherAlarm() { return 0x0020; } static constexpr unsigned int repeater() { return 0x0021; } static constexpr unsigned int speakers() { return 0x0023; } static constexpr unsigned int micSpeakerSource() { return 0x0025; } static constexpr unsigned int gpsMode() { return 0x0026; } static constexpr unsigned int btPTTLatch() { return 0x0027; } static constexpr unsigned int btPTTSleepDelay() { return 0x0028; } static constexpr unsigned int fanControl() { return 0x0029; } static constexpr unsigned int weatherChannelIndex() { return 0x002a; } static constexpr unsigned int manGrpCallHangTime() { return 0x002b; } static constexpr unsigned int manPrivCallHangTime() { return 0x002c; } static constexpr unsigned int chKnobShortPressFunction() { return 0x002d; } static constexpr unsigned int chKnobLongPressFunction() { return 0x002e; } static constexpr unsigned int channelBNameColor() { return 0x002f; } static constexpr unsigned int encryptionType() { return 0x0030; } static constexpr unsigned int uiMode() { return 0x0031; } static constexpr unsigned int steDuration() { return 0x0032; } static constexpr unsigned int micType() { return 0x0033; } static constexpr unsigned int zoneANameColor() { return 0x0034; } static constexpr unsigned int zoneBNameColor() { return 0x0035; } static constexpr unsigned int autoShutdownMode() { return 0x0036; } static constexpr Bit displayColorCode() { return {0x003b, 2}; } static constexpr Bit displayTimeSlot() { return {0x003b, 1}; } static constexpr Bit displayChannelType() { return {0x003b, 0}; } static constexpr unsigned int fmIdleTone() { return 0x003c; } static constexpr unsigned int dateFormat() { return 0x003d; } static constexpr unsigned int analogMicGain() { return 0x003e; } static constexpr unsigned int btSK1ShortPressFunction() { return 0x003f; } static constexpr unsigned int btSK2ShortPressFunction() { return 0x0040; } static constexpr unsigned int btSK3ShortPressFunction() { return 0x0041; } static constexpr unsigned int btSK1LongPressFunction() { return 0x0042; } static constexpr unsigned int btSK2LongPressFunction() { return 0x0043; } static constexpr unsigned int btSK3LongPressFunction() { return 0x0044; } static constexpr unsigned int btHSMicGain() { return 0x0045; } static constexpr unsigned int btHSBacklightDuration() { return 0x0047; } static constexpr unsigned int upDownKeyFunction() { return 0x0048; } static constexpr unsigned int totNotification() { return 0x0049; } static constexpr unsigned int gpsRoaming() { return 0x004a; } static constexpr unsigned int repeaterColorCode() { return 0x004b; } static constexpr unsigned int repeaterATimeslot() { return 0x004c; } static constexpr unsigned int repeaterBTimeslot() { return 0x004d; } static constexpr unsigned int btHSRxNoiseReduction() { return 0x004e; } static constexpr unsigned int btHSShutDown() { return 0x004f; } static constexpr unsigned int btSK1VeryLongPressFunction() { return 0x0050; } static constexpr unsigned int btSK2VeryLongPressFunction() { return 0x0051; } static constexpr unsigned int btSK3VeryLongPressFunction() { return 0x0052; } static constexpr unsigned int btHSTxNoiseReduction() { return 0x0053; } static constexpr unsigned int btHSVOXLevel() { return 0x0054; } static constexpr unsigned int btHSVOXDelay() { return 0x0055; } static constexpr unsigned int btHSVolumeA() { return 0x0056; } static constexpr unsigned int btHSVolumeB() { return 0x0057; } static constexpr unsigned int callEndTones() { return 0x0058; } static constexpr unsigned int callEndDurations() { return 0x0062; } static constexpr unsigned int allCallTones() { return 0x006c; } static constexpr unsigned int allCallDurations() { return 0x0076; } static constexpr unsigned int headerRep() { return 0x0080; } /// @endcond }; }; /** Represents the hot-key settings of the radio within the D578UV binary codeplug. * * This class extends the common @c AnytoneCodeplug::HotKeySettings element, encoding 24 instead * of 17 @c HotKeySettingsElement. */ class HotKeySettingsElement: public AnytoneCodeplug::HotKeySettingsElement { protected: /** Hidden constructor. */ HotKeySettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ HotKeySettingsElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0970; } uint8_t *hotKeySetting(unsigned int n) const; public: /** Some limits for this element. */ struct Limit { static constexpr unsigned int numEntries() { return 24; } ///< Maximum number of hot-key entries. }; }; /** Implements the air-band receiver channel. * * Memory layout of the air-band channel list (size 0x0020 bytes): * @verbinclude d578uv_airbandchannel.txt */ class AirBandChannelElement: public Element { protected: /** Hidden constructor. */ AirBandChannelElement(uint8_t *ptr, size_t size); public: /** Constructor. */ AirBandChannelElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } /** Resets the element. */ void clear(); /** The channel frequency. */ virtual Frequency frequency() const; /** Sets the channel frequency. */ virtual void setFrequency(Frequency freq); /** The name of the channel. */ virtual QString name() const; /** Sets the name of the channel. */ virtual void setName(const QString &name); /** Encodes the given AM channel. */ virtual bool encode(AMChannel *ch, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes the given AM channel. */ virtual AMChannel *decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; public: /** Some limits of the channel. */ struct Limit { static constexpr unsigned int nameLength() { return 16; } ///< Maximum name length. }; public: /** Internal used offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int frequency() { return 0x0000; } static constexpr unsigned int name() { return 0x0004; } /// @endcond }; }; /** Represents the bitmap indicating which channels are valid and which are included in the * air-band scan. */ class AirBandBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ AirBandBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ AirBandBitmapElement(uint8_t *ptr); /** The element size. */ static constexpr unsigned int size() { return 0x0020; } }; protected: /** Hidden constructor. */ explicit D578UVCodeplug(const QString &label, QObject *parent = nullptr); public: /** Empty constructor. */ explicit D578UVCodeplug(QObject *parent = nullptr); Config *preprocess(Config *config, const ErrorStack &err) const override; protected: bool allocateBitmaps() override; void setBitmaps(Context &ctx) override; void allocateForDecoding() override; void allocateForEncoding() override; void allocateHotKeySettings() override; bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) override; bool createElements(Context &ctx, const ErrorStack &err=ErrorStack()) override; bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) override; bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()) override; bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()) override; /** Allocate Air band channels. */ virtual bool allocateAirBandChannels(); /** Encode all defined air band channels. */ virtual bool encodeAirBandChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decode all air band channels. */ virtual bool createAirBandChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void allocateContacts() override; bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) override; void allocateGeneralSettings() override; bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) override; bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) override; bool linkGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) override; public: /** Some limits for the codeplug. */ struct Limit: D878UVCodeplug::Limit { /// Maximum number of air-band channels. static constexpr unsigned int airBandChannels() { return 100; } }; protected: /** Internal used offsets within the codeplug. */ struct Offset: D878UVCodeplug::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int contactIdTable() { return 0x04800000; } static constexpr unsigned int settings() { return 0x02500000; } static constexpr unsigned int gpsMessages() { return 0x02501280; } static constexpr unsigned int settingsExtension() { return 0x02501400; } static constexpr unsigned int airBandChannels() { return 0x02BC0000; } static constexpr unsigned int airBandVFO() { return 0x02BC1000; } static constexpr unsigned int airBandChannelBitmap() { return 0x02BC1020; } static constexpr unsigned int airBandScanBitmap() { return 0x02BC1040; } /// @endcond }; }; #endif // D578UV_CODEPLUG_HH ================================================ FILE: lib/d578uv_limits.cc ================================================ #include "d578uv_limits.hh" #include "anytone_codeplug.hh" #include "d578uv_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "d578uv_codeplug.hh" #include "d578uv_codeplug.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingzone.hh" D578UVLimits::D578UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent) : AnytoneLimits(hardwareRevision, "V111", true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 500000; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), unsigned(Channel::Power::Max)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(D578UVCodeplug::BootSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 250, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, 0, 128, new RadioLimitObject { { "name", new RadioLimitString(1, 15, RadioLimitString::ASCII) }, { "number", new RadioLimitString(1, 14, RadioLimitString::DTMF) } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1, 64) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 4000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), unsigned(Channel::Power::Max)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRef(FMAPRSSystem::staticMetaObject)}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), unsigned(Channel::Power::Max)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, true)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false)}, {"aprs", new RadioLimitObjRef(PositionReportingSystem::staticMetaObject, true)}, {"roaming", new RadioLimitObjRef(RoamingZone::staticMetaObject, true) }, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } }, { AMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies( {{Frequency::fromMHz(108), Frequency::fromMHz(136)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"rxOnly", new RadioLimitBool()}, } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 250, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } })); /* Handle positioning systems. */ add("positioning", new RadioLimitList{ { DMRAPRSSystem::staticMetaObject, 0, 8, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, 1, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, FMChannel::staticMetaObject}, false) }, { "icon", new RadioLimitEnum{} }, { "message", new RadioLimitString(0, 60, RadioLimitString::ASCII) } ///@todo extend APRSSystem to expose other settings as properties. }} } ); /* Handle roaming zones. */ add("roaming", new RadioLimitList(RoamingZone::staticMetaObject, 0, 64, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "channels", new RadioLimitRefList(0, 64, DMRChannel::staticMetaObject) } } ) ); } ================================================ FILE: lib/d578uv_limits.hh ================================================ #ifndef D578UVLIMITS_HH #define D578UVLIMITS_HH #include "anytone_limits.hh" /** Implements the limits for the AnyTone AT-D878UV. * @ingroup d578 */ class D578UVLimits: public AnytoneLimits { Q_OBJECT public: /** Constructor. */ D578UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent=nullptr); }; #endif // D578UVLIMITS_HH ================================================ FILE: lib/d868uv.cc ================================================ #include "d868uv.hh" #include "userdatabase.hh" #include "d868uv_codeplug.hh" #include "d868uv_callsigndb.hh" #include "d868uv_limits.hh" #include "config.hh" #include "logger.hh" #define RBSIZE 16 #define WBSIZE 16 D868UV::D868UV(AnytoneInterface *device, QObject *parent) : AnytoneRadio("Anytone AT-D868UV", device, parent), _limits(nullptr) { _codeplug = new D868UVCodeplug(this); _codeplug->clear(); _callsigns = new D868UVCallsignDB(this); // Get device info and determine supported TX frequency bands AnytoneInterface::RadioVariant info; if (_dev) _dev->getInfo(info); switch (info.bands) { case 0x00: _limits = new D868UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x01: _limits = new D868UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x02: _limits = new D868UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x03: _limits = new D868UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x04: _limits = new D868UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x05: _limits = new D868UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x06: _limits = new D868UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x07: _limits = new D868UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x08: _limits = new D868UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x09: _limits = new D868UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, info.version, this); break; case 0x0a: _limits = new D868UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x0b: _limits = new D868UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x0c: _limits = new D868UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, info.version, this); break; default: logInfo() << "Unknown band-code" << QString::number(int(info.bands), 16) << ": Ignore frequency limits."; _limits = new D868UVLimits({}, {}, info.version, this); break; } } const RadioLimits & D868UV::limits() const { return *_limits; } RadioInfo D868UV::defaultRadioInfo() { return RadioInfo( RadioInfo::D868UVE, "d868uve", "AT-D868UVE", "AnyTone", {AnytoneGD32Interface::interfaceInfo()}, QList{ RadioInfo(RadioInfo::D868UV, "d868uv", "AT-D868UV", "AnyTone", {AnytoneGD32Interface::interfaceInfo()}) }); } ================================================ FILE: lib/d868uv.hh ================================================ /** @defgroup d868uv Anytone AT-D868UV * Device specific classes for Anytone AT-D868UV. * * \image html d878uv.jpg "AT-D868UV" width=200px * \image latex d878uv.jpg "AT-D868UV" width=200px * * @ingroup anytone */ #ifndef __D868UV_HH__ #define __D868UV_HH__ #include "radio.hh" #include "anytone_radio.hh" /** Implements an interface to Anytone AT-D868UV VHF/UHF 7W DMR (Tier I & II) radios. * * The reverse-engineering of the D868UVCodeplug was quiet hard as it is huge and the radio * provides a lot of bells and whistles. Moreover, the binary code-plug file created by the * windows CPS does not directly relate to the data being written to the device. These two issues * (a lot of features and a huge codeplug) require that the transfer of the codeplug to the * device is performed in 4 steps. * * First only the bitmaps of all lists are downloaded from the device. Then all elements that are * not touched or only updated by the common code-plug config are downloaded. Then, the common * config gets applied to the binary codeplug. That is, all channels, contacts, zones, group-lists * and scan-lists are generated and their bitmaps gets updated accordingly. Also the general config * gets updated from the common codeplug settings. Finally, the resulting binary codeplug gets * written back to the device. * * This rather complex method of writing a codeplug to the device is needed to maintain all * settings within the radio that are not defined within the common codeplug config while keeping * the amount of data being read from and written to the device small. * * @ingroup d868uv */ class D868UV: public AnytoneRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit D868UV(AnytoneInterface *device=nullptr, QObject *parent=nullptr); const RadioLimits &limits() const; /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); protected: /** Holds the limits for this radio.*/ RadioLimits *_limits; }; #endif // __D868UV_HH__ ================================================ FILE: lib/d868uv_callsigndb.cc ================================================ #include "userdatabase.hh" #include "d868uv_callsigndb.hh" #include "utils.hh" #include "logger.hh" #include /* ********************************************************************************************* * * Implementation of D868UVCallsignDB::EntryElement * ********************************************************************************************* */ D868UVCallsignDB::EntryElement::EntryElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } D868UVCallsignDB::EntryElement::EntryElement(uint8_t *ptr) : Element(ptr, Limit::totalLength()) { // pass... } void D868UVCallsignDB::EntryElement::clear() { // Clear header and all strings. memset(_data, 0x00, 12); } void D868UVCallsignDB::EntryElement::setCallType(DMRContact::Type type) { switch (type) { case DMRContact::PrivateCall: setUInt8(Offset::callType(), (unsigned int)CallType::Private); break; case DMRContact::GroupCall: setUInt8(Offset::callType(), (unsigned int)CallType::Group); break; case DMRContact::AllCall: setUInt8(Offset::callType(), (unsigned int)CallType::All); break; } } void D868UVCallsignDB::EntryElement::setNumber(unsigned num) { setBCD8_be(Offset::number(), num); } void D868UVCallsignDB::EntryElement::setFriendFlag(bool set) { setBit(Offset::friendFlag(), set); } void D868UVCallsignDB::EntryElement::setRingTone(RingTone tone) { setUInt2(Offset::ringTone(), (unsigned)tone); } void D868UVCallsignDB::EntryElement::setContent( const QString &name, const QString &city, const QString &call, const QString &state, const QString &country, const QString &comment) { unsigned addr = Offset::name(), flen; flen = std::min(qsizetype(Limit::nameLength()), name.size()); writeASCII(addr, name, flen+1); addr += flen+1; flen = std::min(qsizetype(Limit::cityLength()), city.size()); writeASCII(addr, city, flen+1); addr += flen+1; flen = std::min(qsizetype(Limit::callLength()), call.size()); writeASCII(addr, call, flen+1); addr += flen+1; flen = std::min(qsizetype(Limit::stateLength()), state.size()); writeASCII(addr, state, flen+1); addr += flen+1; flen = std::min(qsizetype(Limit::countryLength()), country.size()); writeASCII(addr, country, flen+1); addr += flen+1; flen = std::min(qsizetype(Limit::commentLength()), comment.size()); writeASCII(addr, comment, flen+1); } unsigned D868UVCallsignDB::EntryElement::fromUser(const UserDatabase::User &user) { clear(); setCallType(DMRContact::PrivateCall); setNumber(user.id); setRingTone(RingTone::Off); setContent(user.name, user.city, user.call, user.state, user.country, ""); return size(user); } unsigned D868UVCallsignDB::EntryElement::size(const UserDatabase::User &user) { return Limit::headerLength() // header + std::min(qsizetype(Limit::nameLength()), user.name.size())+1 // name + std::min(qsizetype(Limit::cityLength()), user.city.size())+1 // city + std::min(qsizetype(Limit::callLength()), user.call.size())+1 // call + std::min(qsizetype(Limit::stateLength()), user.state.size())+1 // state + std::min(qsizetype(Limit::countryLength()), user.country.size())+1 // country + 1; // no comment but 0x00 terminator } /* ********************************************************************************************* * * Implementation of D868UVCallsignDB::LimitsElement * ********************************************************************************************* */ D868UVCallsignDB::LimitsElement::LimitsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } D868UVCallsignDB::LimitsElement::LimitsElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void D868UVCallsignDB::LimitsElement::clear() { memset(_data, 0x00, _size); setTotalSize(0); } unsigned D868UVCallsignDB::LimitsElement::count() const { return getUInt32_le(Offset::count()); } void D868UVCallsignDB::LimitsElement::setCount(unsigned count) { setUInt32_le(Offset::count(), count); } unsigned D868UVCallsignDB::LimitsElement::endOfDB() const { return getUInt32_le(Offset::endOfDB()); } void D868UVCallsignDB::LimitsElement::setEndOfDB(unsigned addr) { setUInt32_le(Offset::endOfDB(), addr); } void D868UVCallsignDB::LimitsElement::setTotalSize(unsigned size) { setEndOfDB(D868UVCallsignDB::Offset::callsigns() + size); } /* ********************************************************************************************* * * Implementation of D868UVCallsignDB * ********************************************************************************************* */ D868UVCallsignDB::D868UVCallsignDB(QObject *parent) : CallsignDB(parent) { // allocate and clear DB memory addImage("AnyTone AT-D878UV Callsign database."); } bool D868UVCallsignDB::encode(UserDatabase *db, const Flags &selection, const ErrorStack &err) { Q_UNUSED(err) // Determine size of call-sign DB in memory qint64 n = std::min(db->count(), qint64(Limit::entries())); // If DB size is limited by settings if (selection.hasCountLimit()) n = std::min(n, (qint64)selection.countLimit()); logDebug() << "Encode " << n << " entries."; // Select n users and sort them in ascending order of their IDs QVector users; users.reserve(n); for (unsigned i=0; iuser(i)); std::sort(users.begin(), users.end(), [](const UserDatabase::User &a, const UserDatabase::User &b) { return a.id < b.id; }); logDebug() << "Encode call-signs from " << users.first().id << ": " << users.first().call << ", " << users.first().name << " in " << users.first().city << " to " << users.last().id << ": " << users.last().call << ", " << users.last().name << " in " << users.last().city << "."; // Compute total size of callsign db entries size_t dbSize = 0; size_t indexSize = n*IndexEntryElement::size(); for (qint64 i=0; i * Callsign database * Start Size Content * 04000000 max. 186a00 Index of callsign entries. Follows the same * weird format as @c D868UVCodeplug::contact_map_t. Sorted by ID. Empty entries set to * 0xffffffffffffffff. * 044c0000 unknown Database limits, see @c limits_t. * 04500000 unknown The actual DB entries, each entry is of * variable size but shares the same header, see @c entry_t. Order arbitrary. * Filled with 0x00. * * * @ingroup d868uv */ class D868UVCallsignDB : public CallsignDB { Q_OBJECT public: /** Represents the header of an entry in the callsign database. * Each entry is of variable size. That is, every entry has a common header containing the * number, call-type etc. All strings, that is contact name, city, callsign, state, country and * comment are 0x00 terminated strings in a lists. * * Max length for name is 16, city is 15, callsign is 8, state is 16, country is 16 and * comment is 16, excluding terminating 0x00. */ class EntryElement: public Codeplug::Element { public: /** Notification tones for callsign entry. */ enum class RingTone { Off = 0, Tone = 1, Online = 2 }; /** Possible call types. */ enum class CallType { Private = 0, Group = 1, All = 2 }; protected: /** Hidden constructor. */ EntryElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit EntryElement(uint8_t *ptr); void clear(); /** Sets the call type. */ virtual void setCallType(DMRContact::Type type); /** Sets the DMR ID number. */ virtual void setNumber(unsigned num); /** Set/clear friend flag. */ virtual void setFriendFlag(bool set); /** Sets the ring tone. */ virtual void setRingTone(RingTone tone); /** Sets the entry content. */ virtual void setContent(const QString &name, const QString &city, const QString &call, const QString &state, const QString &country, const QString &comment); /** Constructs a database entry from the given user. * @returns The size of the entry. */ virtual unsigned fromUser(const UserDatabase::User &user); /** Computes the size of the database entry for the given user. */ static unsigned size(const UserDatabase::User &user); public: /** Some limits for the entry. */ struct Limit: Element::Limit { static constexpr unsigned int headerLength() { return 6; } ///< Header length (fixed). static constexpr unsigned int nameLength() { return 16; } ///< Maximum name length. static constexpr unsigned int cityLength() { return 15; } ///< Maximum city length. static constexpr unsigned int callLength() { return 8; } ///< Maximum call length. static constexpr unsigned int stateLength() { return 16; } ///< Maximum state length. static constexpr unsigned int countryLength() { return 16; } ///< Maximum country length. static constexpr unsigned int commentLength() { return 0; } ///< Maximum comment length. /** Maximum entry size. */ static constexpr unsigned int totalLength() { return headerLength() + nameLength()+1 + cityLength()+1 + callLength()+1 + stateLength()+1 + countryLength()+1 + commentLength()+1; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int callType() { return 0x0000; } static constexpr unsigned int number() { return 0x0001; } static constexpr Bit friendFlag() { return {0x0005, 4}; } static constexpr Bit ringTone() { return { 0x0005, 0}; } static constexpr unsigned int name() { return Limit::headerLength(); } /// @endcond }; }; /** Represents a bank of call-sign DB entries. */ class EntryBankElement: public Codeplug::Element { protected: /** Hidden constructor. */ EntryBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit EntryBankElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x000186a0; } void clear(); /** Returns the i-th element of the bank. */ uint8_t *entry(unsigned int i) const; }; /** Same index entry used by the codeplug to map normal digital contacts to an contact index. Here * it maps to the byte offset within the database entries. */ typedef D868UVCodeplug::ContactMapElement IndexEntryElement; /** Represents a bank of index entries. */ class IndexBankElement: public Codeplug::Element { protected: /** Hidden constructor. */ IndexBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit IndexBankElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0001f400; } void clear(); /** Returns the i-th element of the bank. */ uint8_t *entry(unsigned int i) const; }; /** Stores some basic limits of the callsign db. */ class LimitsElement: public Codeplug::Element { protected: /** Hidden constructor. */ LimitsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ LimitsElement(uint8_t *ptr); /** Size of the element. */ static constexpr unsigned int size() { return 0x0010; } /** Resets the limits. */ void clear(); /** Returns the number of entries in the DB. */ virtual unsigned count() const; /** Sets the number of entries. */ virtual void setCount(unsigned count); /** Returns the end-of-db address. */ virtual unsigned endOfDB() const; /** Sets the end-of-db address. */ virtual void setEndOfDB(unsigned addr); /** Sets the total size of the DB (updated end-of-db address). */ virtual void setTotalSize(unsigned size); protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } static constexpr unsigned int endOfDB() { return 0x0004; } /// @endcond }; }; public: /** Constructor, does not allocate any memory yet. */ explicit D868UVCallsignDB(QObject *parent=nullptr); /** Tries to encode as many entries of the given user-database. */ bool encode(UserDatabase *db, const Flags &selection=Flags(), const ErrorStack &err=ErrorStack()); public: /** Some limits for the call-sign DB. */ struct Limit { /// Specifies the max number of entries in the DB. static constexpr unsigned int entries() { return 200000; } }; protected: /** Some internal used offsets within the DB. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int index() { return 0x04000000; } static constexpr unsigned int betweenIndexBanks() { return 0x00040000; } static constexpr unsigned int callsigns() { return 0x04500000; } static constexpr unsigned int betweenCallsignBanks() { return 0x00040000; } static constexpr unsigned int limits() { return 0x044C0000; } /// @endcond }; }; #endif // D868UVCALLSIGNDB_HH ================================================ FILE: lib/d868uv_codeplug.cc ================================================ #include "d868uv_codeplug.hh" #include "config.hh" #include "utils.hh" #include "channel.hh" #include "gpssystem.hh" #include "smsextension.hh" #include "config.h" #include "logger.hh" #include "utils.hh" #include "intermediaterepresentation.hh" #include #include #include #include /* ******************************************************************************************** * * Implementation of D868UVCodeplug::Color * ******************************************************************************************** */ AnytoneDisplaySettingsExtension::Color D868UVCodeplug::Color::decode(uint8_t code) { switch((CodedColor) code) { case White: return AnytoneDisplaySettingsExtension::Color::White; case Red: return AnytoneDisplaySettingsExtension::Color::Red; default: break; } return AnytoneDisplaySettingsExtension::Color::White; } uint8_t D868UVCodeplug::Color::encode(AnytoneDisplaySettingsExtension::Color color) { switch(color) { case AnytoneDisplaySettingsExtension::Color::White: return (uint8_t) White; case AnytoneDisplaySettingsExtension::Color::Black: return (uint8_t) Red; case AnytoneDisplaySettingsExtension::Color::Orange: return (uint8_t) Red; case AnytoneDisplaySettingsExtension::Color::Red: return (uint8_t) Red; case AnytoneDisplaySettingsExtension::Color::Yellow: return (uint8_t) White; case AnytoneDisplaySettingsExtension::Color::Green: return (uint8_t) White; case AnytoneDisplaySettingsExtension::Color::Turquoise: return (uint8_t) White; case AnytoneDisplaySettingsExtension::Color::Blue: return (uint8_t) Red; default: break; } return (uint8_t) White; } /* ******************************************************************************************** * * Implementation of D868UVCodeplug::ChannelElement * ******************************************************************************************** */ D868UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, unsigned size) : AnytoneCodeplug::ChannelElement(ptr, size) { // pass... } D868UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : AnytoneCodeplug::ChannelElement(ptr) { // pass... } bool D868UVCodeplug::ChannelElement::ranging() const { return getBit(Offset::ranging()); } void D868UVCodeplug::ChannelElement::enableRanging(bool enable) { setBit(Offset::ranging(), enable); } bool D868UVCodeplug::ChannelElement::throughMode() const { return getBit(Offset::throughMode()); } void D868UVCodeplug::ChannelElement::enableThroughMode(bool enable) { setBit(Offset::throughMode(), enable); } bool D868UVCodeplug::ChannelElement::dataACK() const { return !getBit(Offset::dataACK()); } void D868UVCodeplug::ChannelElement::enableDataACK(bool enable) { setBit(Offset::dataACK(), !enable); } D868UVCodeplug::ChannelElement::APRSType D868UVCodeplug::ChannelElement::txAPRSType() const { return (APRSType)getUInt8(Offset::txAPRSType()); } void D868UVCodeplug::ChannelElement::setTXAPRSType(APRSType aprsType) { setUInt8(Offset::txAPRSType(), (uint8_t)aprsType); } unsigned D868UVCodeplug::ChannelElement::digitalAPRSSystemIndex() const { return getUInt8(Offset::digitalAPRSSystemIndex()); } void D868UVCodeplug::ChannelElement::setDigitalAPRSSystemIndex(unsigned idx) { setUInt8(Offset::digitalAPRSSystemIndex(), idx); } D868UVCodeplug::ChannelElement::DMREncryptionType D868UVCodeplug::ChannelElement::dmrEncryptionType() const { return getBit(Offset::dmrEncryptionType()) ? DMREncryptionType::Enhanced : DMREncryptionType::Basic; } void D868UVCodeplug::ChannelElement::setDMREncryptionType(DMREncryptionType type) { setBit(Offset::dmrEncryptionType(), DMREncryptionType::Enhanced == type); } bool D868UVCodeplug::ChannelElement::hasDMREncryptionKeyIndex() const { return 0 != getUInt8(Offset::dmrEncryptionKey()); } unsigned D868UVCodeplug::ChannelElement::dmrEncryptionKeyIndex() const { return getUInt8(Offset::dmrEncryptionKey()) - 1; } void D868UVCodeplug::ChannelElement::setDMREncryptionKeyIndex(unsigned idx) { setUInt8(Offset::dmrEncryptionKey(), idx+1); } void D868UVCodeplug::ChannelElement::clearDMREncryptionKeyIndex() { setUInt8(Offset::dmrEncryptionKey(), 0); } bool D868UVCodeplug::ChannelElement::multipleKeyEncryption() const { return getBit(Offset::multipleKeyEncryption()); } void D868UVCodeplug::ChannelElement::enableMultipleKeyEncryption(bool enable) { setBit(Offset::multipleKeyEncryption(), enable); } bool D868UVCodeplug::ChannelElement::randomKey() const { return getBit(Offset::randomKey()); } void D868UVCodeplug::ChannelElement::enableRandomKey(bool enable) { setBit(Offset::randomKey(), enable); } bool D868UVCodeplug::ChannelElement::sms() const { return !getBit(Offset::sms()); } void D868UVCodeplug::ChannelElement::enableSMS(bool enable) { setBit(Offset::sms(), !enable); } Channel * D868UVCodeplug::ChannelElement::toChannelObj(Context &ctx) const { Channel *ch = AnytoneCodeplug::ChannelElement::toChannelObj(ctx); if (nullptr == ch) return nullptr; if (ch->is()) { DMRChannel *dch = ch->as(); dch->extended()->enableSMS(sms()); dch->extended()->enableDataConfirm(dataACK()); if (AnytoneDMRChannelExtension *ext = dch->anytoneChannelExtension()) { ext->enableThroughMode(throughMode()); } } return ch; } bool D868UVCodeplug::ChannelElement::linkChannelObj(Channel *c, Context &ctx) const { if (! AnytoneCodeplug::ChannelElement::linkChannelObj(c, ctx)) return false; if (c->is()) { DMRChannel *dc = c->as(); // Link to GPS system if ((APRSType::Off != txAPRSType()) && (! ctx.has(digitalAPRSSystemIndex()))) logWarn() << "Cannot link to DMR APRS system index " << digitalAPRSSystemIndex() << ": undefined DMR APRS system."; else if (ctx.has(digitalAPRSSystemIndex())) dc->setAPRS(ctx.get(digitalAPRSSystemIndex())); // Link to encryption key (only basic implemented) if (hasDMREncryptionKeyIndex() && (DMREncryptionType::Basic == dmrEncryptionType())) { if (ctx.has(dmrEncryptionKeyIndex())) { auto cex = dc->commercialExtension(); if (nullptr == cex) dc->setCommercialExtension(cex = new CommercialChannelExtension()); cex->setEncryptionKey(ctx.get(dmrEncryptionKeyIndex())); } else { logWarn() << "Cannot link DMR encryption: no key with index " << dmrEncryptionKeyIndex() << " found."; } } } return true; } bool D868UVCodeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx) { if (! AnytoneCodeplug::ChannelElement::fromChannelObj(c, ctx)) return false; if (c->is()) { const DMRChannel *dc = c->as(); // Set GPS system index if (dc->aprs() && dc->aprs()->is()) { setDigitalAPRSSystemIndex(ctx.index(dc->aprs()->as())); setTXAPRSType(APRSType::DMR); enableRXAPRS(false); } else { setTXAPRSType(APRSType::Off); enableRXAPRS(false); } enableSMS(dc->extended()->sms()); enableDataACK(dc->extended()->dataConfirm()); clearDMREncryptionKeyIndex(); bool hasStrongEncryption = ctx.config()->settings()->anytoneExtension() && (AnytoneDMRSettingsExtension::EncryptionType::AES == ctx.config()->settings()->anytoneExtension()->dmrSettings()->encryption()); // Handle commercial extension if (auto cex = dc->commercialExtension()) { if (cex->encryptionKey() && cex->encryptionKey()->is() && (! hasStrongEncryption)) { auto key = cex->encryptionKey()->as(); setDMREncryptionType(DMREncryptionType::Basic); setDMREncryptionKeyIndex(ctx.index(key)); } } // Handle extension if (AnytoneDMRChannelExtension *ext = dc->anytoneChannelExtension()) { enableThroughMode(ext->throughMode()); } } return true; } /* ******************************************************************************************** * * Implementation of D868UVCodeplug::GeneralSettingsElement::KeyFunction * ******************************************************************************************** */ uint8_t D868UVCodeplug::GeneralSettingsElement::KeyFunction::encode(AnytoneKeySettingsExtension::KeyFunction func) { switch (func) { case AnytoneKeySettingsExtension::KeyFunction::Off: return (uint8_t)KeyFunction::Off; case AnytoneKeySettingsExtension::KeyFunction::Voltage: return (uint8_t)KeyFunction::Voltage; case AnytoneKeySettingsExtension::KeyFunction::Power: return (uint8_t)KeyFunction::Power; case AnytoneKeySettingsExtension::KeyFunction::Repeater: return (uint8_t)KeyFunction::Repeater; case AnytoneKeySettingsExtension::KeyFunction::Reverse: return (uint8_t)KeyFunction::Reverse; case AnytoneKeySettingsExtension::KeyFunction::Encryption: return (uint8_t)KeyFunction::Encryption; case AnytoneKeySettingsExtension::KeyFunction::Call: return (uint8_t)KeyFunction::Call; case AnytoneKeySettingsExtension::KeyFunction::VOX: return (uint8_t)KeyFunction::VOX; case AnytoneKeySettingsExtension::KeyFunction::ToggleVFO: return (uint8_t)KeyFunction::ToggleVFO; case AnytoneKeySettingsExtension::KeyFunction::SubPTT: return (uint8_t)KeyFunction::SubPTT; case AnytoneKeySettingsExtension::KeyFunction::Scan: return (uint8_t)KeyFunction::Scan; case AnytoneKeySettingsExtension::KeyFunction::WFM: return (uint8_t)KeyFunction::WFM; case AnytoneKeySettingsExtension::KeyFunction::Alarm: return (uint8_t)KeyFunction::Alarm; case AnytoneKeySettingsExtension::KeyFunction::RecordSwitch: return (uint8_t)KeyFunction::RecordSwitch; case AnytoneKeySettingsExtension::KeyFunction::Record: return (uint8_t)KeyFunction::Record; case AnytoneKeySettingsExtension::KeyFunction::SMS: return (uint8_t)KeyFunction::SMS; case AnytoneKeySettingsExtension::KeyFunction::Dial: return (uint8_t)KeyFunction::Dial; case AnytoneKeySettingsExtension::KeyFunction::GPSInformation: return (uint8_t)KeyFunction::GPSInformation; case AnytoneKeySettingsExtension::KeyFunction::Monitor: return (uint8_t)KeyFunction::Monitor; case AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel: return (uint8_t)KeyFunction::ToggleMainChannel; case AnytoneKeySettingsExtension::KeyFunction::HotKey1: return (uint8_t)KeyFunction::HotKey1; case AnytoneKeySettingsExtension::KeyFunction::HotKey2: return (uint8_t)KeyFunction::HotKey2; case AnytoneKeySettingsExtension::KeyFunction::HotKey3: return (uint8_t)KeyFunction::HotKey3; case AnytoneKeySettingsExtension::KeyFunction::HotKey4: return (uint8_t)KeyFunction::HotKey4; case AnytoneKeySettingsExtension::KeyFunction::HotKey5: return (uint8_t)KeyFunction::HotKey5; case AnytoneKeySettingsExtension::KeyFunction::HotKey6: return (uint8_t)KeyFunction::HotKey6; case AnytoneKeySettingsExtension::KeyFunction::WorkAlone: return (uint8_t)KeyFunction::WorkAlone; case AnytoneKeySettingsExtension::KeyFunction::SkipChannel: return (uint8_t)KeyFunction::SkipChannel; case AnytoneKeySettingsExtension::KeyFunction::DMRMonitor: return (uint8_t)KeyFunction::DMRMonitor; case AnytoneKeySettingsExtension::KeyFunction::SubChannel: return (uint8_t)KeyFunction::SubChannel; case AnytoneKeySettingsExtension::KeyFunction::PriorityZone: return (uint8_t)KeyFunction::PriorityZone; case AnytoneKeySettingsExtension::KeyFunction::VFOScan: return (uint8_t)KeyFunction::VFOScan; case AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality: return (uint8_t)KeyFunction::MICSoundQuality; case AnytoneKeySettingsExtension::KeyFunction::LastCallReply: return (uint8_t)KeyFunction::LastCallReply; case AnytoneKeySettingsExtension::KeyFunction::ChannelType: return (uint8_t)KeyFunction::ChannelType; case AnytoneKeySettingsExtension::KeyFunction::Ranging: return (uint8_t)KeyFunction::Ranging; case AnytoneKeySettingsExtension::KeyFunction::ChannelRanging: return (uint8_t)KeyFunction::ChannelRanging; case AnytoneKeySettingsExtension::KeyFunction::MaxVolume: return (uint8_t)KeyFunction::MaxVolume; case AnytoneKeySettingsExtension::KeyFunction::Slot: return (uint8_t)KeyFunction::Slot; default: return (uint8_t)KeyFunction::Off; } } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::KeyFunction::decode(uint8_t code) { switch ((KeyFunction::KeyFunctionCode)code) { case KeyFunction::Off: return AnytoneKeySettingsExtension::KeyFunction::Off; case KeyFunction::Voltage: return AnytoneKeySettingsExtension::KeyFunction::Voltage; case KeyFunction::Power: return AnytoneKeySettingsExtension::KeyFunction::Power; case KeyFunction::Repeater: return AnytoneKeySettingsExtension::KeyFunction::Repeater; case KeyFunction::Reverse: return AnytoneKeySettingsExtension::KeyFunction::Reverse; case KeyFunction::Encryption: return AnytoneKeySettingsExtension::KeyFunction::Encryption; case KeyFunction::Call: return AnytoneKeySettingsExtension::KeyFunction::Call; case KeyFunction::VOX: return AnytoneKeySettingsExtension::KeyFunction::VOX; case KeyFunction::ToggleVFO: return AnytoneKeySettingsExtension::KeyFunction::ToggleVFO; case KeyFunction::SubPTT: return AnytoneKeySettingsExtension::KeyFunction::SubPTT; case KeyFunction::Scan: return AnytoneKeySettingsExtension::KeyFunction::Scan; case KeyFunction::WFM: return AnytoneKeySettingsExtension::KeyFunction::WFM; case KeyFunction::Alarm: return AnytoneKeySettingsExtension::KeyFunction::Alarm; case KeyFunction::RecordSwitch: return AnytoneKeySettingsExtension::KeyFunction::RecordSwitch; case KeyFunction::Record: return AnytoneKeySettingsExtension::KeyFunction::Record; case KeyFunction::SMS: return AnytoneKeySettingsExtension::KeyFunction::SMS; case KeyFunction::Dial: return AnytoneKeySettingsExtension::KeyFunction::Dial; case KeyFunction::GPSInformation: return AnytoneKeySettingsExtension::KeyFunction::GPSInformation; case KeyFunction::Monitor: return AnytoneKeySettingsExtension::KeyFunction::Monitor; case KeyFunction::ToggleMainChannel: return AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel; case KeyFunction::HotKey1: return AnytoneKeySettingsExtension::KeyFunction::HotKey1; case KeyFunction::HotKey2: return AnytoneKeySettingsExtension::KeyFunction::HotKey2; case KeyFunction::HotKey3: return AnytoneKeySettingsExtension::KeyFunction::HotKey3; case KeyFunction::HotKey4: return AnytoneKeySettingsExtension::KeyFunction::HotKey4; case KeyFunction::HotKey5: return AnytoneKeySettingsExtension::KeyFunction::HotKey5; case KeyFunction::HotKey6: return AnytoneKeySettingsExtension::KeyFunction::HotKey6; case KeyFunction::WorkAlone: return AnytoneKeySettingsExtension::KeyFunction::WorkAlone; case KeyFunction::SkipChannel: return AnytoneKeySettingsExtension::KeyFunction::SkipChannel; case KeyFunction::DMRMonitor: return AnytoneKeySettingsExtension::KeyFunction::DMRMonitor; case KeyFunction::SubChannel: return AnytoneKeySettingsExtension::KeyFunction::SubChannel; case KeyFunction::PriorityZone: return AnytoneKeySettingsExtension::KeyFunction::PriorityZone; case KeyFunction::VFOScan: return AnytoneKeySettingsExtension::KeyFunction::VFOScan; case KeyFunction::MICSoundQuality: return AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality; case KeyFunction::LastCallReply: return AnytoneKeySettingsExtension::KeyFunction::LastCallReply; case KeyFunction::ChannelType: return AnytoneKeySettingsExtension::KeyFunction::ChannelType; case KeyFunction::Ranging: return AnytoneKeySettingsExtension::KeyFunction::Ranging; case KeyFunction::ChannelRanging: return AnytoneKeySettingsExtension::KeyFunction::ChannelRanging; case KeyFunction::MaxVolume: return AnytoneKeySettingsExtension::KeyFunction::MaxVolume; case KeyFunction::Slot: return AnytoneKeySettingsExtension::KeyFunction::Slot; default: return AnytoneKeySettingsExtension::KeyFunction::Off; } } /* ******************************************************************************************** * * Implementation of D868UVCodeplug::GeneralSettingsElement * ******************************************************************************************** */ D868UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, unsigned size) : AnytoneCodeplug::GeneralSettingsElement(ptr, size) { // pass.... } D868UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : D868UVCodeplug::GeneralSettingsElement(ptr, GeneralSettingsElement::size()) { // pass... } void D868UVCodeplug::GeneralSettingsElement::clear() { AnytoneCodeplug::GeneralSettingsElement::clear(); } bool D868UVCodeplug::GeneralSettingsElement::keyToneEnabled() const { return 0x00 != getUInt8(Offset::enableKeyTone()); } void D868UVCodeplug::GeneralSettingsElement::enableKeyTone(bool enable) { setUInt8(Offset::enableKeyTone(), enable ? 0x01 : 0x00); } AnytonePowerSaveSettingsExtension::PowerSave D868UVCodeplug::GeneralSettingsElement::powerSave() const { return (AnytonePowerSaveSettingsExtension::PowerSave)getUInt8(Offset::powerSaveMode()); } void D868UVCodeplug::GeneralSettingsElement::setPowerSave(AnytonePowerSaveSettingsExtension::PowerSave mode) { setUInt8(Offset::powerSaveMode(), (unsigned int)mode); } Level D868UVCodeplug::GeneralSettingsElement::voxLevel() const { return Level::fromValue(getUInt8(Offset::voxLevel()), Limit::vox()); } void D868UVCodeplug::GeneralSettingsElement::setVOXLevel(Level level) { setUInt8(Offset::voxLevel(), level.mapTo(Limit::vox())); } Interval D868UVCodeplug::GeneralSettingsElement::voxDelay() const { return Interval::fromMilliseconds(100 + 500*((unsigned)getUInt8(Offset::voxDelay()))); } void D868UVCodeplug::GeneralSettingsElement::setVOXDelay(Interval intv) { setUInt8(Offset::voxDelay(), (std::max(100ULL, intv.milliseconds())-100)/500); } AnytoneSettingsExtension::VFOScanType D868UVCodeplug::GeneralSettingsElement::vfoScanType() const { return (AnytoneSettingsExtension::VFOScanType) getUInt8(Offset::vfoScanType()); } void D868UVCodeplug::GeneralSettingsElement::setVFOScanType(AnytoneSettingsExtension::VFOScanType type) { setUInt8(Offset::vfoScanType(), (unsigned)type); } Level D868UVCodeplug::GeneralSettingsElement::dmrMicGain() const { return Level::fromValue(getUInt8(Offset::dmrMicGain()), Limit::micGain()); } void D868UVCodeplug::GeneralSettingsElement::setDMRMicGain(Level gain) { setUInt8(Offset::dmrMicGain(), gain.mapTo(Limit::micGain())); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKeyAShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyAShort())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyAShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKeyBShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyBShort())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyBShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKeyCShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyCShort())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyCShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKey1Short() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1Short())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey1Short(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKey2Short() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2Short())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey2Short(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKeyALong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyALong())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyALong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKeyBLong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyBLong())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyBLong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKeyCLong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyCLong())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyCLong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKey1Long() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1Long())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey1Long(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D868UVCodeplug::GeneralSettingsElement::funcKey2Long() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2Long())); } void D868UVCodeplug::GeneralSettingsElement::setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey2Long(), KeyFunction::encode(func)); } Interval D868UVCodeplug::GeneralSettingsElement::longPressDuration() const { return Interval::fromSeconds(((unsigned)getUInt8(Offset::longPressDuration()))+1); } void D868UVCodeplug::GeneralSettingsElement::setLongPressDuration(Interval ms) { setUInt8(Offset::longPressDuration(), std::max(1ULL,ms.seconds())-1); } bool D868UVCodeplug::GeneralSettingsElement::vfoModeA() const { return getUInt8(Offset::vfoModeA()); } void D868UVCodeplug::GeneralSettingsElement::enableVFOModeA(bool enable) { setUInt8(Offset::vfoModeA(), (enable ? 0x01 : 0x00)); } bool D868UVCodeplug::GeneralSettingsElement::vfoModeB() const { return getUInt8(Offset::vfoModeB()); } void D868UVCodeplug::GeneralSettingsElement::enableVFOModeB(bool enable) { setUInt8(Offset::vfoModeB(), (enable ? 0x01 : 0x00)); } unsigned D868UVCodeplug::GeneralSettingsElement::memoryZoneA() const { return getUInt8(Offset::memZoneA()); } void D868UVCodeplug::GeneralSettingsElement::setMemoryZoneA(unsigned zone) { setUInt8(Offset::memZoneA(), zone); } unsigned D868UVCodeplug::GeneralSettingsElement::memoryZoneB() const { return getUInt8(Offset::memZoneB()); } void D868UVCodeplug::GeneralSettingsElement::setMemoryZoneB(unsigned zone) { setUInt8(Offset::memZoneB(), zone); } bool D868UVCodeplug::GeneralSettingsElement::recording() const { return getUInt8(Offset::enableRecoding()); } void D868UVCodeplug::GeneralSettingsElement::enableRecording(bool enable) { setUInt8(Offset::enableRecoding(), (enable ? 0x01 : 0x00)); } unsigned D868UVCodeplug::GeneralSettingsElement::brightness() const { return (getUInt8(Offset::displayBrightness())*10)/4; } void D868UVCodeplug::GeneralSettingsElement::setBrightness(unsigned level) { setUInt8(Offset::displayBrightness(), (level*4)/10); } bool D868UVCodeplug::GeneralSettingsElement::backlightPermanent() const { return backlightDuration().isInfinite(); } Interval D868UVCodeplug::GeneralSettingsElement::backlightDuration() const { switch ((BacklightDuration)getUInt8(Offset::backlightDuration())) { case BacklightDuration::Infinite: return Interval::infinity(); case BacklightDuration::_5s: return Interval::fromSeconds(5); case BacklightDuration::_10s: return Interval::fromSeconds(10); case BacklightDuration::_15s: return Interval::fromSeconds(15); case BacklightDuration::_20s: return Interval::fromSeconds(20); case BacklightDuration::_25s: return Interval::fromSeconds(25); case BacklightDuration::_30s: return Interval::fromSeconds(30); case BacklightDuration::_1min: return Interval::fromMinutes(1); case BacklightDuration::_2min: return Interval::fromMinutes(2); case BacklightDuration::_3min: return Interval::fromMinutes(3); case BacklightDuration::_4min: return Interval::fromMinutes(4); case BacklightDuration::_5min: return Interval::fromMinutes(5); } return Interval::infinity(); } void D868UVCodeplug::GeneralSettingsElement::setBacklightDuration(Interval intv) { if (intv <= Interval::fromSeconds(5)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_5s); else if (intv <= Interval::fromSeconds(10)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_10s); else if (intv <= Interval::fromSeconds(15)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_15s); else if (intv <= Interval::fromSeconds(20)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_20s); else if (intv <= Interval::fromSeconds(25)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_25s); else if (intv <= Interval::fromSeconds(30)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_30s); else if (intv <= Interval::fromMinutes(1)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_1min); else if (intv <= Interval::fromMinutes(2)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_2min); else if (intv <= Interval::fromMinutes(3)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_3min); else if (intv <= Interval::fromMinutes(4)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_4min); else if (intv <= Interval::fromMinutes(5)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_5min); else setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::Infinite); } void D868UVCodeplug::GeneralSettingsElement::enableBacklightPermanent() { setBacklightDuration(Interval::infinity()); } bool D868UVCodeplug::GeneralSettingsElement::gps() const { return getUInt8(Offset::gpsEnable()); } void D868UVCodeplug::GeneralSettingsElement::enableGPS(bool enable) { setUInt8(Offset::gpsEnable(), (enable ? 0x01 : 0x00)); } bool D868UVCodeplug::GeneralSettingsElement::smsAlert() const { return getUInt8(Offset::smsAlert()); } void D868UVCodeplug::GeneralSettingsElement::enableSMSAlert(bool enable) { setUInt8(Offset::smsAlert(), (enable ? 0x01 : 0x00)); } bool D868UVCodeplug::GeneralSettingsElement::activeChannelB() const { return getUInt8(Offset::activeChannelB()); } void D868UVCodeplug::GeneralSettingsElement::enableActiveChannelB(bool enable) { setUInt8(Offset::activeChannelB(), (enable ? 0x01 : 0x00)); } bool D868UVCodeplug::GeneralSettingsElement::subChannel() const { return getUInt8(Offset::subChannel()); } void D868UVCodeplug::GeneralSettingsElement::enableSubChannel(bool enable) { setUInt8(Offset::subChannel(), (enable ? 0x01 : 0x00)); } bool D868UVCodeplug::GeneralSettingsElement::callAlert() const { return getUInt8(Offset::callAlert()); } void D868UVCodeplug::GeneralSettingsElement::enableCallAlert(bool enable) { setUInt8(Offset::callAlert(), (enable ? 0x01 : 0x00)); } QTimeZone D868UVCodeplug::GeneralSettingsElement::gpsTimeZone() const { return QTimeZone((((int)getUInt8(Offset::gpsTimeZone()))-12)*3600); } void D868UVCodeplug::GeneralSettingsElement::setGPSTimeZone(const QTimeZone &zone) { int offset = zone.offsetFromUtc(QDateTime::currentDateTime()); setUInt8(Offset::gpsTimeZone(), (12 + offset/3600)); } bool D868UVCodeplug::GeneralSettingsElement::dmrTalkPermit() const { return getBit(Offset::talkPermit(), 0); } void D868UVCodeplug::GeneralSettingsElement::enableDMRTalkPermit(bool enable) { return setBit(Offset::talkPermit(), 0, enable); } bool D868UVCodeplug::GeneralSettingsElement::fmTalkPermit() const { return getBit(Offset::talkPermit(), 1); } void D868UVCodeplug::GeneralSettingsElement::enableFMTalkPermit(bool enable) { return setBit(Offset::talkPermit(), 1, enable); } bool D868UVCodeplug::GeneralSettingsElement::dmrResetTone() const { return getUInt8(Offset::dmrResetTone()); } void D868UVCodeplug::GeneralSettingsElement::enableDMRResetTone(bool enable) { return setUInt8(Offset::dmrResetTone(), (enable ? 0x01 : 0x00)); } AnytoneAudioSettingsExtension::VoxSource D868UVCodeplug::GeneralSettingsElement::voxSource() const { return (AnytoneAudioSettingsExtension::VoxSource)getUInt8(Offset::voxSource()); } void D868UVCodeplug::GeneralSettingsElement::setVOXSource(AnytoneAudioSettingsExtension::VoxSource source) { setUInt8(Offset::voxSource(), (unsigned)source); } bool D868UVCodeplug::GeneralSettingsElement::idleChannelTone() const { return getUInt8(Offset::idleChannelTone()); } void D868UVCodeplug::GeneralSettingsElement::enableIdleChannelTone(bool enable) { return setUInt8(Offset::idleChannelTone(), (enable ? 0x01 : 0x00)); } Interval D868UVCodeplug::GeneralSettingsElement::menuExitTime() const { return Interval::fromSeconds(5 + 5*((unsigned) getUInt8(Offset::menuExitTime()))); } void D868UVCodeplug::GeneralSettingsElement::setMenuExitTime(Interval intv) { setUInt8(Offset::menuExitTime(), (std::max(5ULL, intv.seconds())-5)/5); } bool D868UVCodeplug::GeneralSettingsElement::startupTone() const { return getUInt8(Offset::startupTone()); } void D868UVCodeplug::GeneralSettingsElement::enableStartupTone(bool enable) { return setUInt8(Offset::startupTone(), (enable ? 0x01 : 0x00)); } bool D868UVCodeplug::GeneralSettingsElement::callEndPrompt() const { return getUInt8(Offset::callEndPrompt()); } void D868UVCodeplug::GeneralSettingsElement::enableCallEndPrompt(bool enable) { return setUInt8(Offset::callEndPrompt(), (enable ? 0x01 : 0x00)); } Level D868UVCodeplug::GeneralSettingsElement::maxSpeakerVolume() const { return Level::fromValue(getUInt8(Offset::maxSpeakerVolume()), Limit::volume()); } void D868UVCodeplug::GeneralSettingsElement::setMaxSpeakerVolume(Level level) { setUInt8(Offset::maxSpeakerVolume(), level.mapTo(Limit::volume())); } bool D868UVCodeplug::GeneralSettingsElement::getGPSPosition() const { return getUInt8(Offset::getGPSPosition()); } void D868UVCodeplug::GeneralSettingsElement::enableGetGPSPosition(bool enable) { return setUInt8(Offset::getGPSPosition(), (enable ? 0x01 : 0x00)); } bool D868UVCodeplug::GeneralSettingsElement::volumeChangePrompt() const { return 0x01 == getUInt8(Offset::volumeChangePrompt()); } void D868UVCodeplug::GeneralSettingsElement::enableVolumeChangePrompt(bool enable) { setUInt8(Offset::volumeChangePrompt(), (enable ? 0x01 : 0x00)); } AnytoneAutoRepeaterSettingsExtension::Direction D868UVCodeplug::GeneralSettingsElement::autoRepeaterDirectionA() const { return (AnytoneAutoRepeaterSettingsExtension::Direction) getUInt8(Offset::autoRepeaterDirA()); } void D868UVCodeplug::GeneralSettingsElement::setAutoRepeaterDirectionA(AnytoneAutoRepeaterSettingsExtension::Direction dir) { setUInt8(Offset::autoRepeaterDirA(), (unsigned)dir); } AnytoneDisplaySettingsExtension::LastCallerDisplayMode D868UVCodeplug::GeneralSettingsElement::lastCallerDisplayMode() const { return (AnytoneDisplaySettingsExtension::LastCallerDisplayMode)getUInt8(Offset::lastCallerDisplay()); } void D868UVCodeplug::GeneralSettingsElement::setLastCallerDisplayMode(AnytoneDisplaySettingsExtension::LastCallerDisplayMode mode) { setUInt8(Offset::lastCallerDisplay(), (unsigned)mode); } bool D868UVCodeplug::GeneralSettingsElement::displayClock() const { return getUInt8(Offset::showClock()); } void D868UVCodeplug::GeneralSettingsElement::enableDisplayClock(bool enable) { setUInt8(Offset::showClock(), (enable ? 0x01 : 0x00)); } Level D868UVCodeplug::GeneralSettingsElement::maxHeadphoneVolume() const { return Level::fromValue((unsigned)getUInt8(Offset::maxHeadPhoneVolume()), Limit::volume()); } void D868UVCodeplug::GeneralSettingsElement::setMaxHeadphoneVolume(Level max) { setUInt8(Offset::maxHeadPhoneVolume(), max.mapTo(Limit::volume())); } bool D868UVCodeplug::GeneralSettingsElement::enhanceAudio() const { return getUInt8(Offset::enhanceAudio()); } void D868UVCodeplug::GeneralSettingsElement::enableEnhancedAudio(bool enable) { setUInt8(Offset::enhanceAudio(), (enable ? 0x01 : 0x00)); } Frequency D868UVCodeplug::GeneralSettingsElement::minVFOScanFrequencyUHF() const { return Frequency::fromHz(((unsigned)getUInt32_le(Offset::minVFOScanUHF()))*10); } void D868UVCodeplug::GeneralSettingsElement::setMinVFOScanFrequencyUHF(Frequency freq) { setUInt32_le(Offset::minVFOScanUHF(), freq.inHz()/10); } Frequency D868UVCodeplug::GeneralSettingsElement::maxVFOScanFrequencyUHF() const { return Frequency::fromHz(((unsigned)getUInt32_le(Offset::maxVFOScanUHF()))*10); } void D868UVCodeplug::GeneralSettingsElement::setMaxVFOScanFrequencyUHF(Frequency freq) { setUInt32_le(Offset::maxVFOScanUHF(), freq.inHz()/10); } Frequency D868UVCodeplug::GeneralSettingsElement::minVFOScanFrequencyVHF() const { return Frequency::fromHz(((unsigned)getUInt32_le(Offset::minVFOScanVHF()))*10); } void D868UVCodeplug::GeneralSettingsElement::setMinVFOScanFrequencyVHF(Frequency freq) { setUInt32_le(Offset::minVFOScanVHF(), freq.inHz()/10); } Frequency D868UVCodeplug::GeneralSettingsElement::maxVFOScanFrequencyVHF() const { return Frequency::fromHz(((unsigned)getUInt32_le(Offset::maxVFOScanVHF()))*10); } void D868UVCodeplug::GeneralSettingsElement::setMaxVFOScanFrequencyVHF(Frequency freq) { setUInt32_le(Offset::maxVFOScanVHF(), freq.inHz()/10); } bool D868UVCodeplug::GeneralSettingsElement::hasAutoRepeaterOffsetFrequencyIndexUHF() const { return 0xff != autoRepeaterOffsetFrequencyIndexUHF(); } unsigned D868UVCodeplug::GeneralSettingsElement::autoRepeaterOffsetFrequencyIndexUHF() const { return getUInt8(Offset::autoRepOffsetUHF()); } void D868UVCodeplug::GeneralSettingsElement::setAutoRepeaterOffsetFrequenyIndexUHF(unsigned idx) { setUInt8(Offset::autoRepOffsetUHF(), idx); } void D868UVCodeplug::GeneralSettingsElement::clearAutoRepeaterOffsetFrequencyIndexUHF() { setAutoRepeaterOffsetFrequenyIndexUHF(0xff); } bool D868UVCodeplug::GeneralSettingsElement::hasAutoRepeaterOffsetFrequencyIndexVHF() const { return 0xff != autoRepeaterOffsetFrequencyIndexVHF(); } unsigned D868UVCodeplug::GeneralSettingsElement::autoRepeaterOffsetFrequencyIndexVHF() const { return getUInt8(Offset::autoRepOffsetVHF()); } void D868UVCodeplug::GeneralSettingsElement::setAutoRepeaterOffsetFrequenyIndexVHF(unsigned idx) { setUInt8(Offset::autoRepOffsetVHF(), idx); } void D868UVCodeplug::GeneralSettingsElement::clearAutoRepeaterOffsetFrequencyIndexVHF() { setAutoRepeaterOffsetFrequenyIndexVHF(0xff); } void D868UVCodeplug::GeneralSettingsElement::callToneMelody(Melody &melody) const { QVector> tones; tones.reserve(5); for (int i=0; i<5; i++) { double freq = getUInt16_le(Offset::callToneTones()+2*i); unsigned int duration = getUInt16_le(Offset::callToneDurations()+2*i); if (duration) tones.append({freq, duration}); } melody.infer(tones); } void D868UVCodeplug::GeneralSettingsElement::setCallToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; i> tones; tones.reserve(5); for (int i=0; i<5; i++) { double frequency = getUInt16_le(Offset::idleToneTones()+2*i); unsigned int duration = getUInt16_le(Offset::idleToneDurations()+2*i); if (duration) tones.append({frequency, duration}); } melody.infer(tones); } void D868UVCodeplug::GeneralSettingsElement::setIdleToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; i> tones; tones.reserve(5); for (int i=0; i<5; i++) { double frequency = getUInt16_le(Offset::resetToneTones()+2*i); unsigned int duration = getUInt16_le(Offset::resetToneDurations()+2*i); if (duration) tones.append({frequency, duration}); } melody.infer(tones); } void D868UVCodeplug::GeneralSettingsElement::setResetToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; isettings()->audio()->vox()); setVOXDelay(ctx.config()->settings()->audio()->voxDelay()); setMaxHeadphoneVolume(ctx.config()->settings()->audio()->maxHeadphoneVolume()); // Encode tone settings enableIdleChannelTone(ctx.config()->settings()->tone()->channelIdle().testAnyFlags( Channel::Type::FM | Channel::Type::DMR)); setKeyToneLevel(ctx.config()->settings()->tone()->keyToneVolume()); AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return true; // Power save settings setPowerSave(ext->powerSaveSettings()->powerSave()); // Encode key settings enableKnobLock(ext->keySettings()->knobLockEnabled()); enableKeypadLock(ext->keySettings()->keypadLockEnabled()); enableSidekeysLock(ext->keySettings()->sideKeysLockEnabled()); enableKeyLockForced(ext->keySettings()->forcedKeyLockEnabled()); // Encode audio settings setVOXSource(ext->audioSettings()->voxSource()); // Encode display settings setBacklightDuration(ext->displaySettings()->backlightDuration()); enableShowCurrentContact(ext->displaySettings()->showContact()); // Check encryption type if (AnytoneDMRSettingsExtension::EncryptionType::AES == ext->dmrSettings()->encryption()) { logInfo() << "D868UVE does not support AES/ARC4 encryption."; } return true; } bool D868UVCodeplug::GeneralSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::GeneralSettingsElement::updateConfig(ctx, err)) return false; ctx.config()->settings()->audio()->setVox(voxLevel()); ctx.config()->settings()->audio()->setVOXDelay(voxDelay()); ctx.config()->settings()->audio()->setMaxHeadphoneVolume(this->maxHeadphoneVolume()); // Decode tone settings ctx.config()->settings()->tone()->setChannelIdle( idleChannelTone() ? (Channel::Type::DMR | Channel::Type::FM) : Channel::Type::None ); ctx.config()->settings()->tone()->setKeyToneVolume(keyToneLevel()); // Get or add settings extension AnytoneSettingsExtension *ext = nullptr; if (ctx.config()->settings()->anytoneExtension()) { ext = ctx.config()->settings()->anytoneExtension(); } else { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Decode power save settings: ext->powerSaveSettings()->setPowerSave(powerSave()); // Decode key settings ext->keySettings()->enableKnobLock(this->knobLock()); ext->keySettings()->enableKeypadLock(this->keypadLock()); ext->keySettings()->enableSideKeysLock(this->sidekeysLock()); ext->keySettings()->enableForcedKeyLock(this->keyLockForced()); // Decode audio settings ext->audioSettings()->setVOXSource(voxSource()); // Decode display settings ext->displaySettings()->enableShowContact(this->showCurrentContact()); ext->displaySettings()->setBacklightDuration(backlightDuration()); // Set encryption type ext->dmrSettings()->setEncryption(AnytoneDMRSettingsExtension::EncryptionType::DMR); return true; } bool D868UVCodeplug::GeneralSettingsElement::linkSettings(RadioSettings *settings, Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::GeneralSettingsElement::linkSettings(settings, ctx, err)) return false; // Get or add settings extension AnytoneSettingsExtension *ext = settings->anytoneExtension(); if (nullptr == ext) { ext = new AnytoneSettingsExtension(); settings->setAnytoneExtension(ext); } // Nothing to link return true; } /* ******************************************************************************************** * * Implementation of D868UVCodeplug * ******************************************************************************************** */ D868UVCodeplug::D868UVCodeplug(const QString &label, QObject *parent) : AnytoneCodeplug(label, parent) { // pass... } D868UVCodeplug::D868UVCodeplug(QObject *parent) : AnytoneCodeplug("AnyTone AT-D868UV Codeplug", parent) { // pass... } void D868UVCodeplug::allocateUpdated() { this->allocateVFOSettings(); // General config this->allocateGeneralSettings(); this->allocateZoneChannelList(); this->allocateDTMFNumbers(); this->allocateBootSettings(); this->allocateRepeaterOffsetFrequencies(); this->allocateGPSSystems(); this->allocatEnhancedEncryptionKeys(); this->allocateHotKeySettings(); this->allocateAlarmSettings(); this->allocateFMBroadcastSettings(); this->allocate5ToneIDs(); this->allocate5ToneFunctions(); this->allocate5ToneSettings(); this->allocate2ToneIDs(); this->allocate2ToneFunctions(); this->allocate2ToneSettings(); this->allocateDTMFSettings(); } void D868UVCodeplug::allocateForEncoding() { this->allocateChannels(); this->allocateZones(); this->allocateContacts(); this->allocateAnalogContacts(); this->allocateRXGroupLists(); this->allocateScanLists(); this->allocateRadioIDs(); this->allocateSMSMessages(); // Encryption this->allocatDMREncryptionKeys(); } void D868UVCodeplug::allocateForDecoding() { this->allocateRadioIDs(); this->allocateChannels(); this->allocateZones(); this->allocateContacts(); this->allocateAnalogContacts(); this->allocateRXGroupLists(); this->allocateScanLists(); // General config this->allocateGeneralSettings(); this->allocateZoneChannelList(); this->allocateBootSettings(); this->allocateRepeaterOffsetFrequencies(); this->allocateSMSMessages(); // Encryption this->allocatDMREncryptionKeys(); // GPS settings this->allocateGPSSystems(); } bool D868UVCodeplug::allocateBitmaps() { // Channel bitmap image(0).addElement(Offset::channelBitmap(), ChannelBitmapElement::size()); // Zone bitmap image(0).addElement(Offset::zoneBitmap(), ZoneBitmapElement::size()); // Contacts bitmap image(0).addElement(Offset::contactBitmap(), ContactBitmapElement::size()); // Analog contacts bytemap image(0).addElement(Offset::dtmfContactBytemap(), DTMFContactBytemapElement::size()); // RX group list bitmaps image(0).addElement(Offset::groupListBitmap(), GroupListBitmapElement::size()); // Scan list bitmaps image(0).addElement(Offset::scanListBitmap(), ScanListBitmapElement::size()); // Radio IDs bitmaps image(0).addElement(Offset::radioIDBitmap(), RadioIDBitmapElement::size()); // Message bitmaps image(0).addElement(Offset::messageBytemap(), MessageBytemapElement::size()); // Status messages image(0).addElement(Offset::statusMessageBitmap(), StatusMessageBitmapElement::size()); // FM Broadcast bitmaps image(0).addElement(Offset::wfmChannelBitmap(), WFMChannelBitmapElement::size()); // 5-Tone function bitmaps image(0).addElement(Offset::fiveToneIdBitmap(), FiveToneIDBitmapElement::size()); // 2-Tone function bitmaps image(0).addElement(Offset::twoToneIdBitmap(), TwoToneIDBitmapElement::size()); image(0).addElement(Offset::twoToneFunctionBitmap(), TwoToneFunctionBitmapElement::size()); return true; } void D868UVCodeplug::setBitmaps(Context& ctx) { // Mark first radio ID as valid RadioIDBitmapElement radioid_bitmap(data(Offset::radioIDBitmap())); unsigned int num_radio_ids = std::min(Limit::numRadioIDs(), ctx.count()); radioid_bitmap.clear(); radioid_bitmap.enableFirst(num_radio_ids); // Mark valid channels (set bit) ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); unsigned int num_channels = std::min(Limit::numChannels(), ctx.count()); channel_bitmap.clear(); channel_bitmap.enableFirst(num_channels); // Mark valid contacts (clear bit) ContactBitmapElement contact_bitmap(data(Offset::contactBitmap())); unsigned int num_contacts = std::min(Limit::numContacts(), ctx.count()); contact_bitmap.clear(); contact_bitmap.enableFirst(num_contacts); // Mark valid analog contacts (clear bytes) DTMFContactBytemapElement analog_contact_bitmap(data(Offset::dtmfContactBytemap())); unsigned int num_dtmf_contacts = std::min(Limit::numDTMFContacts(), ctx.count()); analog_contact_bitmap.clear(); analog_contact_bitmap.enableFirst(num_dtmf_contacts); // Mark valid zones (set bits) ZoneBitmapElement zone_bitmap(data(Offset::zoneBitmap())); unsigned int num_zones = std::min(Limit::numZones(), ctx.count()); zone_bitmap.clear(); zone_bitmap.enableFirst(num_zones); // Mark group lists GroupListBitmapElement group_bitmap(data(Offset::groupListBitmap())); unsigned int num_group_lists = std::min(Limit::numGroupLists(), ctx.count()); group_bitmap.clear(); group_bitmap.enableFirst(num_group_lists); // Mark scan lists ScanListBitmapElement scan_bitmap(data(Offset::scanListBitmap())); unsigned int num_scan_lists = std::min(Limit::numScanLists(), ctx.count()); scan_bitmap.clear(); scan_bitmap.enableFirst(num_scan_lists); // Mark SMS messages MessageBytemapElement sms_bytemap(data(Offset::messageBytemap())); unsigned int num_messages = std::min(Limit::numMessages(), ctx.count()); sms_bytemap.clear(); sms_bytemap.enableFirst(num_messages); } Config * D868UVCodeplug::preprocess(Config *config, const ErrorStack &err) const { // Apply base preprocessing auto intermediate = AnytoneCodeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot apply preprocessing for D868UVE."; return nullptr; } // Remove all but 16bit DMR encryption keys. EncryptionKeyFilterVisitor filter( { EncryptionKeyFilterVisitor::Filter(BasicEncryptionKey::staticMetaObject, 16, 16) }); if (! filter.process(intermediate, err)) { errMsg(err) << "Cannot remove unsupported exncryption."; delete intermediate; return nullptr; } // Remove DMR encryption keys, that are not 16bit wide. return intermediate; } bool D868UVCodeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! this->encodeRadioID(flags, ctx, err)) return false; if (! this->encodeGeneralSettings(flags, ctx, err)) return false; if (! this->encodeSMSMessages(flags, ctx, err)) return false; if (! this->encodeRepeaterOffsetFrequencies(flags, ctx, err)) return false; if (! this->encodeBootSettings(flags, ctx, err)) return false; if (! this->encodeChannels(flags, ctx, err)) return false; if (! this->encodeContacts(flags, ctx, err)) return false; if (! this->encodeAnalogContacts(flags, ctx, err)) return false; if (! this->encodeRXGroupLists(flags, ctx, err)) return false; if (! this->encodeZones(flags, ctx, err)) return false; if (! this->encodeScanLists(flags, ctx, err)) return false; if (! this->encodeGPSSystems(flags, ctx, err)) return false; if (! this->encodeDMREncryptionKeys(flags, ctx, err)) return false; return true; } bool D868UVCodeplug::createElements(Context &ctx, const ErrorStack &err) { if (! this->setRadioID(ctx, err)) return false; if (! this->decodeGeneralSettings(ctx, err)) return false; if (! this->createSMSMessages(ctx, err)) return false; if (! this->decodeRepeaterOffsetFrequencies(ctx, err)) return false; if (! this->decodeBootSettings(ctx, err)) return false; if (! this->decodeDMREncryptionKeys(ctx, err)) return false; if (! this->createChannels(ctx, err)) return false; if (! this->createContacts(ctx, err)) return false; if (! this->createAnalogContacts(ctx, err)) return false; if (! this->createRXGroupLists(ctx, err)) return false; if (! this->createZones(ctx, err)) return false; if (! this->createScanLists(ctx, err)) return false; if (! this->createGPSSystems(ctx, err)) return false; return true; } bool D868UVCodeplug::linkElements(Context &ctx, const ErrorStack &err) { if (! this->linkRXGroupLists(ctx, err)) return false; if (! this->linkZones(ctx, err)) return false; if (! this->linkScanLists(ctx, err)) return false; if (! this->linkChannels(ctx, err)) return false; if (! this->linkGPSSystems(ctx, err)) return false; if (! this->linkGeneralSettings(ctx, err)) { return false; } return true; } void D868UVCodeplug::allocateChannels() { /* Allocate channels */ ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); for (uint16_t i=0; i skip if (! channel_bitmap.isEncoded(i)) continue; // compute address for channel uint16_t bank = i/Limit::channelsPerBank(), idx = i%Limit::channelsPerBank(); uint32_t addr = Offset::channelBanks() + + bank * Offset::betweenChannelBanks() + idx * ChannelElement::size(); if (!isAllocated(addr, 0)) { image(0).addElement(addr, ChannelElement::size()); } } } bool D868UVCodeplug::encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // Encode channels for (unsigned int i=0; i(); i++) { // enable channel uint16_t bank = i/Limit::channelsPerBank(), idx = i%Limit::channelsPerBank(); ChannelElement ch(data(Offset::channelBanks() + bank * Offset::betweenChannelBanks() + idx * ChannelElement::size())); if (! ch.fromChannelObj(ctx.get(i), ctx)) return false; } return true; } bool D868UVCodeplug::createChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); // Create channels for (uint16_t i=0; ichannelList()->add(obj); ctx.add(obj, i); } } return true; } bool D868UVCodeplug::linkChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); // Link channel objects for (uint16_t i=0; i(i)) ch.linkChannelObj(ctx.get(i), ctx); } return true; } void D868UVCodeplug::allocateVFOSettings() { // Allocate VFO channels image(0).addElement(Offset::vfoA(), ChannelElement::size()); image(0).addElement(Offset::vfoA()+0x2000, ChannelElement::size()); image(0).addElement(Offset::vfoB(), ChannelElement::size()); image(0).addElement(Offset::vfoB()+0x2000, ChannelElement::size()); } void D868UVCodeplug::allocateContacts() { /* Allocate contacts */ ContactBitmapElement contact_bitmap(data(Offset::contactBitmap())); unsigned contactCount=0; for (uint16_t i=0; i contacts; // Encode contacts and also collect id<->index map for (unsigned int i=0; i(); i++) { uint32_t bank_addr = Offset::contactBanks() + (i/Limit::contactsPerBank())*Offset::betweenContactBanks(); uint32_t addr = bank_addr + (i%Limit::contactsPerBank())*ContactElement::size(); ContactElement con(data(addr)); DMRContact *contact = ctx.get(i); if(! con.fromContactObj(contact, ctx)) return false; ((uint32_t *)data(Offset::contactIndex()))[i] = qToLittleEndian(i); contacts.append(contact); } // encode index map for contacts std::sort(contacts.begin(), contacts.end(), [](DMRContact *a, DMRContact *b) { return a->number() < b->number(); }); for (int i=0; inumber(), (DMRContact::GroupCall==contacts[i]->type())); el.setIndex(ctx.index(contacts[i])); } return true; } bool D868UVCodeplug::createContacts(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create digital contacts ContactBitmapElement contact_bitmap(data(Offset::contactBitmap())); for (uint16_t i=0; icontacts()->add(obj); ctx.add(obj, i); } } return true; } void D868UVCodeplug::allocateAnalogContacts() { /* Allocate analog contacts */ DTMFContactBytemapElement analog_contact_bytemap(data(Offset::dtmfContactBytemap())); for (uint8_t i=0; i skip if (! analog_contact_bytemap.isEncoded(i)) continue; uint32_t bank_addr = Offset::dtmfContacts() + (i/2)*(2*DTMFContactElement::size()); if (! isAllocated(bank_addr, 0)) { image(0).addElement(bank_addr, 2*DTMFContactElement::size()); } } image(0).addElement(Offset::dtmfIndex(), 1*Limit::numDTMFContacts()); } bool D868UVCodeplug::encodeAnalogContacts(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) uint8_t *idxlst = data(Offset::dtmfIndex()); memset(idxlst, 0xff, 1*Limit::numDTMFContacts()); for (unsigned int i=0; i(); i++) { DTMFContactElement cont(data(Offset::dtmfContacts() + i*DTMFContactElement::size())); cont.fromContact(ctx.get(i)); idxlst[i] = i; } return true; } bool D868UVCodeplug::createAnalogContacts(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) DTMFContactBytemapElement analog_contact_bytemap(data(Offset::dtmfContactBytemap())); for (unsigned int i=0; i skip if (! analog_contact_bytemap.isEncoded(i)) continue; DTMFContactElement cont(data(Offset::dtmfContacts() + i*DTMFContactElement::size())); if (DTMFContact *dtmf = cont.toContact()) { ctx.config()->contacts()->add(dtmf); ctx.add(dtmf, i); } } return true; } void D868UVCodeplug::allocateRadioIDs() { /* Allocate radio IDs */ RadioIDBitmapElement radioid_bitmap(data(Offset::radioIDBitmap())); for (uint8_t i=0; i skip if (! radioid_bitmap.isEncoded(i)) continue; // Allocate radio IDs individually uint32_t addr = Offset::radioIDs() + i*RadioIDElement::size(); if (! isAllocated(addr, 0)) { image(0).addElement(addr, RadioIDElement::size()); } } } bool D868UVCodeplug::encodeRadioID(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // Encode radio IDs for (unsigned int i=0; i(); i++) { RadioIDElement(data(Offset::radioIDs() + i*RadioIDElement::size())) .fromRadioID(ctx.get(i)); } return true; } bool D868UVCodeplug::setRadioID(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Find a valid RadioID RadioIDBitmapElement radio_id_bitmap(data(Offset::radioIDBitmap())); for (uint16_t i=0; iradioIDs()->add(rid); ctx.add(rid, i); } } return true; } void D868UVCodeplug::allocateRXGroupLists() { /* * Allocate group lists */ GroupListBitmapElement grouplist_bitmap(data(Offset::groupListBitmap())); for (uint16_t i=0; i skip if (! grouplist_bitmap.isEncoded(i)) continue; // Allocate RX group lists indivitually uint32_t addr = Offset::groupLists() + i*Offset::betweenGroupLists(); if (! isAllocated(addr, 0)) { image(0).addElement(addr, GroupListElement::size()); GroupListElement(data(addr)).clear(); } } } bool D868UVCodeplug::encodeRXGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // Encode RX group-lists for (int i=0; irxGroupLists()->count(); i++) { GroupListElement grp(data(Offset::groupLists() + i*Offset::betweenGroupLists())); grp.fromGroupListObj(ctx.config()->rxGroupLists()->list(i), ctx); } return true; } bool D868UVCodeplug::createRXGroupLists(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create RX group lists GroupListBitmapElement grouplist_bitmap(data(Offset::groupListBitmap())); for (uint16_t i=0; irxGroupLists()->add(obj); ctx.add(obj, i); } } return true; } bool D868UVCodeplug::linkRXGroupLists(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GroupListBitmapElement grouplist_bitmap(data(Offset::groupListBitmap())); for (uint16_t i=0; i(i), ctx)) { logError() << "Cannot link RX group list idx=" << i; return false; } } return true; } void D868UVCodeplug::allocateZones() { ZoneBitmapElement zone_bitmap(data(Offset::zoneBitmap())); for (uint16_t i=0; i skip if (! zone_bitmap.isEncoded(i)) continue; // Allocate zone itself image(0).addElement(Offset::zoneChannels()+i*Offset::betweenZoneChannels(), Size::zoneChannels()); image(0).addElement(Offset::zoneNames()+i*Offset::betweenZoneNames(), Size::zoneName()); } } bool D868UVCodeplug::encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // Encode zones for (unsigned int i=0; i(); i++) { Zone *zone = ctx.get(i); // Clear name and channel list uint8_t *name = (uint8_t *)data(Offset::zoneNames() + i*Offset::betweenZoneNames()); uint16_t *channels = (uint16_t *)data(Offset::zoneChannels() + i*Offset::betweenZoneChannels()); encode_ascii(name, zone->name(), Limit::zoneNameLength(), 0); memset(channels, 0xff, Size::zoneChannels()); // Handle list A for (int j=0; jA()->count(); j++) { channels[j] = qToLittleEndian(ctx.index(zone->A()->get(j)->as())); } if (! encodeZone(i, zone, flags, ctx, err)) return false; } return true; } bool D868UVCodeplug::encodeZone(int i, Zone *zone, const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(i); Q_UNUSED(zone); Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err) return true; } bool D868UVCodeplug::createZones(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create zones ZoneBitmapElement zone_bitmap(data(Offset::zoneBitmap())); for (uint16_t i=0; ideleteLater(); return false; } // add to config ctx.config()->zones()->add(zone); ctx.add(zone, i); } return true; } bool D868UVCodeplug::decodeZone(int i, Zone *zone, Context &ctx, const ErrorStack &err) { Q_UNUSED(i); Q_UNUSED(zone); Q_UNUSED(ctx); Q_UNUSED(err) return true; } bool D868UVCodeplug::linkZones(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create zones ZoneBitmapElement zone_bitmap(data(Offset::zoneBitmap())); for (uint16_t i=0; i(i); // link zone uint16_t *channels = (uint16_t *)data(Offset::zoneChannels() + i*Offset::betweenZoneChannels()); for (uint8_t j=0; j continue if (0xffff == *channels) continue; // Get channel index and check if defined uint16_t cidx = qFromLittleEndian(*channels); if (! ctx.has(cidx)) continue; zone->A()->add(ctx.get(cidx)); } if (! linkZone(i, zone, false, ctx, err)) return false; } return true; } bool D868UVCodeplug::linkZone(int i, Zone *zone, bool isB, Context &ctx, const ErrorStack &err) { Q_UNUSED(i); Q_UNUSED(zone); Q_UNUSED(isB); Q_UNUSED(ctx); Q_UNUSED(err) return true; } void D868UVCodeplug::allocateScanLists() { ScanListBitmapElement scanlist_bitmap(data(Offset::scanListBitmap())); for (uint8_t i=0; i skip if (! scanlist_bitmap.isEncoded(i)) continue; // Allocate scan lists indivitually uint8_t bank = (i/Limit::numScanListsPerBank()), bank_idx = (i%Limit::numScanListsPerBank()); uint32_t addr = Offset::scanListBanks() + bank*Offset::betweenScanListBanks() + bank_idx*Offset::betweenScanLists(); if (!isAllocated(addr, 0)) { image(0).addElement(addr, ScanListElement::size()); ScanListElement(data(addr)).clear(); } } } bool D868UVCodeplug::encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // Encode scan lists unsigned int num_scan_lists = std::min(Limit::numScanLists(), ctx.count()); for (unsigned int i=0; iscanlists()->scanlist(i), ctx); } return true; } bool D868UVCodeplug::createScanLists(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create scan lists ScanListBitmapElement scanlist_bitmap(data(Offset::scanListBitmap())); for (unsigned int i=0; iscanlists()->add(obj); ctx.add(obj, i); } return true; } bool D868UVCodeplug::linkScanLists(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ScanListBitmapElement scanlist_bitmap(data(Offset::scanListBitmap())); for (unsigned i=0; i(i); // Link scanlists immediately, all channels are defined already ctx.config()->scanlists()->add(obj); scanl.linkScanListObj(obj, ctx); } return true; } void D868UVCodeplug::allocateGeneralSettings() { image(0).addElement(Offset::settings(), GeneralSettingsElement::size()); } bool D868UVCodeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings())).fromConfig(flags, ctx, err); } bool D868UVCodeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings())).updateConfig(ctx, err); } bool D868UVCodeplug::linkGeneralSettings(Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings())).linkSettings(ctx.config()->settings(), ctx, err); } void D868UVCodeplug::allocateZoneChannelList() { image(0).addElement(Offset::zoneChannelList(), ZoneChannelListElement::size()); } void D868UVCodeplug::allocateDTMFNumbers() { image(0).addElement(Offset::dtmfIDList(), DTMFIDListElement::size()); } void D868UVCodeplug::allocateBootSettings() { image(0).addElement(Offset::bootSettings(), BootSettingsElement::size()); } bool D868UVCodeplug::encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return BootSettingsElement(data(Offset::bootSettings())).fromConfig(flags, ctx); } bool D868UVCodeplug::decodeBootSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return BootSettingsElement(data(Offset::bootSettings())).updateConfig(ctx); } void D868UVCodeplug::allocateGPSSystems() { image(0).addElement(Offset::aprsSettings(), DMRAPRSSettingsElement::size()); image(0).addElement(Offset::dmrAPRSMessage(), DMRAPRSMessageElement::size()); } bool D868UVCodeplug::encodeGPSSystems(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) DMRAPRSSettingsElement gps(data(Offset::aprsSettings())); return gps.fromConfig(flags, ctx); } bool D868UVCodeplug::createGPSSystems(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) QSet systems; // First find all GPS systems linked, that is referenced by any channel // Create channels ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); for (uint16_t i=0; i(i)->is()) continue; ChannelElement ch(data(Offset::channelBanks() + bank*Offset::betweenChannelBanks() + idx*ChannelElement::size())); if (ChannelElement::APRSType::Off != ch.txAPRSType()) systems.insert(ch.digitalAPRSSystemIndex()); } // Then create all referenced GPS systems DMRAPRSSettingsElement gps(data(Offset::aprsSettings())); if (! gps.updateConfig(ctx, err)) { errMsg(err) << "Cannot update config from DMR-APRS settings."; return false; } for (QSet::iterator idx=systems.begin(); idx!=systems.end(); idx++) gps.createGPSSystem(*idx, ctx); return true; } bool D868UVCodeplug::linkGPSSystems(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) DMRAPRSSettingsElement gps(data(Offset::aprsSettings())); // Then link all referenced GPS systems for (uint8_t i=0; i(i)) continue; gps.linkGPSSystem(i, ctx); } return true; } void D868UVCodeplug::allocateSMSMessages() { // Prefab. SMS messages MessageBytemapElement messages_bytemap(data(Offset::messageBytemap())); unsigned message_count = 0; for (uint8_t i=0; i()); for (unsigned int i=0; i(i)->message()); MessageListElement listElement(data(Offset::messageIndex() + i*Size::messageIndex())); listElement.setIndex(i); if (i > 0) { MessageListElement prevElement(data(Offset::messageIndex() + (i-1)*Size::messageIndex())); prevElement.setIndex(i); } } return true; } bool D868UVCodeplug::createSMSMessages(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) MessageBytemapElement messages_bytemap(data(Offset::messageBytemap())); for (unsigned int i=0; isetName(QString("SMS %1").arg(i+1)); temp->setMessage(message.message()); ctx.config()->smsExtension()->smsTemplates()->add(temp); ctx.add(temp, i); } return true; } bool D868UVCodeplug::linkSMSMessages(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); // nothing to do return true; } void D868UVCodeplug::allocateHotKeySettings() { // Allocate Hot Keys image(0).addElement(Offset::analogQuickCall(), AnalogQuickCallsElement::size()); image(0).addElement(Offset::statusMessages(), StatusMessagesElement::size()); image(0).addElement(Offset::hotKeySettings(), HotKeySettingsElement::size()); } void D868UVCodeplug::allocateRepeaterOffsetFrequencies() { // Offset frequencies image(0).addElement(Offset::offsetFrequencies(), RepeaterOffsetListElement::size()); } bool D868UVCodeplug::encodeRepeaterOffsetFrequencies(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // If no AnyTone extension is present -> leave untouched. if (! ctx.config()->settings()->anytoneExtension()) return true; RepeaterOffsetListElement offsets(data(Offset::offsetFrequencies())); offsets.clear(); for (unsigned int i=0; i(); i++) { offsets.setOffset(i, ctx.get(i)->offset()); } return true; } bool D868UVCodeplug::decodeRepeaterOffsetFrequencies(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Allocate extension, if not present. AnytoneSettingsExtension *ext = nullptr; if (ctx.config()->settings()->anytoneExtension()) { ext = ctx.config()->settings()->anytoneExtension(); } else { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Decode offsets. RepeaterOffsetListElement offsets(data(Offset::offsetFrequencies())); for (unsigned int i=0; isetOffset(offsets.offset(i)); offset->setName(QString("%1 offset").arg(offsets.offset(i).format())); ext->autoRepeaterSettings()->offsets()->add(offset); ctx.add(offset, i); } } return true; } void D868UVCodeplug::allocatDMREncryptionKeys() { image(0).addElement(Offset::dmrEncryptionKeys(), DMREncryptionKeyListElement::size()); } bool D868UVCodeplug::encodeDMREncryptionKeys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); DMREncryptionKeyListElement keys(data(Offset::dmrEncryptionKeys())); keys.clear(); for (unsigned int i=0; i(i)) continue; keys.setKey(i, *ctx.get(i)); } return true; } bool D868UVCodeplug::decodeDMREncryptionKeys(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); DMREncryptionKeyListElement keys(data(Offset::dmrEncryptionKeys())); for (unsigned int i=0; isetName(QString("Basic Key %1").arg(i)); key->setKey(keys.key(i)); ctx.add(key, i); ctx.config()->commercialExtension()->encryptionKeys()->add(key); } return true; } void D868UVCodeplug::allocatEnhancedEncryptionKeys() { image(0).addElement(Offset::enhancedEncryptionKeys(), EnhancedEncryptionKeyListElement::size()); } void D868UVCodeplug::allocateAlarmSettings() { // Alarm settings image(0).addElement(Offset::alarmSettings(), AlarmSettingElement::size()); image(0).addElement(Offset::alarmSettingsExtension(), DigitalAlarmExtensionElement::size()); } void D868UVCodeplug::allocateFMBroadcastSettings() { // FM broad-cast settings image(0).addElement(Offset::wfmChannels(), WFMChannelListElement::size()); image(0).addElement(Offset::wfmVFO(), WFMVFOElement::size()); } void D868UVCodeplug::allocate5ToneIDs() { // Allocate 5-tone functions FiveToneIDBitmapElement bitmap(data(Offset::fiveToneIdBitmap())); for (uint8_t i=0; i #include "anytone_codeplug.hh" class Channel; class DMRContact; class Zone; class RXGroupList; class ScanList; class DMRAPRSSystem; /** Represents the device specific binary codeplug for Anytone AT-D868UV radios. * * In contrast to many other code-plugs, the code-plug for Anytone radios are spread over a large * memory area. The amount of fragmentation of the codeplug is overwhelming. * For example, while channels are organized more or less nicely in continuous banks, zones are * distributed throughout the entire code-plug. That is, the names of zones are located in a * different memory section that the channel lists. Some lists are defined though bit-masks others * by byte-masks. All bit-masks are positive, that is 1 indicates an enabled item while the * bit-mask for contacts is inverted. * * In general the code-plug is huge and complex. Moreover, the radio provides a huge amount of * options and features. To this end, reverse-engineering this code-plug was a nightmare. * * More over, the binary code-plug file generate by the windows CPS does not directly relate to * the data being written to the radio. To this end the code-plug has been reverse-engineered * using wireshark to monitor the USB communication between the windows CPS (running in a virtual * box) and the device. The latter makes the reverse-engineering particularly cumbersome. * * * @ingroup d868uv */ class D868UVCodeplug : public AnytoneCodeplug { Q_OBJECT protected: /** Colors supported by the D868UVE. */ struct Color { public: /** Maps code -> color. */ static AnytoneDisplaySettingsExtension::Color decode(uint8_t code); /** Maps color -> code. */ static uint8_t encode(AnytoneDisplaySettingsExtension::Color color); protected: /** Encoding of the supported colors. */ typedef enum { White = 0, Red=1 } CodedColor; }; public: /** Represents the channel element for AnyTone D868UV devices. * This class derives from @c AnytoneCodeplug::ChannelElement and implements the device-specific * encoding of channels for the AnyTone D868UV. * * Memory layout of the encoded channel element (size 0x0040 bytes): * @verbinclude d868uv_channel.txt */ class ChannelElement: public AnytoneCodeplug::ChannelElement { public: /** Possible encryption types. */ enum class DMREncryptionType { Basic = 0, Enhanced = 1 }; /** Possible APRS modes. */ enum class APRSType { Off = 0, DMR = 1 }; protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ChannelElement(uint8_t *ptr); /** Returns @c true if ranging is enabled. */ virtual bool ranging() const; /** Enables/disables ranging. */ virtual void enableRanging(bool enable); /** Returns @c true if through mode is enabled. */ virtual bool throughMode() const; /** Enables/disables though mode. */ virtual void enableThroughMode(bool enable); /** Returns @c true if data ACK is enabled. */ virtual bool dataACK() const; /** Enables/disables data ACK. */ virtual void enableDataACK(bool enable); /** Returns APRS type for reporting the position. */ APRSType txAPRSType() const; /** Sets APRS type for reporting the position. */ void setTXAPRSType(APRSType aprsType); /** Returns the DMR APRS system index. */ virtual unsigned digitalAPRSSystemIndex() const; /** Sets the DMR APRS system index. */ virtual void setDigitalAPRSSystemIndex(unsigned idx); /** Returns the encryption type. */ virtual DMREncryptionType dmrEncryptionType() const; /** Sets the encryption type. */ virtual void setDMREncryptionType(DMREncryptionType type); /** Returns @c true if a DMR encryption key is set. */ virtual bool hasDMREncryptionKeyIndex() const; /** Returns the DMR encryption key index (+1), 0=Off. */ virtual unsigned dmrEncryptionKeyIndex() const; /** Sets the DMR encryption key index (+1), 0=Off. */ virtual void setDMREncryptionKeyIndex(unsigned idx); /** Clears the DMR encryption key index. */ virtual void clearDMREncryptionKeyIndex(); /** Returns @c true if multiple key encryption is enabled. */ virtual bool multipleKeyEncryption() const; /** Enables/disables multiple key encryption. */ virtual void enableMultipleKeyEncryption(bool enable); /** Returns @c true if random key is enabled. */ virtual bool randomKey() const; /** Enables/disables random key. */ virtual void enableRandomKey(bool enable); /** Returns @c true if SMS is enabled. */ virtual bool sms() const; /** Enables/disables SMS. */ virtual void enableSMS(bool enable); /** Constructs a generic @c Channel object from the codeplug channel. */ virtual Channel *toChannelObj(Context &ctx) const; /** Links a previously constructed channel to the rest of the configuration. */ virtual bool linkChannelObj(Channel *c, Context &ctx) const; /** Initializes this codeplug channel from the given generic configuration. */ virtual bool fromChannelObj(const Channel *c, Context &ctx); protected: /** Internal used offsets within the channel element. */ struct Offset: public AnytoneCodeplug::ChannelElement::Offset { /// @cond DO_NOT_DOCUMENT static Bit dmrEncryptionType() { return {0x0021, 6}; } static unsigned int dmrEncryptionKey() { return 0x0022; } static Bit ranging() { return {0x0034, 0}; } static Bit throughMode() { return {0x0034, 1}; } static Bit dataACK() { return {0x0034, 2}; } static unsigned int txAPRSType() { return 0x0035; } static unsigned int digitalAPRSSystemIndex() { return 0x0036; } static Bit multipleKeyEncryption() { return {0x003b, 0}; } static Bit randomKey() { return {0x003b, 1}; } static Bit sms() { return {0x003b, 2}; } /// @endcond }; }; /** Represents the general config of the radio within the D868UV binary codeplug. * * This class only implements the differences to the generic * @c AnytoneCodeplug::GeneralSettingsElement. * * Memory layout of encoded general settings (size 0x00d0 bytes): * @verbinclude d868uv_generalsettings.txt */ class GeneralSettingsElement: public AnytoneCodeplug::GeneralSettingsElement { protected: /** Device specific key functions. */ struct KeyFunction { public: /** Encodes key function. */ static uint8_t encode(AnytoneKeySettingsExtension::KeyFunction tone); /** Decodes key function. */ static AnytoneKeySettingsExtension::KeyFunction decode(uint8_t code); protected: /** Encoded key functions. */ typedef enum { Off = 0x00, Voltage = 0x01, Power = 0x02, Repeater = 0x03, Reverse = 0x04, Encryption = 0x05, Call = 0x06, VOX = 0x07, ToggleVFO = 0x08, SubPTT = 0x09, Scan = 0x0a, WFM = 0x0b, Alarm = 0x0c, RecordSwitch = 0x0d, Record = 0x0e, SMS = 0x0f, Dial = 0x10, GPSInformation = 0x11, Monitor = 0x12, ToggleMainChannel = 0x13, HotKey1 = 0x14, HotKey2 = 0x15, HotKey3 = 0x16, HotKey4 = 0x17, HotKey5 = 0x18, HotKey6 = 0x19, WorkAlone = 0x1a, SkipChannel = 0x1b, DMRMonitor = 0x1c, SubChannel = 0x1d, PriorityZone = 0x1e, VFOScan = 0x1f, MICSoundQuality = 0x20, LastCallReply = 0x21, ChannelType = 0x22, Ranging = 0x23, ChannelRanging = 0x24, MaxVolume = 0x25, Slot = 0x26 } KeyFunctionCode; }; /** Possible backlight duration values. */ enum class BacklightDuration { Infinite = 0, _5s = 1, _10s = 2, _15s = 3, _20s = 4, _25s = 5, _30s = 6, _1min=7, _2min=8, _3min = 9, _4min = 10, _5min = 11 }; protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ GeneralSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00d0; } /** Resets the general settings. */ void clear(); /** Returns the power-save mode. */ virtual AnytonePowerSaveSettingsExtension::PowerSave powerSave() const; /** Sets the power-save mode. */ virtual void setPowerSave(AnytonePowerSaveSettingsExtension::PowerSave mode); /** Returns the VOX level. */ virtual Level voxLevel() const; /** Sets the VOX level. */ virtual void setVOXLevel(Level level); /** Returns the VOX delay in ms. */ virtual Interval voxDelay() const; /** Sets the VOX delay in ms. */ virtual void setVOXDelay(Interval ms); /** Returns the VOX source. */ virtual AnytoneAudioSettingsExtension::VoxSource voxSource() const; /** Sets the VOX source. */ virtual void setVOXSource(AnytoneAudioSettingsExtension::VoxSource source); Level dmrMicGain() const; void setDMRMicGain(Level gain); Level maxSpeakerVolume() const; void setMaxSpeakerVolume(Level level); /** Returns the maximum headphone volume. */ virtual Level maxHeadphoneVolume() const; /** Sets the maximum headphone volume. */ virtual void setMaxHeadphoneVolume(Level max); bool enhanceAudio() const; void enableEnhancedAudio(bool enable); bool recording() const; void enableRecording(bool enable); /** Returns the recording delay in ms. */ virtual unsigned recordingDelay() const; /** Sets the recording delay in ms. */ virtual void setRecodringDelay(unsigned ms); AnytoneSettingsExtension::VFOScanType vfoScanType() const; void setVFOScanType(AnytoneSettingsExtension::VFOScanType type); Frequency minVFOScanFrequencyUHF() const; void setMinVFOScanFrequencyUHF(Frequency hz); Frequency maxVFOScanFrequencyUHF() const; void setMaxVFOScanFrequencyUHF(Frequency hz); Frequency minVFOScanFrequencyVHF() const; void setMinVFOScanFrequencyVHF(Frequency hz); Frequency maxVFOScanFrequencyVHF() const; void setMaxVFOScanFrequencyVHF(Frequency hz); AnytoneKeySettingsExtension::KeyFunction funcKeyAShort() const; void setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyBShort() const; void setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyCShort() const; void setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey1Short() const; void setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey2Short() const; void setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyALong() const; void setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyBLong() const; void setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyCLong() const; void setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey1Long() const; void setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey2Long() const; void setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func); Interval longPressDuration() const; void setLongPressDuration(Interval ms); bool vfoModeA() const; void enableVFOModeA(bool enable); bool vfoModeB() const; void enableVFOModeB(bool enable); unsigned memoryZoneA() const; void setMemoryZoneA(unsigned zone); unsigned memoryZoneB() const; void setMemoryZoneB(unsigned zone); bool gps() const; void enableGPS(bool enable); QTimeZone gpsTimeZone() const; void setGPSTimeZone(const QTimeZone &zone); bool getGPSPosition() const; void enableGetGPSPosition(bool enable); bool gpsUnitsImperial() const ; void enableGPSUnitsImperial(bool enable); /** Returns the GPS update period in seconds. */ virtual Interval gpsUpdatePeriod() const; /** Sets the GPS update period in seconds. */ virtual void setGPSUpdatePeriod(Interval sec); bool keyToneEnabled() const; void enableKeyTone(bool enable); bool smsAlert() const; void enableSMSAlert(bool enable); bool callAlert() const; void enableCallAlert(bool enable); bool dmrTalkPermit() const; void enableDMRTalkPermit(bool enable); bool fmTalkPermit() const; void enableFMTalkPermit(bool enable); bool dmrResetTone() const; void enableDMRResetTone(bool enable); bool idleChannelTone() const; void enableIdleChannelTone(bool enable); bool startupTone() const; void enableStartupTone(bool enable); void callToneMelody(Melody &melody) const; void setCallToneMelody(const Melody &melody); void idleToneMelody(Melody &melody) const; void setIdleToneMelody(const Melody &melody); void resetToneMelody(Melody &melody) const; void setResetToneMelody(const Melody &melody); bool activeChannelB() const; void enableActiveChannelB(bool enable); bool subChannel() const; void enableSubChannel(bool enable); Interval menuExitTime() const; void setMenuExitTime(Interval intv); bool callEndPrompt() const; void enableCallEndPrompt(bool enable); bool volumeChangePrompt() const; void enableVolumeChangePrompt(bool enable); AnytoneDisplaySettingsExtension::LastCallerDisplayMode lastCallerDisplayMode() const; void setLastCallerDisplayMode(AnytoneDisplaySettingsExtension::LastCallerDisplayMode mode); bool displayClock() const; void enableDisplayClock(bool enable); bool displayCall() const; void enableDisplayCall(bool enable); AnytoneDisplaySettingsExtension::Color callDisplayColor() const; void setCallDisplayColor(AnytoneDisplaySettingsExtension::Color color); bool showLastHeard() const; void enableShowLastHeard(bool enable); unsigned brightness() const; void setBrightness(unsigned level); /** Returns @c true if the backlight is always on. */ virtual bool backlightPermanent() const; /** Returns the backlight duration in seconds. */ virtual Interval backlightDuration() const; /** Sets the backlight duration in seconds. */ virtual void setBacklightDuration(Interval sec); /** Sets the backlight to permanent (always on). */ virtual void enableBacklightPermanent(); AnytoneAutoRepeaterSettingsExtension::Direction autoRepeaterDirectionA() const; void setAutoRepeaterDirectionA(AnytoneAutoRepeaterSettingsExtension::Direction dir); AnytoneAutoRepeaterSettingsExtension::Direction autoRepeaterDirectionB() const; void setAutoRepeaterDirectionB(AnytoneAutoRepeaterSettingsExtension::Direction dir); bool hasAutoRepeaterOffsetFrequencyIndexUHF() const; unsigned autoRepeaterOffsetFrequencyIndexUHF() const; void setAutoRepeaterOffsetFrequenyIndexUHF(unsigned idx); void clearAutoRepeaterOffsetFrequencyIndexUHF(); bool hasAutoRepeaterOffsetFrequencyIndexVHF() const; unsigned autoRepeaterOffsetFrequencyIndexVHF() const; void setAutoRepeaterOffsetFrequenyIndexVHF(unsigned idx); void clearAutoRepeaterOffsetFrequencyIndexVHF(); Frequency autoRepeaterMinFrequencyVHF() const; void setAutoRepeaterMinFrequencyVHF(Frequency Hz); Frequency autoRepeaterMaxFrequencyVHF() const; void setAutoRepeaterMaxFrequencyVHF(Frequency Hz); Frequency autoRepeaterMinFrequencyUHF() const; void setAutoRepeaterMinFrequencyUHF(Frequency Hz); Frequency autoRepeaterMaxFrequencyUHF() const; void setAutoRepeaterMaxFrequencyUHF(Frequency Hz); bool showCurrentContact() const; void enableShowCurrentContact(bool enable); /** Returns @c true if the key-tone level is adjustable. */ virtual bool keyToneLevelAdjustable() const; /** Returns the key-tone level (0=adjustable). */ virtual Level keyToneLevel() const; /** Sets the key-tone level. */ virtual void setKeyToneLevel(Level level); /** Sets the key-tone level adjustable. */ virtual void setKeyToneLevelAdjustable(); bool knobLock() const; void enableKnobLock(bool enable); bool keypadLock() const; void enableKeypadLock(bool enable); bool sidekeysLock() const; void enableSidekeysLock(bool enable); bool keyLockForced() const; void enableKeyLockForced(bool enable); bool defaultChannel() const; void enableDefaultChannel(bool enable); unsigned defaultZoneIndexA() const; void setDefaultZoneIndexA(unsigned idx); unsigned defaultZoneIndexB() const; void setDefaultZoneIndexB(unsigned idx); bool defaultChannelAIsVFO() const; unsigned defaultChannelAIndex() const; void setDefaultChannelAIndex(unsigned idx); void setDefaultChannelAToVFO(); bool defaultChannelBIsVFO() const; unsigned defaultChannelBIndex() const; void setDefaultChannelBIndex(unsigned idx); void setDefaultChannelBToVFO(); bool keepLastCaller() const; void enableKeepLastCaller(bool enable); bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err); bool updateConfig(Context &ctx, const ErrorStack &err); bool linkSettings(RadioSettings *settings, Context &ctx, const ErrorStack &err); public: /** Some limits for the settings. */ struct Limit: Element::Limit { /** Valid VOX sensitivity levels. */ static constexpr Range vox() { return {1,3}; } /** Valid mic gain settings. */ static constexpr Range micGain() { return {0,4}; } /** Valid settings for the speaker and headphone volume settings. */ static constexpr Range volume() { return {0, 8}; } /** Valid range for key tone volume. */ static constexpr Range keyTone() { return {0,15}; } }; protected: /** Some internal used offsets within the element. */ struct Offset: public AnytoneCodeplug::GeneralSettingsElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int enableKeyTone() { return 0x0000; } static constexpr unsigned int powerSaveMode() { return 0x000b; } static constexpr unsigned int voxLevel() { return 0x000c; } static constexpr unsigned int voxDelay() { return 0x000d; } static constexpr unsigned int vfoScanType() { return 0x000e; } static constexpr unsigned int dmrMicGain() { return 0x000f; } static constexpr unsigned int progFuncKeyAShort() { return 0x0010; } static constexpr unsigned int progFuncKeyBShort() { return 0x0011; } static constexpr unsigned int progFuncKeyCShort() { return 0x0012; } static constexpr unsigned int progFuncKey1Short() { return 0x0013; } static constexpr unsigned int progFuncKey2Short() { return 0x0014; } static constexpr unsigned int vfoModeA() { return 0x0015; } static constexpr unsigned int vfoModeB() { return 0x0016; } static constexpr unsigned int memZoneA() { return 0x001f; } static constexpr unsigned int memZoneB() { return 0x0020; } static constexpr unsigned int enableRecoding() { return 0x0022; } static constexpr unsigned int displayBrightness() { return 0x0026; } static constexpr unsigned int backlightDuration() { return 0x0027; } static constexpr unsigned int gpsEnable() { return 0x0028; } static constexpr unsigned int smsAlert() { return 0x0029; } static constexpr unsigned int activeChannelB() { return 0x002c; } static constexpr unsigned int subChannel() { return 0x002d; } static constexpr unsigned int callAlert() { return 0x002f; } static constexpr unsigned int gpsTimeZone() { return 0x0030; } static constexpr unsigned int talkPermit() { return 0x0031; } static constexpr unsigned int dmrResetTone() { return 0x0032; } static constexpr unsigned int voxSource() { return 0x0033; } static constexpr unsigned int idleChannelTone() { return 0x0036; } static constexpr unsigned int menuExitTime() { return 0x0037; } static constexpr unsigned int startupTone() { return 0x0039; } static constexpr unsigned int callEndPrompt() { return 0x003a; } static constexpr unsigned int maxSpeakerVolume() { return 0x003b; } static constexpr unsigned int getGPSPosition() { return 0x003f; } static constexpr unsigned int progFuncKeyALong() { return 0x0041; } static constexpr unsigned int progFuncKeyBLong() { return 0x0042; } static constexpr unsigned int progFuncKeyCLong() { return 0x0043; } static constexpr unsigned int progFuncKey1Long() { return 0x0044; } static constexpr unsigned int progFuncKey2Long() { return 0x0045; } static constexpr unsigned int longPressDuration() { return 0x0046; } static constexpr unsigned int volumeChangePrompt(){ return 0x0047; } static constexpr unsigned int autoRepeaterDirA() { return 0x0048; } static constexpr unsigned int lastCallerDisplay() { return 0x004d; } static constexpr unsigned int showClock() { return 0x0051; } static constexpr unsigned int maxHeadPhoneVolume(){ return 0x0052; } static constexpr unsigned int enhanceAudio() { return 0x0057; } static constexpr unsigned int minVFOScanUHF() { return 0x0058; } static constexpr unsigned int maxVFOScanUHF() { return 0x005c; } static constexpr unsigned int minVFOScanVHF() { return 0x0060; } static constexpr unsigned int maxVFOScanVHF() { return 0x0064; } static constexpr unsigned int autoRepOffsetUHF() { return 0x0068; } static constexpr unsigned int autoRepOffsetVHF() { return 0x0069; } static constexpr unsigned int callToneTones() { return 0x0072; } static constexpr unsigned int callToneDurations() { return 0x007c; } static constexpr unsigned int idleToneTones() { return 0x0086; } static constexpr unsigned int idleToneDurations() { return 0x0090; } static constexpr unsigned int resetToneTones() { return 0x009a; } static constexpr unsigned int resetToneDurations(){ return 0x00a4; } static constexpr unsigned int recordingDelay() { return 0x00ae; } static constexpr unsigned int callDisplayMode() { return 0x00af; } static constexpr unsigned int callColor() { return 0x00b0; } static constexpr unsigned int gpsPeriod() { return 0x00b1; } static constexpr unsigned int showCurrentContact(){ return 0x00b2; } static constexpr unsigned int keyToneLevel() { return 0x00b3; } static constexpr unsigned int gpsUnits() { return 0x00b4; } static constexpr unsigned int knobLock() { return 0x00b5; } static constexpr unsigned int keypadLock() { return 0x00b5; } static constexpr unsigned int sideKeyLock() { return 0x00b5; } static constexpr unsigned int forceKeyLock() { return 0x00b5; } static constexpr unsigned int showLastHeard() { return 0x00b6; } static constexpr unsigned int autoRepMinVHF() { return 0x00b8; } static constexpr unsigned int autoRepMaxVHF() { return 0x00bc; } static constexpr unsigned int autoRepMinUHF() { return 0x00c0; } static constexpr unsigned int autoRepMaxUHF() { return 0x00c4; } static constexpr unsigned int autoRepeaterDirB() { return 0x00c8; } static constexpr unsigned int defaultChannels() { return 0x00ca; } static constexpr unsigned int defaultZoneA() { return 0x00cb; } static constexpr unsigned int defaultZoneB() { return 0x00cc; } static constexpr unsigned int defaultChannelA() { return 0x00cd; } static constexpr unsigned int defaultChannelB() { return 0x00ce; } static constexpr unsigned int keepLastCaller() { return 0x00cf; } /// @endcond }; }; protected: /** Hidden constructor constructor. */ explicit D868UVCodeplug(const QString &label, QObject *parent = nullptr); public: /** Empty constructor. */ explicit D868UVCodeplug(QObject *parent = nullptr); Config* preprocess(Config *config, const ErrorStack &err) const; protected: bool allocateBitmaps(); virtual void setBitmaps(Context &ctx); virtual void allocateUpdated(); virtual void allocateForDecoding(); virtual void allocateForEncoding(); virtual bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); virtual bool createElements(Context &ctx, const ErrorStack &err=ErrorStack()); virtual bool linkElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocate channels from bitmap. */ virtual void allocateChannels(); /** Encode channels into codeplug. */ virtual bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Create channels from codeplug. */ virtual bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link channels. */ virtual bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocate VFO settings. */ virtual void allocateVFOSettings(); /** Allocate contacts from bitmaps. */ virtual void allocateContacts(); /** Encode contacts into codeplug. */ virtual bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Create contacts from codeplug. */ virtual bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocate analog contacts from bitmaps. */ virtual void allocateAnalogContacts(); /** Encode analog contacts into codeplug. */ virtual bool encodeAnalogContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Create analog contacts from codeplug. */ virtual bool createAnalogContacts(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocate radio IDs from bitmaps. */ virtual void allocateRadioIDs(); /** Encode radio ID into codeplug. */ virtual bool encodeRadioID(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Set radio ID from codeplug. */ virtual bool setRadioID(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates RX group lists from bitmaps. */ virtual void allocateRXGroupLists(); /** Encode RX group lists into codeplug. */ virtual bool encodeRXGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Create RX group lists from codeplug. */ virtual bool createRXGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link RX group lists. */ virtual bool linkRXGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocate zones from bitmaps. */ virtual void allocateZones(); /** Encode zones into codeplug. */ virtual bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Function to encode a single zone. */ virtual bool encodeZone(int i, Zone *zone, const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Create zones from codeplug. */ virtual bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); /** Function to decode a single zone. */ virtual bool decodeZone(int i, Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); /** Link zones. */ virtual bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); /** Function to link a single zone. */ virtual bool linkZone(int i, Zone *zone, bool isB, Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocate scanlists from bitmaps. */ virtual void allocateScanLists(); /** Encode scan lists into codeplug. */ virtual bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Create scan lists from codeplug. */ virtual bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link scan lists. */ virtual bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates general settings memory section. */ virtual void allocateGeneralSettings(); /** Encodes the general settings section. */ virtual bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes the general settings section. */ virtual bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link the general settings. */ virtual bool linkGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates zone channel list memory section. */ virtual void allocateZoneChannelList(); /** Allocates DTMF number list memory section. */ virtual void allocateDTMFNumbers(); /** Allocates boot settings memory section. */ virtual void allocateBootSettings(); /** Encodes the boot settings section. */ virtual bool encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes the boot settings section. */ virtual bool decodeBootSettings(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates GPS settings memory section. */ virtual void allocateGPSSystems(); /** Encodes the GPS settings section. */ virtual bool encodeGPSSystems(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Create GPS systems from codeplug. */ virtual bool createGPSSystems(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link GPS systems. */ virtual bool linkGPSSystems(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocate prefab SMS messages. */ virtual void allocateSMSMessages(); /** Encodes prefab SMS messages. */ virtual bool encodeSMSMessages(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Create SMS messages from codeplug. */ virtual bool createSMSMessages(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link SMS messages. */ virtual bool linkSMSMessages(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates hot key settings memory section. */ virtual void allocateHotKeySettings(); /** Allocates repeater offset settings memory section. */ virtual void allocateRepeaterOffsetFrequencies(); /** Encodes auto-repeater offset frequencies. */ virtual bool encodeRepeaterOffsetFrequencies(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes auto-repeater offset frequencies. */ virtual bool decodeRepeaterOffsetFrequencies(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates DMR encryption keys. */ virtual void allocatDMREncryptionKeys(); /** Encodes DMR encryption keys. */ virtual bool encodeDMREncryptionKeys(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes DMR encryption keys */ virtual bool decodeDMREncryptionKeys(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates 'enhanced' encryption keys. */ virtual void allocatEnhancedEncryptionKeys(); /** Allocates alarm settings memory section. */ virtual void allocateAlarmSettings(); /** Allocates FM broadcast settings memory section. */ virtual void allocateFMBroadcastSettings(); /** Allocates all 5-Tone IDs used. */ virtual void allocate5ToneIDs(); /** Allocates 5-Tone functions. */ virtual void allocate5ToneFunctions(); /** Allocates 5-Tone settings. */ virtual void allocate5ToneSettings(); /** Allocates all 2-Tone IDs used. */ virtual void allocate2ToneIDs(); /** Allocates 2-Tone functions. */ virtual void allocate2ToneFunctions(); /** Allocates 2-Tone settings. */ virtual void allocate2ToneSettings(); /** Allocates DTMF settings. */ virtual void allocateDTMFSettings(); public: /** Some limits for the codeplug. */ struct Limit { static constexpr unsigned int channelsPerBank() { return 128; } ///< Max number of channels per bank. static constexpr unsigned int numChannels() { return 4000; } ///< Max number of channels. static constexpr unsigned int contactsPerBank() { return 1000; } ///< Max number of contacts per bank. static constexpr unsigned int contactsPerBlock() { return 4; } ///< Max number of contacts per block. static constexpr unsigned int numContacts() { return 10000; } ///< Max number of contacts. static constexpr unsigned int numDTMFContacts() { return 128; } ///< Max number of DTMF contacts. static constexpr unsigned int numGroupLists() { return 250; } ///< Max number of group lists. static constexpr unsigned int numScanLists() { return 250; } ///< Max number of scan lists. static constexpr unsigned int numScanListsPerBank() { return 16; } ///< Max number of scan lists per bank. static constexpr unsigned int numRadioIDs() { return 250; } ///< Max number of radio IDs. // There is no zone element -> hence all attributes must be defied within the codeplug. static constexpr unsigned int numZones() { return 250; } ///< Max number of zones. static constexpr unsigned int numChannelsPerZone() { return 250; } ///< Max number of channels per zone. static constexpr unsigned int zoneNameLength() { return 16; } ///< Max zone name length. static constexpr unsigned int dmrAPRSSystems() { return 8; } ///< Max number of DMR APRS systems. static constexpr unsigned int numMessages() { return 100; } ///< Max number of preset SMS. static constexpr unsigned int numMessagePerBank() { return 8; } ///< Max number of SMS per bank. static constexpr unsigned int numTwoToneIDs() { return 24; } ///< Max number of two-tone IDs. static constexpr unsigned int numTwoToneFunctions() { return 16; } ///< Max number of two-tone functions. }; protected: /** Some internal used offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int channelBitmap() { return 0x024c1500; } static constexpr unsigned int channelBanks() { return 0x00800000; } static constexpr unsigned int betweenChannelBanks() { return 0x00040000; } static constexpr unsigned int vfoA() { return 0x00fc0800; } static constexpr unsigned int vfoB() { return 0x00fc0840; } static constexpr unsigned int contactBitmap() { return 0x02640000; } static constexpr unsigned int contactBanks() { return 0x02680000; } static constexpr unsigned int betweenContactBanks() { return 0x00040000; } static constexpr unsigned int betweenContactBlocks() { return 0x00000190; } static constexpr unsigned int contactIndex() { return 0x02600000; } static constexpr unsigned int contactIdTable() { return 0x04340000; } static constexpr unsigned int dtmfContactBytemap() { return 0x02900100; } static constexpr unsigned int dtmfContacts() { return 0x02940000; } static constexpr unsigned int dtmfIndex() { return 0x02900000; } static constexpr unsigned int dtmfSettings() { return 0x024C1080; } static constexpr unsigned int dtmfIDList() { return 0x02500500; } static constexpr unsigned int groupListBitmap() { return 0x025C0B10; } static constexpr unsigned int groupLists() { return 0x02980000; } static constexpr unsigned int betweenGroupLists() { return 0x00000200; } static constexpr unsigned int scanListBitmap() { return 0x024c1340; } static constexpr unsigned int scanListBanks() { return 0x01080000; } static constexpr unsigned int betweenScanLists() { return 0x00000200; } static constexpr unsigned int betweenScanListBanks() { return 0x00040000; } static constexpr unsigned int radioIDBitmap() { return 0x024c1320; } static constexpr unsigned int radioIDs() { return 0x02580000; } static constexpr unsigned int settings() { return 0x02500000; } static constexpr unsigned int zoneChannelList() { return 0x02500100; } static constexpr unsigned int bootSettings() { return 0x02500600; } static constexpr unsigned int aprsSettings() { return 0x02501000; } static constexpr unsigned int dmrAPRSMessage() { return 0x02501100; } static constexpr unsigned int offsetFrequencies() { return 0x024C2000; } static constexpr unsigned int zoneBitmap() { return 0x024c1300; } static constexpr unsigned int zoneChannels() { return 0x01000000; } static constexpr unsigned int betweenZoneChannels() { return 0x00000200; } static constexpr unsigned int zoneNames() { return 0x02540000; } static constexpr unsigned int betweenZoneNames() { return 0x00000020; } static constexpr unsigned int messageBytemap() { return 0x01640800; } static constexpr unsigned int messageBanks() { return 0x02140000; } static constexpr unsigned int betweenMessageBanks() { return 0x00040000; } static constexpr unsigned int messageIndex() { return 0x01640000; } static constexpr unsigned int analogQuickCall() { return 0x025C0000; } static constexpr unsigned int statusMessageBitmap() { return 0x025C0B00; } static constexpr unsigned int statusMessages() { return 0x025C0100; } static constexpr unsigned int hotKeySettings() { return 0x025C0500; } static constexpr unsigned int alarmSettings() { return 0x024C1400; } static constexpr unsigned int alarmSettingsExtension() { return 0x024c1440; } static constexpr unsigned int fiveToneIdBitmap() { return 0x024C0C80; } static constexpr unsigned int fiveToneIdList() { return 0x024C0000; } static constexpr unsigned int fiveToneFunctions() { return 0x024C0D00; } static constexpr unsigned int fiveToneSettings() { return 0x024C1000; } static constexpr unsigned int twoToneIdBitmap() { return 0x024C1280; } static constexpr unsigned int twoToneIdList() { return 0x024C1100; } static constexpr unsigned int twoToneFunctionBitmap(){ return 0x024c2600; } static constexpr unsigned int twoToneFunctionList() { return 0x024c2400; } static constexpr unsigned int twoToneSettings() { return 0x024C1290; } static constexpr unsigned int wfmChannelBitmap() { return 0x02480210; } static constexpr unsigned int wfmChannels() { return 0x02480000; } static constexpr unsigned int wfmVFO() { return 0x02480200; } static constexpr unsigned int dmrEncryptionKeys() { return 0x024C1700; } static constexpr unsigned int enhancedEncryptionKeys() { return 0x024C1800; } /// @endcond }; /** Internal used sizes. Usually sizes are specified by static methods of the element, however, * some stuff is not represented by Elements in the AnyTone codeplug, hence this table is needed. */ struct Size { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int zoneName() { return 0x0020; } static constexpr unsigned int zoneChannels() { return 0x0200; } static constexpr unsigned int messageBank() { return 0x0800; } static constexpr unsigned int messageIndex() { return 0x0010; } /// @endcond }; }; #endif // D868UVCODEPLUG_HH ================================================ FILE: lib/d868uv_filereader.cc ================================================ #include "d868uv_filereader.hh" #include #include "logger.hh" #include "utils.hh" #define HEADER_SIZE 0x000000e2 /* ********************************************************************************************* * * Implementation of D868UVFileReader::ChannelElement * ********************************************************************************************* */ D868UVFileReader::ChannelElement::ChannelElement(const uint8_t *ptr) : Element(ptr), _nameLength(0) { _nameLength = strnlen((char *)_data+0x30,16); } uint16_t D868UVFileReader::ChannelElement::index() const { return qFromLittleEndian(*(uint16_t *)_data + 0x00); } D868UVCodeplug::channel_t::Mode D868UVFileReader::ChannelElement::mode() const { return (D868UVCodeplug::channel_t::Mode) *(_data + 0x0b); } double D868UVFileReader::ChannelElement::rxFrequency() const { return double(qFromLittleEndian(*(uint32_t *)(_data + 0x02)))/1e5; } double D868UVFileReader::ChannelElement::txFrequency() const { double rx = double(qFromLittleEndian(*(uint32_t *)(_data + 0x02)))/1e5; double off = double(qFromLittleEndian(*(uint32_t *)(_data + 0x07)))/1e5; D868UVCodeplug::channel_t::RepeaterMode repeater_type = (D868UVCodeplug::channel_t::RepeaterMode)*(_data+0x06); switch(repeater_type) { case D868UVCodeplug::channel_t::RM_TXPOS: return rx+off; case D868UVCodeplug::channel_t::RM_TXNEG: return rx-off; case D868UVCodeplug::channel_t::RM_SIMPLEX: default: break; } return rx; } Channel::Power D868UVFileReader::ChannelElement::power() const { D868UVCodeplug::channel_t::Power pwr = (D868UVCodeplug::channel_t::Power) * (_data + 0x0c); switch(pwr) { case D868UVCodeplug::channel_t::POWER_LOW: return Channel::LowPower; case D868UVCodeplug::channel_t::POWER_MIDDLE: return Channel::MidPower; case D868UVCodeplug::channel_t::POWER_HIGH: return Channel::HighPower; case D868UVCodeplug::channel_t::POWER_TURBO: return Channel::MaxPower; default: break; } return Channel::MinPower; } AnalogChannel::Bandwidth D868UVFileReader::ChannelElement::bandwidth() const { D868UVCodeplug::channel_t::Bandwidth bw = (D868UVCodeplug::channel_t::Bandwidth) * (_data + 0x0d); return (D868UVCodeplug::channel_t::BW_25_KHZ == bw) ? AnalogChannel::BWWide : AnalogChannel::BWNarrow; } bool D868UVFileReader::ChannelElement::rxOnly() const { return *(_data + 0x0f); } Signaling::Code D868UVFileReader::ChannelElement::rxSignaling() const { ///@todo Implement. return Signaling::SIGNALING_NONE; } Signaling::Code D868UVFileReader::ChannelElement::txSignaling() const { ///@todo Implement. return Signaling::SIGNALING_NONE; } DigitalChannel::Admit D868UVFileReader::ChannelElement::admitDigital() const { D868UVCodeplug::channel_t::Admit admit = (D868UVCodeplug::channel_t::Admit) * (_data + 0x20); switch (admit) { case D868UVCodeplug::channel_t::ADMIT_CH_FREE: return DigitalChannel::AdmitFree; case D868UVCodeplug::channel_t::ADMIT_COLORCODE: return DigitalChannel::AdmitColorCode; case D868UVCodeplug::channel_t::ADMIT_ALWAYS: default: break; } return DigitalChannel::AdmitNone; } AnalogChannel::Admit D868UVFileReader::ChannelElement::admitAnalog() const { D868UVCodeplug::channel_t::Admit admit = (D868UVCodeplug::channel_t::Admit) * (_data + 0x20); switch (admit) { case D868UVCodeplug::channel_t::ADMIT_CH_FREE: return AnalogChannel::AdmitFree; case D868UVCodeplug::channel_t::ADMIT_ALWAYS: default: break; } return AnalogChannel::AdmitNone; } uint8_t D868UVFileReader::ChannelElement::colorCode() const { return *(_data + 0x28); } DigitalChannel::TimeSlot D868UVFileReader::ChannelElement::timeSlot() const { return (0==(*(_data + 0x2a))) ? DigitalChannel::TimeSlot1 : DigitalChannel::TimeSlot2; } QString D868UVFileReader::ChannelElement::name() const { return QString::fromLocal8Bit((char *)(_data + 0x30), _nameLength); } size_t D868UVFileReader::ChannelElement::size() const { return 0x30 + _nameLength+1 + 0x14; } /* ********************************************************************************************* * * Implementation of D868UVFileReader::RadioIDElement * ********************************************************************************************* */ D868UVFileReader::RadioIDElement::RadioIDElement(const uint8_t *ptr) : AnytoneFileReader::Element(ptr) { _nameLength = strnlen((char *)(_data+0x04), 16); } uint8_t D868UVFileReader::RadioIDElement::index() const { return *(_data + 0x00); } uint32_t D868UVFileReader::RadioIDElement::id() const { uint32_t a=_data[1], b=_data[2], c=_data[3]; return ((c<<16) | (b<<8) | a); } QString D868UVFileReader::RadioIDElement::name() const { return QString::fromLocal8Bit((char *)(_data+4), _nameLength); } size_t D868UVFileReader::RadioIDElement::size() const { return 4 + _nameLength+1; } /* ********************************************************************************************* * * Implementation of D868UVFileReader::ZoneElement * ********************************************************************************************* */ D868UVFileReader::ZoneElement::ZoneElement(const uint8_t *ptr) : AnytoneFileReader::Element(ptr) { _numChannels = *(_data + 0x01); _nameLength = strnlen((char *)(_data+0x02 + 2*_numChannels + 2*2), 16); } uint8_t D868UVFileReader::ZoneElement::index() const { return *(_data+0x00); } uint8_t D868UVFileReader::ZoneElement::numChannels() const { return _numChannels; } uint16_t D868UVFileReader::ZoneElement::channel(uint8_t index) const { return qFromLittleEndian(*(uint16_t *)(_data + 0x02 + 2*index)); } QString D868UVFileReader::ZoneElement::name() const { return QString::fromLocal8Bit((char *)(_data+0x02+2*_numChannels+2*2), _nameLength); } size_t D868UVFileReader::ZoneElement::size() const { return 0x02 + 2*_numChannels + 2*2 + _nameLength+1; } /* ********************************************************************************************* * * Implementation of D868UVFileReader::ScanListElement * ********************************************************************************************* */ D868UVFileReader::ScanListElement::ScanListElement(const uint8_t *ptr) : AnytoneFileReader::Element(ptr) { _nameLength = strnlen((char *)(_data+0x01), 16); _numChannels = *(_data + 0x01 + _nameLength+1 + 0x0b); } uint8_t D868UVFileReader::ScanListElement::index() const { return *(_data+0x00); } uint8_t D868UVFileReader::ScanListElement::numChannels() const { return _numChannels; } D868UVCodeplug::scanlist_t::PriChannel D868UVFileReader::ScanListElement::prioChannelSelect() const { return (D868UVCodeplug::scanlist_t::PriChannel)(* (_data + 0x01 + _nameLength+1 + 0x01)); } uint16_t D868UVFileReader::ScanListElement::prioChannel1() const { return qFromLittleEndian((uint16_t)(* (_data + 0x01 + _nameLength+1 + 0x02))); } uint16_t D868UVFileReader::ScanListElement::prioChannel2() const { return qFromLittleEndian((uint16_t)(* (_data + 0x01 + _nameLength+1 + 0x04))); } uint16_t D868UVFileReader::ScanListElement::channel(uint8_t index) const { return qFromLittleEndian(*(uint16_t *)(_data + 0x01 + _nameLength+1 + 0x0b + 0x03 + 2*index)); } QString D868UVFileReader::ScanListElement::name() const { return QString::fromLocal8Bit((char *)(_data+1), _nameLength); } size_t D868UVFileReader::ScanListElement::size() const { return 0x0f + 2*_numChannels + _nameLength+1; } /* ********************************************************************************************* * * Implementation of D868UVFileReader::AnalogContactElement * ********************************************************************************************* */ D868UVFileReader::AnalogContactElement::AnalogContactElement(const uint8_t *ptr) : AnytoneFileReader::Element(ptr) { _numberLength = *(_data+1); _nameLength = strnlen((char *)(_data+0x02+_numberLength), 16); } uint8_t D868UVFileReader::AnalogContactElement::index() const { return *(_data + 0x00); } QString D868UVFileReader::AnalogContactElement::number() const { return QString::fromLocal8Bit((char *)(_data+0x02), _numberLength); } QString D868UVFileReader::AnalogContactElement::name() const { return QString::fromLocal8Bit((char *)(_data+0x02+_numberLength), _nameLength); } size_t D868UVFileReader::AnalogContactElement::size() const { return 0x02 + _numberLength + _nameLength+1; } /* ********************************************************************************************* * * Implementation of D868UVFileReader * ********************************************************************************************* */ D868UVFileReader::D868UVFileReader(Config *config, const uint8_t *data, size_t size, QString &message) : AnytoneFileReader(config, data, size, message) { // pass... } bool D868UVFileReader::readHeader() { // Header content is ignored, only advance pointer to end of header _data += HEADER_SIZE; return true; } bool D868UVFileReader::linkHeader() { // Header content is ignored, only advance pointer to end of header _data += HEADER_SIZE; return true; } bool D868UVFileReader::readChannels() { // Read number of channels and advance pointer uint16_t numChannels = qFromLittleEndian(*(uint16_t *)_data); _data += 0x02; // Read each channel for (uint16_t i=0; ireadChannel()) return false; } return true; } bool D868UVFileReader::readChannel() { ChannelElement channel(_data); // Assemble channel Channel *ch = nullptr; if (D868UVCodeplug::channel_t::MODE_ANALOG == channel.mode()) { ch = new AnalogChannel( channel.name(), channel.rxFrequency(), channel.txFrequency(), channel.power(), 0, channel.rxOnly(), channel.admitAnalog(), 2, channel.rxSignaling(), channel.txSignaling(), channel.bandwidth(), nullptr, nullptr, nullptr); } else if (D868UVCodeplug::channel_t::MODE_DIGITAL == channel.mode()) { ch = new DigitalChannel( channel.name(), channel.rxFrequency(), channel.txFrequency(), channel.power(), 0, channel.rxOnly(), channel.admitDigital(), channel.colorCode(), channel.timeSlot(), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr, nullptr); } // Store channel in context & config if not VFO channel if ((4000 != channel.index()) && (4001 != channel.index())) { logDebug() << QString("0x%1").arg(uint(_data-_start), 8,16,QChar('0')) << ": Add channel '" << channel.name() << "'."; _context.addChannel(ch, channel.index()); } // Advance pointer _data += channel.size(); return true; } bool D868UVFileReader::linkChannels() { // Get number of channels and advance pointer uint16_t numChannels = qFromLittleEndian(*(uint16_t *)_data); _data += 0x02; // Link each channel for (uint16_t i=0; ilinkChannel()) return false; } return true; } bool D868UVFileReader::linkChannel() { ChannelElement channel(_data); // Advance pointer _data += channel.size(); return true; } bool D868UVFileReader::readRadioIDs() { // Read number of radio IDs and advance pointer uint8_t numIDs = *_data; _data += 1; // Read each radio ID for (uint8_t i=0; ireadRadioID()) return false; } return true; } bool D868UVFileReader::readRadioID() { RadioIDElement radioid(_data); // Assemble radio ID & add to context/config if (0 == radioid.index()) { _context.setDefaultRadioId(radioid.id(), radioid.index()); _context.config()->setName(radioid.name()); logDebug() << QString("0x%1").arg(uint(_data-_start), 8,16,QChar('0')) << ": Set default radio ID " << radioid.id() << " ('" << radioid.name() << "')."; } else { _context.addRadioId(radioid.id(), radioid.index()); logDebug() << QString("0x%1").arg(uint(_data-_start), 8,16,QChar('0')) << ": Add radio ID " << radioid.id() << " ('" << radioid.name() << "')."; } // Advance pointer _data += radioid.size(); return true; } bool D868UVFileReader::linkRadioIDs() { // Read number of radio IDs and advance pointer uint8_t numIDs = *_data; _data += 1; // skip each radio ID for (uint8_t i=0; ireadZone()) return false; } return true; } bool D868UVFileReader::readZone() { ZoneElement zone(_data); // Assemble Zone & add to context/config Zone *z = new Zone(zone.name()); if (! _context.config()->zones()->addZone(z)) { _message = QObject::tr("0x%1: Cannot add zone to config.").arg(uint(_data-_start), 8,16,QChar('0')); return false; } for (uint8_t i=0; iA()->addChannel(_context.getChannel(zone.channel(i))); logDebug() << "Add channel '" << _context.getChannel(zone.channel(i))->name() << "' to zone '" << zone.name() << "'."; } logDebug() << QString("0x%1").arg(uint(_data-_start), 8,16,QChar('0')) << ": Add zone '" << zone.name() << "'."; // Advance pointer _data += zone.size(); return true; } bool D868UVFileReader::linkZones() { // Read number of zones and advance pointer uint8_t numZones = *_data; _data += 1; // Skip each zone, zones are linked during creation for (uint8_t i=0; ireadScanList()) return false; } return true; } bool D868UVFileReader::readScanList() { ScanListElement sl(_data); ScanList *scanlist = new ScanList(sl.name()); _context.addScanList(scanlist, sl.index()); logDebug() << QString("0x%1").arg(uint(_data-_start), 8,16,QChar('0')) << ": Add scan list '" << sl.name() << "'."; for (uint8_t i=0; iaddChannel(_context.getChannel(sl.channel(i))); logDebug() << "Add channel '" << _context.getChannel(sl.channel(i))->name() << "' to zone '" << sl.name() << "'."; if ((D868UVCodeplug::scanlist_t::PRIO_CHAN_SEL1 & sl.prioChannelSelect()) && (0 != sl.prioChannel1())) { if (1 == sl.prioChannel1()) scanlist->setPriorityChannel(SelectedChannel::get()); else if (_context.hasChannel(sl.prioChannel1()-2)) scanlist->setPriorityChannel(_context.getChannel(sl.prioChannel1()-2)); else logWarn() << QString("0x%1").arg(uint(_data-_start), 8,16,QChar('0')) << "Cannot link priority channel index " << (sl.prioChannel1()-2) << " in scanlist '" << sl.name() << "'."; } if ((D868UVCodeplug::scanlist_t::PRIO_CHAN_SEL2 & sl.prioChannelSelect()) && (0 != sl.prioChannel2())) { if (1 == sl.prioChannel2()) scanlist->setSecPriorityChannel(SelectedChannel::get()); else if (_context.hasChannel(sl.prioChannel2()-2)) scanlist->setSecPriorityChannel(_context.getChannel(sl.prioChannel2()-2)); else logWarn() << QString("0x%1").arg(uint(_data-_start), 8,16,QChar('0')) << "Cannot link sec. priority channel index " << (sl.prioChannel2()-2) << " in scanlist '" << sl.name() << "'."; } } return true; } bool D868UVFileReader::linkScanLists() { // Read number of scan lists and advance pointer uint8_t numScanlists = *_data; _data += 1; // Skip each scan list, scan lists are linked during creation for (uint8_t i=0; ireadAnalogContact(); } return true; } bool D868UVFileReader::readAnalogContact() { AnalogContactElement ac(_data); if (validDTMFNumber(ac.number())) { DTMFContact *contact = new DTMFContact(ac.name(), ac.number()); _context.addAnalogContact(contact, ac.index()); } else { logWarn() << QString("0x%1").arg(uint(_data-_start), 8,16,QChar('0')) << ": Skip analog contact '" << ac.name() << ". Invalid DTMF number '" << ac.number() << "'."; } _data += ac.size(); return true; } bool D868UVFileReader::linkAnalogContacts() { // Read number of scan lists and advance pointer uint8_t numContacts = *_data; _data += 1; // Skip each analog contact, analog contacts need no linking for (uint8_t i=0; i > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent) : AnytoneLimits(hardwareRevision, "V102", true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 200000; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(D868UVCodeplug::BootSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 250, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum { (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, 0, 128, new RadioLimitObject { { "name", new RadioLimitString(1, 15, RadioLimitString::ASCII) }, { "number", new RadioLimitString(1, 14, RadioLimitString::DTMF) } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1,16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1, 64) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 4000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored()} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false)}, {"aprs", new RadioLimitObjRef(DMRAPRSSystem::staticMetaObject, true)}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get()) }, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 250, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } })); /* Ignore positioning systems. */ add("positioning", new RadioLimitList({ { DMRAPRSSystem::staticMetaObject, 0, 1, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitUInt(0, 7650) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, -1, new RadioLimitIgnored() } } ) ); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); } ================================================ FILE: lib/d868uv_limits.hh ================================================ #ifndef D868UVLIMITS_HH #define D868UVLIMITS_HH #include "anytone_limits.hh" /** Implements the limits for the AnyTone AT-D878UV. * @ingroup d868uv */ class D868UVLimits: public AnytoneLimits { Q_OBJECT public: /** Constructor. */ D868UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent=nullptr); }; #endif // D868UVLIMITS_HH ================================================ FILE: lib/d878uv.cc ================================================ #include "userdatabase.hh" #include "d878uv.hh" #include "config.hh" #include "logger.hh" #include "d878uv_codeplug.hh" #include "d878uv_limits.hh" // uses same callsign db as 878 #include "d868uv_callsigndb.hh" #define RBSIZE 16 #define WBSIZE 16 D878UV::D878UV(AnytoneInterface *device, QObject *parent) : AnytoneRadio("Anytone AT-D878UV", device, parent), _limits(nullptr) { _codeplug = new D878UVCodeplug(this); _codeplug->clear(); _callsigns = new D868UVCallsignDB(this); // Get device info and determine supported TX frequency bands AnytoneInterface::RadioVariant info; if (_dev) _dev->getInfo(info); switch (info.bands) { case 0x00: case 0x01: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x02: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x03: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x04: _limits = new D878UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(438.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(438.)} }, info.version, this); break; case 0x05: _limits = new D878UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(437.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(437.)} }, info.version, this); break; case 0x06: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x07: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x08: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x09: _limits = new D878UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, info.version, this); break; case 0x0a: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x0b: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x0c: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(490.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(490.)} }, info.version, this); break; case 0x0d: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x0e: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x0f: _limits = new D878UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x10: _limits = new D878UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(147.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(147.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x11: _limits = new D878UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)} }, info.version, this); break; default: logInfo() << "Unknown band-code" << QString::number(int(info.bands), 16) << ": Do not check frequency range."; _limits = new D878UVLimits({}, {}, info.version, this); break; } } const RadioLimits & D878UV::limits() const { return *_limits; } RadioInfo D878UV::defaultRadioInfo() { return RadioInfo( RadioInfo::D878UV, "d878uv", "AT-D878UV", "AnyTone", {AnytoneGD32Interface::interfaceInfo()}); } ================================================ FILE: lib/d878uv.hh ================================================ /** @defgroup d878uv Anytone AT-D878UV * Device specific classes for Anytone AT-D878UV. * * \image html d878uv.jpg "AT-D878UV" width=200px * \image latex d878uv.jpg "AT-D878UV" width=200px * * @ingroup anytone */ #ifndef __D878UV_HH__ #define __D878UV_HH__ #include "anytone_radio.hh" #include "anytone_interface.hh" /** Implements an interface to Anytone AT-D878UV VHF/UHF 7W DMR (Tier I & II) radios. * * The reverse-engineering of the D878UVCodeplug was quiet hard as it is huge and the radio * provides a lot of bells and whistles. Moreover, the binary code-plug file created by the * windows CPS does not directly relate to the data being written to the device. These two issues * (a lot of features and a huge code-plug) require that the transfer of the code-plug to the * device is performed in 4 steps. * * First only the bitmaps of all lists are downloaded from the device. Then all elements that are * not touched or only updated by the common code-plug config are downloaded. Then, the common * config gets applied to the binary code-plug. That is, all channels, contacts, zones, group-lists * and scan-lists are generated and their bitmaps gets updated accordingly. Also the general config * gets updated from the common code-plug settings. Finally, the resulting binary code-plug gets * written back to the device. * * This rather complex method of writing a code-plug to the device is needed to maintain all * settings within the radio that are not defined within the common code-plug config while keeping * the amount of data being read from and written to the device small. * * @ingroup d878uv */ class D878UV: public AnytoneRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit D878UV(AnytoneInterface *device=nullptr, QObject *parent=nullptr); const RadioLimits &limits() const; /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: RadioLimits *_limits; }; #endif // __D878UV_HH__ ================================================ FILE: lib/d878uv2.cc ================================================ #include "userdatabase.hh" #include "d878uv2.hh" #include "config.hh" #include "logger.hh" #include "d878uv2_limits.hh" #include "d878uv2_codeplug.hh" #include "d878uv2_callsigndb.hh" #define RBSIZE 16 #define WBSIZE 16 D878UV2::D878UV2(AnytoneInterface *device, QObject *parent) : AnytoneRadio("Anytone AT-D878UVII", device, parent), _limits(nullptr) { _codeplug = new D878UV2Codeplug(this); _codeplug->clear(); _callsigns = new D878UV2CallsignDB(this); // Get device info and determine supported TX frequency bands AnytoneInterface::RadioVariant info; if (_dev) _dev->getInfo(info); switch (info.bands) { case 0x00: case 0x01: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x02: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x03: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x04: _limits = new D878UV2Limits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(438.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(438.)} }, info.version, this); break; case 0x05: _limits = new D878UV2Limits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(437.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(434.), Frequency::fromMHz(437.)} }, info.version, this); break; case 0x06: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x07: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x08: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x09: _limits = new D878UV2Limits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, info.version, this); break; case 0x0a: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x0b: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x0c: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(490.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(490.)} }, info.version, this); break; case 0x0d: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x0e: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(220.),Frequency::fromMHz(225.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x0f: _limits = new D878UV2Limits({ {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(520.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(520.)} }, info.version, this); break; case 0x10: _limits = new D878UV2Limits({ {Frequency::fromMHz(144.), Frequency::fromMHz(147.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(147.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x11: _limits = new D878UV2Limits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)} }, info.version, this); break; default: logInfo() << "Unknown band-code" << QString::number(int(info.bands), 16) << ": Do not check frequency range."; _limits = new D878UV2Limits({}, {}, info.version, this); break; } } const RadioLimits & D878UV2::limits() const { return *_limits; } RadioInfo D878UV2::defaultRadioInfo() { return RadioInfo( RadioInfo::D878UVII, "d878uv2", "AT-D878UVII", "AnyTone", {AnytoneGD32Interface::interfaceInfo()}); } ================================================ FILE: lib/d878uv2.hh ================================================ /** @defgroup d878uv2 Anytone AT-D878UVII * Device specific classes for Anytone AT-D878UVII. * * \image html d878uv.jpg "AT-D878UV" width=200px * \image latex d878uv.jpg "AT-D878UV" width=200px * * @ingroup anytone */ #ifndef __D878UV2_HH__ #define __D878UV2_HH__ #include "anytone_radio.hh" #include "anytone_interface.hh" #include "d878uv2_callsigndb.hh" /** Implements an interface to Anytone AT-D878UVII VHF/UHF 7W DMR (Tier I & II) radios. * * The reverse-engineering of the D878UVCodeplug was quiet hard as it is huge and the radio * provides a lot of bells and whistles. Moreover, the binary code-plug file created by the * windows CPS does not directly relate to the data being written to the device. These two issues * (a lot of features and a huge code-plug) require that the transfer of the code-plug to the * device is performed in 4 steps. * * First only the bitmaps of all lists are downloaded from the device. Then all elements that are * not touched or only updated by the common code-plug config are downloaded. Then, the common * config gets applied to the binary code-plug. That is, all channels, contacts, zones, group-lists * and scan-lists are generated and their bitmaps gets updated accordingly. Also the general config * gets updated from the common code-plug settings. Finally, the resulting binary code-plug gets * written back to the device. * * This rather complex method of writing a code-plug to the device is needed to maintain all * settings within the radio that are not defined within the common code-plug config while keeping * the amount of data being read from and written to the device small. * * @ingroup d878uv2 */ class D878UV2: public AnytoneRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit D878UV2(AnytoneInterface *device=nullptr, QObject *parent=nullptr); const RadioLimits &limits() const; /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: /** The limits for the radio. */ RadioLimits *_limits; }; #endif // __D878UV2_HH__ ================================================ FILE: lib/d878uv2_callsigndb.cc ================================================ #include "userdatabase.hh" #include "d878uv2_callsigndb.hh" #include "logger.hh" #include "utils.hh" #include /* ********************************************************************************************* * * Implementation of D878UVCallsignDB * ********************************************************************************************* */ D878UV2CallsignDB::D878UV2CallsignDB(QObject *parent) : D868UVCallsignDB(parent) { // pass... } bool D878UV2CallsignDB::encode(UserDatabase *db, const Flags &selection, const ErrorStack &err) { Q_UNUSED(err) // Determine size of call-sign DB in memory qint64 n = std::min(db->count(), qint64(Limit::entries())); // If DB size is limited by settings if (selection.hasCountLimit()) n = std::min(n, (qint64)selection.countLimit()); logDebug() << "Encode " << n << " entries."; // Select n users and sort them in ascending order of their IDs QVector users; users.reserve(n); for (unsigned i=0; iuser(i)); std::sort(users.begin(), users.end(), [](const UserDatabase::User &a, const UserDatabase::User &b) { return a.id < b.id; }); logDebug() << "Encode call-signs from " << users.first().id << ": " << users.first().call << ", " << users.first().name << " in " << users.first().city << " to " << users.last().id << ": " << users.last().call << ", " << users.last().name << " in " << users.last().city << "."; // Compute total size of callsign db entries size_t dbSize = 0; size_t indexSize = n*IndexEntryElement::size(); for (qint64 i=0; i * Callsign database * Start Size Content * 04000000 variable Index of callsign entries. Follows the same * weird format as @c D868UVCodeplug::contact_map_t. Sorted by ID. Empty entries set to * 0xffffffffffffffff. * 04840000 000010 Database limits, see @c limits_t. * 05500000 variable The actual DB entries, each entry is of * variable size but shares the same header, see @c entry_t. Order arbitrary. * Filled with 0x00. * * * @ingroup d878uv2 */ class D878UV2CallsignDB : public D868UVCallsignDB { Q_OBJECT public: /** Same index entry used by the codeplug to map normal digital contacts to an contact index. Here * it maps to the byte offset within the database entries. */ typedef D868UVCodeplug::ContactMapElement IndexEntryElement; public: /** Constructor, does not allocate any memory yet. */ explicit D878UV2CallsignDB(QObject *parent=nullptr); /** Tries to encode as many entries of the given user-database. */ bool encode(UserDatabase *db, const Flags &selection=Flags(), const ErrorStack &err=ErrorStack()); public: /** Some limits of the call-sign DB. */ struct Limit : public D868UVCallsignDB::Limit { /// Specifies the max number of entries in the call-sign DB. */ static constexpr unsigned int entries() { return 500000; } }; protected: /** Some internal offsets within the call-sign DB. */ struct Offset : public D868UVCallsignDB::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int callsigns() { return 0x05500000; } static constexpr unsigned int limits() { return 0x04840000; } /// @endcond }; }; #endif // D868UVCALLSIGNDB_HH ================================================ FILE: lib/d878uv2_codeplug.cc ================================================ #include "gpssystem.hh" #include "d878uv2_codeplug.hh" #include "utils.hh" #include "channel.hh" #include "config.h" #include #include /* ******************************************************************************************** * * Implementation of D878UV2Codeplug * ******************************************************************************************** */ D878UV2Codeplug::D878UV2Codeplug(const QString &label, QObject *parent) : D878UVCodeplug(label, parent) { // pass... } D878UV2Codeplug::D878UV2Codeplug(QObject *parent) : D878UVCodeplug("AnyTone AT-D878UVII Codeplug", parent) { // pass... } /* The address of the contact ID<->Index table has changed hence allocation and encoding must * be reimplemented. Otherwise, everything remains the same. */ void D878UV2Codeplug::allocateContacts() { /* Allocate contacts */ ContactBitmapElement contact_bitmap(data(Offset::contactBitmap())); unsigned contactCount=0; for (uint16_t i=0; i contacts; // Encode contacts and also collect id<->index map for (unsigned int i=0; i(); i++) { uint32_t bank_addr = Offset::contactBanks() + (i/Limit::contactsPerBank())*Offset::betweenContactBanks(); uint32_t addr = bank_addr + (i%Limit::contactsPerBank())*ContactElement::size(); ContactElement con(data(addr)); DMRContact *contact = ctx.get(i); if(! con.fromContactObj(contact, ctx)) return false; ((uint32_t *)data(Offset::contactIndex()))[i] = qToLittleEndian(i); contacts.append(contact); } // encode index map for contacts std::sort(contacts.begin(), contacts.end(), [](DMRContact *a, DMRContact *b) { return a->number() < b->number(); }); for (int i=0; inumber(), (DMRContact::GroupCall==contacts[i]->type())); el.setIndex(ctx.index(contacts[i])); } return true; } ================================================ FILE: lib/d878uv2_codeplug.hh ================================================ #ifndef D878UV2_CODEPLUG_HH #define D878UV2_CODEPLUG_HH #include #include "d878uv_codeplug.hh" class Channel; class DMRContact; class Zone; class RXGroupList; class ScanList; class DMRAPRSSystem; /** Represents the device specific binary codeplug for AnyTone AT-D878UVII radios. * * This class only implements the difference to the AT-D878UV codeplug. In fact there is only a * difference in the address of the contact ID<->Index map. * * @ingroup d878uv2 */ class D878UV2Codeplug : public D878UVCodeplug { Q_OBJECT protected: /** Hidden constructor. */ explicit D878UV2Codeplug(const QString &label, QObject *parent = nullptr); public: /** Empty constructor. */ explicit D878UV2Codeplug(QObject *parent = nullptr); protected: void allocateContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Internal used offsets within the codeplug. */ struct Offset: public D878UVCodeplug::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int contactIdTable() { return 0x04800000; } /// @endcond }; }; #endif // D878UVCODEPLUG_HH ================================================ FILE: lib/d878uv2_limits.cc ================================================ #include "d878uv2_limits.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "d878uv2_codeplug.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingzone.hh" D878UV2Limits::D878UV2Limits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent) : AnytoneLimits(hardwareRevision, "V101", true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 500000; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), unsigned(Channel::Power::Max)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(D878UV2Codeplug::BootSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 250, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, 0, 128, new RadioLimitObject { { "name", new RadioLimitString(1, 15, RadioLimitString::ASCII) }, { "number", new RadioLimitString(1, 14, RadioLimitString::DTMF) } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1, 64) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 4000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), unsigned(Channel::Power::Max)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRef(FMAPRSSystem::staticMetaObject)}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), unsigned(Channel::Power::Max)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, true)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false)}, {"aprs", new RadioLimitObjRef(PositionReportingSystem::staticMetaObject, true)}, {"roaming", new RadioLimitObjRef(RoamingZone::staticMetaObject, true) }, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 250, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } })); /* Handle positioning systems. */ add("positioning", new RadioLimitList{ { DMRAPRSSystem::staticMetaObject, 0, 8, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, 1, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, FMChannel::staticMetaObject}, false) }, { "icon", new RadioLimitEnum{} }, { "message", new RadioLimitString(0, 60, RadioLimitString::ASCII) } ///@todo extend APRSSystem to expose other settings as properties. }} } ); /* Handle roaming zones. */ add("roaming", new RadioLimitList(RoamingZone::staticMetaObject, 0, 64, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "channels", new RadioLimitRefList(0, 64, DMRChannel::staticMetaObject) } } ) ); } ================================================ FILE: lib/d878uv2_limits.hh ================================================ #ifndef D878UV2LIMITS_HH #define D878UV2LIMITS_HH #include "anytone_limits.hh" /** Implements the limits for the AnyTone AT-D878UV2. * @ingroup d878uv2 */ class D878UV2Limits: public AnytoneLimits { Q_OBJECT public: /** Constructor. */ D878UV2Limits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent=nullptr); }; #endif // D878UV2LIMITS_HH ================================================ FILE: lib/d878uv_codeplug.cc ================================================ #include "gpssystem.hh" #include "roamingchannel.hh" #include "d878uv_codeplug.hh" #include "config.hh" #include "utils.hh" #include "channel.hh" #include "config.h" #include "logger.hh" #include "channel.hh" #include "intermediaterepresentation.hh" #include #include /* ******************************************************************************************** * * Implementation of D878UVCodeplug::NameColor * ******************************************************************************************** */ AnytoneDisplaySettingsExtension::Color D878UVCodeplug::NameColor::decode(uint8_t code) { switch((CodedColor) code) { case White: return AnytoneDisplaySettingsExtension::Color::White; case Orange: return AnytoneDisplaySettingsExtension::Color::Orange; case Red: return AnytoneDisplaySettingsExtension::Color::Red; case Yellow: return AnytoneDisplaySettingsExtension::Color::Yellow; case Green: return AnytoneDisplaySettingsExtension::Color::Green; case Turquoise: return AnytoneDisplaySettingsExtension::Color::Turquoise; case Blue: return AnytoneDisplaySettingsExtension::Color::Blue; default: break; } return AnytoneDisplaySettingsExtension::Color::White; } uint8_t D878UVCodeplug::NameColor::encode(AnytoneDisplaySettingsExtension::Color color) { switch(color) { case AnytoneDisplaySettingsExtension::Color::White: return (uint8_t) White; case AnytoneDisplaySettingsExtension::Color::Orange: return (uint8_t) Orange; case AnytoneDisplaySettingsExtension::Color::Red: return (uint8_t) Red; case AnytoneDisplaySettingsExtension::Color::Yellow: return (uint8_t) Yellow; case AnytoneDisplaySettingsExtension::Color::Green: return (uint8_t) Green; case AnytoneDisplaySettingsExtension::Color::Turquoise: return (uint8_t) Turquoise; case AnytoneDisplaySettingsExtension::Color::Blue: return (uint8_t) Blue; default: break; } return (uint8_t) White; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::TextColor * ******************************************************************************************** */ AnytoneDisplaySettingsExtension::Color D878UVCodeplug::TextColor::decode(uint8_t code) { switch((CodedColor) code) { case White: return AnytoneDisplaySettingsExtension::Color::White; case Orange: return AnytoneDisplaySettingsExtension::Color::Orange; case Red: return AnytoneDisplaySettingsExtension::Color::Red; case Yellow: return AnytoneDisplaySettingsExtension::Color::Yellow; case Green: return AnytoneDisplaySettingsExtension::Color::Green; case Turquoise: return AnytoneDisplaySettingsExtension::Color::Turquoise; case Blue: return AnytoneDisplaySettingsExtension::Color::Blue; default: break; } return AnytoneDisplaySettingsExtension::Color::White; } uint8_t D878UVCodeplug::TextColor::encode(AnytoneDisplaySettingsExtension::Color color) { switch(color) { case AnytoneDisplaySettingsExtension::Color::White: return (uint8_t) White; case AnytoneDisplaySettingsExtension::Color::Orange: return (uint8_t) Orange; case AnytoneDisplaySettingsExtension::Color::Red: return (uint8_t) Red; case AnytoneDisplaySettingsExtension::Color::Yellow: return (uint8_t) Yellow; case AnytoneDisplaySettingsExtension::Color::Green: return (uint8_t) Green; case AnytoneDisplaySettingsExtension::Color::Turquoise: return (uint8_t) Turquoise; case AnytoneDisplaySettingsExtension::Color::Blue: return (uint8_t) Blue; default: break; } return (uint8_t) White; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::ChannelElement * ******************************************************************************************** */ D878UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, unsigned size) : D868UVCodeplug::ChannelElement(ptr, size) { // pass... } D878UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : D868UVCodeplug::ChannelElement(ptr, ChannelElement::size()) { // pass... } void D878UVCodeplug::ChannelElement::clear() { D868UVCodeplug::ChannelElement::clear(); setPTTIDSetting(PTTId::Off); } D878UVCodeplug::ChannelElement::PTTId D878UVCodeplug::ChannelElement::pttIDSetting() const { return (PTTId)getUInt2(Offset::pttIDSetting(), 0); } void D878UVCodeplug::ChannelElement::setPTTIDSetting(PTTId ptt) { setUInt2(Offset::pttIDSetting(), 0, (unsigned)ptt); } bool D878UVCodeplug::ChannelElement::roamingEnabled() const { // inverted return !getBit(Offset::roaming()); } void D878UVCodeplug::ChannelElement::enableRoaming(bool enable) { // inverted setBit(Offset::roaming(), !enable); } bool D878UVCodeplug::ChannelElement::dataACK() const { // inverted return !getBit(Offset::dataACK()); } void D878UVCodeplug::ChannelElement::enableDataACK(bool enable) { // inverted setBit(Offset::dataACK(), !enable); } bool D878UVCodeplug::ChannelElement::autoScan() const { return getBit(Offset::autoScan()); } void D878UVCodeplug::ChannelElement::enableAutoScan(bool enable) { setBit(Offset::autoScan(), enable); } D878UVCodeplug::ChannelElement::APRSType D878UVCodeplug::ChannelElement::txAPRSType() const { return (APRSType)getUInt8(Offset::txAPRSType()); } void D878UVCodeplug::ChannelElement::setTXAPRSType(APRSType aprsType) { setUInt8(Offset::txAPRSType(), (uint8_t)aprsType); } AnytoneChannelExtension::APRSPTT D878UVCodeplug::ChannelElement::analogAPRSPTTSetting() const { switch ((APRSPTT)getUInt8(Offset::fmAPRSPTTSetting())) { case APRSPTT::Off: return AnytoneChannelExtension::APRSPTT::Off; case APRSPTT::Start: return AnytoneChannelExtension::APRSPTT::Start; case APRSPTT::End: return AnytoneChannelExtension::APRSPTT::End; } return AnytoneChannelExtension::APRSPTT::Start; } void D878UVCodeplug::ChannelElement::setAnalogAPRSPTTSetting(AnytoneChannelExtension::APRSPTT ptt) { // There is no start/end option. Either send it or not. If enabled -> encode as "at start". switch (ptt) { case AnytoneChannelExtension::APRSPTT::Off: setUInt8(Offset::fmAPRSPTTSetting(), (unsigned int)APRSPTT::Off); break; case AnytoneChannelExtension::APRSPTT::Start: setUInt8(Offset::fmAPRSPTTSetting(), (unsigned int)APRSPTT::Start); break; case AnytoneChannelExtension::APRSPTT::End: setUInt8(Offset::fmAPRSPTTSetting(), (unsigned int)APRSPTT::End); break; } } AnytoneChannelExtension::APRSPTT D878UVCodeplug::ChannelElement::digitalAPRSPTTSetting() const { return getUInt8(Offset::dmrAPRSPTTSetting()) ? AnytoneChannelExtension::APRSPTT::Start : AnytoneChannelExtension::APRSPTT::Off; } void D878UVCodeplug::ChannelElement::setDigitalAPRSPTTSetting(AnytoneChannelExtension::APRSPTT ptt) { // There is no start/end option. Either send it or not. If enabled -> encode as "at start". switch (ptt) { case AnytoneChannelExtension::APRSPTT::Off: setUInt8(Offset::dmrAPRSPTTSetting(), 0); break; case AnytoneChannelExtension::APRSPTT::Start: case AnytoneChannelExtension::APRSPTT::End: setUInt8(Offset::dmrAPRSPTTSetting(), 1); break; } } unsigned D878UVCodeplug::ChannelElement::digitalAPRSSystemIndex() const { return getUInt8(Offset::dmrAPRSSystemIndex()); } void D878UVCodeplug::ChannelElement::setDigitalAPRSSystemIndex(unsigned idx) { setUInt8(Offset::dmrAPRSSystemIndex(), idx); } int D878UVCodeplug::ChannelElement::frequenyCorrection() const { return ((int)getInt8(Offset::frequenyCorrection()))*10; } void D878UVCodeplug::ChannelElement::setFrequencyCorrection(int corr) { setInt8(Offset::frequenyCorrection(), corr/10); } unsigned int D878UVCodeplug::ChannelElement::fmAPRSFrequencyIndex() const { return getUInt8(Offset::fmAPRSFrequencyIndex()); } void D878UVCodeplug::ChannelElement::setFMAPRSFrequencyIndex(unsigned int idx) { setUInt8(Offset::fmAPRSFrequencyIndex(), std::min(7U, idx)); } bool D878UVCodeplug::ChannelElement::sendTalkerAlias() const { return getBit(Offset::talkerAlias()); } void D878UVCodeplug::ChannelElement::enableSendTalkerAlias(bool enable) { setBit(Offset::talkerAlias(), enable); } D878UVCodeplug::ChannelElement::AdvancedEncryptionType D878UVCodeplug::ChannelElement::advancedEncryptionType() const { return getBit(Offset::dmrEncryptionType()) ? AdvancedEncryptionType::ARC4 : AdvancedEncryptionType::AES; } void D878UVCodeplug::ChannelElement::setEncryptionType(AdvancedEncryptionType type) { setBit(Offset::dmrEncryptionType(), AdvancedEncryptionType::ARC4 == type); } D878UVCodeplug::ChannelElement::DMREncryptionType D878UVCodeplug::ChannelElement::dmrEncryptionType() const { return DMREncryptionType::Basic; } void D878UVCodeplug::ChannelElement::setDMREncryptionType(DMREncryptionType type) { Q_UNUSED(type); } bool D878UVCodeplug::ChannelElement::hasDMREncryptionKeyIndex() const { return false; } unsigned D878UVCodeplug::ChannelElement::dmrEncryptionKeyIndex() const { return 0xff; } void D878UVCodeplug::ChannelElement::setDMREncryptionKeyIndex(unsigned idx) { Q_UNUSED(idx); } void D878UVCodeplug::ChannelElement::clearDMREncryptionKeyIndex() { // pass... } bool D878UVCodeplug::ChannelElement::adaptiveTDMA() const { // Removed feature return false; } void D878UVCodeplug::ChannelElement::enableAdaptiveTDMA(bool enable) { Q_UNUSED(enable); // removed feature. } bool D878UVCodeplug::ChannelElement::hasAESEncryptionKeyIndex() const { return 0 != getUInt8(Offset::aesKeyIndex()); } unsigned D878UVCodeplug::ChannelElement::aesEncryptionKeyIndex() const { return getUInt8(Offset::aesKeyIndex()) - 1; } void D878UVCodeplug::ChannelElement::setAESEncryptionKeyIndex(unsigned idx) { setUInt8(Offset::aesKeyIndex(), idx+1); } void D878UVCodeplug::ChannelElement::clearAESEncryptionKeyIndex() { setUInt8(Offset::aesKeyIndex(), 0); } bool D878UVCodeplug::ChannelElement::hasARC4EncryptionKeyIndex() const { return 0 != getUInt8(Offset::arc4KeyIndex()); } unsigned D878UVCodeplug::ChannelElement::arc4EncryptionKeyIndex() const { return getUInt8(Offset::arc4KeyIndex()) - 1; } void D878UVCodeplug::ChannelElement::setARC4EncryptionKeyIndex(unsigned idx) { setUInt8(Offset::arc4KeyIndex(), idx+1); } void D878UVCodeplug::ChannelElement::clearARC4EncryptionKeyIndex() { setUInt8(Offset::arc4KeyIndex(), 0); } Channel * D878UVCodeplug::ChannelElement::toChannelObj(Context &ctx) const { Channel *ch = D868UVCodeplug::ChannelElement::toChannelObj(ctx); if (nullptr == ch) return nullptr; // Get extensions AnytoneChannelExtension *ext = nullptr; if (DMRChannel *dch = ch->as()) { ext = dch->anytoneChannelExtension(); } else if (FMChannel *fch = ch->as()){ ext = fch->anytoneChannelExtension(); } // If extension is present, update if (nullptr != ext) { ext->setFrequencyCorrection(frequenyCorrection()); if (APRSType::FM == txAPRSType()) ext->setAPRSPTT(analogAPRSPTTSetting()); else if (APRSType::DMR == txAPRSType()) ext->setAPRSPTT(digitalAPRSPTTSetting()); else ext->setAPRSPTT(AnytoneChannelExtension::APRSPTT::Off); } return ch; } bool D878UVCodeplug::ChannelElement::linkChannelObj(Channel *c, Context &ctx) const { if (! AnytoneCodeplug::ChannelElement::linkChannelObj(c, ctx)) return false; if (c->is()) { DMRChannel *dc = c->as(); // Link to GPS system if ((APRSType::DMR == txAPRSType()) && ctx.has(digitalAPRSSystemIndex())) dc->setAPRS(ctx.get(digitalAPRSSystemIndex())); // Link APRS system if one is defined // There can only be one active APRS system, hence the index is fixed to one. if ((APRSType::FM == txAPRSType()) && ctx.has(0)) dc->setAPRS(ctx.get(0)); // If roaming is not disabled -> link to default roaming zone if (roamingEnabled()) dc->setRoaming(DefaultRoamingZone::get()); if (auto *ext = dc->anytoneChannelExtension()) { // If not default FM APRS frequency if (0 != fmAPRSFrequencyIndex()) { if (ctx.has(fmAPRSFrequencyIndex())) ext->fmAPRSFrequency()->set(ctx.get(fmAPRSFrequencyIndex())); } } bool hasExtension = ctx.config()->settings()->anytoneExtension(); bool hasStrongEncryption = hasExtension && (AnytoneDMRSettingsExtension::EncryptionType::AES == ctx.config()->settings()->anytoneExtension()->dmrSettings()->encryption()); if (hasAESEncryptionKeyIndex()) { auto cex = dc->commercialExtension(); if (nullptr == cex) dc->setCommercialExtension(cex = new CommercialChannelExtension()); if (hasStrongEncryption && (AdvancedEncryptionType::AES == advancedEncryptionType())) { if (! ctx.has(aesEncryptionKeyIndex())) { logWarn() << "Cannot link encryption key: no AES key with index " << aesEncryptionKeyIndex() << " defined."; } else { cex->setEncryptionKey(ctx.get(aesEncryptionKeyIndex())); } } } else if (hasARC4EncryptionKeyIndex()) { auto cex = dc->commercialExtension(); if (nullptr == cex) dc->setCommercialExtension(cex = new CommercialChannelExtension()); if (hasStrongEncryption && (AdvancedEncryptionType::ARC4 == advancedEncryptionType())) { if (! ctx.has(arc4EncryptionKeyIndex())) { logWarn() << "Cannot link encryption key: no ARC4 key with index " << arc4EncryptionKeyIndex() << " defined."; } else { cex->setEncryptionKey(ctx.get(arc4EncryptionKeyIndex())); } } } else if (hasDMREncryptionKeyIndex()) { auto cex = dc->commercialExtension(); if (nullptr == cex) dc->setCommercialExtension(cex = new CommercialChannelExtension()); if ((! hasStrongEncryption) && (D868UVCodeplug::ChannelElement::DMREncryptionType::Basic == D868UVCodeplug::ChannelElement::dmrEncryptionType())){ if (! ctx.has(dmrEncryptionKeyIndex())) { logWarn() << "Cannot link encryption key: no basic DMR key with index " << dmrEncryptionKeyIndex() << " defined."; } else { cex->setEncryptionKey(ctx.get(dmrEncryptionKeyIndex())); } } } } else if (c->is()) { FMChannel *ac = c->as(); // Link APRS system if one is defined // There can only be one active APRS system, hence the index is fixed to one. if ((APRSType::FM == txAPRSType()) && ctx.has(0)) ac->setAPRS(ctx.get(0)); if (auto *ext = ac->anytoneChannelExtension()) { // If not default FM APRS frequency if (0 != fmAPRSFrequencyIndex()) { if (ctx.has(fmAPRSFrequencyIndex())) ext->fmAPRSFrequency()->set(ctx.get(fmAPRSFrequencyIndex())); } } } return true; } bool D878UVCodeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx) { if (! AnytoneCodeplug::ChannelElement::fromChannelObj(c, ctx)) return false; AnytoneChannelExtension *ch_ext = nullptr; if (const DMRChannel *dc = c->as()) { // Set GPS system index setTXAPRSType(APRSType::Off); enableRXAPRS(false); if (dc->aprs() && dc->aprs()->is()) { enableRXAPRS(true); setTXAPRSType(APRSType::DMR); setDigitalAPRSSystemIndex(ctx.index(dc->aprs()->as())); } else if (dc->aprs() && dc->aprs()->is()) { setTXAPRSType(APRSType::FM); } // Enable roaming if (dc->roamingRef()) enableRoaming(true); enableDataACK(dc->extended()->dataConfirm()); /// Handles bug in AnyTone firmware. /// @todo Remove once fixed by AnyTone. enableRXAPRS(! dc->extended()->sms()); clearDMREncryptionKeyIndex(); clearAESEncryptionKeyIndex(); clearARC4EncryptionKeyIndex(); // By default, we assume we have strong encryption unless otherwise set by AnyTone DMR extension. bool hasStrongEncryption = (! ctx.config()->settings()->anytoneExtension()) || ( ctx.config()->settings()->anytoneExtension() && (AnytoneDMRSettingsExtension::EncryptionType::AES == ctx.config()->settings()->anytoneExtension()->dmrSettings()->encryption()) ); // Apply commercial extension if (CommercialChannelExtension *cex = dc->commercialExtension()) { if (hasStrongEncryption && cex->encryptionKey() && cex->encryptionKey()->is()) { setEncryptionType(AdvancedEncryptionType::AES); setAESEncryptionKeyIndex(ctx.index(cex->encryptionKey())); } else if (hasStrongEncryption && cex->encryptionKey() && cex->encryptionKey()->is()) { setEncryptionType(AdvancedEncryptionType::ARC4); setARC4EncryptionKeyIndex(ctx.index(cex->encryptionKey())); } else if ((! hasStrongEncryption) && cex->encryptionKey() && cex->encryptionKey()->is()) { D868UVCodeplug::ChannelElement::setDMREncryptionType( D868UVCodeplug::ChannelElement::DMREncryptionType::Basic); setDMREncryptionKeyIndex(ctx.index(cex->encryptionKey())); } } } else if (const FMChannel *ac = c->as()) { // Set APRS system enableRXAPRS(false); setTXAPRSType(APRSType::Off); if (nullptr != ac->aprs()) { setTXAPRSType(APRSType::FM); if (ac == ac->aprs()->revertChannel()) { enableRXAPRS(true); } } // Apply extension settings if (AnytoneFMChannelExtension *ext = ac->anytoneChannelExtension()) { ch_ext = ext; if (! ext->fmAPRSFrequency()->isNull()) { int idx = ctx.index(ext->fmAPRSFrequency()->as()); if ((0 <= idx) && (7 >= idx)) setFMAPRSFrequencyIndex(idx); else setFMAPRSFrequencyIndex(0); } else { // Use default setFMAPRSFrequencyIndex(0); } } } // Apply common channel extension if (nullptr != ch_ext) { setFrequencyCorrection(ch_ext->frequencyCorrection()); if (APRSType::DMR == txAPRSType()) setDigitalAPRSPTTSetting(ch_ext->aprsPTT()); else if (APRSType::FM == txAPRSType()) setAnalogAPRSPTTSetting(ch_ext->aprsPTT()); } return true; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::ChannelExtensionElement * ******************************************************************************************** */ D878UVCodeplug::ChannelExtensionElement::ChannelExtensionElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } D878UVCodeplug::ChannelExtensionElement::ChannelExtensionElement(uint8_t *ptr) : Element(ptr, size()) { /// pass... } void D878UVCodeplug::ChannelExtensionElement::clear() { Element::clear(); memset(_data, 0, size()); } unsigned int D878UVCodeplug::ChannelExtensionElement::bot5ToneIDIndex() const { return getUInt8(Offset::bot5ToneIDIndex()); } void D878UVCodeplug::ChannelExtensionElement::setBOT5ToneIDIndex(unsigned int idx) { setUInt8(Offset::bot5ToneIDIndex(), idx); } unsigned int D878UVCodeplug::ChannelExtensionElement::eot5ToneIDIndex() const { return getUInt8(Offset::eot5ToneIDIndex()); } void D878UVCodeplug::ChannelExtensionElement::setEOT5ToneIDIndex(unsigned int idx) { setUInt8(Offset::eot5ToneIDIndex(), idx); } unsigned int D878UVCodeplug::ChannelExtensionElement::txColorCode() const { return getUInt8(Offset::txColorCode()); } void D878UVCodeplug::ChannelExtensionElement::setTXColorCode(unsigned int cc) { setUInt8(Offset::txColorCode(), cc); } bool D878UVCodeplug::ChannelExtensionElement::updateChannelObj(Channel *c, Context &ctx) const { Q_UNUSED(c); Q_UNUSED(ctx); return true; } bool D878UVCodeplug::ChannelExtensionElement::linkChannelObj(Channel *c, Context &ctx) const { Q_UNUSED(c); Q_UNUSED(ctx); return true; } bool D878UVCodeplug::ChannelExtensionElement::fromChannelObj(const Channel *c, Context &ctx) { Q_UNUSED(ctx); if (c->is()) { auto dmr = c->as(); setTXColorCode(dmr->colorCode()); } return true; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::RoamingChannelElement * ******************************************************************************************** */ D878UVCodeplug::RoamingChannelElement::RoamingChannelElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } D878UVCodeplug::RoamingChannelElement::RoamingChannelElement(uint8_t *ptr) : Element(ptr, RoamingChannelElement::size()) { // pass... } void D878UVCodeplug::RoamingChannelElement::clear() { memset(_data, 0x00, _size); } unsigned D878UVCodeplug::RoamingChannelElement::rxFrequency() const { return getBCD8_be(Offset::rxFrequency())*10; } void D878UVCodeplug::RoamingChannelElement::setRXFrequency(unsigned hz) { setBCD8_be(Offset::rxFrequency(), hz/10); } unsigned D878UVCodeplug::RoamingChannelElement::txFrequency() const { return getBCD8_be(Offset::txFrequency())*10; } void D878UVCodeplug::RoamingChannelElement::setTXFrequency(unsigned hz) { setBCD8_be(Offset::txFrequency(), hz/10); } bool D878UVCodeplug::RoamingChannelElement::hasColorCode() const { return ColorCodeValue::Disabled != getUInt8(Offset::colorCode()); } unsigned D878UVCodeplug::RoamingChannelElement::colorCode() const { return std::min(15u, (unsigned)getUInt8(Offset::colorCode())); } void D878UVCodeplug::RoamingChannelElement::setColorCode(unsigned cc) { setUInt8(Offset::colorCode(), cc); } void D878UVCodeplug::RoamingChannelElement::disableColorCode() { setUInt8(Offset::colorCode(), ColorCodeValue::Disabled); } DMRChannel::TimeSlot D878UVCodeplug::RoamingChannelElement::timeSlot() const { switch (getUInt8(Offset::timeSlot())) { case TimeSlotValue::TS1: return DMRChannel::TimeSlot::TS1; case TimeSlotValue::TS2: return DMRChannel::TimeSlot::TS2; } return DMRChannel::TimeSlot::TS1; } void D878UVCodeplug::RoamingChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { switch (ts) { case DMRChannel::TimeSlot::TS1: setUInt8(Offset::timeSlot(), TimeSlotValue::TS1); break; case DMRChannel::TimeSlot::TS2: setUInt8(Offset::timeSlot(), TimeSlotValue::TS2); break; } } QString D878UVCodeplug::RoamingChannelElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void D878UVCodeplug::RoamingChannelElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } bool D878UVCodeplug::RoamingChannelElement::fromChannel(const RoamingChannel* ch) { setName(ch->name()); setRXFrequency(ch->rxFrequency().inHz()); setTXFrequency(ch->txFrequency().inHz()); if (ch->colorCodeOverridden()) setColorCode(ch->colorCode()); else disableColorCode(); setTimeSlot(ch->timeSlot()); return true; } RoamingChannel * D878UVCodeplug::RoamingChannelElement::toChannel(Context &ctx) { RoamingChannel *roam = new RoamingChannel(); roam->setName(name()); roam->setRXFrequency(Frequency::fromHz(rxFrequency())); roam->setTXFrequency(Frequency::fromHz(txFrequency())); if (hasColorCode()) roam->setColorCode(colorCode()); else roam->overrideColorCode(false); roam->overrideTimeSlot(true); roam->setTimeSlot(timeSlot()); ctx.config()->roamingChannels()->add(roam); return roam; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::RoamingChannelBitmapElement * ******************************************************************************************** */ D878UVCodeplug::RoamingChannelBitmapElement::RoamingChannelBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } D878UVCodeplug::RoamingChannelBitmapElement::RoamingChannelBitmapElement(uint8_t *ptr) : BitmapElement(ptr, RoamingChannelBitmapElement::size()) { // pass... } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::RoamingZoneElement * ******************************************************************************************** */ D878UVCodeplug::RoamingZoneElement::RoamingZoneElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } D878UVCodeplug::RoamingZoneElement::RoamingZoneElement(uint8_t *ptr) : Element(ptr, RoamingZoneElement::size()) { // pass... } void D878UVCodeplug::RoamingZoneElement::clear() { memset(_data, 0x00, _size); memset(_data+Offset::members(), 0xff, Limit::numMembers()); } bool D878UVCodeplug::RoamingZoneElement::hasMember(unsigned n) const { return (0xff != member(n)); } unsigned D878UVCodeplug::RoamingZoneElement::member(unsigned n) const { return getUInt8(Offset::members() + n*Offset::betweenMembers()); } void D878UVCodeplug::RoamingZoneElement::setMember(unsigned n, unsigned idx) { if (n >= Limit::numMembers()) return; setUInt8(Offset::members() + n*Offset::betweenMembers(), idx); } void D878UVCodeplug::RoamingZoneElement::clearMember(unsigned n) { if (n >= Limit::numMembers()) return; setMember(n, 0xff); } QString D878UVCodeplug::RoamingZoneElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void D878UVCodeplug::RoamingZoneElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } bool D878UVCodeplug::RoamingZoneElement::fromRoamingZone(RoamingZone *zone, Context &ctx, const ErrorStack& err) { Q_UNUSED(err) clear(); setName(zone->name()); for (unsigned int i=0; icount()); i++) { setMember(i, ctx.index(zone->channel(i))); } return true; } RoamingZone * D878UVCodeplug::RoamingZoneElement::toRoamingZone(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); return new RoamingZone(name()); } bool D878UVCodeplug::RoamingZoneElement::linkRoamingZone(RoamingZone *zone, Context &ctx, const ErrorStack &err) { for (uint8_t i=0; (i(member(i))) { zone->addChannel(ctx.get(member(i))); } else { errMsg(err) << "Cannot link roaming zone '" << zone->name() << "': Roaming channel index " << member(i) << " is not defined."; return false; } } return true; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::RoamingZoneBitmapElement * ******************************************************************************************** */ D878UVCodeplug::RoamingZoneBitmapElement::RoamingZoneBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } D878UVCodeplug::RoamingZoneBitmapElement::RoamingZoneBitmapElement(uint8_t *ptr) : BitmapElement(ptr, RoamingZoneBitmapElement::size()) { // pass... } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::GeneralSettingsElement::KeyFunction * ******************************************************************************************** */ uint8_t D878UVCodeplug::GeneralSettingsElement::KeyFunction::encode(AnytoneKeySettingsExtension::KeyFunction func) { switch (func) { case AnytoneKeySettingsExtension::KeyFunction::Off: return (uint8_t)KeyFunction::Off; case AnytoneKeySettingsExtension::KeyFunction::Voltage: return (uint8_t)KeyFunction::Voltage; case AnytoneKeySettingsExtension::KeyFunction::Power: return (uint8_t)KeyFunction::Power; case AnytoneKeySettingsExtension::KeyFunction::Repeater: return (uint8_t)KeyFunction::Repeater; case AnytoneKeySettingsExtension::KeyFunction::Reverse: return (uint8_t)KeyFunction::Reverse; case AnytoneKeySettingsExtension::KeyFunction::Encryption: return (uint8_t)KeyFunction::Encryption; case AnytoneKeySettingsExtension::KeyFunction::Call: return (uint8_t)KeyFunction::Call; case AnytoneKeySettingsExtension::KeyFunction::VOX: return (uint8_t)KeyFunction::VOX; case AnytoneKeySettingsExtension::KeyFunction::ToggleVFO: return (uint8_t)KeyFunction::ToggleVFO; case AnytoneKeySettingsExtension::KeyFunction::SubPTT: return (uint8_t)KeyFunction::SubPTT; case AnytoneKeySettingsExtension::KeyFunction::Scan: return (uint8_t)KeyFunction::Scan; case AnytoneKeySettingsExtension::KeyFunction::WFM: return (uint8_t)KeyFunction::WFM; case AnytoneKeySettingsExtension::KeyFunction::Alarm: return (uint8_t)KeyFunction::Alarm; case AnytoneKeySettingsExtension::KeyFunction::RecordSwitch: return (uint8_t)KeyFunction::RecordSwitch; case AnytoneKeySettingsExtension::KeyFunction::Record: return (uint8_t)KeyFunction::Record; case AnytoneKeySettingsExtension::KeyFunction::SMS: return (uint8_t)KeyFunction::SMS; case AnytoneKeySettingsExtension::KeyFunction::Dial: return (uint8_t)KeyFunction::Dial; case AnytoneKeySettingsExtension::KeyFunction::GPSInformation: return (uint8_t)KeyFunction::GPSInformation; case AnytoneKeySettingsExtension::KeyFunction::Monitor: return (uint8_t)KeyFunction::Monitor; case AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel: return (uint8_t)KeyFunction::ToggleMainChannel; case AnytoneKeySettingsExtension::KeyFunction::HotKey1: return (uint8_t)KeyFunction::HotKey1; case AnytoneKeySettingsExtension::KeyFunction::HotKey2: return (uint8_t)KeyFunction::HotKey2; case AnytoneKeySettingsExtension::KeyFunction::HotKey3: return (uint8_t)KeyFunction::HotKey3; case AnytoneKeySettingsExtension::KeyFunction::HotKey4: return (uint8_t)KeyFunction::HotKey4; case AnytoneKeySettingsExtension::KeyFunction::HotKey5: return (uint8_t)KeyFunction::HotKey5; case AnytoneKeySettingsExtension::KeyFunction::HotKey6: return (uint8_t)KeyFunction::HotKey6; case AnytoneKeySettingsExtension::KeyFunction::WorkAlone: return (uint8_t)KeyFunction::WorkAlone; case AnytoneKeySettingsExtension::KeyFunction::SkipChannel: return (uint8_t)KeyFunction::SkipChannel; case AnytoneKeySettingsExtension::KeyFunction::DMRMonitor: return (uint8_t)KeyFunction::DMRMonitor; case AnytoneKeySettingsExtension::KeyFunction::SubChannel: return (uint8_t)KeyFunction::SubChannel; case AnytoneKeySettingsExtension::KeyFunction::PriorityZone: return (uint8_t)KeyFunction::PriorityZone; case AnytoneKeySettingsExtension::KeyFunction::VFOScan: return (uint8_t)KeyFunction::VFOScan; case AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality: return (uint8_t)KeyFunction::MICSoundQuality; case AnytoneKeySettingsExtension::KeyFunction::LastCallReply: return (uint8_t)KeyFunction::LastCallReply; case AnytoneKeySettingsExtension::KeyFunction::ChannelType: return (uint8_t)KeyFunction::ChannelType; case AnytoneKeySettingsExtension::KeyFunction::Ranging: return (uint8_t)KeyFunction::Ranging; case AnytoneKeySettingsExtension::KeyFunction::Roaming: return (uint8_t)KeyFunction::Roaming; case AnytoneKeySettingsExtension::KeyFunction::ChannelRanging: return (uint8_t)KeyFunction::ChannelRanging; case AnytoneKeySettingsExtension::KeyFunction::MaxVolume: return (uint8_t)KeyFunction::MaxVolume; case AnytoneKeySettingsExtension::KeyFunction::Slot: return (uint8_t)KeyFunction::Slot; case AnytoneKeySettingsExtension::KeyFunction::APRSTypeSwitch: return (uint8_t)KeyFunction::APRSType; case AnytoneKeySettingsExtension::KeyFunction::Zone: return (uint8_t)KeyFunction::Zone; case AnytoneKeySettingsExtension::KeyFunction::RoamingSet: return (uint8_t)KeyFunction::RoamingSet; case AnytoneKeySettingsExtension::KeyFunction::APRSSet: return (uint8_t)KeyFunction::APRSSet; case AnytoneKeySettingsExtension::KeyFunction::Mute: return (uint8_t)KeyFunction::Mute; case AnytoneKeySettingsExtension::KeyFunction::CtcssDcsSet: return (uint8_t)KeyFunction::CtcssDcsSet; case AnytoneKeySettingsExtension::KeyFunction::TBSTSend: return (uint8_t)KeyFunction::TBSTSend; case AnytoneKeySettingsExtension::KeyFunction::Bluetooth: return (uint8_t)KeyFunction::Bluetooth; case AnytoneKeySettingsExtension::KeyFunction::GPS: return (uint8_t)KeyFunction::GPS; case AnytoneKeySettingsExtension::KeyFunction::ChannelName: return (uint8_t)KeyFunction::ChannelName; case AnytoneKeySettingsExtension::KeyFunction::CDTScan: return (uint8_t)KeyFunction::CDTScan; case AnytoneKeySettingsExtension::KeyFunction::APRSSend: return (uint8_t)KeyFunction::APRSSend; case AnytoneKeySettingsExtension::KeyFunction::APRSInfo: return (uint8_t)KeyFunction::APRSInfo; case AnytoneKeySettingsExtension::KeyFunction::GPSRoaming: return (uint8_t)KeyFunction::GPSRoaming; case AnytoneKeySettingsExtension::KeyFunction::DIMShut: return (uint8_t)KeyFunction::DIMShut; case AnytoneKeySettingsExtension::KeyFunction::Squelch: return (uint8_t)KeyFunction::Squelch; default: return (uint8_t)KeyFunction::Off; } } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::KeyFunction::decode(uint8_t code) { switch ((KeyFunctionCode)code) { case KeyFunction::Off: return AnytoneKeySettingsExtension::KeyFunction::Off; case KeyFunction::Voltage: return AnytoneKeySettingsExtension::KeyFunction::Voltage; case KeyFunction::Power: return AnytoneKeySettingsExtension::KeyFunction::Power; case KeyFunction::Repeater: return AnytoneKeySettingsExtension::KeyFunction::Repeater; case KeyFunction::Reverse: return AnytoneKeySettingsExtension::KeyFunction::Reverse; case KeyFunction::Encryption: return AnytoneKeySettingsExtension::KeyFunction::Encryption; case KeyFunction::Call: return AnytoneKeySettingsExtension::KeyFunction::Call; case KeyFunction::VOX: return AnytoneKeySettingsExtension::KeyFunction::VOX; case KeyFunction::ToggleVFO: return AnytoneKeySettingsExtension::KeyFunction::ToggleVFO; case KeyFunction::SubPTT: return AnytoneKeySettingsExtension::KeyFunction::SubPTT; case KeyFunction::Scan: return AnytoneKeySettingsExtension::KeyFunction::Scan; case KeyFunction::WFM: return AnytoneKeySettingsExtension::KeyFunction::WFM; case KeyFunction::Alarm: return AnytoneKeySettingsExtension::KeyFunction::Alarm; case KeyFunction::RecordSwitch: return AnytoneKeySettingsExtension::KeyFunction::RecordSwitch; case KeyFunction::Record: return AnytoneKeySettingsExtension::KeyFunction::Record; case KeyFunction::SMS: return AnytoneKeySettingsExtension::KeyFunction::SMS; case KeyFunction::Dial: return AnytoneKeySettingsExtension::KeyFunction::Dial; case KeyFunction::GPSInformation: return AnytoneKeySettingsExtension::KeyFunction::GPSInformation; case KeyFunction::Monitor: return AnytoneKeySettingsExtension::KeyFunction::Monitor; case KeyFunction::ToggleMainChannel: return AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel; case KeyFunction::HotKey1: return AnytoneKeySettingsExtension::KeyFunction::HotKey1; case KeyFunction::HotKey2: return AnytoneKeySettingsExtension::KeyFunction::HotKey2; case KeyFunction::HotKey3: return AnytoneKeySettingsExtension::KeyFunction::HotKey3; case KeyFunction::HotKey4: return AnytoneKeySettingsExtension::KeyFunction::HotKey4; case KeyFunction::HotKey5: return AnytoneKeySettingsExtension::KeyFunction::HotKey5; case KeyFunction::HotKey6: return AnytoneKeySettingsExtension::KeyFunction::HotKey6; case KeyFunction::WorkAlone: return AnytoneKeySettingsExtension::KeyFunction::WorkAlone; case KeyFunction::SkipChannel: return AnytoneKeySettingsExtension::KeyFunction::SkipChannel; case KeyFunction::DMRMonitor: return AnytoneKeySettingsExtension::KeyFunction::DMRMonitor; case KeyFunction::SubChannel: return AnytoneKeySettingsExtension::KeyFunction::SubChannel; case KeyFunction::PriorityZone: return AnytoneKeySettingsExtension::KeyFunction::PriorityZone; case KeyFunction::VFOScan: return AnytoneKeySettingsExtension::KeyFunction::VFOScan; case KeyFunction::MICSoundQuality: return AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality; case KeyFunction::LastCallReply: return AnytoneKeySettingsExtension::KeyFunction::LastCallReply; case KeyFunction::ChannelType: return AnytoneKeySettingsExtension::KeyFunction::ChannelType; case KeyFunction::Ranging: return AnytoneKeySettingsExtension::KeyFunction::Ranging; case KeyFunction::Roaming: return AnytoneKeySettingsExtension::KeyFunction::Roaming; case KeyFunction::ChannelRanging: return AnytoneKeySettingsExtension::KeyFunction::ChannelRanging; case KeyFunction::MaxVolume: return AnytoneKeySettingsExtension::KeyFunction::MaxVolume; case KeyFunction::Slot: return AnytoneKeySettingsExtension::KeyFunction::Slot; case KeyFunction::APRSType: return AnytoneKeySettingsExtension::KeyFunction::APRSTypeSwitch; case KeyFunction::Zone: return AnytoneKeySettingsExtension::KeyFunction::Zone; case KeyFunction::RoamingSet: return AnytoneKeySettingsExtension::KeyFunction::RoamingSet; case KeyFunction::APRSSet: return AnytoneKeySettingsExtension::KeyFunction::APRSSet; case KeyFunction::Mute: return AnytoneKeySettingsExtension::KeyFunction::Mute; case KeyFunction::CtcssDcsSet: return AnytoneKeySettingsExtension::KeyFunction::CtcssDcsSet; case KeyFunction::TBSTSend: return AnytoneKeySettingsExtension::KeyFunction::TBSTSend; case KeyFunction::Bluetooth: return AnytoneKeySettingsExtension::KeyFunction::Bluetooth; case KeyFunction::GPS: return AnytoneKeySettingsExtension::KeyFunction::GPS; case KeyFunction::ChannelName: return AnytoneKeySettingsExtension::KeyFunction::ChannelName; case KeyFunction::CDTScan: return AnytoneKeySettingsExtension::KeyFunction::CDTScan; case KeyFunction::APRSSend: return AnytoneKeySettingsExtension::KeyFunction::APRSSend; case KeyFunction::APRSInfo: return AnytoneKeySettingsExtension::KeyFunction::APRSInfo; case KeyFunction::GPSRoaming: return AnytoneKeySettingsExtension::KeyFunction::GPSRoaming; case KeyFunction::DIMShut: return AnytoneKeySettingsExtension::KeyFunction::DIMShut; case KeyFunction::Squelch: return AnytoneKeySettingsExtension::KeyFunction::Squelch; default: { logWarn() << "Unmapped Key Code: 0x" << QString::number(code, 16); return AnytoneKeySettingsExtension::KeyFunction::Off; } } } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::GeneralSettingsElement::TimeZone * ******************************************************************************************** */ QVector D878UVCodeplug::GeneralSettingsElement::TimeZone::_timeZones = { QTimeZone(-43200), QTimeZone(-41400), QTimeZone(-39600), QTimeZone(-37800), QTimeZone(-36000), QTimeZone(-34200), QTimeZone(-32400), QTimeZone(-30600), QTimeZone(-28800), QTimeZone(-27000), QTimeZone(-25200), QTimeZone(-23400), QTimeZone(-21600), QTimeZone(-19800), QTimeZone(-18000), QTimeZone(-16200), QTimeZone(-14400), QTimeZone(-12600), QTimeZone(-10800), QTimeZone( -9000), QTimeZone( -7200), QTimeZone( -5400), QTimeZone( -3600), QTimeZone( -1800), QTimeZone( 0), QTimeZone( 1800), QTimeZone( 3600), QTimeZone( 5400), QTimeZone( 7200), QTimeZone( 9000), QTimeZone( 10800), QTimeZone( 12600), QTimeZone( 14400), QTimeZone( 16200), QTimeZone( 18000), QTimeZone( 19800), QTimeZone( 21600), QTimeZone( 23400), QTimeZone( 25200), QTimeZone( 27000), QTimeZone( 28800), QTimeZone( 30600), QTimeZone( 32400), QTimeZone( 34200), QTimeZone( 36000), QTimeZone( 37800), QTimeZone( 39600), QTimeZone( 41400), QTimeZone( 43200), QTimeZone( 45000), QTimeZone( 46800) }; QTimeZone D878UVCodeplug::GeneralSettingsElement::TimeZone::decode(uint8_t code) { if (code >= _timeZones.size()) return _timeZones.back(); return _timeZones.at(code); } uint8_t D878UVCodeplug::GeneralSettingsElement::TimeZone::encode(const QTimeZone &zone) { if (! _timeZones.contains(zone)) return 13; //<- UTC return _timeZones.indexOf(zone); } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::GeneralSettingsElement * ******************************************************************************************** */ D878UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, unsigned size) : D868UVCodeplug::GeneralSettingsElement(ptr, size) { // pass... } D878UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : D868UVCodeplug::GeneralSettingsElement(ptr, GeneralSettingsElement::size()) { // pass... } void D878UVCodeplug::GeneralSettingsElement::clear() { AnytoneCodeplug::GeneralSettingsElement::clear(); } QTimeZone D878UVCodeplug::GeneralSettingsElement::gpsTimeZone() const { return TimeZone::decode(getUInt8(Offset::gpsTimeZone())); } void D878UVCodeplug::GeneralSettingsElement::setGPSTimeZone(const QTimeZone &zone) { setUInt8(Offset::gpsTimeZone(), TimeZone::encode(zone)); // <- Set to UTC } Interval D878UVCodeplug::GeneralSettingsElement::transmitTimeout() const { return Interval::fromSeconds((unsigned)getUInt8(Offset::transmitTimeout())*30); } void D878UVCodeplug::GeneralSettingsElement::setTransmitTimeout(const Interval &tot) { setUInt8(Offset::transmitTimeout(), tot.seconds()/30); } AnytoneDisplaySettingsExtension::Language D878UVCodeplug::GeneralSettingsElement::language() const { return (AnytoneDisplaySettingsExtension::Language)getUInt8(Offset::language()); } void D878UVCodeplug::GeneralSettingsElement::setLanguage(AnytoneDisplaySettingsExtension::Language lang) { setUInt8(Offset::language(), (unsigned)lang); } Frequency D878UVCodeplug::GeneralSettingsElement::vfoFrequencyStep() const { // Directly map enum to frequency switch (getUInt8(Offset::vfoFrequencyStep())) { case FREQ_STEP_2_5kHz: return Frequency::fromkHz(2.5); case FREQ_STEP_5kHz: return Frequency::fromkHz(5); case FREQ_STEP_6_25kHz: return Frequency::fromkHz(6.25); case FREQ_STEP_8_33kHz: return Frequency::fromkHz(8.33); case FREQ_STEP_10kHz: return Frequency::fromkHz(10); case FREQ_STEP_12_5kHz: return Frequency::fromkHz(12.5); case FREQ_STEP_20kHz: return Frequency::fromkHz(20); case FREQ_STEP_25kHz: return Frequency::fromkHz(25); case FREQ_STEP_50kHz: return Frequency::fromkHz(50); } // otherwise return default step size (2.5kHz) return Frequency::fromkHz(2.5); } void D878UVCodeplug::GeneralSettingsElement::setVFOFrequencyStep(Frequency freq) { static auto map = Frequency::MapNearest( { {Frequency::fromkHz(2.5), FREQ_STEP_2_5kHz}, {Frequency::fromkHz(5), FREQ_STEP_5kHz}, {Frequency::fromkHz(6.25), FREQ_STEP_6_25kHz}, {Frequency::fromkHz(8.33), FREQ_STEP_8_33kHz}, {Frequency::fromkHz(10), FREQ_STEP_10kHz}, {Frequency::fromkHz(12.5), FREQ_STEP_12_5kHz}, {Frequency::fromkHz(20), FREQ_STEP_20kHz}, {Frequency::fromkHz(25), FREQ_STEP_25kHz}, {Frequency::fromkHz(50), FREQ_STEP_50kHz}, }); setUInt8(Offset::vfoFrequencyStep(), (uint8_t) map.value(freq)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKeyAShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyAShort())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyAShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKeyBShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyBShort())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyBShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKeyCShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyCShort())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyCShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKey1Short() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1Short())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey1Short(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKey2Short() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2Short())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey2Short(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKeyALong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyALong())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyALong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKeyBLong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyBLong())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyBLong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKeyCLong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyCLong())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyCLong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKey1Long() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1Long())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey1Long(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction D878UVCodeplug::GeneralSettingsElement::funcKey2Long() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2Long())); } void D878UVCodeplug::GeneralSettingsElement::setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey2Long(), KeyFunction::encode(func)); } AnytoneSettingsExtension::STEType D878UVCodeplug::GeneralSettingsElement::steType() const { return (AnytoneSettingsExtension::STEType)getUInt8(Offset::steType()); } void D878UVCodeplug::GeneralSettingsElement::setSTEType(AnytoneSettingsExtension::STEType type) { setUInt8(Offset::steType(), (unsigned)type); } double D878UVCodeplug::GeneralSettingsElement::steFrequency() const { switch ((STEFrequency)getUInt8(Offset::steFrequency())) { case STEFrequency::Off: return 0; case STEFrequency::Hz55_2: return 55.2; case STEFrequency::Hz259_2: return 259.2; } return 0; } void D878UVCodeplug::GeneralSettingsElement::setSTEFrequency(double freq) { if (0 >= freq) { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Off); } else if (100 > freq) { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Hz55_2); } else { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Hz259_2); } } Interval D878UVCodeplug::GeneralSettingsElement::groupCallHangTime() const { return Interval::fromSeconds(getUInt8(Offset::groupCallHangTime())); } void D878UVCodeplug::GeneralSettingsElement::setGroupCallHangTime(Interval intv) { setUInt8(Offset::groupCallHangTime(), intv.seconds()); } Interval D878UVCodeplug::GeneralSettingsElement::privateCallHangTime() const { return Interval::fromSeconds(getUInt8(Offset::privateCallHangTime())); } void D878UVCodeplug::GeneralSettingsElement::setPrivateCallHangTime(Interval intv) { setUInt8(Offset::privateCallHangTime(), intv.seconds()); } Interval D878UVCodeplug::GeneralSettingsElement::preWaveDelay() const { return Interval::fromMilliseconds((unsigned)getUInt8(Offset::preWaveDelay())*20); } void D878UVCodeplug::GeneralSettingsElement::setPreWaveDelay(Interval intv) { setUInt8(Offset::preWaveDelay(), intv.milliseconds()/20); } Interval D878UVCodeplug::GeneralSettingsElement::wakeHeadPeriod() const { return Interval::fromMilliseconds(((unsigned)getUInt8(Offset::wakeHeadPeriod()))*20); } void D878UVCodeplug::GeneralSettingsElement::setWakeHeadPeriod(Interval intv) { setUInt8(Offset::wakeHeadPeriod(), intv.milliseconds()/20); } unsigned D878UVCodeplug::GeneralSettingsElement::wfmChannelIndex() const { return getUInt8(Offset::wfmChannelIndex()); } void D878UVCodeplug::GeneralSettingsElement::setWFMChannelIndex(unsigned idx) { setUInt8(Offset::wfmChannelIndex(), idx); } bool D878UVCodeplug::GeneralSettingsElement::wfmVFOEnabled() const { return getUInt8(Offset::wfmVFOEnabled()); } void D878UVCodeplug::GeneralSettingsElement::enableWFMVFO(bool enable) { setUInt8(Offset::wfmVFOEnabled(), (enable ? 0x01 : 0x00)); } Interval D878UVCodeplug::GeneralSettingsElement::backlightDuration() const { switch ((BacklightDuration)getUInt8(Offset::backlightDuration())) { case BacklightDuration::Infinite: return Interval::infinity(); case BacklightDuration::_5s: return Interval::fromSeconds(5); case BacklightDuration::_10s: return Interval::fromSeconds(10); case BacklightDuration::_15s: return Interval::fromSeconds(15); case BacklightDuration::_20s: return Interval::fromSeconds(20); case BacklightDuration::_25s: return Interval::fromSeconds(25); case BacklightDuration::_30s: return Interval::fromSeconds(30); case BacklightDuration::_1min: return Interval::fromMinutes(1); case BacklightDuration::_2min: return Interval::fromMinutes(2); case BacklightDuration::_3min: return Interval::fromMinutes(3); case BacklightDuration::_4min: return Interval::fromMinutes(4); case BacklightDuration::_5min: return Interval::fromMinutes(5); case BacklightDuration::_15min: return Interval::fromMinutes(15); case BacklightDuration::_30min: return Interval::fromMinutes(30); case BacklightDuration::_45min: return Interval::fromMinutes(45); case BacklightDuration::_1h: return Interval::fromMinutes(60); } return Interval::infinity(); } void D878UVCodeplug::GeneralSettingsElement::setBacklightDuration(Interval intv) { if (intv <= Interval::fromSeconds(5)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_5s); else if (intv <= Interval::fromSeconds(10)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_10s); else if (intv <= Interval::fromSeconds(15)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_15s); else if (intv <= Interval::fromSeconds(20)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_20s); else if (intv <= Interval::fromSeconds(25)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_25s); else if (intv <= Interval::fromSeconds(30)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_30s); else if (intv <= Interval::fromMinutes(1)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_1min); else if (intv <= Interval::fromMinutes(2)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_2min); else if (intv <= Interval::fromMinutes(3)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_3min); else if (intv <= Interval::fromMinutes(4)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_4min); else if (intv <= Interval::fromMinutes(5)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_5min); else if (intv <= Interval::fromMinutes(15)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_15min); else if (intv <= Interval::fromMinutes(30)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_30min); else if (intv <= Interval::fromMinutes(45)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_45min); else if (intv <= Interval::fromMinutes(60)) setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::_1h); else setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::Infinite); } unsigned D878UVCodeplug::GeneralSettingsElement::dtmfToneDuration() const { switch (getUInt8(Offset::dtmfToneDuration())) { case DTMF_DUR_50ms: return 50; case DTMF_DUR_100ms: return 100; case DTMF_DUR_200ms: return 200; case DTMF_DUR_300ms: return 300; case DTMF_DUR_500ms: return 500; } return 50; } void D878UVCodeplug::GeneralSettingsElement::setDTMFToneDuration(unsigned ms) { if (ms<=50) { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_50ms); } else if (ms<=100) { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_100ms); } else if (ms<=200) { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_200ms); } else if (ms<=300) { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_300ms); } else { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_500ms); } } bool D878UVCodeplug::GeneralSettingsElement::manDown() const { return getUInt8(Offset::manDown()); } void D878UVCodeplug::GeneralSettingsElement::enableManDown(bool enable) { setUInt8(Offset::manDown(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::wfmMonitor() const { return getUInt8(Offset::wfmMonitor()); } void D878UVCodeplug::GeneralSettingsElement::enableWFMMonitor(bool enable) { setUInt8(Offset::wfmMonitor(), (enable ? 0x01 : 0x00)); } Frequency D878UVCodeplug::GeneralSettingsElement::tbstFrequency() const { switch ((TBSTFrequency)getUInt8(Offset::tbstFrequency())) { case TBSTFrequency::Hz1000: return Frequency::fromHz(1000); case TBSTFrequency::Hz1450: return Frequency::fromHz(1450); case TBSTFrequency::Hz1750: return Frequency::fromHz(1750); case TBSTFrequency::Hz2100: return Frequency::fromHz(2100); } return Frequency::fromHz(1750); } void D878UVCodeplug::GeneralSettingsElement::setTBSTFrequency(Frequency freq) { if (1000 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1000); } else if (1450 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1450); } else if (1750 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1750); } else if (2100 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz2100); } else { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1750); } } bool D878UVCodeplug::GeneralSettingsElement::proMode() const { return getUInt8(Offset::proMode()); } void D878UVCodeplug::GeneralSettingsElement::enableProMode(bool enable) { setUInt8(Offset::proMode(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::filterOwnID() const { return getUInt8(Offset::filterOwnID()); } void D878UVCodeplug::GeneralSettingsElement::enableFilterOwnID(bool enable) { setUInt8(Offset::filterOwnID(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::remoteStunKill() const { return getUInt8(Offset::remoteStunKill()); } void D878UVCodeplug::GeneralSettingsElement::enableRemoteStunKill(bool enable) { setUInt8(Offset::remoteStunKill(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::remoteMonitor() const { return getUInt8(Offset::remoteMonitor()); } void D878UVCodeplug::GeneralSettingsElement::enableRemoteMonitor(bool enable) { setUInt8(Offset::remoteMonitor(), (enable ? 0x01 : 0x00)); } AnytoneDMRSettingsExtension::SlotMatch D878UVCodeplug::GeneralSettingsElement::monitorSlotMatch() const { return (AnytoneDMRSettingsExtension::SlotMatch)getUInt8(Offset::monSlotMatch()); } void D878UVCodeplug::GeneralSettingsElement::setMonitorSlotMatch(AnytoneDMRSettingsExtension::SlotMatch match) { setUInt8(Offset::monSlotMatch(), (unsigned)match); } bool D878UVCodeplug::GeneralSettingsElement::monitorColorCodeMatch() const { return getUInt8(Offset::monColorCodeMatch()); } void D878UVCodeplug::GeneralSettingsElement::enableMonitorColorCodeMatch(bool enable) { setUInt8(Offset::monColorCodeMatch(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::monitorIDMatch() const { return getUInt8(Offset::monIDMatch()); } void D878UVCodeplug::GeneralSettingsElement::enableMonitorIDMatch(bool enable) { setUInt8(Offset::monIDMatch(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::monitorTimeSlotHold() const { return getUInt8(Offset::monTimeSlotHold()); } void D878UVCodeplug::GeneralSettingsElement::enableMonitorTimeSlotHold(bool enable) { setUInt8(Offset::monTimeSlotHold(), (enable ? 0x01 : 0x00)); } Interval D878UVCodeplug::GeneralSettingsElement::manDownDelay() const { return Interval::fromSeconds(getUInt8(Offset::manDownDelay())); } void D878UVCodeplug::GeneralSettingsElement::setManDownDelay(Interval sec) { setUInt8(Offset::manDownDelay(), sec.seconds()); } unsigned D878UVCodeplug::GeneralSettingsElement::fmCallHold() const { return getUInt8(Offset::fmCallHold()); } void D878UVCodeplug::GeneralSettingsElement::setFMCallHold(unsigned sec) { setUInt8(Offset::fmCallHold(), sec); } bool D878UVCodeplug::GeneralSettingsElement::gpsMessageEnabled() const { return getUInt8(Offset::enableGPSMessage()); } void D878UVCodeplug::GeneralSettingsElement::enableGPSMessage(bool enable) { setUInt8(Offset::enableGPSMessage(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::maintainCallChannel() const { return getUInt8(Offset::maintainCallChannel()); } void D878UVCodeplug::GeneralSettingsElement::enableMaintainCallChannel(bool enable) { setUInt8(Offset::maintainCallChannel(), (enable ? 0x01 : 0x00)); } unsigned D878UVCodeplug::GeneralSettingsElement::priorityZoneAIndex() const { return getUInt8(Offset::priorityZoneA()); } void D878UVCodeplug::GeneralSettingsElement::setPriorityZoneAIndex(unsigned idx) { setUInt8(Offset::priorityZoneA(), idx); } unsigned D878UVCodeplug::GeneralSettingsElement::priorityZoneBIndex() const { return getUInt8(Offset::priorityZoneB()); } void D878UVCodeplug::GeneralSettingsElement::setPriorityZoneBIndex(unsigned idx) { setUInt8(Offset::priorityZoneB(), idx); } bool D878UVCodeplug::GeneralSettingsElement::bluetooth() const { return 0 != getUInt8(Offset::bluetooth()); } void D878UVCodeplug::GeneralSettingsElement::enableBluetooth(bool enable) { setUInt8(Offset::bluetooth(), enable ? 0x01 : 0x00); } bool D878UVCodeplug::GeneralSettingsElement::btAndInternalMic() const { return 0 != getUInt8(Offset::btAndInternalMic()); } void D878UVCodeplug::GeneralSettingsElement::enableBTAndInternalMic(bool enable) { setUInt8(Offset::btAndInternalMic(), enable ? 0x01 : 0x00); } bool D878UVCodeplug::GeneralSettingsElement::btAndInternalSpeaker() const { return 0 != getUInt8(Offset::btAndInternalSpeaker()); } void D878UVCodeplug::GeneralSettingsElement::enableBTAndInternalSpeaker(bool enable) { setUInt8(Offset::btAndInternalSpeaker(), enable ? 0x01 : 0x00); } bool D878UVCodeplug::GeneralSettingsElement::pluginRecTone() const { return 0 != getUInt8(Offset::pluginRecTone()); } void D878UVCodeplug::GeneralSettingsElement::enablePluginRecTone(bool enable) { setUInt8(Offset::pluginRecTone(), enable ? 0x01 : 0x00); } Interval D878UVCodeplug::GeneralSettingsElement::gpsUpdatePeriod() const { return Interval::fromSeconds(getUInt8(Offset::gpsRangingInterval())); } void D878UVCodeplug::GeneralSettingsElement::setGPSUpdatePeriod(Interval intv) { setUInt8(Offset::gpsRangingInterval(), intv.seconds()); } unsigned int D878UVCodeplug::GeneralSettingsElement::btMicGain() const { return (getUInt8(Offset::btMicGain())+1)*2; } void D878UVCodeplug::GeneralSettingsElement::setBTMicGain(unsigned int gain) { gain = std::min(10U, std::max(2U, gain)); setUInt8(Offset::btMicGain(), gain/2-1); } unsigned int D878UVCodeplug::GeneralSettingsElement::btSpeakerGain() const { return (getUInt8(Offset::btSpeakerGain())+1)*2; } void D878UVCodeplug::GeneralSettingsElement::setBTSpeakerGain(unsigned int gain) { gain = std::min(10U, std::max(2U, gain)); setUInt8(Offset::btSpeakerGain(), gain/2-1); } bool D878UVCodeplug::GeneralSettingsElement::displayChannelNumber() const { return getUInt8(Offset::showChannelNumber()); } void D878UVCodeplug::GeneralSettingsElement::enableDisplayChannelNumber(bool enable) { setUInt8(Offset::showChannelNumber(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::showCurrentContact() const { return getUInt8(Offset::showCurrentContact()); } void D878UVCodeplug::GeneralSettingsElement::enableShowCurrentContact(bool enable) { setUInt8(Offset::showCurrentContact(), (enable ? 0x01 : 0x00)); } Interval D878UVCodeplug::GeneralSettingsElement::autoRoamPeriod() const { return Interval::fromMinutes(getUInt8(Offset::autoRoamPeriod())); } void D878UVCodeplug::GeneralSettingsElement::setAutoRoamPeriod(Interval intv) { setUInt8(Offset::autoRoamPeriod(), intv.minutes()); } bool D878UVCodeplug::GeneralSettingsElement::keyToneLevelAdjustable() const { return keyToneLevel().isNull(); } Level D878UVCodeplug::GeneralSettingsElement::keyToneLevel() const { return Level::fromValue(getUInt8(Offset::keyToneLevel()), Limit::keyTone()); } void D878UVCodeplug::GeneralSettingsElement::setKeyToneLevel(Level level) { setUInt8(Offset::keyToneLevel(), level.mapTo(Limit::keyTone())); } void D878UVCodeplug::GeneralSettingsElement::setKeyToneLevelAdjustable() { setUInt8(Offset::keyToneLevel(), 0); } AnytoneDisplaySettingsExtension::Color D878UVCodeplug::GeneralSettingsElement::callDisplayColor() const { return NameColor::decode(getUInt8(Offset::callColor())); } void D878UVCodeplug::GeneralSettingsElement::setCallDisplayColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::callColor(), NameColor::encode(color)); } bool D878UVCodeplug::GeneralSettingsElement::gpsUnitsImperial() const { return getUInt8(Offset::gpsUnits()); } void D878UVCodeplug::GeneralSettingsElement::enableGPSUnitsImperial(bool enable) { setUInt8(Offset::gpsUnits(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::knobLock() const { return getBit(Offset::knobLock(), 0); } void D878UVCodeplug::GeneralSettingsElement::enableKnobLock(bool enable) { setBit(Offset::knobLock(), 0, enable); } bool D878UVCodeplug::GeneralSettingsElement::keypadLock() const { return getBit(Offset::keypadLock(), 1); } void D878UVCodeplug::GeneralSettingsElement::enableKeypadLock(bool enable) { setBit(Offset::keypadLock(), 1, enable); } bool D878UVCodeplug::GeneralSettingsElement::sidekeysLock() const { return getBit(Offset::sideKeyLock(), 3); } void D878UVCodeplug::GeneralSettingsElement::enableSidekeysLock(bool enable) { setBit(Offset::sideKeyLock(), 3, enable); } bool D878UVCodeplug::GeneralSettingsElement::keyLockForced() const { return getBit(Offset::forceKeyLock(), 4); } void D878UVCodeplug::GeneralSettingsElement::enableKeyLockForced(bool enable) { setBit(Offset::forceKeyLock(), 4, enable); } Interval D878UVCodeplug::GeneralSettingsElement::autoRoamDelay() const { return Interval::fromSeconds(getUInt8(Offset::autoRoamDelay())); } void D878UVCodeplug::GeneralSettingsElement::setAutoRoamDelay(Interval intv) { setUInt8(Offset::autoRoamDelay(), intv.seconds()); } AnytoneDisplaySettingsExtension::Color D878UVCodeplug::GeneralSettingsElement::standbyTextColor() const { return TextColor::decode(getUInt8(Offset::standbyTextColor())); } void D878UVCodeplug::GeneralSettingsElement::setStandbyTextColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::standbyTextColor(), TextColor::encode(color)); } D878UVCodeplug::GeneralSettingsElement::BackgroundImage D878UVCodeplug::GeneralSettingsElement::standbyBackgroundImage() const { return (D878UVCodeplug::GeneralSettingsElement::BackgroundImage)getUInt8(Offset::standbyBackground()); } void D878UVCodeplug::GeneralSettingsElement::setStandbyBackgroundImage(D878UVCodeplug::GeneralSettingsElement::BackgroundImage img) { setUInt8(Offset::standbyBackground(), (unsigned)img); } bool D878UVCodeplug::GeneralSettingsElement::showLastHeard() const { return getUInt8(Offset::showLastHeard()); } void D878UVCodeplug::GeneralSettingsElement::enableShowLastHeard(bool enable) { setUInt8(Offset::showLastHeard(), (enable ? 0x01 : 0x00)); } SMSExtension::Format D878UVCodeplug::GeneralSettingsElement::smsFormat() const { switch((SMSFormat) getUInt8(Offset::smsFormat())) { case SMSFormat::Motorola: return SMSExtension::Format::Motorola; case SMSFormat::Hytera: return SMSExtension::Format::Hytera; case SMSFormat::DMR: return SMSExtension::Format::DMR; } return SMSExtension::Format::Motorola; } void D878UVCodeplug::GeneralSettingsElement::setSMSFormat(SMSExtension::Format fmt) { switch (fmt) { case SMSExtension::Format::Motorola: setUInt8(Offset::smsFormat(), (unsigned)SMSFormat::Motorola); break; case SMSExtension::Format::Hytera: setUInt8(Offset::smsFormat(), (unsigned)SMSFormat::Hytera); break; case SMSExtension::Format::DMR: setUInt8(Offset::smsFormat(), (unsigned)SMSFormat::DMR); break; } } Frequency D878UVCodeplug::GeneralSettingsElement::autoRepeaterMinFrequencyVHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMinVHF())*10); } void D878UVCodeplug::GeneralSettingsElement::setAutoRepeaterMinFrequencyVHF(Frequency freq) { setUInt32_le(Offset::autoRepMinVHF(), freq.inHz()/10); } Frequency D878UVCodeplug::GeneralSettingsElement::autoRepeaterMaxFrequencyVHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMaxVHF())*10); } void D878UVCodeplug::GeneralSettingsElement::setAutoRepeaterMaxFrequencyVHF(Frequency freq) { setUInt32_le(Offset::autoRepMaxVHF(), freq.inHz()/10); } Frequency D878UVCodeplug::GeneralSettingsElement::autoRepeaterMinFrequencyUHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMinUHF())*10); } void D878UVCodeplug::GeneralSettingsElement::setAutoRepeaterMinFrequencyUHF(Frequency freq) { setUInt32_le(Offset::autoRepMinUHF(), freq.inHz()/10); } Frequency D878UVCodeplug::GeneralSettingsElement::autoRepeaterMaxFrequencyUHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMaxUHF())*10); } void D878UVCodeplug::GeneralSettingsElement::setAutoRepeaterMaxFrequencyUHF(Frequency freq) { setUInt32_le(Offset::autoRepMaxUHF(), freq.inHz()/10); } AnytoneAutoRepeaterSettingsExtension::Direction D878UVCodeplug::GeneralSettingsElement::autoRepeaterDirectionB() const { return (AnytoneAutoRepeaterSettingsExtension::Direction)getUInt8(Offset::autoRepeaterDirB()); } void D878UVCodeplug::GeneralSettingsElement::setAutoRepeaterDirectionB(AnytoneAutoRepeaterSettingsExtension::Direction dir) { setUInt8(Offset::autoRepeaterDirB(), (unsigned)dir); } bool D878UVCodeplug::GeneralSettingsElement::fmSendIDAndContact() const { return 0 != getUInt8(Offset::fmSendIDAndContact()); } void D878UVCodeplug::GeneralSettingsElement::enableFMSendIDAndContact(bool enable) { setUInt8(Offset::fmSendIDAndContact(), enable ? 0x01 : 0x00); } bool D878UVCodeplug::GeneralSettingsElement::defaultChannel() const { return getUInt8(Offset::defaultChannels()); } void D878UVCodeplug::GeneralSettingsElement::enableDefaultChannel(bool enable) { setUInt8(Offset::defaultChannels(), (enable ? 0x01 : 0x00)); } unsigned D878UVCodeplug::GeneralSettingsElement::defaultZoneIndexA() const { return getUInt8(Offset::defaultZoneA()); } void D878UVCodeplug::GeneralSettingsElement::setDefaultZoneIndexA(unsigned idx) { setUInt8(Offset::defaultZoneA(), idx); } unsigned D878UVCodeplug::GeneralSettingsElement::defaultZoneIndexB() const { return getUInt8(Offset::defaultZoneB()); } void D878UVCodeplug::GeneralSettingsElement::setDefaultZoneIndexB(unsigned idx) { setUInt8(Offset::defaultZoneB(), idx); } bool D878UVCodeplug::GeneralSettingsElement::defaultChannelAIsVFO() const { return 0xff == defaultChannelAIndex(); } unsigned D878UVCodeplug::GeneralSettingsElement::defaultChannelAIndex() const { return getUInt8(Offset::defaultChannelA()); } void D878UVCodeplug::GeneralSettingsElement::setDefaultChannelAIndex(unsigned idx) { setUInt8(Offset::defaultChannelA(), idx); } void D878UVCodeplug::GeneralSettingsElement::setDefaultChannelAToVFO() { setDefaultChannelAIndex(0xff); } bool D878UVCodeplug::GeneralSettingsElement::defaultChannelBIsVFO() const { return 0xff == defaultChannelBIndex(); } unsigned D878UVCodeplug::GeneralSettingsElement::defaultChannelBIndex() const { return getUInt8(Offset::defaultChannelB()); } void D878UVCodeplug::GeneralSettingsElement::setDefaultChannelBIndex(unsigned idx) { setUInt8(Offset::defaultChannelB(), idx); } void D878UVCodeplug::GeneralSettingsElement::setDefaultChannelBToVFO() { setDefaultChannelBIndex(0xff); } unsigned D878UVCodeplug::GeneralSettingsElement::defaultRoamingZoneIndex() const { return getUInt8(Offset::defaultRoamingZone()); } void D878UVCodeplug::GeneralSettingsElement::setDefaultRoamingZoneIndex(unsigned idx) { setUInt8(Offset::defaultRoamingZone(), idx); } bool D878UVCodeplug::GeneralSettingsElement::repeaterRangeCheck() const { return getUInt8(Offset::repRangeCheck()); } void D878UVCodeplug::GeneralSettingsElement::enableRepeaterRangeCheck(bool enable) { setUInt8(Offset::repRangeCheck(), (enable ? 0x01 : 0x00)); } Interval D878UVCodeplug::GeneralSettingsElement::repeaterRangeCheckInterval() const { return Interval::fromSeconds(((unsigned)getUInt8(Offset::rangeCheckInterval()))*5); } void D878UVCodeplug::GeneralSettingsElement::setRepeaterRangeCheckInterval(Interval intv) { setUInt8(Offset::rangeCheckInterval(), intv.seconds()/5); } unsigned D878UVCodeplug::GeneralSettingsElement::repeaterRangeCheckCount() const { return getUInt8(Offset::rangeCheckCount()); } void D878UVCodeplug::GeneralSettingsElement::setRepeaterRangeCheckCount(unsigned n) { setUInt8(Offset::rangeCheckCount(), n); } AnytoneRoamingSettingsExtension::RoamStart D878UVCodeplug::GeneralSettingsElement::roamingStartCondition() const { return (AnytoneRoamingSettingsExtension::RoamStart)getUInt8(Offset::roamStartCondition()); } void D878UVCodeplug::GeneralSettingsElement::setRoamingStartCondition(AnytoneRoamingSettingsExtension::RoamStart cond) { setUInt8(Offset::roamStartCondition(), (unsigned)cond); } Interval D878UVCodeplug::GeneralSettingsElement::txBacklightDuration() const { return Interval::fromSeconds(getUInt8(Offset::txBacklightDuration())); } void D878UVCodeplug::GeneralSettingsElement::setTXBacklightDuration(Interval intv) { auto seconds = std::min(30ULL, intv.seconds()); setUInt8(Offset::txBacklightDuration(), seconds); } bool D878UVCodeplug::GeneralSettingsElement::separateDisplay() const { return getUInt8(Offset::displaySeparator()); } void D878UVCodeplug::GeneralSettingsElement::enableSeparateDisplay(bool enable) { setUInt8(Offset::displaySeparator(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::keepLastCaller() const { return getUInt8(Offset::keepLastCaller()); } void D878UVCodeplug::GeneralSettingsElement::enableKeepLastCaller(bool enable) { setUInt8(Offset::keepLastCaller(), (enable ? 0x01 : 0x00)); } AnytoneDisplaySettingsExtension::Color D878UVCodeplug::GeneralSettingsElement::channelNameColor() const { return NameColor::decode(getUInt8(Offset::channelNameColor())); } void D878UVCodeplug::GeneralSettingsElement::setChannelNameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::channelNameColor(), NameColor::encode(color)); } bool D878UVCodeplug::GeneralSettingsElement::repeaterCheckNotification() const { return getUInt8(Offset::repCheckNotify()); } void D878UVCodeplug::GeneralSettingsElement::enableRepeaterCheckNotification(bool enable) { setUInt8(Offset::repCheckNotify(), (enable ? 0x01 : 0x00)); } Interval D878UVCodeplug::GeneralSettingsElement::rxBacklightDuration() const { auto seconds = getUInt8(Offset::rxBacklightDuration()); if (0 == seconds) return Interval::infinity(); return Interval::fromSeconds(seconds); } void D878UVCodeplug::GeneralSettingsElement::setRXBacklightDuration(Interval intv) { if (intv.isFinite() || intv > Interval::fromSeconds(30)) setUInt8(Offset::rxBacklightDuration(), 0); setUInt8(Offset::rxBacklightDuration(), intv.seconds()); } bool D878UVCodeplug::GeneralSettingsElement::roaming() const { return getUInt8(Offset::roaming()); } void D878UVCodeplug::GeneralSettingsElement::enableRoaming(bool enable) { setUInt8(Offset::roaming(), (enable ? 0x01 : 0x00)); } Interval D878UVCodeplug::GeneralSettingsElement::muteDelay() const { return Interval::fromMinutes(getUInt8(Offset::muteDelay())+1); } void D878UVCodeplug::GeneralSettingsElement::setMuteDelay(Interval min) { setUInt8(Offset::muteDelay(), std::max(1ULL, min.minutes())-1); } unsigned D878UVCodeplug::GeneralSettingsElement::repeaterCheckNumNotifications() const { return getUInt8(Offset::repCheckNumNotify())+1; } void D878UVCodeplug::GeneralSettingsElement::setRepeaterCheckNumNotifications(unsigned num) { num = Limit::repeaterOORNotificationCount().limit(num); setUInt8(Offset::repCheckNumNotify(), num-1); } bool D878UVCodeplug::GeneralSettingsElement::bootGPSCheck() const { return getUInt8(Offset::bootGPSCheck()); } void D878UVCodeplug::GeneralSettingsElement::enableBootGPSCheck(bool enable) { setUInt8(Offset::bootGPSCheck(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::bootReset() const { return getUInt8(Offset::bootReset()); } void D878UVCodeplug::GeneralSettingsElement::enableBootReset(bool enable) { setUInt8(Offset::bootReset(), (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::GeneralSettingsElement::btHoldTimeEnabled() const { return 0x00 != getUInt8(Offset::btHoldTime()); } bool D878UVCodeplug::GeneralSettingsElement::btHoldTimeInfinite() const { return 121U == getUInt8(Offset::btHoldTime()); } Interval D878UVCodeplug::GeneralSettingsElement::btHoldTime() const { return Interval::fromSeconds(getUInt8(Offset::btHoldTime())); } void D878UVCodeplug::GeneralSettingsElement::setBTHoldTime(Interval interval) { unsigned int seconds = std::min(120ULL, std::max(1ULL, interval.seconds())); setUInt8(Offset::btHoldTime(), seconds); } void D878UVCodeplug::GeneralSettingsElement::setBTHoldTimeInfinite() { setUInt8(Offset::btHoldTime(), 121); } void D878UVCodeplug::GeneralSettingsElement::disableBTHoldTime() { setUInt8(Offset::btHoldTime(), 0); } Interval D878UVCodeplug::GeneralSettingsElement::btRXDelay() const { if (0 == getUInt8(Offset::btRXDelay())) return Interval::fromMilliseconds(30); return Interval::fromMilliseconds(((unsigned int)getUInt8(Offset::btRXDelay())+1)*500); } void D878UVCodeplug::GeneralSettingsElement::setBTRXDelay(Interval delay) { if (500 >= delay.milliseconds()) { setUInt8(Offset::btRXDelay(), 0); } else { unsigned int millis = std::min(5500ULL, std::max(500ULL, delay.milliseconds())); setUInt8(Offset::btRXDelay(), (millis-500)/500); } } bool D878UVCodeplug::GeneralSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::GeneralSettingsElement::fromConfig(flags, ctx, err)) return false; // Set transmit timeout setTransmitTimeout(ctx.config()->settings()->tot()); enableBootReset(ctx.config()->settings()->boot()->resetEnabled()); // Encode tone settings enableIdleChannelTone( ctx.config()->settings()->tone()->channelIdle().testFlag(Channel::Type::DMR)); setKeyToneLevel(ctx.config()->settings()->tone()->keyToneVolume()); enableGPSUnitsImperial(GNSSSettings::Units::Archaic == ctx.config()->settings()->gnss()->units()); setGroupCallHangTime(ctx.config()->settings()->dmr()->groupCallHangTime()); setPrivateCallHangTime(ctx.config()->settings()->dmr()->privateCallHangTime()); setPreWaveDelay(ctx.config()->settings()->dmr()->preamble()); setSMSFormat(ctx.config()->smsExtension()->format()); AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return true; // Encode boot settings if (ext->bootSettings()->priorityZoneA()->isNull()) setPriorityZoneAIndex(0xff); else setPriorityZoneAIndex(ctx.index(ext->bootSettings()->priorityZoneA()->as())); if (ext->bootSettings()->priorityZoneB()->isNull()) setPriorityZoneBIndex(0xff); else setPriorityZoneBIndex(ctx.index(ext->bootSettings()->priorityZoneB()->as())); if (! ext->roamingSettings()->defaultZone()->isNull()) setDefaultRoamingZoneIndex(ctx.index(ext->roamingSettings()->defaultZone()->as())); enableBootGPSCheck(ext->bootSettings()->gpsCheckEnabled()); // Encode key settings enableKnobLock(ext->keySettings()->knobLockEnabled()); enableKeypadLock(ext->keySettings()->keypadLockEnabled()); enableSidekeysLock(ext->keySettings()->sideKeysLockEnabled()); enableKeyLockForced(ext->keySettings()->forcedKeyLockEnabled()); // Encode audio settings setMuteDelay(ext->audioSettings()->muteDelay()); // Encode display settings setCallDisplayColor(ext->displaySettings()->callColor()); setLanguage(ext->displaySettings()->language()); enableDisplayChannelNumber(ext->displaySettings()->showChannelNumberEnabled()); enableShowCurrentContact(ext->displaySettings()->showContact()); setStandbyTextColor(ext->displaySettings()->standbyTextColor()); //setStandbyBackgroundImage(ext->displaySettings()->standbyBackgroundColor()); enableShowLastHeard(ext->displaySettings()->showLastHeardEnabled()); setChannelNameColor(ext->displaySettings()->channelNameColor()); setBacklightDuration(ext->displaySettings()->backlightDuration()); setRXBacklightDuration(ext->displaySettings()->backlightDurationRX()); setTXBacklightDuration(ext->displaySettings()->backlightDurationTX()); // Encode menu settings enableSeparateDisplay(ext->menuSettings()->separatorEnabled()); // Encode auto-repeater settings setAutoRepeaterDirectionB(ext->autoRepeaterSettings()->directionB()); setAutoRepeaterMinFrequencyVHF(ext->autoRepeaterSettings()->vhfMin()); setAutoRepeaterMaxFrequencyVHF(ext->autoRepeaterSettings()->vhfMax()); setAutoRepeaterMinFrequencyUHF(ext->autoRepeaterSettings()->uhfMin()); setAutoRepeaterMaxFrequencyUHF(ext->autoRepeaterSettings()->uhfMax()); // Encode DMR settings setWakeHeadPeriod(ext->dmrSettings()->wakeHeadPeriod()); enableFilterOwnID(ext->dmrSettings()->filterOwnIDEnabled()); setMonitorSlotMatch(ext->dmrSettings()->monitorSlotMatch()); enableMonitorColorCodeMatch(ext->dmrSettings()->monitorColorCodeMatchEnabled()); enableMonitorIDMatch(ext->dmrSettings()->monitorIDMatchEnabled()); enableMonitorTimeSlotHold(ext->dmrSettings()->monitorTimeSlotHoldEnabled()); // Encode GPS settings setGPSTimeZone(ext->gpsSettings()->timeZone()); enableGPSMessage(ext->gpsSettings()->positionReportingEnabled()); setGPSUpdatePeriod(ext->gpsSettings()->updatePeriod()); // Encode ranging/roaming settings. setAutoRoamPeriod(ext->roamingSettings()->autoRoamPeriod()); setAutoRoamDelay(ext->roamingSettings()->autoRoamDelay()); enableRepeaterRangeCheck(ext->roamingSettings()->repeaterRangeCheckEnabled()); setRepeaterRangeCheckInterval(ext->roamingSettings()->repeaterCheckInterval()); setRepeaterRangeCheckCount(ext->roamingSettings()->repeaterRangeCheckCount()); setRoamingStartCondition(ext->roamingSettings()->roamingStartCondition()); enableRepeaterCheckNotification(ext->roamingSettings()->notificationEnabled()); setRepeaterCheckNumNotifications(ext->roamingSettings()->notificationCount()); // Encode other settings enableKeepLastCaller(ext->keepLastCallerEnabled()); setVFOFrequencyStep(ext->vfoStep()); setSTEType(ext->steType()); setSTEFrequency(ext->steFrequency()); setTBSTFrequency(ext->tbstFrequency()); enableProMode(ext->proModeEnabled()); enableMaintainCallChannel(ext->maintainCallChannelEnabled()); return true; } bool D878UVCodeplug::GeneralSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::GeneralSettingsElement::updateConfig(ctx, err)) return false; ctx.config()->settings()->setTOT(transmitTimeout()); ctx.config()->settings()->boot()->enableReset(this->bootReset()); // Decode tone settings ctx.config()->settings()->tone()->setKeyToneVolume(keyToneLevel()); ctx.config()->settings()->tone()->setChannelIdle( idleChannelTone() ? Channel::Type::DMR : Channel::Type::None ); ctx.config()->settings()->gnss()->setUnits( this->gpsUnitsImperial() ? GNSSSettings::Units::Archaic : GNSSSettings::Units::Metric); ctx.config()->settings()->dmr()->setGroupCallHangTime(this->groupCallHangTime()); ctx.config()->settings()->dmr()->setPrivateCallHangTime(this->privateCallHangTime()); ctx.config()->settings()->dmr()->setPreamble(this->preWaveDelay()); ctx.config()->smsExtension()->setFormat(this->smsFormat()); // Get or add settings extension AnytoneSettingsExtension *ext = nullptr; if (ctx.config()->settings()->anytoneExtension()) { ext = ctx.config()->settings()->anytoneExtension(); } else { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Decode boot settings ext->bootSettings()->enableGPSCheck(this->bootGPSCheck()); // Decode key settings ext->keySettings()->enableKnobLock(this->knobLock()); ext->keySettings()->enableKeypadLock(this->keypadLock()); ext->keySettings()->enableSideKeysLock(this->sidekeysLock()); ext->keySettings()->enableForcedKeyLock(this->keyLockForced()); // Store audio settings ext->audioSettings()->setMuteDelay(this->muteDelay()); // Decode display settings ext->displaySettings()->setCallColor(this->callDisplayColor()); ext->displaySettings()->setLanguage(this->language()); ext->displaySettings()->enableShowChannelNumber(this->displayChannelNumber()); ext->displaySettings()->enableShowContact(this->showCurrentContact()); ext->displaySettings()->setStandbyTextColor(this->standbyTextColor()); //ext->displaySettings()->setStandbyBackgroundColor(this->standbyBackgroundImage()); ext->displaySettings()->enableShowLastHeard(this->showLastHeard()); ext->displaySettings()->setBacklightDurationTX(this->txBacklightDuration()); ext->displaySettings()->setChannelNameColor(this->channelNameColor()); ext->displaySettings()->setBacklightDuration(this->backlightDuration()); ext->displaySettings()->setBacklightDurationTX(this->txBacklightDuration()); ext->displaySettings()->setBacklightDurationRX(this->rxBacklightDuration()); // Decode menu settings ext->menuSettings()->enableSeparator(this->separateDisplay()); // Decode auto-repeater settings ext->autoRepeaterSettings()->setDirectionB(autoRepeaterDirectionB()); ext->autoRepeaterSettings()->setVHFMin(this->autoRepeaterMinFrequencyVHF()); ext->autoRepeaterSettings()->setVHFMax(this->autoRepeaterMaxFrequencyVHF()); ext->autoRepeaterSettings()->setUHFMin(this->autoRepeaterMinFrequencyUHF()); ext->autoRepeaterSettings()->setUHFMax(this->autoRepeaterMaxFrequencyUHF()); // Decode dmr settings ext->dmrSettings()->setWakeHeadPeriod(this->wakeHeadPeriod()); ext->dmrSettings()->enableFilterOwnID(this->filterOwnID()); ext->dmrSettings()->setMonitorSlotMatch(this->monitorSlotMatch()); ext->dmrSettings()->enableMonitorColorCodeMatch(this->monitorColorCodeMatch()); ext->dmrSettings()->enableMonitorIDMatch(this->monitorIDMatch()); ext->dmrSettings()->enableMonitorTimeSlotHold(this->monitorTimeSlotHold()); // Encode GPS settings ext->gpsSettings()->enablePositionReporting(this->gpsMessageEnabled()); ext->gpsSettings()->setUpdatePeriod(this->gpsUpdatePeriod()); // Encode ranging/roaming settings ext->roamingSettings()->setAutoRoamPeriod(this->autoRoamPeriod()); ext->roamingSettings()->setAutoRoamDelay(this->autoRoamDelay()); ext->roamingSettings()->enableRepeaterRangeCheck(this->repeaterRangeCheck()); ext->roamingSettings()->setRepeaterCheckInterval(this->repeaterRangeCheckInterval()); ext->roamingSettings()->setRepeaterRangeCheckCount(this->repeaterRangeCheckCount()); ext->roamingSettings()->setRoamingStartCondition(this->roamingStartCondition()); ext->roamingSettings()->enableNotification(this->repeaterCheckNotification()); ext->roamingSettings()->setNotificationCount(this->repeaterCheckNumNotifications()); // Decode other settings ext->enableKeepLastCaller(this->keepLastCaller()); ext->setVFOStep(this->vfoFrequencyStep()); ext->setSTEType(this->steType()); ext->setSTEFrequency(this->steFrequency()); ext->setTBSTFrequency(this->tbstFrequency()); ext->enableProMode(this->proMode()); ext->enableMaintainCallChannel(this->maintainCallChannel()); return true; } bool D878UVCodeplug::GeneralSettingsElement::linkSettings(RadioSettings *settings, Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::GeneralSettingsElement::linkSettings(settings, ctx, err)) return false; AnytoneSettingsExtension *ext = settings->anytoneExtension(); if (0xff != priorityZoneAIndex()) { if (! ctx.has(priorityZoneAIndex())) { errMsg(err) << "Cannot link priority zone A index " << priorityZoneAIndex() << ": Zone with that index not defined."; return false; } ext->bootSettings()->priorityZoneA()->set(ctx.get(priorityZoneAIndex())); } if (0xff != priorityZoneBIndex()) { if (! ctx.has(priorityZoneBIndex())) { errMsg(err) << "Cannot link priority zone B index " << priorityZoneBIndex() << ": Zone with that index not defined."; return false; } ext->bootSettings()->priorityZoneB()->set(ctx.get(priorityZoneBIndex())); } if (ctx.has(defaultRoamingZoneIndex())) { ext->roamingSettings()->defaultZone()->set(ctx.get(this->defaultRoamingZoneIndex())); } return true; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::GeneralSettingsExtensionElement * ******************************************************************************************** */ D878UVCodeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr, unsigned size) : AnytoneCodeplug::ExtendedSettingsElement(ptr, size) { // pass... } D878UVCodeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr) : AnytoneCodeplug::ExtendedSettingsElement(ptr, ExtendedSettingsElement::size()) { // pass... } void D878UVCodeplug::ExtendedSettingsElement::clear() { memset(_data, 0x00, _size); setEncryption(AnytoneDMRSettingsExtension::EncryptionType::AES); clearAutoRepeaterVHF2OffsetIndex(); clearAutoRepeaterUHF2OffsetIndex(); } bool D878UVCodeplug::ExtendedSettingsElement::sendTalkerAlias() const { return getUInt8(Offset::sendTalkerAlias()); } void D878UVCodeplug::ExtendedSettingsElement::enableSendTalkerAlias(bool enable) { setUInt8(Offset::sendTalkerAlias(), (enable ? 0x01 : 0x00)); } AnytoneDMRSettingsExtension::TalkerAliasSource D878UVCodeplug::ExtendedSettingsElement::talkerAliasSource() const { return (AnytoneDMRSettingsExtension::TalkerAliasSource)getUInt8(Offset::talkerAliasDisplay()); } void D878UVCodeplug::ExtendedSettingsElement::setTalkerAliasSource(AnytoneDMRSettingsExtension::TalkerAliasSource mode) { setUInt8(Offset::talkerAliasDisplay(), (unsigned)mode); } DMRSettings::TalkerAliasEncoding D878UVCodeplug::ExtendedSettingsElement::talkerAliasEncoding() const { switch ((TalkerAliasEncoding)getUInt8(Offset::talkerAliasEncoding())) { case TalkerAliasEncoding::ISO7: return DMRSettings::TalkerAliasEncoding::Iso7; case TalkerAliasEncoding::ISO8: return DMRSettings::TalkerAliasEncoding::Iso8; case TalkerAliasEncoding::Unicode: return DMRSettings::TalkerAliasEncoding::Unicode; } return DMRSettings::TalkerAliasEncoding::Iso8; } void D878UVCodeplug::ExtendedSettingsElement::setTalkerAliasEncoding(DMRSettings::TalkerAliasEncoding enc) { switch (enc) { case DMRSettings::TalkerAliasEncoding::Iso7: setUInt8(Offset::talkerAliasEncoding(), (unsigned)TalkerAliasEncoding::ISO7); break; case DMRSettings::TalkerAliasEncoding::Iso8: setUInt8(Offset::talkerAliasEncoding(), (unsigned)TalkerAliasEncoding::ISO8); break; case DMRSettings::TalkerAliasEncoding::Unicode: setUInt8(Offset::talkerAliasEncoding(), (unsigned)TalkerAliasEncoding::Unicode); break; } } bool D878UVCodeplug::ExtendedSettingsElement::bluetoothPTTLatch() const { return getUInt8(Offset::btPTTLatch()); } void D878UVCodeplug::ExtendedSettingsElement::enableBluetoothPTTLatch(bool enable) { setUInt8(Offset::btPTTLatch(), enable ? 0x01 : 0x00); } bool D878UVCodeplug::ExtendedSettingsElement::infiniteBluetoothPTTSleepDelay() const { return bluetoothPTTSleepDelay().isNull(); } Interval D878UVCodeplug::ExtendedSettingsElement::bluetoothPTTSleepDelay() const { return Interval::fromMinutes(getUInt8(Offset::btPTTSleepDelay())); } void D878UVCodeplug::ExtendedSettingsElement::setBluetoothPTTSleepDelay(Interval delay) { unsigned int t = std::min(Limit::maxBluetoothPTTSleepDelay(), (unsigned int)delay.minutes()); setUInt8(Offset::btPTTSleepDelay(), t); } void D878UVCodeplug::ExtendedSettingsElement::setInfiniteBluetoothPTTSleepDelay() { setBluetoothPTTSleepDelay(Interval::fromMinutes(0)); } bool D878UVCodeplug::ExtendedSettingsElement::hasAutoRepeaterUHF2OffsetIndex() const { return 0xff != getUInt8(Offset::autoRepeaterUHF2OffsetIndex()); } unsigned D878UVCodeplug::ExtendedSettingsElement::autoRepeaterUHF2OffsetIndex() const { return getUInt8(Offset::autoRepeaterUHF2OffsetIndex()); } void D878UVCodeplug::ExtendedSettingsElement::setAutoRepeaterUHF2OffsetIndex(unsigned idx) { setUInt8(Offset::autoRepeaterUHF2OffsetIndex(), idx); } void D878UVCodeplug::ExtendedSettingsElement::clearAutoRepeaterUHF2OffsetIndex() { setUInt8(Offset::autoRepeaterUHF2OffsetIndex(), 0xff); } bool D878UVCodeplug::ExtendedSettingsElement::hasAutoRepeaterVHF2OffsetIndex() const { return 0xff != getUInt8(Offset::autoRepeaterVHF2OffsetIndex()); } unsigned D878UVCodeplug::ExtendedSettingsElement::autoRepeaterVHF2OffsetIndex() const { return getUInt8(Offset::autoRepeaterVHF2OffsetIndex()); } void D878UVCodeplug::ExtendedSettingsElement::setAutoRepeaterVHF2OffsetIndex(unsigned idx) { setUInt8(Offset::autoRepeaterVHF2OffsetIndex(), idx); } void D878UVCodeplug::ExtendedSettingsElement::clearAutoRepeaterVHF2OffsetIndex() { setUInt8(Offset::autoRepeaterVHF2OffsetIndex(), 0xff); } Frequency D878UVCodeplug::ExtendedSettingsElement::autoRepeaterVHF2MinFrequency() const { return Frequency::fromHz(((unsigned long long)getUInt32_le(Offset::autoRepeaterVHF2MinFrequency()))*10); } void D878UVCodeplug::ExtendedSettingsElement::setAutoRepeaterVHF2MinFrequency(Frequency hz) { setUInt32_le(Offset::autoRepeaterVHF2MinFrequency(), hz.inHz()/10); } Frequency D878UVCodeplug::ExtendedSettingsElement::autoRepeaterVHF2MaxFrequency() const { return Frequency::fromHz(((unsigned long long)getUInt32_le(Offset::autoRepeaterVHF2MaxFrequency()))*10); } void D878UVCodeplug::ExtendedSettingsElement::setAutoRepeaterVHF2MaxFrequency(Frequency hz) { setUInt32_le(Offset::autoRepeaterVHF2MaxFrequency(), hz.inHz()/10); } Frequency D878UVCodeplug::ExtendedSettingsElement::autoRepeaterUHF2MinFrequency() const { return Frequency::fromHz(((unsigned long long)getUInt32_le(Offset::autoRepeaterUHF2MinFrequency()))*10); } void D878UVCodeplug::ExtendedSettingsElement::setAutoRepeaterUHF2MinFrequency(Frequency hz) { setUInt32_le(Offset::autoRepeaterUHF2MinFrequency(), hz.inHz()/10); } Frequency D878UVCodeplug::ExtendedSettingsElement::autoRepeaterUHF2MaxFrequency() const { return Frequency::fromHz(((unsigned long long)getUInt32_le(Offset::autoRepeaterUHF2MaxFrequency()))*10); } void D878UVCodeplug::ExtendedSettingsElement::setAutoRepeaterUHF2MaxFrequency(Frequency hz) { setUInt32_le(Offset::autoRepeaterUHF2MaxFrequency(), hz.inHz()/10); } GNSSSettings::Systems D878UVCodeplug::ExtendedSettingsElement::gnss() const { switch ((GNSS)getUInt8(Offset::gpsMode())) { case GNSS::GPS: return GNSSSettings::System::GPS; case GNSS::Beidou: return GNSSSettings::System::Beidou; case GNSS::GPS_Beidou: return GNSSSettings::System::GPS | GNSSSettings::System::Beidou; case GNSS::Glonass: return GNSSSettings::System::Glonass; case GNSS::GPS_Glonass: return GNSSSettings::System::GPS | GNSSSettings::System::Glonass; case GNSS::Beidou_Glonass: return GNSSSettings::System::Beidou | GNSSSettings::System::Glonass; case GNSS::GPS_Beidou_Glonass: return GNSSSettings::System::GPS | GNSSSettings::System::Beidou | GNSSSettings::System::Glonass; } return GNSSSettings::System::GPS; } void D878UVCodeplug::ExtendedSettingsElement::setGNSS(GNSSSettings::Systems mode) { int value = 0; if (mode.testFlags(GNSSSettings::System::GPS)) value = (int)GNSS::GPS; if (mode.testFlags(GNSSSettings::System::Beidou)) value = (int)GNSS::Beidou; if (mode.testFlags(GNSSSettings::System::GPS|GNSSSettings::System::Beidou)) value = (int)GNSS::GPS_Beidou; if (mode.testFlags(GNSSSettings::System::Glonass)) value = (int)GNSS::Glonass; if (mode.testFlags(GNSSSettings::System::GPS|GNSSSettings::System::Glonass)) value = (int)GNSS::GPS_Glonass; if (mode.testFlags(GNSSSettings::System::Beidou|GNSSSettings::System::Glonass)) value = (int)GNSS::Beidou_Glonass; if (mode.testFlags(GNSSSettings::System::GPS|GNSSSettings::System::Beidou|GNSSSettings::System::Glonass)) value = (int)GNSS::GPS_Beidou_Glonass; setUInt8(Offset::gpsMode(), value); } Interval D878UVCodeplug::ExtendedSettingsElement::steDuration() const { return Interval::fromMilliseconds((getUInt8(Offset::steDuration())+1)*10); } void D878UVCodeplug::ExtendedSettingsElement::setSTEDuration(Interval dur) { unsigned int t = std::max(10U, std::min(1000U, (unsigned int)dur.milliseconds())); setUInt8(Offset::steDuration(), t/10-1); } bool D878UVCodeplug::ExtendedSettingsElement::infiniteManDialGroupCallHangTime() const { return manDialGroupCallHangTime().isNull(); } Interval D878UVCodeplug::ExtendedSettingsElement::manDialGroupCallHangTime() const { unsigned int t = getUInt8(Offset::manGrpCallHangTime())+1; if (t<=30) return Interval::fromSeconds(t); else if (31==t) return Interval::fromMinutes(30); return Interval(); } void D878UVCodeplug::ExtendedSettingsElement::setManDialGroupCallHangTime(Interval dur) { unsigned int t = dur.seconds(); if (t > 30) t = 31; // = 30min else if (0 == t) t = 32; // = infinite setUInt8(Offset::manGrpCallHangTime(), t-1); } void D878UVCodeplug::ExtendedSettingsElement::setManDialGroupCallHangTimeInfinite() { setManDialGroupCallHangTime(Interval::fromSeconds(0)); } bool D878UVCodeplug::ExtendedSettingsElement::infiniteManDialPrivateCallHangTime() const { return manDialPrivateCallHangTime().isNull(); } Interval D878UVCodeplug::ExtendedSettingsElement::manDialPrivateCallHangTime() const { unsigned int t = getUInt8(Offset::manPrivCallHangTime())+1; if (t<=30) return Interval::fromSeconds(t); else if (31==t) return Interval::fromMinutes(30); return Interval(); } void D878UVCodeplug::ExtendedSettingsElement::setManDialPrivateCallHangTime(Interval dur) { unsigned int t = dur.seconds(); if (t > 30) t = 31; // = 30min else if (0 == t) t = 32; // = infinite setUInt8(Offset::manPrivCallHangTime(), t-1); } void D878UVCodeplug::ExtendedSettingsElement::setManDialPrivateCallHangTimeInfinite() { setManDialPrivateCallHangTime(Interval::fromSeconds(0)); } AnytoneDisplaySettingsExtension::Color D878UVCodeplug::ExtendedSettingsElement::channelBNameColor() const { return NameColor::decode(getUInt8(Offset::channelBNameColor())); } void D878UVCodeplug::ExtendedSettingsElement::setChannelBNameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::channelBNameColor(), NameColor::encode(color)); } AnytoneDMRSettingsExtension::EncryptionType D878UVCodeplug::ExtendedSettingsElement::encryption() const { return (AnytoneDMRSettingsExtension::EncryptionType)getUInt8(Offset::encryptionType()); } void D878UVCodeplug::ExtendedSettingsElement::setEncryption(AnytoneDMRSettingsExtension::EncryptionType mode) { setUInt8(Offset::encryptionType(), (unsigned int)mode); } bool D878UVCodeplug::ExtendedSettingsElement::totNotification() const { return 0x00 != getUInt8(Offset::totNotification()); } void D878UVCodeplug::ExtendedSettingsElement::enableTOTNotification(bool enable) { setUInt8(Offset::totNotification(), enable ? 0x01 : 0x00); } bool D878UVCodeplug::ExtendedSettingsElement::atpc() const { return 0x00 != getUInt8(Offset::atpc()); } void D878UVCodeplug::ExtendedSettingsElement::enableATPC(bool enable) { setUInt8(Offset::atpc(), enable ? 0x01 : 0x00); } AnytoneDisplaySettingsExtension::Color D878UVCodeplug::ExtendedSettingsElement::zoneANameColor() const { return NameColor::decode(getUInt8(Offset::zoneANameColor())); } void D878UVCodeplug::ExtendedSettingsElement::setZoneANameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::zoneANameColor(), NameColor::encode(color)); } AnytoneDisplaySettingsExtension::Color D878UVCodeplug::ExtendedSettingsElement::zoneBNameColor() const { return NameColor::decode(getUInt8(Offset::zoneBNameColor())); } void D878UVCodeplug::ExtendedSettingsElement::setZoneBNameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::zoneBNameColor(), NameColor::encode(color)); } bool D878UVCodeplug::ExtendedSettingsElement::resetAutoShutdownOnCall() const { return 0x00 != getUInt8(Offset::autoShutdownMode()); } void D878UVCodeplug::ExtendedSettingsElement::enableResetAutoShutdownOnCall(bool enable) { setUInt8(Offset::autoShutdownMode(), enable ? 0x01 : 0x00); } bool D878UVCodeplug::ExtendedSettingsElement::showColorCode() const { return getBit(Offset::displayColorCode()); } void D878UVCodeplug::ExtendedSettingsElement::enableShowColorCode(bool enable) { setBit(Offset::displayColorCode(), enable); } bool D878UVCodeplug::ExtendedSettingsElement::showTimeSlot() const { return getBit(Offset::displayTimeSlot()); } void D878UVCodeplug::ExtendedSettingsElement::enableShowTimeSlot(bool enable) { setBit(Offset::displayTimeSlot(), enable); } bool D878UVCodeplug::ExtendedSettingsElement::showChannelType() const { return getBit(Offset::displayChannelType()); } void D878UVCodeplug::ExtendedSettingsElement::enableShowChannelType(bool enable) { setBit(Offset::displayChannelType(), enable); } bool D878UVCodeplug::ExtendedSettingsElement::fmIdleTone() const { return 0x00 != getUInt8(Offset::fmIdleTone()); } void D878UVCodeplug::ExtendedSettingsElement::enableFMIdleTone(bool enable) { setUInt8(Offset::fmIdleTone(), enable ? 0x01 : 0x00); } AnytoneDisplaySettingsExtension::DateFormat D878UVCodeplug::ExtendedSettingsElement::dateFormat() const { return (AnytoneDisplaySettingsExtension::DateFormat)getUInt8(Offset::dateFormat()); } void D878UVCodeplug::ExtendedSettingsElement::setDateFormat(AnytoneDisplaySettingsExtension::DateFormat format) { setUInt8(Offset::dateFormat(), (unsigned int)format); } Level D878UVCodeplug::ExtendedSettingsElement::fmMicGain() const { return Level::fromValue(getUInt8(Offset::analogMicGain()), Limit::micGain()); } void D878UVCodeplug::ExtendedSettingsElement::setFMMicGain(Level gain) { setUInt8(Offset::analogMicGain(), gain.mapTo(Limit::micGain())); } bool D878UVCodeplug::ExtendedSettingsElement::gpsRoaming() const { return 0x00 != getUInt8(Offset::gpsRoaming()); } void D878UVCodeplug::ExtendedSettingsElement::enableGPSRoaming(bool enable) { setUInt8(Offset::gpsRoaming(), enable ? 0x01 : 0x00); } void D878UVCodeplug::ExtendedSettingsElement::callEndToneMelody(Melody &melody) const { QVector> tones; tones.reserve(5); for (int i=0; i<5; i++) { double freq = getUInt16_le(Offset::callEndTones()+2*i); unsigned int duration = getUInt16_le(Offset::callEndDurations()+2*i); if (duration) tones.append({freq, duration}); } melody.infer(tones); } void D878UVCodeplug::ExtendedSettingsElement::setCallEndToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; i> tones; tones.reserve(5); for (int i=0; i<5; i++) { double freq = getUInt16_le(Offset::allCallTones()+2*i); unsigned int duration = getUInt16_le(Offset::allCallDurations()+2*i); if (duration) tones.append({freq, duration}); } melody.infer(tones); } void D878UVCodeplug::ExtendedSettingsElement::setAllCallToneMelody(const Melody &melody) { unsigned int n=std::min(5U, (unsigned int)melody.count()); QVector> tones = melody.toTones(); for (unsigned int i=0; iclear(); if (! AnytoneCodeplug::ExtendedSettingsElement::fromConfig(flags, ctx, err)) return false; // Encode audio settings if (ctx.config()->settings()->audio()->fmMicGainEnabled()) setFMMicGain(ctx.config()->settings()->audio()->fmMicGain()); else setFMMicGain(ctx.config()->settings()->audio()->micGain()); // Encode tone settings. enableFMIdleTone(ctx.config()->settings()->tone()->channelIdle().testFlag(Channel::Type::FM)); setCallEndToneMelody(*ctx.config()->settings()->tone()->callEndMelody()); // Encode GPS settings setGNSS(ctx.config()->settings()->gnss()->systems()); enableSendTalkerAlias(ctx.config()->settings()->dmr()->sendTalkerAliasEnabled()); setTalkerAliasEncoding(ctx.config()->settings()->dmr()->talkerAliasEncoding()); // Get extension AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return true; // Encode DMR settings setTalkerAliasSource(ext->dmrSettings()->talkerAliasSource()); // Some general settings setSTEDuration(ext->steDuration()); // Power save settings enableATPC(ext->powerSaveSettings()->atpc()); enableResetAutoShutdownOnCall(ext->powerSaveSettings()->resetAutoShutdownOnCall()); // Encode tone settings enableTOTNotification(ext->toneSettings()->totNotification()); // Encode DMR settings setManDialGroupCallHangTime(ext->dmrSettings()->manualGroupCallHangTime()); setManDialPrivateCallHangTime(ext->dmrSettings()->manualPrivateCallHangTime()); setEncryption(ext->dmrSettings()->encryption()); // Encode display settings enableShowColorCode(ext->displaySettings()->showColorCode()); enableShowTimeSlot(ext->displaySettings()->showTimeSlot()); enableShowChannelType(ext->displaySettings()->showChannelType()); setDateFormat(ext->displaySettings()->dateFormat()); // Encode auto-repeater frequency ranges setAutoRepeaterVHF2MinFrequency(ext->autoRepeaterSettings()->vhf2Min()); setAutoRepeaterVHF2MaxFrequency(ext->autoRepeaterSettings()->vhf2Max()); setAutoRepeaterUHF2MinFrequency(ext->autoRepeaterSettings()->uhf2Min()); setAutoRepeaterUHF2MaxFrequency(ext->autoRepeaterSettings()->uhf2Max()); // Encode auto-repeater offset indices clearAutoRepeaterVHF2OffsetIndex(); if (! ext->autoRepeaterSettings()->vhf2Ref()->isNull()) { setAutoRepeaterVHF2OffsetIndex( ctx.index( ext->autoRepeaterSettings()->vhf2Ref()->as())); } clearAutoRepeaterUHF2OffsetIndex(); if (! ext->autoRepeaterSettings()->uhf2Ref()->isNull()) { setAutoRepeaterUHF2OffsetIndex( ctx.index( ext->autoRepeaterSettings()->uhf2Ref()->as())); } // Encode roaming settings enableGPSRoaming(ext->roamingSettings()->gpsRoaming()); // Encode bluetooth settings enableBluetoothPTTLatch(ext->bluetoothSettings()->pttLatch()); setBluetoothPTTSleepDelay(ext->bluetoothSettings()->pttSleepTimer()); return true; } bool D878UVCodeplug::ExtendedSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); if (! AnytoneCodeplug::ExtendedSettingsElement::updateConfig(ctx, err)) return false; // Store GPS settings ctx.config()->settings()->gnss()->setSystems(this->gnss()); // Store tone settings ctx.config()->settings()->tone()->setChannelIdle( ctx.config()->settings()->tone()->channelIdle() | (fmIdleTone() ? Channel::Type::FM : Channel::Type::None)); callEndToneMelody(*ctx.config()->settings()->tone()->callEndMelody()); // Store DMR settings ctx.config()->settings()->dmr()->enableSendTalkerAlias(sendTalkerAlias()); ctx.config()->settings()->dmr()->setTalkerAliasEncoding(talkerAliasEncoding()); // Get or add extension if not present AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Store DMR settings ext->dmrSettings()->setTalkerAliasSource(talkerAliasSource()); // Some general settings ext->setSTEDuration(this->steDuration()); // Some power-save settings ext->powerSaveSettings()->enableATPC(this->atpc()); ext->powerSaveSettings()->enableResetAutoShutdownOnCall(this->resetAutoShutdownOnCall()); // Store tone settings ext->toneSettings()->enableTOTNotification(this->totNotification()); // Store display settings ext->displaySettings()->enableShowColorCode(this->showColorCode()); ext->displaySettings()->enableShowTimeSlot(this->showTimeSlot()); ext->displaySettings()->enableShowChannelType(this->showChannelType()); ext->displaySettings()->setDateFormat(this->dateFormat()); // Store some DMR settings ext->dmrSettings()->setManualGroupCallHangTime(this->manDialGroupCallHangTime()); ext->dmrSettings()->setManualPrivateCallHangTime(this->manDialPrivateCallHangTime()); ext->dmrSettings()->setEncryption(this->encryption()); // Store auto-repeater frequency ranges ext->autoRepeaterSettings()->setVHF2Min(this->autoRepeaterVHF2MinFrequency()); ext->autoRepeaterSettings()->setVHF2Max(this->autoRepeaterVHF2MaxFrequency()); ext->autoRepeaterSettings()->setUHF2Min(this->autoRepeaterUHF2MinFrequency()); ext->autoRepeaterSettings()->setUHF2Max(this->autoRepeaterUHF2MaxFrequency()); // Store roaming settings ext->roamingSettings()->enableGPSRoaming(this->gpsRoaming()); // Store bluetooth settings ext->bluetoothSettings()->enablePTTLatch(this->bluetoothPTTLatch()); ext->bluetoothSettings()->setPTTSleepTimer(this->bluetoothPTTSleepDelay()); return true; } bool D878UVCodeplug::ExtendedSettingsElement::linkConfig(Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::ExtendedSettingsElement::linkConfig(ctx, err)) return false; // Store FM mic gain separately, if different if (ctx.config()->settings()->audio()->micGain() == fmMicGain()) ctx.config()->settings()->audio()->disableFMMicGain(); else ctx.config()->settings()->audio()->setFMMicGain(fmMicGain()); // Get or add extension if not present AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { errMsg(err) << "Cannot link config extension: not set."; return false; } if (hasAutoRepeaterVHF2OffsetIndex()) { if (! ctx.has(this->autoRepeaterVHF2OffsetIndex())) { errMsg(err) << "Cannot link AnyTone settings extension: " "second auto-repeater VHF offset index " << this->autoRepeaterVHF2OffsetIndex() << " not defined."; return false; } ext->autoRepeaterSettings()->vhf2Ref()->set( ctx.get( this->autoRepeaterVHF2OffsetIndex())); } if (hasAutoRepeaterUHF2OffsetIndex()) { if (! ctx.has(this->autoRepeaterUHF2OffsetIndex())) { errMsg(err) << "Cannot link AnyTone settings extension: " "second auto-repeater UHF offset index " << this->autoRepeaterUHF2OffsetIndex() << " not defined."; return false; } ext->autoRepeaterSettings()->uhf2Ref()->set( ctx.get( this->autoRepeaterUHF2OffsetIndex())); } return true; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::AnalogAPRSSettingsElement * ******************************************************************************************** */ D878UVCodeplug::APRSSettingsElement::APRSSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } D878UVCodeplug::APRSSettingsElement::APRSSettingsElement(uint8_t *ptr) : Element(ptr, APRSSettingsElement::size()) { // pass... } void D878UVCodeplug::APRSSettingsElement::clear() { memset(_data, 0x00, _size); setUInt8(0x0000, 0xff); setFMTXDelay(Interval::fromMilliseconds(60)); setUInt8(0x003d, 0x01); setUInt8(0x003e, 0x03); setUInt8(0x003f, 0xff); } bool D878UVCodeplug::APRSSettingsElement::isValid() const { if (! Codeplug::Element::isValid()) return false; return (! destination().simplified().isEmpty()) && (! source().simplified().isEmpty()); } Interval D878UVCodeplug::APRSSettingsElement::fmTXDelay() const { return Interval::fromMilliseconds( ((unsigned)getUInt8(Offset::fmTXDelay())) * 20); } void D878UVCodeplug::APRSSettingsElement::setFMTXDelay(Interval ms) { setUInt8(Offset::fmTXDelay(), ms.milliseconds()/20); } SelectiveCall D878UVCodeplug::APRSSettingsElement::txTone() const { if ((uint8_t)SignalingType::Off ==getUInt8(Offset::fmSigType())) { // none return SelectiveCall(); } else if ((uint8_t)SignalingType::CTCSS == getUInt8(Offset::fmSigType())) { // CTCSS return CTCSS::decode(getUInt8(Offset::fmCTCSS())); } else if ((uint8_t)SignalingType::DCS == getUInt8(Offset::fmSigType())) { // DCS uint16_t code = getUInt16_le(Offset::fmDCS()); if (512 < code) return SelectiveCall::fromBinaryDCS(code, false); return SelectiveCall::fromBinaryDCS(code-512, true); } return SelectiveCall(); } void D878UVCodeplug::APRSSettingsElement::setTXTone(const SelectiveCall &code) { if (code.isInvalid()) { setUInt8(Offset::fmSigType(), (uint8_t)SignalingType::Off); } else if (code.isCTCSS()) { setUInt8(Offset::fmSigType(), (uint8_t)SignalingType::CTCSS); setUInt8(Offset::fmCTCSS(), CTCSS::encode(code)); } else if (code.isDCS()) { setUInt8(Offset::fmSigType(), (uint8_t)SignalingType::DCS); setUInt16_le(Offset::fmDCS(), code.binCode() + (code.isInverted() ? 512 : 0)); } } Interval D878UVCodeplug::APRSSettingsElement::manualTXInterval() const { return Interval::fromSeconds(getUInt8(Offset::manualTXInterval())); } void D878UVCodeplug::APRSSettingsElement::setManualTXInterval(Interval sec) { setUInt8(Offset::manualTXInterval(), sec.seconds()); } bool D878UVCodeplug::APRSSettingsElement::autoTX() const { return ! autoTXInterval().isNull(); } Interval D878UVCodeplug::APRSSettingsElement::autoTXInterval() const { return Interval::fromSeconds( ((unsigned)getUInt8(Offset::autoTXInterval()))*30 ); } void D878UVCodeplug::APRSSettingsElement::setAutoTXInterval(Interval sec) { setUInt8(Offset::autoTXInterval(), sec.seconds()/30); } void D878UVCodeplug::APRSSettingsElement::disableAutoTX() { setAutoTXInterval(Interval::fromMilliseconds(0)); } bool D878UVCodeplug::APRSSettingsElement::fixedLocationEnabled() const { return getUInt8(Offset::fixedLocation()); } QGeoCoordinate D878UVCodeplug::APRSSettingsElement::fixedLocation() const { double latitude = getUInt8(Offset::fixedLatDeg()) + double(getUInt8(Offset::fixedLatMin()))/60 + double(getUInt8(Offset::fixedLatSec()))/3600; if (getUInt8(Offset::fixedLatSouth())) latitude *= -1; double longitude = getUInt8(Offset::fixedLonDeg()) + double(getUInt8(Offset::fixedLonMin()))/60 + double(getUInt8(Offset::fixedLonSec()))/3600; if (getUInt8(Offset::fixedLonWest())) longitude *= -1; double height_ft = getUInt16_le(Offset::fixedHeight()); return QGeoCoordinate(latitude, longitude, height_ft/3.281); } void D878UVCodeplug::APRSSettingsElement::setFixedLocation(const QGeoCoordinate &loc) { double latitude = loc.latitude(); bool south = (0 > latitude); latitude = std::abs(latitude); unsigned lat_deg = int(latitude); latitude -= lat_deg; latitude *= 60; unsigned lat_min = int(latitude); latitude -= lat_min; latitude *= 60; unsigned lat_sec = int(latitude); double longitude = loc.longitude(); bool west = (0 > longitude); longitude = std::abs(longitude); unsigned lon_deg = int(longitude); longitude -= lon_deg; longitude *= 60; unsigned lon_min = int(longitude); longitude -= lon_min; longitude *= 60; unsigned lon_sec = int(longitude); unsigned height_ft = 0; if (QGeoCoordinate::Coordinate3D == loc.type()) height_ft = int(loc.altitude()*3.281); setUInt8(Offset::fixedLatDeg(), lat_deg); setUInt8(Offset::fixedLatMin(), lat_min); setUInt8(Offset::fixedLatSec(), lat_sec); setUInt8(Offset::fixedLatSouth(), (south ? 0x01 : 0x00)); setUInt8(Offset::fixedLonDeg(), lon_deg); setUInt8(Offset::fixedLonMin(), lon_min); setUInt8(Offset::fixedLonSec(), lon_sec); setUInt8(Offset::fixedLonWest(), (west ? 0x01 : 0x00)); setUInt16_le(Offset::fixedHeight(), height_ft); } void D878UVCodeplug::APRSSettingsElement::enableFixedLocation(bool enable) { setUInt8(Offset::fixedLocation(), enable ? 0x01 : 0x00); } QString D878UVCodeplug::APRSSettingsElement::destination() const { // Terminated/padded with space return readASCII(Offset::destinationCall(), 6, ' '); } unsigned D878UVCodeplug::APRSSettingsElement::destinationSSID() const { return getUInt8(Offset::destinationSSID()); } void D878UVCodeplug::APRSSettingsElement::setDestination(const QString &call, unsigned ssid) { // Terminated/padded with space writeASCII(Offset::destinationCall(), call, 6, ' '); setUInt8(Offset::destinationSSID(), ssid); } QString D878UVCodeplug::APRSSettingsElement::source() const { // Terminated/padded with space return readASCII(Offset::sourceCall(), 6, ' '); } unsigned D878UVCodeplug::APRSSettingsElement::sourceSSID() const { return getUInt8(Offset::sourceSSID()); } void D878UVCodeplug::APRSSettingsElement::setSource(const QString &call, unsigned ssid) { // Terminated/padded with space writeASCII(Offset::sourceCall(), call, 6, ' '); setUInt8(Offset::sourceSSID(), ssid); } QString D878UVCodeplug::APRSSettingsElement::path() const { return readASCII(Offset::path(), 20, 0x00); } void D878UVCodeplug::APRSSettingsElement::setPath(const QString &path) { writeASCII(Offset::path(), path, 20, 0x00); } FMAPRSSystem::Icon D878UVCodeplug::APRSSettingsElement::icon() const { return code2aprsicon(getUInt8(Offset::symbolTable()), getUInt8(Offset::symbol())); } void D878UVCodeplug::APRSSettingsElement::setIcon(FMAPRSSystem::Icon icon) { setUInt8(Offset::symbolTable(), aprsicon2tablecode(icon)); setUInt8(Offset::symbol(), aprsicon2iconcode(icon)); } Channel::Power D878UVCodeplug::APRSSettingsElement::power() const { switch (getUInt8(Offset::fmPower())) { case 0: return Channel::Power::Low; case 1: return Channel::Power::Mid; case 2: return Channel::Power::High; case 3: return Channel::Power::Max; } return Channel::Power::Low; } void D878UVCodeplug::APRSSettingsElement::setPower(Channel::Power power) { switch (power) { case Channel::Power::Min: case Channel::Power::Low: setUInt8(Offset::fmPower(), 0x00); break; case Channel::Power::Mid: setUInt8(Offset::fmPower(), 0x01); break; case Channel::Power::High: setUInt8(Offset::fmPower(), 0x02); break; case Channel::Power::Max: setUInt8(Offset::fmPower(), 0x03); break; } } Interval D878UVCodeplug::APRSSettingsElement::fmPreWaveDelay() const { return Interval::fromMilliseconds(((unsigned)getUInt8(Offset::fmPrewaveDelay()))*10); } void D878UVCodeplug::APRSSettingsElement::setFMPreWaveDelay(Interval ms) { setUInt8(Offset::fmPrewaveDelay(), ms.milliseconds()/10); } bool D878UVCodeplug::APRSSettingsElement::dmrChannelIsSelected(unsigned n) const { return 0xfa2 == dmrChannelIndex(n); } unsigned D878UVCodeplug::APRSSettingsElement::dmrChannelIndex(unsigned n) const { return getUInt16_le(Offset::dmrChannelIndices() + n*Offset::betweenDMRChannelIndices()); } void D878UVCodeplug::APRSSettingsElement::setDMRChannelIndex(unsigned n, unsigned idx) { setUInt16_le(Offset::dmrChannelIndices() + n*Offset::betweenDMRChannelIndices(), idx); } void D878UVCodeplug::APRSSettingsElement::setDMRChannelSelected(unsigned n) { setDMRChannelIndex(n, 0xfa2); } unsigned D878UVCodeplug::APRSSettingsElement::dmrDestination(unsigned n) const { return getBCD8_be(Offset::dmrDestinations() + n*Offset::betweenDMRDestinations()); } void D878UVCodeplug::APRSSettingsElement::setDMRDestination(unsigned n, unsigned idx) { setBCD8_be(Offset::dmrDestinations() + n*Offset::betweenDMRDestinations(), idx); } DMRContact::Type D878UVCodeplug::APRSSettingsElement::dmrCallType(unsigned n) const { switch(getUInt8(Offset::dmrCallTypes() + n*Offset::betweenDMRCallTypes())) { case 0: return DMRContact::PrivateCall; case 1: return DMRContact::GroupCall; case 2: return DMRContact::AllCall; } return DMRContact::PrivateCall; } void D878UVCodeplug::APRSSettingsElement::setDMRCallType(unsigned n, DMRContact::Type type) { switch(type) { case DMRContact::PrivateCall: setUInt8(Offset::dmrCallTypes() + n*Offset::betweenDMRCallTypes(), 0x00); break; case DMRContact::GroupCall: setUInt8(Offset::dmrCallTypes() + n*Offset::betweenDMRCallTypes(), 0x01); break; case DMRContact::AllCall: setUInt8(Offset::dmrCallTypes() + n*Offset::betweenDMRCallTypes(), 0x02); break; } } bool D878UVCodeplug::APRSSettingsElement::dmrTimeSlotOverride(unsigned n) { return 0x00 != getUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots()); } DMRChannel::TimeSlot D878UVCodeplug::APRSSettingsElement::dmrTimeSlot(unsigned n) const { switch (getUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots())) { case 1: return DMRChannel::TimeSlot::TS1; case 2: return DMRChannel::TimeSlot::TS2; } return DMRChannel::TimeSlot::TS1; } void D878UVCodeplug::APRSSettingsElement::setDMRTimeSlot(unsigned n, DMRChannel::TimeSlot ts) { switch (ts) { case DMRChannel::TimeSlot::TS1: setUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots(), 0x01); break; case DMRChannel::TimeSlot::TS2: setUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots(), 0x02); break; } } void D878UVCodeplug::APRSSettingsElement::clearDMRTimeSlotOverride(unsigned n) { setUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots(), 0); } bool D878UVCodeplug::APRSSettingsElement::dmrRoaming() const { return getUInt8(Offset::roamingSupport()); } void D878UVCodeplug::APRSSettingsElement::enableDMRRoaming(bool enable) { setUInt8(Offset::roamingSupport(), (enable ? 0x01 : 0x00)); } Interval D878UVCodeplug::APRSSettingsElement::dmrPreWaveDelay() const { return Interval::fromMilliseconds(((unsigned)getUInt8(Offset::dmrPrewaveDelay()))*100); } void D878UVCodeplug::APRSSettingsElement::setDMRPreWaveDelay(Interval ms) { setUInt8(Offset::dmrPrewaveDelay(), ms.milliseconds()/100); } bool D878UVCodeplug::APRSSettingsElement::infiniteDisplayTime() const { return 13 == getUInt8(Offset::displayInterval()); } Interval D878UVCodeplug::APRSSettingsElement::displayTime() const { return Interval::fromSeconds(3 + getUInt8(Offset::displayInterval())); } void D878UVCodeplug::APRSSettingsElement::setDisplayTime(Interval dur) { unsigned int sec = dur.seconds(); sec = std::min(16U, std::max(3U, sec)); setUInt8(Offset::displayInterval(), sec-3); } void D878UVCodeplug::APRSSettingsElement::setDisplayTimeInifinite() { setUInt8(Offset::displayInterval(), 13); } bool D878UVCodeplug::APRSSettingsElement::reportPosition() const { return getBit(Offset::reportPosition(), 0); } void D878UVCodeplug::APRSSettingsElement::enableReportPosition(bool enable) { setBit(Offset::reportPosition(), 0, enable); } bool D878UVCodeplug::APRSSettingsElement::reportMicE() const { return getBit(Offset::reportMicE(), 1); } void D878UVCodeplug::APRSSettingsElement::enableReportMicE(bool enable) { setBit(Offset::reportMicE(), 1, enable); } bool D878UVCodeplug::APRSSettingsElement::reportObject() const { return getBit(Offset::reportObject(), 2); } void D878UVCodeplug::APRSSettingsElement::enableReportObject(bool enable) { setBit(Offset::reportObject(), 2, enable); } bool D878UVCodeplug::APRSSettingsElement::reportItem() const { return getBit(Offset::reportItem(), 3); } void D878UVCodeplug::APRSSettingsElement::enableReportItem(bool enable) { setBit(Offset::reportItem(), 3, enable); } bool D878UVCodeplug::APRSSettingsElement::reportMessage() const { return getBit(Offset::reportMessage(), 4); } void D878UVCodeplug::APRSSettingsElement::enableReportMessage(bool enable) { setBit(Offset::reportMessage(), 4, enable); } bool D878UVCodeplug::APRSSettingsElement::reportWeather() const { return getBit(Offset::reportWeather(), 5); } void D878UVCodeplug::APRSSettingsElement::enableReportWeather(bool enable) { setBit(Offset::reportWeather(), 5, enable); } bool D878UVCodeplug::APRSSettingsElement::reportNMEA() const { return getBit(Offset::reportNMEA(), 6); } void D878UVCodeplug::APRSSettingsElement::enableReportNMEA(bool enable) { setBit(Offset::reportNMEA(), 6, enable); } bool D878UVCodeplug::APRSSettingsElement::reportStatus() const { return getBit(Offset::reportStatus(), 7); } void D878UVCodeplug::APRSSettingsElement::enableReportStatus(bool enable) { setBit(Offset::reportStatus(), 7, enable); } bool D878UVCodeplug::APRSSettingsElement::reportOther() const { return getBit(Offset::reportOther(), 0); } void D878UVCodeplug::APRSSettingsElement::enableReportOther(bool enable) { setBit(Offset::reportOther(), 0, enable); } AnytoneFMAPRSSettingsExtension::Bandwidth D878UVCodeplug::APRSSettingsElement::fmChannelWidth() const { return (AnytoneFMAPRSSettingsExtension::Bandwidth)getUInt8(Offset::fmWidth()); } void D878UVCodeplug::APRSSettingsElement::setFMChannelWidth(AnytoneFMAPRSSettingsExtension::Bandwidth width) { setUInt8(Offset::fmWidth(), (uint8_t)width); } bool D878UVCodeplug::APRSSettingsElement::fmPassAll() const { return 0x00 != getUInt8(Offset::passAll()); } void D878UVCodeplug::APRSSettingsElement::enableFMPassAll(bool enable) { setUInt8(Offset::passAll(), enable ? 0x01 : 0x00); } bool D878UVCodeplug::APRSSettingsElement::fmFrequencySet(unsigned int n) const { n = std::min(Limit::fmFrequencies()-1, n); return 0 != getBCD8_be(Offset::fmFrequencies() + n*Offset::betweenFMFrequencies()); } Frequency D878UVCodeplug::APRSSettingsElement::fmFrequency(unsigned int n) const { n = std::min(Limit::fmFrequencies()-1, n); return Frequency::fromHz(getBCD8_be(Offset::fmFrequencies() + n*Offset::betweenFMFrequencies())*10); } void D878UVCodeplug::APRSSettingsElement::setFMFrequency(unsigned int n, Frequency f) { n = std::min(Limit::fmFrequencies()-1, n); setBCD8_be(Offset::fmFrequencies() + n*Offset::betweenFMFrequencies(), f.inHz()/10); } void D878UVCodeplug::APRSSettingsElement::clearFMFrequency(unsigned int n) { n = std::min(Limit::fmFrequencies()-1, n); setBCD8_be(Offset::fmFrequencies() + n*Offset::betweenFMFrequencies(), 0); } bool D878UVCodeplug::APRSSettingsElement::fromConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Encode fixed location if valid if (ctx.config()->settings()->gnss()->fixedPosition().isValid()) { setFixedLocation(ctx.config()->settings()->gnss()->fixedPosition()); // Enable if there are no GNSS enabled enableFixedLocation(ctx.config()->settings()->gnss()->fixedPositionEnabled()); } return true; } bool D878UVCodeplug::APRSSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); if (fixedLocation().isValid()) { ctx.config()->settings()->gnss()->setFixedPosition(fixedLocation()); ctx.config()->settings()->gnss()->enableFixedPosition(fixedLocationEnabled()); } return true; } bool D878UVCodeplug::APRSSettingsElement::fromFMAPRSSystem( const FMAPRSSystem *sys, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) clear(); if (! sys->hasRevertChannel()) { errMsg(err) << "Cannot encode APRS settings: " << "No revert channel defined for APRS system '" << sys->name() <<"'."; return false; } setFMFrequency(0, sys->revertChannel()->txFrequency()); setTXTone(sys->revertChannel()->txTone()); setPower(sys->revertChannel()->power()); setFMChannelWidth(FMChannel::Bandwidth::Wide == sys->revertChannel()->bandwidth() ? AnytoneFMAPRSSettingsExtension::Bandwidth::Wide : AnytoneFMAPRSSettingsExtension::Bandwidth::Narrow); setManualTXInterval(sys->period()); setAutoTXInterval(sys->period()); setDestination(sys->destination(), sys->destSSID()); setSource(sys->source(), sys->srcSSID()); setPath(sys->path()); setIcon(sys->icon()); setFMPreWaveDelay(Interval()); // Handle FM APRS Settings if set AnytoneFMAPRSSettingsExtension *ext = sys->anytoneExtension(); if (nullptr == ext) return true; setFMTXDelay(ext->txDelay()); setFMPreWaveDelay(ext->preWaveDelay()); enableFMPassAll(ext->passAll()); enableReportPosition(ext->reportPosition()); enableReportMicE(ext->reportMicE()); enableReportObject(ext->reportObject()); enableReportItem(ext->reportItem()); enableReportMessage(ext->reportMessage()); enableReportWeather(ext->reportWeather()); enableReportNMEA(ext->reportNMEA()); enableReportStatus(ext->reportStatus()); enableReportOther(ext->reportOther()); // Encode additional FM APRS frequencies for (int i=0; ifrequencies()->count(); i++) { setFMFrequency(ctx.index(ext->frequencies()->get(i)), ext->frequencies()->get(i)->as()->frequency()); } return true; } FMAPRSSystem * D878UVCodeplug::APRSSettingsElement::toFMAPRSSystem(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) QString name = QString("APRS %1").arg(destination()); FMAPRSSystem *sys = new FMAPRSSystem( name, nullptr, destination(), destinationSSID(), source(), sourceSSID(), path(), icon(), "", autoTXInterval()); // Decode extension AnytoneFMAPRSSettingsExtension *ext = new AnytoneFMAPRSSettingsExtension(); sys->setAnytoneExtension(ext); ext->setTXDelay(fmTXDelay()); ext->setPreWaveDelay(fmPreWaveDelay()); ext->enablePassAll(fmPassAll()); ext->enableReportPosition(reportPosition()); ext->enableReportMicE(reportMicE()); ext->enableReportObject(reportObject()); ext->enableReportItem(reportItem()); ext->enableReportMessage(reportMessage()); ext->enableReportWeather(reportWeather()); ext->enableReportNMEA(reportNMEA()); ext->enableReportStatus(reportStatus()); ext->enableReportOther(reportOther()); for (unsigned int i=1; isetFrequency(fmFrequency(i)); f->setName(QString("APRS %1").arg(i)); ext->frequencies()->add(f); ctx.add(f, i); } return sys; } bool D878UVCodeplug::APRSSettingsElement::linkFMAPRSSystem(FMAPRSSystem *sys, Context &ctx) { // First, try to find a matching analog channel in list FMChannel *ch = ctx.config()->channelList()->findFMChannelByTxFreq(fmFrequency(0)); if (! ch) { // If no channel is found, create one with the settings from APRS channel: ch = new FMChannel(); ch->setName("APRS Channel"); ch->setRXFrequency(fmFrequency(0)); ch->setTXFrequency(fmFrequency(0)); ch->setPower(power()); ch->setTXTone(txTone()); ch->setBandwidth(AnytoneFMAPRSSettingsExtension::Bandwidth::Wide == fmChannelWidth() ? FMChannel::Bandwidth::Wide : FMChannel::Bandwidth::Narrow); logInfo() << "No matching APRS channel found for TX frequency " << double(fmFrequency(0).inHz())/1e6 << "MHz, create one as 'APRS Channel'"; ctx.config()->channelList()->add(ch); } sys->setRevertChannel(ch); return true; } bool D878UVCodeplug::APRSSettingsElement::fromDMRAPRSSystems(Context &ctx) { unsigned int n = std::min(ctx.count(), Limit::dmrSystems()); for (unsigned int idx=0; idx(idx), ctx); return true; } bool D878UVCodeplug::APRSSettingsElement::fromDMRAPRSSystemObj(unsigned int idx, DMRAPRSSystem *sys, Context &ctx) { if (sys->hasContact()) { setDMRDestination(idx, sys->contact()->number()); setDMRCallType(idx, sys->contact()->type()); } if (sys->hasRevertChannel()) { setDMRChannelIndex(idx, ctx.index(sys->revertChannel())); clearDMRTimeSlotOverride(idx); } else { // no revert channel specified or "selected channel": setDMRChannelSelected(idx); } return true; } DMRAPRSSystem * D878UVCodeplug::APRSSettingsElement::toDMRAPRSSystemObj(int idx) const { if (0 == dmrDestination(idx)) return nullptr; return new DMRAPRSSystem(tr("GPS Sys #%1").arg(idx+1)); } bool D878UVCodeplug::APRSSettingsElement::linkDMRAPRSSystem(int idx, DMRAPRSSystem *sys, Context &ctx) const { // if a revert channel is defined -> link to it if (dmrChannelIsSelected(idx)) sys->resetRevertChannel(); else if (ctx.has(dmrChannelIndex(idx)) && ctx.get(dmrChannelIndex(idx))->is()) sys->setRevertChannel(ctx.get(dmrChannelIndex(idx))->as()); // Search for a matching contact in contacts DMRContact *cont = nullptr; for (unsigned int i=0; i(); i++) { if (ctx.get(i)->number() == dmrDestination(idx)) { cont = ctx.get(i); break; } } // If no matching contact is found, create one if (nullptr == cont) { cont = new DMRContact(dmrCallType(idx), tr("GPS #%1 Contact").arg(idx+1), dmrDestination(idx), false); ctx.config()->contacts()->add(cont); } // link contact to GPS system. sys->setContact(cont); return true; } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::AnalogAPRSMessageElement * ******************************************************************************************** */ D878UVCodeplug::AnalogAPRSMessageElement::AnalogAPRSMessageElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } D878UVCodeplug::AnalogAPRSMessageElement::AnalogAPRSMessageElement(uint8_t *ptr) : Element(ptr, AnalogAPRSMessageElement::size()) { // pass... } void D878UVCodeplug::AnalogAPRSMessageElement::clear() { memset(_data, 0x00, _size); } QString D878UVCodeplug::AnalogAPRSMessageElement::message() const { return readASCII(0, Limit::length(), 0x00); } void D878UVCodeplug::AnalogAPRSMessageElement::setMessage(const QString &msg) { writeASCII(0, msg, Limit::length(), 0x00); } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::AnalogAPRSRXEntryElement * ******************************************************************************************** */ D878UVCodeplug::AnalogAPRSRXEntryElement::AnalogAPRSRXEntryElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } D878UVCodeplug::AnalogAPRSRXEntryElement::AnalogAPRSRXEntryElement(uint8_t *ptr) : Element(ptr, 0x0008) { // pass... } void D878UVCodeplug::AnalogAPRSRXEntryElement::clear() { memset(_data, 0x00, _size); } bool D878UVCodeplug::AnalogAPRSRXEntryElement::isValid() const { return Element::isValid() && (0 != getUInt8(0x0000)); } QString D878UVCodeplug::AnalogAPRSRXEntryElement::call() const { return readASCII(0x0001, 6, 0x00); } unsigned D878UVCodeplug::AnalogAPRSRXEntryElement::ssid() const { return getUInt8(0x0007); } void D878UVCodeplug::AnalogAPRSRXEntryElement::setCall(const QString &call, unsigned ssid) { writeASCII(0x0001, call, 6, 0x00); // Store call setUInt8(0x0007, ssid); // Store SSID setUInt8(0x0000, 0x01); // Enable entry. } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::AESEncryptionKeyElement * ******************************************************************************************** */ D878UVCodeplug::AESEncryptionKeyElement::AESEncryptionKeyElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } D878UVCodeplug::AESEncryptionKeyElement::AESEncryptionKeyElement(uint8_t *ptr) : Element(ptr, AESEncryptionKeyElement::size()) { // pass... } void D878UVCodeplug::AESEncryptionKeyElement::clear() { memset(_data, 0x00, _size); } bool D878UVCodeplug::AESEncryptionKeyElement::isValid() const { return Element::isValid() && (0 != getUInt8(Offset::index())) && (0 != getUInt8(Offset::size())); } unsigned D878UVCodeplug::AESEncryptionKeyElement::index() const { return getUInt8(Offset::index())-1; } void D878UVCodeplug::AESEncryptionKeyElement::setIndex(unsigned idx) { setUInt8(Offset::index(), idx+1); } QByteArray D878UVCodeplug::AESEncryptionKeyElement::key() const { unsigned int size = getUInt8(Offset::size()); // convert nibble to bytes size += (size%2); size /= 2; // Limit size to 32 byte (256 bit) size = std::min(Limit::keySize(), size); // Determine, where the key starts unsigned int o = Limit::keySize() - size; // Copy QByteArray ar(size, 0); memcpy(ar.data(), _data+Offset::key()+o, size); return ar; } void D878UVCodeplug::AESEncryptionKeyElement::setKey(const QByteArray &key) { if (Limit::keySize() < key.size()) return; unsigned int o = Limit::keySize() - key.size(); memcpy(_data+Offset::key()+o, key.constData(), key.size()); setUInt8(Offset::size(), key.size()*2); } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::AESEncryptionKeyBitmapElement * ******************************************************************************************** */ D878UVCodeplug::AESEncryptionKeyBitmapElement::AESEncryptionKeyBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } D878UVCodeplug::AESEncryptionKeyBitmapElement::AESEncryptionKeyBitmapElement(uint8_t *ptr) : BitmapElement(ptr, AESEncryptionKeyBitmapElement::size()) { // pass... } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::ARC4EncryptionKeyElement * ******************************************************************************************** */ D878UVCodeplug::ARC4EncryptionKeyElement::ARC4EncryptionKeyElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } D878UVCodeplug::ARC4EncryptionKeyElement::ARC4EncryptionKeyElement(uint8_t *ptr) : Element(ptr, ARC4EncryptionKeyElement::size()) { // pass... } void D878UVCodeplug::ARC4EncryptionKeyElement::clear() { memset(_data, 0x00, _size); } bool D878UVCodeplug::ARC4EncryptionKeyElement::isValid() const { return Element::isValid() && (0 != getUInt8(Offset::index())); } unsigned D878UVCodeplug::ARC4EncryptionKeyElement::index() const { return getUInt8(Offset::index())-1; } void D878UVCodeplug::ARC4EncryptionKeyElement::setIndex(unsigned idx) { setUInt8(Offset::index(), idx+1); } QByteArray D878UVCodeplug::ARC4EncryptionKeyElement::key() const { QByteArray ar(Limit::keySize(), 0); memcpy(ar.data(), _data+Offset::key(), Limit::keySize()); return ar; } void D878UVCodeplug::ARC4EncryptionKeyElement::setKey(const QByteArray &key) { if (Limit::keySize() < key.size()) return; unsigned int o = Limit::keySize() - key.size(); memcpy(_data+Offset::key()+o, key.constData(), key.size()); } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::ARC4EncryptionKeyBitmapElement * ******************************************************************************************** */ D878UVCodeplug::ARC4EncryptionKeyBitmapElement::ARC4EncryptionKeyBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } D878UVCodeplug::ARC4EncryptionKeyBitmapElement::ARC4EncryptionKeyBitmapElement(uint8_t *ptr) : BitmapElement(ptr, ARC4EncryptionKeyBitmapElement::size()) { // pass... } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::HiddenZoneBitmapElement * ******************************************************************************************** */ D878UVCodeplug::HiddenZoneBitmapElement::HiddenZoneBitmapElement(uint8_t *ptr, size_t size) : BitmapElement(ptr, size) { // pass... } D878UVCodeplug::HiddenZoneBitmapElement::HiddenZoneBitmapElement(uint8_t *ptr) : BitmapElement(ptr, HiddenZoneBitmapElement::size()) { // pass... } /* ******************************************************************************************** * * Implementation of D878UVCodeplug::RadioInfoElement * ******************************************************************************************** */ D878UVCodeplug::RadioInfoElement::RadioInfoElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } D878UVCodeplug::RadioInfoElement::RadioInfoElement(uint8_t *ptr) : Element(ptr, 0x0100) { // pass... } void D878UVCodeplug::RadioInfoElement::clear() { setBandSelectPassword(""); setProgramPassword(""); } bool D878UVCodeplug::RadioInfoElement::fullTest() const { return getUInt8(0x0002); } D878UVCodeplug::RadioInfoElement::FrequencyRange D878UVCodeplug::RadioInfoElement::frequencyRange() const { return (FrequencyRange)getUInt8(0x0003); } void D878UVCodeplug::RadioInfoElement::setFrequencyRange(FrequencyRange range) { setUInt8(0x0003, (unsigned)range); } bool D878UVCodeplug::RadioInfoElement::international() const { return getUInt8(0x0004); } void D878UVCodeplug::RadioInfoElement::enableInternational(bool enable) { setUInt8(0x0004, (enable ? 0x01 : 0x00)); } bool D878UVCodeplug::RadioInfoElement::bandSelect() const { return getUInt8(0x0006); } void D878UVCodeplug::RadioInfoElement::enableBandSelect(bool enable) { setUInt8(0x0006, (enable ? 0x01 : 0x00)); } QString D878UVCodeplug::RadioInfoElement::bandSelectPassword() const { return readASCII(0x000b, 4, 0x00); } void D878UVCodeplug::RadioInfoElement::setBandSelectPassword(const QString &passwd) { writeASCII(0x000b, passwd, 4, 0x00); } QString D878UVCodeplug::RadioInfoElement::radioType() const { return readASCII(0x0010, 7, 0x00); } QString D878UVCodeplug::RadioInfoElement::programPassword() const { return readASCII(0x0028, 4, 0x00); } void D878UVCodeplug::RadioInfoElement::setProgramPassword(const QString &passwd) { writeASCII(0x0028, passwd, 4, 0x00); } QString D878UVCodeplug::RadioInfoElement::areaCode() const { return readASCII(0x002c, 4, 0x00); } QString D878UVCodeplug::RadioInfoElement::serialNumber() const { return readASCII(0x0030, 16, 0x00); } QString D878UVCodeplug::RadioInfoElement::productionDate() const { return readASCII(0x0040, 10, 0x00); } QString D878UVCodeplug::RadioInfoElement::manufacturerCode() const { return readASCII(0x0050, 8, 0x00); } QString D878UVCodeplug::RadioInfoElement::maintainedDate() const { return readASCII(0x0060, 16, 0x00); } QString D878UVCodeplug::RadioInfoElement::dealerCode() const { return readASCII(0x0070, 16, 0x00); } QString D878UVCodeplug::RadioInfoElement::stockDate() const { return readASCII(0x0080, 16, 0x00); } QString D878UVCodeplug::RadioInfoElement::sellDate() const { return readASCII(0x0090, 16, 0x00); } QString D878UVCodeplug::RadioInfoElement::seller() const { return readASCII(0x00a0, 16, 0x00); } QString D878UVCodeplug::RadioInfoElement::maintainerNote() const { return readASCII(0x00b0, 128, 0x00); } /* ******************************************************************************************** * * Implementation of D878UVCodeplug * ******************************************************************************************** */ D878UVCodeplug::D878UVCodeplug(const QString &label, QObject *parent) : D868UVCodeplug(label, parent) { // pass... } D878UVCodeplug::D878UVCodeplug(QObject *parent) : D868UVCodeplug("Anytone AT-D878UV Codeplug", parent) { // pass... } bool D878UVCodeplug::allocateBitmaps() { if (! D868UVCodeplug::allocateBitmaps()) return false; // Roaming channel bitmaps image(0).addElement(Offset::roamingChannelBitmap(), RoamingChannelBitmapElement::size()); // Roaming zone bitmaps image(0).addElement(Offset::roamingZoneBitmap(), RoamingZoneBitmapElement::size()); // AES encryption keys image(0).addElement(Offset::aesKeyBitmap(), AESEncryptionKeyBitmapElement::size()); // ARC4 encryption keys image(0).addElement(Offset::arc4KeyBitmap(), ARC4EncryptionKeyBitmapElement::size()); return true; } void D878UVCodeplug::allocateUpdated() { // First allocate everything common between D868UV and D878UV codeplugs. D868UVCodeplug::allocateUpdated(); // allocate APRS RX list image(0).addElement(Offset::analogAPRSRXEntries(), Limit::analogAPRSRXEntries()*AnalogAPRSRXEntryElement::size()); } void D878UVCodeplug::allocateForEncoding() { // First allocate everything common between D868UV and D878UV codeplugs. D868UVCodeplug::allocateForEncoding(); this->allocateRoaming(); this->allocateAESKeys(); this->allocateARC4Keys(); } void D878UVCodeplug::allocateForDecoding() { // First allocate everything common between D868UV and D878UV codeplugs. D868UVCodeplug::allocateForDecoding(); this->allocateRoaming(); this->allocateAESKeys(); this->allocateARC4Keys(); } void D878UVCodeplug::setBitmaps(Context& ctx) { // First set everything common between D868UV and D878UV codeplugs. D868UVCodeplug::setBitmaps(ctx); // Mark roaming zones RoamingZoneBitmapElement roaming_zone_bitmap(data(Offset::roamingZoneBitmap())); unsigned int num_roaming_zones = std::min(Limit::roamingZones(), ctx.count()); roaming_zone_bitmap.clear(); roaming_zone_bitmap.enableFirst(num_roaming_zones); // Mark roaming channels RoamingChannelBitmapElement roaming_ch_bitmap(data(Offset::roamingChannelBitmap())); unsigned int num_roaming_channel = std::min(Limit::roamingChannels(), ctx.count()); roaming_ch_bitmap.clear(); roaming_ch_bitmap.enableFirst(num_roaming_channel); // Mark AES keys AESEncryptionKeyBitmapElement aes_key_bitmap(data(Offset::aesKeyBitmap())); unsigned int num_aes_keys = std::min(Limit::aesKeys(), ctx.count()); aes_key_bitmap.clear(); aes_key_bitmap.enableFirst(num_aes_keys); // Mark ARC4 keys ARC4EncryptionKeyBitmapElement arc4_key_bitmap(data(Offset::arc4KeyBitmap())); unsigned int num_arc4_channels = std::min(Limit::arc4Keys(), ctx.count()); arc4_key_bitmap.clear(); arc4_key_bitmap.enableFirst(num_arc4_channels); } Config * D878UVCodeplug::preprocess(Config *config, const ErrorStack &err) const { // Apply base preprocessing auto intermediate = AnytoneCodeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot apply preprocessing for D868UVE."; return nullptr; } // Keep 16-bit DMR, 128-bit AES and 256-bit AES keys. EncryptionKeyFilterVisitor filter( { EncryptionKeyFilterVisitor::Filter(BasicEncryptionKey::staticMetaObject, 16, 16), EncryptionKeyFilterVisitor::Filter(AESEncryptionKey::staticMetaObject, 128, 128), EncryptionKeyFilterVisitor::Filter(AESEncryptionKey::staticMetaObject, 256, 256),}); if (! filter.process(intermediate, err)) { errMsg(err) << "Cannot remove unsupported encryption."; delete intermediate; return nullptr; } return intermediate; } bool D878UVCodeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { // Encode everything common between d868uv and d878uv radios. if (! D868UVCodeplug::encodeElements(flags, ctx, err)) return false; if (! this->encodeRoaming(flags, ctx, err)) return false; if (! this->encodeAESKeys(flags, ctx, err)) return false; if (! this->encodeARC4Keys(flags, ctx, err)) return false; return true; } bool D878UVCodeplug::createElements(Context &ctx, const ErrorStack &err) { // Create everything commong between d868uv and d878uv codeplugs. if (! D868UVCodeplug::createElements(ctx, err)) return false; if (! this->createAESKeys(ctx, err)) return false; if (! this->createARC4Keys(ctx, err)) return false; if (! this->createRoaming(ctx, err)) return false; return true; } bool D878UVCodeplug::linkElements(Context &ctx, const ErrorStack &err) { // Link all common elements if (! D868UVCodeplug::linkElements(ctx, err)) return false; if (! this->linkRoaming(ctx, err)) return false; return true; } void D878UVCodeplug::allocateChannels() { /* Allocate channels */ ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); for (uint16_t i=0; i skip if (! channel_bitmap.isEncoded(i)) continue; // compute address for channel uint16_t bank = i/Limit::channelsPerBank(), idx=i%Limit::channelsPerBank(); uint32_t addr = Offset::channelBanks() + bank*Offset::betweenChannelBanks() + idx * ChannelElement::size(); if (!isAllocated(addr, 0)) { image(0).addElement(addr, ChannelElement::size()); } if (!isAllocated(addr+Offset::toChannelExtension(), 0)) { image(0).addElement(addr+Offset::toChannelExtension(), ChannelElement::size()); memset(data(addr+Offset::toChannelExtension()), 0x00, ChannelElement::size()); } } } bool D878UVCodeplug::encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // Encode channels for (unsigned int i=0; i(); i++) { // enable channel uint16_t bank = i/Limit::channelsPerBank(), idx = i%Limit::channelsPerBank(); uint32_t addr = Offset::channelBanks() + bank*Offset::betweenChannelBanks() + idx*ChannelElement::size(); ChannelElement ch(data(addr)); ch.fromChannelObj(ctx.get(i), ctx); ChannelExtensionElement ext(data(addr + Offset::toChannelExtension())); ext.fromChannelObj(ctx.get(i), ctx); } return true; } bool D878UVCodeplug::createChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create channels ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); for (uint16_t i=0; ichannelList()->add(obj); ctx.add(obj, i); ext.updateChannelObj(obj, ctx); } } return true; } bool D878UVCodeplug::linkChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); // Link channel objects for (uint16_t i=0; i(i)) { ch.linkChannelObj(ctx.get(i), ctx); ext.linkChannelObj(ctx.get(i), ctx); } } return true; } void D878UVCodeplug::allocateZones() { D868UVCodeplug::allocateZones(); // Hidden zone map image(0).addElement(Offset::hiddenZoneBitmap(), HiddenZoneBitmapElement::size()); } bool D878UVCodeplug::encodeZone(int i, Zone *zone, const Flags &flags, Context &ctx, const ErrorStack &err) { if (! D868UVCodeplug::encodeZone(i, zone, flags, ctx, err)) return false; AnytoneZoneExtension *ext = zone->anytoneExtension(); if (nullptr == ext) return true; HiddenZoneBitmapElement(data(Offset::hiddenZoneBitmap())).setEncoded(i, ext->hidden()); return true; } bool D878UVCodeplug::decodeZone(int i, Zone *zone, Context &ctx, const ErrorStack &err) { if (! D868UVCodeplug::decodeZone(i, zone, ctx, err)) return false; AnytoneZoneExtension *ext = zone->anytoneExtension(); if (nullptr == ext) { ext = new AnytoneZoneExtension(); zone->setAnytoneExtension(ext); } HiddenZoneBitmapElement bitmap(data(Offset::hiddenZoneBitmap())); ext->enableHidden(bitmap.isEncoded(i)); return true; } void D878UVCodeplug::allocateGeneralSettings() { // override allocation of general settings for D878UV code-plug. General settings are larger! image(0).addElement(Offset::settings(), GeneralSettingsElement::size()); image(0).addElement(Offset::dmrAPRSMessage(), DMRAPRSMessageElement::size()); image(0).addElement(Offset::settingsExtension(), ExtendedSettingsElement::size()); image(0).addElement(Offset::primaryId(), PrimaryRadioIdElement::size()); } bool D878UVCodeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GeneralSettingsElement(data(Offset::settings())).fromConfig(flags, ctx, err); DMRAPRSMessageElement(data(Offset::dmrAPRSMessage())).fromConfig(flags, ctx); ExtendedSettingsElement(data(Offset::settingsExtension())).fromConfig(flags, ctx, err); PrimaryRadioIdElement(data(Offset::primaryId())).encode(flags, ctx, err); return true; } bool D878UVCodeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GeneralSettingsElement(data(Offset::settings())).updateConfig(ctx, err); DMRAPRSMessageElement(data(Offset::dmrAPRSMessage())).updateConfig(ctx); ExtendedSettingsElement(data(Offset::settingsExtension())).updateConfig(ctx, err); return true; } bool D878UVCodeplug::linkGeneralSettings(Context &ctx, const ErrorStack &err) { if (! GeneralSettingsElement(data(Offset::settings())).linkSettings(ctx.config()->settings(), ctx, err)) { errMsg(err) << "Cannot link general settings extension."; return false; } if (! ExtendedSettingsElement(data(Offset::settingsExtension())).linkConfig(ctx, err)) { errMsg(err) << "Cannot link general settings extension."; return false; } return true; } void D878UVCodeplug::allocateGPSSystems() { // replaces D868UVCodeplug::allocateGPSSystems // APRS settings image(0).addElement(Offset::aprsSettings(), APRSSettingsElement::size()); image(0).addElement(Offset::analogAPRSMessage(), AnalogAPRSMessageElement::size()); } bool D878UVCodeplug::encodeGPSSystems(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // replaces D868UVCodeplug::encodeGPSSystems APRSSettingsElement aprs(data(Offset::aprsSettings())); // Encode APRS system (there can only be one) if (0 < ctx.count()) { aprs.fromFMAPRSSystem(ctx.get(0), ctx, err); AnalogAPRSMessageElement(data(Offset::analogAPRSMessage())) .setMessage(ctx.get(0)->message()); } if (! aprs.fromConfig(ctx, err)) { errMsg(err) << "Cannot encode global APRS settings."; return false; } // Encode GPS systems if (! aprs.fromDMRAPRSSystems(ctx)) return false; if (0 < ctx.count()) { // If there is at least one GPS system defined -> set auto TX interval. // This setting might be overridden by any analog APRS system below APRSSettingsElement aprs(data(Offset::aprsSettings())); aprs.setAutoTXInterval(ctx.get(0)->period()); aprs.setManualTXInterval(ctx.get(0)->period()); } return true; } bool D878UVCodeplug::createGPSSystems(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // replaces D868UVCodeplug::createGPSSystems // Before creating any GPS/APRS systems, get global auto TX interval APRSSettingsElement aprs(data(Offset::aprsSettings())); AnalogAPRSMessageElement aprsMessage(data(Offset::analogAPRSMessage())); if (! aprs.updateConfig(ctx, err)) { errMsg(err) << "Cannot update global APRS settings."; return false; } // Create APRS system (if enabled) if (aprs.isValid()) { FMAPRSSystem *sys = aprs.toFMAPRSSystem(ctx, err); if (nullptr == sys) { errMsg(err) << "Cannot decode positioning systems."; return false; } sys->setPeriod(aprs.autoTXInterval()); sys->setMessage(aprsMessage.message()); ctx.config()->posSystems()->add(sys); ctx.add(sys,0); } // Create GPS systems for (unsigned int i=0; iname() << "' at idx " << i << "."; sys->setPeriod(aprs.autoTXInterval()); ctx.config()->posSystems()->add(sys); ctx.add(sys, i); } else { return false; } } return true; } bool D878UVCodeplug::linkGPSSystems(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // replaces D868UVCodeplug::linkGPSSystems // Link APRS system APRSSettingsElement aprs(data(Offset::aprsSettings())); if (aprs.isValid()) { aprs.linkFMAPRSSystem(ctx.get(0), ctx); } // Link GPS systems for (unsigned int i=0; i(i), ctx); } return true; } void D878UVCodeplug::allocateRoaming() { /* Allocate roaming channels */ RoamingChannelBitmapElement roaming_channel_bitmap(data(Offset::roamingChannelBitmap())); for (uint8_t i=0; i skip if (! roaming_channel_bitmap.isEncoded(i)) continue; // Allocate roaming channel uint32_t addr = Offset::roamingChannels() + i*RoamingChannelElement::size(); if (!isAllocated(addr, 0)) { //logDebug() << "Allocate roaming channel at " << QString::number(addr, 16) << "h."; image(0).addElement(addr, RoamingChannelElement::size()); } } /* Allocate roaming zones. */ RoamingZoneBitmapElement roaming_zone_bitmap(data(Offset::roamingZoneBitmap())); for (uint8_t i=0; i skip if (! roaming_zone_bitmap.isEncoded(i)) continue; // Allocate roaming zone uint32_t addr = Offset::roamingZones() + i*RoamingZoneElement::size(); if (!isAllocated(addr, 0)) { logDebug() << "Allocate roaming zone at " << QString::number(addr, 16); image(0).addElement(addr, RoamingZoneElement::size()); } } } bool D878UVCodeplug::encodeRoaming(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // Encode roaming channels unsigned int num_roaming_channel = std::min( Limit::roamingChannels(), ctx.count()); for (uint8_t i=0; iroamingChannels()->get(i)->as(); rch_elm.clear(); rch_elm.fromChannel(rch); if (! ctx.add(rch, i)) { errMsg(err) << "Cannot add index " << i << " for roaming channel '" << rch->name() << "' to codeplug context."; return false; } } // Encode roaming zones for (unsigned int i=0; i(); i++){ uint32_t addr = Offset::roamingZones() + i*RoamingZoneElement::size(); RoamingZoneElement zone(data(addr)); logDebug() << "Encode roaming zone " << ctx.config()->roamingZones()->zone(i)->name() << " (" << (i+1) << ") at " << QString::number(addr, 16) << " with " << ctx.config()->roamingZones()->zone(i)->count() << " elements."; zone.fromRoamingZone(ctx.config()->roamingZones()->zone(i), ctx); } return true; } bool D878UVCodeplug::createRoaming(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create or find roaming channels RoamingChannelBitmapElement roaming_channel_bitmap(data(Offset::roamingChannelBitmap())); for (unsigned int i=0; iroamingZones()->add(zone); ctx.add(zone, i); z.linkRoamingZone(zone, ctx, err); } return true; } bool D878UVCodeplug::linkRoaming(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) // Pass, no need to link roaming channels. return true; } void D878UVCodeplug::allocateAESKeys() { AESEncryptionKeyBitmapElement bitmap(data(Offset::aesKeyBitmap())); for (uint8_t i=0; i skip if (! bitmap.isEncoded(i)) continue; // Allocate key uint32_t addr = Offset::aesKeys() + i*AESEncryptionKeyElement::size(); if (! isAllocated(addr, 0)) { image(0).addElement(addr, AESEncryptionKeyElement::size()); } } } bool D878UVCodeplug::encodeAESKeys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); Q_UNUSED(flags); AESEncryptionKeyBitmapElement bitmap(data(Offset::aesKeyBitmap())); for (uint8_t i=0; i skip if (! bitmap.isEncoded(i)) continue; AESEncryptionKeyElement key(data(Offset::aesKeys() + i*AESEncryptionKeyElement::size())); key.setIndex(i); key.setKey(ctx.get(i)->key()); } return true; } bool D878UVCodeplug::createAESKeys(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); AESEncryptionKeyBitmapElement bitmap(data(Offset::aesKeyBitmap())); for (uint8_t i=0; i skip if (! bitmap.isEncoded(i)) continue; // Decode AESEncryptionKeyElement keyEl(data(Offset::aesKeys() + i*AESEncryptionKeyElement::size())); if (! keyEl.isValid()) { logInfo() << "Skip invalid AES key at index " << i+1 << "."; continue; } if (keyEl.key().size() < 16) { logInfo() << "Skip AES key at index " << i+1 << ": Key size " << keyEl.key().size()*8 << " < 128."; continue; } auto key = new AESEncryptionKey(); key->setName(QString("AES Key %1").arg(i+1)); if (! key->setKey(keyEl.key(), err)) { delete key; return false; } // Store ctx.add(key, i); ctx.config()->commercialExtension()->encryptionKeys()->add(key); } return true; } void D878UVCodeplug::allocateARC4Keys() { ARC4EncryptionKeyBitmapElement bitmap(data(Offset::arc4KeyBitmap())); for (uint8_t i=0; i skip if (! bitmap.isEncoded(i)) continue; // Allocate key uint32_t addr = Offset::arc4Keys() + i*ARC4EncryptionKeyElement::size(); if (! isAllocated(addr, 0)) { image(0).addElement(addr, ARC4EncryptionKeyElement::size()); } } } bool D878UVCodeplug::encodeARC4Keys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); Q_UNUSED(flags); ARC4EncryptionKeyBitmapElement bitmap(data(Offset::arc4KeyBitmap())); for (uint8_t i=0; i skip if (! bitmap.isEncoded(i)) continue; ARC4EncryptionKeyElement key(data(Offset::arc4Keys() + i*ARC4EncryptionKeyElement::size())); key.setIndex(i); key.setKey(ctx.get(i)->key()); } return true; } bool D878UVCodeplug::createARC4Keys(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); ARC4EncryptionKeyBitmapElement bitmap(data(Offset::arc4KeyBitmap())); for (uint8_t i=0; i skip if (! bitmap.isEncoded(i)) continue; // Decode ARC4EncryptionKeyElement keyEl(data(Offset::arc4Keys() + i*ARC4EncryptionKeyElement::size())); auto key = new ARC4EncryptionKey(); key->setName(QString("ARC4 Key %1").arg(i+1)); if (! key->setKey(keyEl.key(), err)) { delete key; return false; } // Store ctx.add(key, i); ctx.config()->commercialExtension()->encryptionKeys()->add(key); } return true; } ================================================ FILE: lib/d878uv_codeplug.hh ================================================ #ifndef D878UV_CODEPLUG_HH #define D878UV_CODEPLUG_HH #include #include "d868uv_codeplug.hh" #include "signaling.hh" #include "gpssystem.hh" #include "gnsssettings.hh" #include "dmrsettings.hh" #include "smsextension.hh" class Channel; class DMRContact; class Zone; class RXGroupList; class ScanList; class DMRAPRSSystem; class RoamingChannel; /** Represents the device specific binary codeplug for Anytone AT-D878UV radios. * * In contrast to many other code-plugs, the code-plug for Anytone radios are spread over a large * memory area. In principle, this is a good idea, as it allows one to upload only the portion of the * codeplug that is actually configured. For example, if only a small portion of the available * contacts and channels are used, the amount of data that is written to the device can be * reduced. * * However, the implementation of this idea in this device is utter shit. The amount of * fragmentation of the codeplug is overwhelming. For example, while channels are organized more or * less nicely in continuous banks, zones are distributed throughout the entire code-plug. That is, * the names of zones are located in a different memory section that the channel lists. Some lists * are defined though bit-masks others by byte-masks. All bit-masks are positive, that is 1 * indicates an enabled item while the bit-mask for contacts is inverted. * * In general the code-plug is huge and complex. Moreover, the radio provides a huge amount of * options and features. To this end, reverse-engineering this code-plug was a nightmare. * * More over, the binary code-plug file generate by the windows CPS does not directly relate to * the data being written to the radio. To this end the code-plug has been reverse-engineered * using wireshark to monitor the USB communication between the windows CPS (running in a virtual * box) and the device. The latter makes the reverse-engineering particularly cumbersome. * * For more details, see https://dmr-tools.github.io/codeplugs. * * @ingroup d878uv */ class D878UVCodeplug : public D868UVCodeplug { Q_OBJECT protected: /** Channel name and call-sign colors supported by the D878UV. */ struct NameColor { public: /** Maps code -> color. */ static AnytoneDisplaySettingsExtension::Color decode(uint8_t code); /** Maps color -> code. */ static uint8_t encode(AnytoneDisplaySettingsExtension::Color color); protected: /** Encoding of the supported colors. */ typedef enum { Orange=0, Red=1, Yellow=2, Green=3, Turquoise=4, Blue=5, White = 6 } CodedColor; }; /** Text colors supported by the D878UV. */ struct TextColor { public: /** Maps code -> color. */ static AnytoneDisplaySettingsExtension::Color decode(uint8_t code); /** Maps color -> code. */ static uint8_t encode(AnytoneDisplaySettingsExtension::Color color); protected: /** Encoding of the supported colors. */ typedef enum { White=0, Black=1, Orange=2, Red=3, Yellow=4, Green=5, Turquoise=6, Blue=7 } CodedColor; }; public: /** Represents the actual channel encoded within the binary D878UV codeplug. * * This class implements only the differences to the generic @c AnytoneCodeplug::ChannelElement * (i.e. D868UVII). * * Memory layout of encoded channel (size 0x40 bytes): * @verbinclude d878uv_channel.txt */ class ChannelElement: public D868UVCodeplug::ChannelElement { public: /** Possible PTT ID settings. */ enum class PTTId { Off = 0, ///< Never send PTT-ID. Start = 1, ///< Send PTT-ID at start. End = 2, ///< Send PTT-ID at end. Both = 3 ///< Send PTT-ID at start and end. }; /** Possible APRS modes. */ enum class APRSType { Off = 0, FM = 1, DMR = 2 }; /** Defines all possible APRS PTT settings. */ enum class APRSPTT { Off = 0, ///< Do not send APRS on PTT. Start = 1, ///< Send APRS at start of transmission. End = 2 ///< Send APRS at end of transmission. }; /** Possible encryption types. */ enum class AdvancedEncryptionType { AES, ARC4 }; protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ChannelElement(uint8_t *ptr); /** Resets the channel. */ void clear() override; /** Returns the PTT ID settings. */ virtual PTTId pttIDSetting() const; /** Sets the PTT ID setting. */ virtual void setPTTIDSetting(PTTId ptt); /** Returns @c true if roaming is enabled. */ virtual bool roamingEnabled() const; /** Enables/disables roaming. */ virtual void enableRoaming(bool enable); // Moved /** Returns @c true if the data ACK is enabled. */ bool dataACK() const override; /** Enables/disables data ACK. */ void enableDataACK(bool enable) override; /** Returns @c true, if auto scan is enabled. */ virtual bool autoScan() const ; /** Enable/disable auto scan. */ virtual void enableAutoScan(bool enable); /** Returns APRS type for reporting the position. */ APRSType txAPRSType() const; /** Sets APRS type for reporting the position. */ void setTXAPRSType(APRSType aprsType); /** Returns the analog APRS PTT setting. */ virtual AnytoneChannelExtension::APRSPTT analogAPRSPTTSetting() const; /** Sets the analog APRS PTT setting. */ virtual void setAnalogAPRSPTTSetting(AnytoneChannelExtension::APRSPTT ptt); /** Returns the digital APRS PTT setting. */ virtual AnytoneChannelExtension::APRSPTT digitalAPRSPTTSetting() const; /** Sets the digital APRS PTT setting. */ virtual void setDigitalAPRSPTTSetting(AnytoneChannelExtension::APRSPTT ptt); /** Returns the DMR APRS system index. */ unsigned digitalAPRSSystemIndex() const override; /** Sets the DMR APRS system index. */ void setDigitalAPRSSystemIndex(unsigned idx) override; /** Returns the frequency correction in ???. */ virtual int frequenyCorrection() const; /** Sets the frequency correction in ???. */ virtual void setFrequencyCorrection(int corr); /** Returns the index of the FM APRS frequency [0,7]. */ virtual unsigned int fmAPRSFrequencyIndex() const; /** Sets the FM APRS frequency index [0,7]. */ virtual void setFMAPRSFrequencyIndex(unsigned int idx); /** If set, transmission of talker alias for this channel is enabled. */ virtual bool sendTalkerAlias() const; /** Enable transmission of talker alias. */ virtual void enableSendTalkerAlias(bool enable); /** Returns the encryption type. */ virtual AdvancedEncryptionType advancedEncryptionType() const; /** Sets the encryptionType. */ virtual void setEncryptionType(AdvancedEncryptionType type); /** Returns @c true, if an AES encryption key index is set. */ virtual bool hasAESEncryptionKeyIndex() const; /** Returns the AES encryption key index. * The index is 0-based. */ virtual unsigned int aesEncryptionKeyIndex() const; /** Sets the AES encryption key index. */ virtual void setAESEncryptionKeyIndex(unsigned int index); /** Clears the AES encryption key index. */ virtual void clearAESEncryptionKeyIndex(); /** Returns @c true, if an ARC4 encryption key index is set. */ virtual bool hasARC4EncryptionKeyIndex() const; /** Returns the ARC4 encryption key index. * The index is 0-based. */ virtual unsigned int arc4EncryptionKeyIndex() const; /** Sets the ARC4 encryption key index. */ virtual void setARC4EncryptionKeyIndex(unsigned int index); /** Clears the ARC4 encryption key index. */ virtual void clearARC4EncryptionKeyIndex(); /** Returns the encryption type. */ DMREncryptionType dmrEncryptionType() const override; /** Sets the encryption type. */ void setDMREncryptionType(DMREncryptionType type) override; /** Returns @c true if a DMR encryption key is set. */ bool hasDMREncryptionKeyIndex() const override; /** Returns the DMR encryption key index (+1), 0=Off. */ unsigned dmrEncryptionKeyIndex() const override; /** Sets the DMR encryption key index (+1), 0=Off. */ void setDMREncryptionKeyIndex(unsigned idx) override; /** Clears the DMR encryption key index. */ void clearDMREncryptionKeyIndex() override; bool adaptiveTDMA() const override; void enableAdaptiveTDMA(bool enable) override; /** Constructs a Channel object from this element. */ Channel *toChannelObj(Context &ctx) const override; /** Links a previously created channel object. */ bool linkChannelObj(Channel *c, Context &ctx) const override; /** Encodes the given channel object. */ bool fromChannelObj(const Channel *c, Context &ctx) override; protected: /** Internal used offsets within the channel element. */ struct Offset : public D868UVCodeplug::ChannelElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int pttIDSetting() { return 0x0019; } static constexpr unsigned int aesKeyIndex() { return 0x0022; } static constexpr Bit roaming() { return {0x0034, 2}; } static constexpr Bit dataACK() { return {0x0034, 3}; } static constexpr Bit autoScan() { return {0x0034, 4}; } static constexpr unsigned int fmAPRSPTTSetting() { return 0x0036; } static constexpr unsigned int dmrAPRSPTTSetting() { return 0x0037; } static constexpr unsigned int dmrAPRSSystemIndex() { return 0x0038; } static constexpr unsigned int frequenyCorrection() { return 0x0039; } static constexpr unsigned int dmrEncryptionKey() { return 0x003a; } static constexpr Bit muteFMAPRS() { return {0x003b, 3}; } static constexpr Bit talkerAlias() { return {0x003b, 4}; } static constexpr Bit advancedEncryptionType() { return {0x003b, 5}; } static constexpr unsigned int fmAPRSFrequencyIndex() { return 0x003c; } static constexpr unsigned int arc4KeyIndex() { return 0x003d; } // Removed static constexpr Bit adaptiveTDMA() { return {0, 0}; } /// @endcond }; }; /** Starting from AT-D878UV, all AnyTone devices encode a channel settings extension element * at an offset 0x2000 to the original channel. Also, the size of the extension element is * identical to the channel element itself. This is weird. Anyway. This class encodes/decodes * these settings. * * This implementation is compatible with firmware version 3.06 */ class ChannelExtensionElement: public Codeplug::Element { protected: /** Hidden constructor. */ ChannelExtensionElement(uint8_t *ptr, size_t size); public: /** Default constructor. */ ChannelExtensionElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return ChannelElement::size(); } /** Resets the channel extension. */ void clear(); /** Returns the BOT 5-tone ID index. */ virtual unsigned int bot5ToneIDIndex() const; /** Sets the BOT 5-tone ID index. */ virtual void setBOT5ToneIDIndex(unsigned int idx); /** Returns the EOT 5-tone ID index. */ virtual unsigned int eot5ToneIDIndex() const; /** Sets the EOT 5-tone ID index. */ virtual void setEOT5ToneIDIndex(unsigned int idx); /** Returns the transmit color code. */ virtual unsigned int txColorCode() const; /** Sets the transmit color code. */ virtual void setTXColorCode(unsigned int cc); /** Constructs a Channel object from this element. */ virtual bool updateChannelObj(Channel *c, Context &ctx) const; /** Links a previously created channel object. */ virtual bool linkChannelObj(Channel *c, Context &ctx) const; /** Encodes the given channel object. */ virtual bool fromChannelObj(const Channel *c, Context &ctx); protected: /** Some internal used offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bot5ToneIDIndex() { return 0x0000; } static constexpr unsigned int eot5ToneIDIndex() { return 0x0001; } static constexpr unsigned int txColorCode() { return 0x0003; } /// @endcond }; }; /** Represents the general config of the radio within the D878UV binary codeplug. * * This class implements only the differences to the generic * @c AnytonCodeplug::GeneralSettingsElement. * * Binary encoding of the general settings (size 0x00f0 bytes): * @verbinclude d878uv_generalsettings.txt */ class GeneralSettingsElement: public D868UVCodeplug::GeneralSettingsElement { protected: /** Device specific key functions. */ struct KeyFunction { public: /** Encodes key function. */ static uint8_t encode(AnytoneKeySettingsExtension::KeyFunction tone); /** Decodes key function. */ static AnytoneKeySettingsExtension::KeyFunction decode(uint8_t code); protected: /** Device specific key functions. */ typedef enum { Off = 0x00, Voltage = 0x01, Power = 0x02, Repeater = 0x03, Reverse = 0x04, Encryption = 0x05, Call = 0x06, VOX = 0x07, ToggleVFO = 0x08, SubPTT = 0x09, Scan = 0x0a, WFM = 0x0b, Alarm = 0x0c, RecordSwitch = 0x0d, Record = 0x0e, SMS = 0x0f, Dial = 0x10, GPSInformation = 0x11, Monitor = 0x12, ToggleMainChannel = 0x13, HotKey1 = 0x14, HotKey2 = 0x15, HotKey3 = 0x16, HotKey4 = 0x17, HotKey5 = 0x18, HotKey6 = 0x19, WorkAlone = 0x1a, SkipChannel = 0x1b, DMRMonitor = 0x1c, SubChannel = 0x1d, PriorityZone = 0x1e, VFOScan = 0x1f, MICSoundQuality = 0x20, LastCallReply = 0x21, ChannelType = 0x22, Ranging = 0x23, Roaming = 0x24, ChannelRanging = 0x25, MaxVolume = 0x26, Slot = 0x27, APRSType = 0x28, Zone = 0x29, RoamingSet = 0x2a, APRSSet = 0x2b, Mute=0x2c, CtcssDcsSet=0x2d, TBSTSend = 0x2e, Bluetooth = 0x2f, GPS = 0x30, ChannelName = 0x31, CDTScan = 0x32, APRSSend = 0x33, APRSInfo = 0x34, GPSRoaming = 0x35, DIMShut = 0x36, Squelch = 0x38 } KeyFunctionCode; }; /** Device specific time zones. */ struct TimeZone { public: /** Encodes time zone. */ static uint8_t encode(const QTimeZone& zone); /** Decodes time zone. */ static QTimeZone decode(uint8_t code); protected: /** Vector of possible time-zones. */ static QVector _timeZones; }; protected: /** Possible VFO frequency steps. */ enum FreqStep { FREQ_STEP_2_5kHz = 0, ///< 2.5kHz FREQ_STEP_5kHz = 1, ///< 5kHz FREQ_STEP_6_25kHz = 2, ///< 6.25kHz FREQ_STEP_8_33kHz = 3, ///< 8.33kHz FREQ_STEP_10kHz = 4, ///< 10kHz FREQ_STEP_12_5kHz = 5, ///< 12.5kHz FREQ_STEP_20kHz = 6, ///< 20kHz FREQ_STEP_25kHz = 7, ///< 25kHz FREQ_STEP_50kHz = 8 ///< 50kHz }; /** DTMF signalling durations. */ enum DTMFDuration { DTMF_DUR_50ms = 0, DTMF_DUR_100ms = 1, DTMF_DUR_200ms = 2, DTMF_DUR_300ms = 3, DTMF_DUR_500ms = 4 }; /** TBST (open repeater) frequencies. */ enum class TBSTFrequency { Hz1000 = 0, Hz1450 = 1, Hz1750 = 2, Hz2100 = 3 }; /** All possible STE (squelch tail eliminate) frequencies. */ enum class STEFrequency { Off = 0, Hz55_2 = 1, Hz259_2 = 2 }; /** Possible background images. */ enum class BackgroundImage { Default=0, Custom1=1, Custom2=2 }; /** Encoding of possible backlight durations. */ enum class BacklightDuration { Infinite = 0, _5s = 1, _10s = 2, _15s = 3, _20s = 4, _25s = 5, _30s = 6, _1min=7, _2min=8, _3min = 9, _4min = 10, _5min = 11, _15min = 12, _30min = 13, _45min = 14, _1h = 15 }; /** Possible SMS formats. */ enum class SMSFormat { Motorola = 0, Hytera = 1, DMR = 2 }; protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ GeneralSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00f0; } /** Resets the general settings. */ void clear() override; /** Returns the transmit timeout in seconds. */ virtual Interval transmitTimeout() const; /** Sets the transmit timeout in seconds. */ virtual void setTransmitTimeout(const Interval &tot); /** Returns the UI language. */ virtual AnytoneDisplaySettingsExtension::Language language() const; /** Sets the UI language. */ virtual void setLanguage(AnytoneDisplaySettingsExtension::Language lang); QTimeZone gpsTimeZone() const override; void setGPSTimeZone(const QTimeZone &zone) override; /** Returns the VFO frequency step in kHz. */ virtual Frequency vfoFrequencyStep() const; /** Sets the VFO frequency step in kHz. */ virtual void setVFOFrequencyStep(Frequency kHz); AnytoneKeySettingsExtension::KeyFunction funcKeyAShort() const override; void setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyBShort() const override; void setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyCShort() const override; void setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey1Short() const override; void setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey2Short() const override; void setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyALong() const override; void setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyBLong() const override; void setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyCLong() const override; void setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey1Long() const override; void setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey2Long() const override; void setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func) override; /** Returns the STE (squelch tail eliminate) type. */ virtual AnytoneSettingsExtension::STEType steType() const; /** Sets the STE (squelch tail eliminate) type. */ virtual void setSTEType(AnytoneSettingsExtension::STEType type); /** Returns the STE (squelch tail eliminate) frequency setting in Hz. * A value of 0 disables the STE. Possible values are 55.2 and 259.2 Hz. */ virtual double steFrequency() const; /** Sets the STE (squelch tail eliminate) frequency setting. * A value of 0 disables the STE. Possible values are 55.2 and 259.2 Hz. */ virtual void setSTEFrequency(double freq); /** Returns the group call hang time in seconds. */ virtual Interval groupCallHangTime() const; /** Sets the group call hang time in seconds. */ virtual void setGroupCallHangTime(Interval sec); /** Returns the private call hang time in seconds. */ virtual Interval privateCallHangTime() const; /** Sets the private call hang time in seconds. */ virtual void setPrivateCallHangTime(Interval sec); /** Returns the pre-wave time in ms. */ virtual Interval preWaveDelay() const; /** Sets the pre-wave time in ms. */ virtual void setPreWaveDelay(Interval ms); /** Returns the wake head-period in ms. */ virtual Interval wakeHeadPeriod() const; /** Sets the wake head-period in ms. */ virtual void setWakeHeadPeriod(Interval ms); /** Returns the wide-FM (broadcast) channel index. */ virtual unsigned wfmChannelIndex() const; /** Sets the wide-FM (broadcast) channel index. */ virtual void setWFMChannelIndex(unsigned idx); /** Returns @c true if the WFM RX is in VFO mode. */ virtual bool wfmVFOEnabled() const; /** Enables/disables VFO mode for WFM RX. */ virtual void enableWFMVFO(bool enable); Interval backlightDuration() const override; void setBacklightDuration(Interval intv) override; /** Returns the DTMF tone duration in ms. */ virtual unsigned dtmfToneDuration() const; /** Sets the DTMF tone duration in ms. */ virtual void setDTMFToneDuration(unsigned ms); /** Returns @c true if "man down" is enabled. */ virtual bool manDown() const; /** Enables/disables "man down". */ virtual void enableManDown(bool enable); /** Returns @c true if WFM monitor is enabled. */ virtual bool wfmMonitor() const; /** Enables/disables WFM monitor. */ virtual void enableWFMMonitor(bool enable); /** Returns the TBST frequency. */ virtual Frequency tbstFrequency() const; /** Sets the TBST frequency. */ virtual void setTBSTFrequency(Frequency freq); /** Returns @c true if the "pro mode" is enabled. */ virtual bool proMode() const; /** Enables/disables the "pro mode". */ virtual void enableProMode(bool enable); /** Returns @c true if the own ID is filtered in call lists. */ virtual bool filterOwnID() const; /** Enables/disables filter of own ID in call lists. */ virtual void enableFilterOwnID(bool enable); /** Returns @c true remote stun/kill is enabled. */ virtual bool remoteStunKill() const; /** Enables/disables remote stun/kill. */ virtual void enableRemoteStunKill(bool enable); /** Returns @c true remote monitor is enabled. */ virtual bool remoteMonitor() const; /** Enables/disables remote monitor. */ virtual void enableRemoteMonitor(bool enable); /** Returns the monitor slot match. */ virtual AnytoneDMRSettingsExtension::SlotMatch monitorSlotMatch() const; /** Sets the monitor slot match. */ virtual void setMonitorSlotMatch(AnytoneDMRSettingsExtension::SlotMatch match); /** Returns @c true if the monitor matches color code. */ virtual bool monitorColorCodeMatch() const; /** Enables/disables monitor color code match. */ virtual void enableMonitorColorCodeMatch(bool enable); /** Returns @c true if the monitor matches ID. */ virtual bool monitorIDMatch() const; /** Enables/disables monitor ID match. */ virtual void enableMonitorIDMatch(bool enable); /** Returns @c true if the monitor holds the time slot. */ virtual bool monitorTimeSlotHold() const; /** Enables/disables monitor time slot hold. */ virtual void enableMonitorTimeSlotHold(bool enable); /** Returns the "man down" delay in seconds. */ virtual Interval manDownDelay() const; /** Sets the "man down" delay in seconds. */ virtual void setManDownDelay(Interval sec); /** Returns the analog call hold in seconds. */ virtual unsigned fmCallHold() const; /** Sets the analog call hold in seconds. */ virtual void setFMCallHold(unsigned sec); /** Returns @c true if the GPS range reporting is enabled. */ virtual bool gpsMessageEnabled() const; /** Enables/disables GPS range reporting. */ virtual void enableGPSMessage(bool enable); /** Returns @c true if the call channel is maintained. */ virtual bool maintainCallChannel() const; /** Enables/disables maintaining the call channel. */ virtual void enableMaintainCallChannel(bool enable); /** Returns the priority Zone A index. */ virtual unsigned priorityZoneAIndex() const; /** Sets the priority zone A index. */ virtual void setPriorityZoneAIndex(unsigned idx); /** Returns the priority Zone B index. */ virtual unsigned priorityZoneBIndex() const; /** Sets the priority zone B index. */ virtual void setPriorityZoneBIndex(unsigned idx); /** Returns @c true if bluetooth is enabled. */ virtual bool bluetooth() const; /** Enables/disables bluetooth. */ virtual void enableBluetooth(bool enable); /** Returns @c true if the internal mic is additionally active when BT is active. */ virtual bool btAndInternalMic() const; /** Enables/disables the internal mic when BT is active. */ virtual void enableBTAndInternalMic(bool enable); /** Returns @c true if the internal speaker is additionally active when BT is active. */ virtual bool btAndInternalSpeaker() const; /** Enables/disables the internal speaker when BT is active. */ virtual void enableBTAndInternalSpeaker(bool enable); /** Returns @c true if the plug-in record tone is enabled. */ virtual bool pluginRecTone() const; /** Enables/disables the plug-in record tone. */ virtual void enablePluginRecTone(bool enable); /** Returns the GPS ranging interval in seconds. */ virtual Interval gpsUpdatePeriod() const override; /** Sets the GPS ranging interval in seconds. */ virtual void setGPSUpdatePeriod(Interval sec) override; /** Returns the bluetooth microphone gain [1,10]. */ virtual unsigned int btMicGain() const; /** Sets the bluetooth microphone gain [1,10]. */ virtual void setBTMicGain(unsigned int gain); /** Returns the bluetooth speaker gain [1,10]. */ virtual unsigned int btSpeakerGain() const; /** Sets the bluetooth speaker gain [1,10]. */ virtual void setBTSpeakerGain(unsigned int gain); /** Returns @c true if the channel number is displayed. */ virtual bool displayChannelNumber() const; /** Enables/disables display of channel number. */ virtual void enableDisplayChannelNumber(bool enable); bool showCurrentContact() const override; void enableShowCurrentContact(bool enable) override; /** Returns the auto roaming period in minutes. */ virtual Interval autoRoamPeriod() const; /** Sets the auto roaming period in minutes. */ virtual void setAutoRoamPeriod(Interval min); bool keyToneLevelAdjustable() const override; Level keyToneLevel() const override; void setKeyToneLevel(Level level) override; void setKeyToneLevelAdjustable() override; AnytoneDisplaySettingsExtension::Color callDisplayColor() const override; void setCallDisplayColor(AnytoneDisplaySettingsExtension::Color color) override; bool gpsUnitsImperial() const override; void enableGPSUnitsImperial(bool enable) override; bool knobLock() const override; void enableKnobLock(bool enable) override; bool keypadLock() const override; void enableKeypadLock(bool enable) override; bool sidekeysLock() const override; void enableSidekeysLock(bool enable) override; bool keyLockForced() const override; void enableKeyLockForced(bool enable) override; /** Returns the auto-roam delay in seconds. */ virtual Interval autoRoamDelay() const; /** Sets the auto-roam delay in seconds. */ virtual void setAutoRoamDelay(Interval sec); /** Returns the standby text color. */ virtual AnytoneDisplaySettingsExtension::Color standbyTextColor() const; /** Sets the standby text color. */ virtual void setStandbyTextColor(AnytoneDisplaySettingsExtension::Color color); /** Returns the standby background image. */ virtual BackgroundImage standbyBackgroundImage() const; /** Sets the standby background image. */ virtual void setStandbyBackgroundImage(D878UVCodeplug::GeneralSettingsElement::BackgroundImage img); bool showLastHeard() const override; void enableShowLastHeard(bool enable) override; /** Returns the SMS format. */ virtual SMSExtension::Format smsFormat() const; /** Sets the SMS format. */ virtual void setSMSFormat(SMSExtension::Format fmt); /** Returns the minimum frequency in Hz for the auto-repeater range in VHF band. */ virtual Frequency autoRepeaterMinFrequencyVHF() const override; /** Sets the minimum frequency in Hz for the auto-repeater range in VHF band. */ virtual void setAutoRepeaterMinFrequencyVHF(Frequency Hz) override; /** Returns the maximum frequency in Hz for the auto-repeater range in VHF band. */ virtual Frequency autoRepeaterMaxFrequencyVHF() const override; /** Sets the maximum frequency in Hz for the auto-repeater range in VHF band. */ virtual void setAutoRepeaterMaxFrequencyVHF(Frequency Hz) override; /** Returns the minimum frequency in Hz for the auto-repeater range in UHF band. */ virtual Frequency autoRepeaterMinFrequencyUHF() const override; /** Sets the minimum frequency in Hz for the auto-repeater range in UHF band. */ virtual void setAutoRepeaterMinFrequencyUHF(Frequency Hz) override; /** Returns the maximum frequency in Hz for the auto-repeater range in UHF band. */ virtual Frequency autoRepeaterMaxFrequencyUHF() const override; /** Sets the maximum frequency in Hz for the auto-repeater range in UHF band. */ virtual void setAutoRepeaterMaxFrequencyUHF(Frequency Hz) override; /** Returns the auto-repeater direction for VFO B. */ virtual AnytoneAutoRepeaterSettingsExtension::Direction autoRepeaterDirectionB() const override; /** Sets the auto-repeater direction for VFO B. */ virtual void setAutoRepeaterDirectionB(AnytoneAutoRepeaterSettingsExtension::Direction dir) override; /** If enabled, the FM ID is sent together with selected contact. */ virtual bool fmSendIDAndContact() const; /** Enables/disables sending contact with FM ID. */ virtual void enableFMSendIDAndContact(bool enable); bool defaultChannel() const override; void enableDefaultChannel(bool enable) override; unsigned defaultZoneIndexA() const override; void setDefaultZoneIndexA(unsigned idx) override; unsigned defaultZoneIndexB() const override; void setDefaultZoneIndexB(unsigned idx) override; bool defaultChannelAIsVFO() const override; unsigned defaultChannelAIndex() const override; void setDefaultChannelAIndex(unsigned idx) override; void setDefaultChannelAToVFO() override; bool defaultChannelBIsVFO() const override; unsigned defaultChannelBIndex() const override; void setDefaultChannelBIndex(unsigned idx) override; void setDefaultChannelBToVFO() override; /** Returns the default roaming zone index. */ virtual unsigned defaultRoamingZoneIndex() const; /** Sets the default roaming zone index. */ virtual void setDefaultRoamingZoneIndex(unsigned idx); /** Returns @c true if repeater range check is enabled. */ virtual bool repeaterRangeCheck() const; /** Enables/disables repeater range check. */ virtual void enableRepeaterRangeCheck(bool enable); /** Returns the repeater range check period in seconds. */ virtual Interval repeaterRangeCheckInterval() const; /** Sets the repeater range check interval in seconds. */ virtual void setRepeaterRangeCheckInterval(Interval sec); /** Returns the number of repeater range checks. */ virtual unsigned repeaterRangeCheckCount() const; /** Sets the number of repeater range checks. */ virtual void setRepeaterRangeCheckCount(unsigned n); /** Returns the roaming start condition. */ virtual AnytoneRoamingSettingsExtension::RoamStart roamingStartCondition() const; /** Sets the roaming start condition. */ virtual void setRoamingStartCondition(AnytoneRoamingSettingsExtension::RoamStart cond); /** Returns the backlight duration during TX in seconds. */ virtual Interval txBacklightDuration() const; /** Sets the backlight duration during TX in seconds. */ virtual void setTXBacklightDuration(Interval sec); /** Returns @c true if the "separate display" is enabled. */ virtual bool separateDisplay() const; /** Enables/disables "separate display. */ virtual void enableSeparateDisplay(bool enable); bool keepLastCaller() const override; void enableKeepLastCaller(bool enable) override; /** Returns the channel name color. */ virtual AnytoneDisplaySettingsExtension::Color channelNameColor() const; /** Sets the channel name color. */ virtual void setChannelNameColor(AnytoneDisplaySettingsExtension::Color color); /** Returns @c true if repeater check notification is enabled. */ virtual bool repeaterCheckNotification() const; /** Enables/disables repeater check notification. */ virtual void enableRepeaterCheckNotification(bool enable); /** Returns the backlight duration during RX in seconds. */ Interval rxBacklightDuration() const; /** Sets the backlight duration during RX in seconds. */ void setRXBacklightDuration(Interval sec); /** Returns @c true if roaming is enabled. */ virtual bool roaming() const; /** Enables/disables repeater check notification. */ virtual void enableRoaming(bool enable); /** Returns the mute delay in minutes. */ virtual Interval muteDelay() const; /** Sets the mute delay in minutes. */ virtual void setMuteDelay(Interval min); /** Returns the number of repeater check notifications. */ virtual unsigned repeaterCheckNumNotifications() const; /** Sets the number of repeater check notifications. */ virtual void setRepeaterCheckNumNotifications(unsigned num); /** Returns @c true if boot GPS check is enabled. */ virtual bool bootGPSCheck() const; /** Enables/disables boot GPS check. */ virtual void enableBootGPSCheck(bool enable); /** Returns @c true if boot reset is enabled. */ virtual bool bootReset() const; /** Enables/disables boot reset. */ virtual void enableBootReset(bool enable); /** Returns @c true, if the bluetooth hold time is enabled. */ virtual bool btHoldTimeEnabled() const; /** Returns @c true, if the bluetooth hold time is infinite. */ virtual bool btHoldTimeInfinite() const; /** Returns the bluetooth hold time. */ virtual Interval btHoldTime() const; /** Sets the Bluetooth hold time (1-120s). */ virtual void setBTHoldTime(Interval interval); /** Sets the Bluetooth hold time to infinite. */ virtual void setBTHoldTimeInfinite(); /** Sets the Bluetooth hold time to infinite. */ virtual void disableBTHoldTime(); /** Returns the bluetooth RX delay in ms. */ virtual Interval btRXDelay() const; /** Sets the bluetooth RX delay in ms. */ virtual void setBTRXDelay(Interval delay); bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) override; bool updateConfig(Context &ctx, const ErrorStack &err) override; bool linkSettings(RadioSettings *settings, Context &ctx, const ErrorStack &err) override; public: /** Some limits for the settings. */ struct Limit: D868UVCodeplug::GeneralSettingsElement::Limit { // Valid repeater-out-of-range notification counts. static constexpr Range repeaterOORNotificationCount() { return {1, 10}; } }; protected: /** Some internal used offsets within the element. */ struct Offset: public D868UVCodeplug::GeneralSettingsElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int transmitTimeout() { return 0x0004; } static constexpr unsigned int language() { return 0x0005; } static constexpr unsigned int vfoFrequencyStep() { return 0x0008; } static constexpr unsigned int steType() { return 0x0017; } static constexpr unsigned int steFrequency() { return 0x0018; } static constexpr unsigned int groupCallHangTime() { return 0x0019; } static constexpr unsigned int privateCallHangTime() { return 0x001a; } static constexpr unsigned int preWaveDelay() { return 0x001b; } static constexpr unsigned int wakeHeadPeriod() { return 0x001c; } static constexpr unsigned int wfmChannelIndex() { return 0x001d; } static constexpr unsigned int wfmVFOEnabled() { return 0x001e; } static constexpr unsigned int dtmfToneDuration() { return 0x0023; } static constexpr unsigned int manDown() { return 0x0024; } static constexpr unsigned int wfmMonitor() { return 0x002b; } static constexpr unsigned int tbstFrequency() { return 0x002e; } static constexpr unsigned int proMode() { return 0x0034; } static constexpr unsigned int filterOwnID() { return 0x0038; } static constexpr unsigned int remoteStunKill() { return 0x003c; } static constexpr unsigned int remoteMonitor() { return 0x003e; } static constexpr unsigned int monSlotMatch() { return 0x0049; } static constexpr unsigned int monColorCodeMatch() { return 0x004a; } static constexpr unsigned int monIDMatch() { return 0x004b; } static constexpr unsigned int monTimeSlotHold() { return 0x004c; } static constexpr unsigned int manDownDelay() { return 0x004f; } static constexpr unsigned int fmCallHold() { return 0x0050; } static constexpr unsigned int enableGPSMessage() { return 0x0053; } static constexpr unsigned int maintainCallChannel() { return 0x006e; } static constexpr unsigned int priorityZoneA() { return 0x006f; } static constexpr unsigned int priorityZoneB() { return 0x0070; } static constexpr unsigned int bluetooth() { return 0x00b1; } static constexpr unsigned int btAndInternalMic() { return 0x00b2; } static constexpr unsigned int btAndInternalSpeaker(){ return 0x00b3; } static constexpr unsigned int pluginRecTone() { return 0x00b4; } static constexpr unsigned int gpsRangingInterval() { return 0x00b5; } static constexpr unsigned int btMicGain() { return 0x00b6; } static constexpr unsigned int btSpeakerGain() { return 0x00b7; } static constexpr unsigned int showChannelNumber() { return 0x00b8; } static constexpr unsigned int showCurrentContact() { return 0x00b9; } static constexpr unsigned int autoRoamPeriod() { return 0x00ba; } static constexpr unsigned int keyToneLevel() { return 0x00bb; } static constexpr unsigned int callColor() { return 0x00bc; } static constexpr unsigned int gpsUnits() { return 0x00bd; } static constexpr unsigned int knobLock() { return 0x00be; } static constexpr unsigned int keypadLock() { return 0x00be; } static constexpr unsigned int sideKeyLock() { return 0x00be; } static constexpr unsigned int forceKeyLock() { return 0x00be; } static constexpr unsigned int autoRoamDelay() { return 0x00bf; } static constexpr unsigned int standbyTextColor() { return 0x00c0; } static constexpr unsigned int standbyBackground() { return 0x00c1; } static constexpr unsigned int showLastHeard() { return 0x00c2; } static constexpr unsigned int smsFormat() { return 0x00c3; } static constexpr unsigned int autoRepMinVHF() { return 0x00c4; } static constexpr unsigned int autoRepMaxVHF() { return 0x00c8; } static constexpr unsigned int autoRepMinUHF() { return 0x00cc; } static constexpr unsigned int autoRepMaxUHF() { return 0x00d0; } static constexpr unsigned int autoRepeaterDirB() { return 0x00d4; } static constexpr unsigned int fmSendIDAndContact() { return 0x00d5; } static constexpr unsigned int defaultChannels() { return 0x00d7; } static constexpr unsigned int defaultZoneA() { return 0x00d8; } static constexpr unsigned int defaultZoneB() { return 0x00d9; } static constexpr unsigned int defaultChannelA() { return 0x00da; } static constexpr unsigned int defaultChannelB() { return 0x00db; } static constexpr unsigned int defaultRoamingZone() { return 0x00dc; } static constexpr unsigned int repRangeCheck() { return 0x00dd; } static constexpr unsigned int rangeCheckInterval() { return 0x00de; } static constexpr unsigned int rangeCheckCount() { return 0x00df; } static constexpr unsigned int roamStartCondition() { return 0x00e0; } static constexpr unsigned int txBacklightDuration() { return 0x00e1; } static constexpr unsigned int displaySeparator() { return 0x00e2; } static constexpr unsigned int keepLastCaller() { return 0x00e3; } static constexpr unsigned int channelNameColor() { return 0x00e4; } static constexpr unsigned int repCheckNotify() { return 0x00e5; } static constexpr unsigned int rxBacklightDuration() { return 0x00e6; } static constexpr unsigned int roaming() { return 0x00e7; } static constexpr unsigned int muteDelay() { return 0x00e9; } static constexpr unsigned int repCheckNumNotify() { return 0x00ea; } static constexpr unsigned int bootGPSCheck() { return 0x00eb; } static constexpr unsigned int bootReset() { return 0x00ec; } static constexpr unsigned int btHoldTime() { return 0x00ed; } static constexpr unsigned int btRXDelay() { return 0x00ee; } /// @endcond }; }; /** General settings extension element for the D878UV. * * Memory representation of the encoded settings element (size 0x200 bytes): * @verbinclude d878uv_generalsettingsextension.txt */ class ExtendedSettingsElement: public AnytoneCodeplug::ExtendedSettingsElement { protected: /** Encoding of possible GNSSs. Could someone explain those AnyTone engineers the concept of * flags? */ enum class GNSS { GPS = 0, Beidou = 1, GPS_Beidou = 2, Glonass = 3, GPS_Glonass = 4, Beidou_Glonass = 5, GPS_Beidou_Glonass = 6 }; /** Talker alias encoding. */ enum class TalkerAliasEncoding { ISO8 = 0, ISO7 = 1, Unicode = 2, }; protected: /** Hidden Constructor. */ ExtendedSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ExtendedSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00000200; } /** Resets the settings. */ void clear(); /** Returns @c true if the talker alias is sent. */ virtual bool sendTalkerAlias() const; /** Enables/disables sending the talker alias. */ virtual void enableSendTalkerAlias(bool enable); /** Returns the talker alias source. */ virtual AnytoneDMRSettingsExtension::TalkerAliasSource talkerAliasSource() const; /** Sets the talker alias source. */ virtual void setTalkerAliasSource(AnytoneDMRSettingsExtension::TalkerAliasSource mode); /** Returns the talker alias encoding. */ virtual DMRSettings::TalkerAliasEncoding talkerAliasEncoding() const; /** Sets the talker alias encoding. */ virtual void setTalkerAliasEncoding(DMRSettings::TalkerAliasEncoding encoding); /** Returns @c true if the BT PTT latch is enabled. */ virtual bool bluetoothPTTLatch() const; /** Enables/disables bluetooth PTT latch. */ virtual void enableBluetoothPTTLatch(bool enable); /** Returns @c true if the bluetooth PTT sleep delay is disabled (infinite). */ virtual bool infiniteBluetoothPTTSleepDelay() const; /** Returns the bluetooth PTT sleep delay in minutes, 0=off. */ virtual Interval bluetoothPTTSleepDelay() const; /** Sets the bluetooth PTT sleep delay in minutes. */ virtual void setBluetoothPTTSleepDelay(Interval delay); /** Sets the bluetooth PTT sleep delay to infinite/disabled. */ virtual void setInfiniteBluetoothPTTSleepDelay(); /** Returns @c true if the auto repeater UHF 2 offset index is set. */ virtual bool hasAutoRepeaterUHF2OffsetIndex() const; /** Returns the index of the UHF 2 offset frequency. */ virtual unsigned autoRepeaterUHF2OffsetIndex() const; /** Sets the index of the UHF 2 offset frequency. */ virtual void setAutoRepeaterUHF2OffsetIndex(unsigned idx); /** Clears the auto repeater UHF 2 offset frequency index. */ virtual void clearAutoRepeaterUHF2OffsetIndex(); /** Returns @c true if the auto repeater VHF 2 offset index is set. */ virtual bool hasAutoRepeaterVHF2OffsetIndex() const; /** Returns the index of the VHF 2 offset frequency. */ virtual unsigned autoRepeaterVHF2OffsetIndex() const; /** Sets the index of the VHF 2 offset frequency. */ virtual void setAutoRepeaterVHF2OffsetIndex(unsigned idx); /** Clears the auto repeater VHF 2 offset frequency index. */ virtual void clearAutoRepeaterVHF2OffsetIndex(); /** Returns the minimum frequency in Hz for the auto-repeater VHF 2 band. */ virtual Frequency autoRepeaterVHF2MinFrequency() const; /** Sets the minimum frequency in Hz for the auto-repeater VHF 2 band. */ virtual void setAutoRepeaterVHF2MinFrequency(Frequency hz); /** Returns the maximum frequency in Hz for the auto-repeater VHF 2 band. */ virtual Frequency autoRepeaterVHF2MaxFrequency() const; /** Sets the maximum frequency in Hz for the auto-repeater VHF 2 band. */ virtual void setAutoRepeaterVHF2MaxFrequency(Frequency hz); /** Returns the minimum frequency in Hz for the auto-repeater UHF 2 band. */ virtual Frequency autoRepeaterUHF2MinFrequency() const; /** Sets the minimum frequency in Hz for the auto-repeater UHF 2 band. */ virtual void setAutoRepeaterUHF2MinFrequency(Frequency hz); /** Returns the maximum frequency in Hz for the auto-repeater UHF 2 band. */ virtual Frequency autoRepeaterUHF2MaxFrequency() const; /** Sets the maximum frequency in Hz for the auto-repeater UHF 2 band. */ virtual void setAutoRepeaterUHF2MaxFrequency(Frequency hz); /** Returns the GPS mode. */ virtual GNSSSettings::Systems gnss() const; /** Sets the GPS mode. */ virtual void setGNSS(GNSSSettings::Systems mode); /** Returns the STE (squelch tail elimination) duration. */ virtual Interval steDuration() const; /** Sets the STE (squelch tail elimination) duration. */ virtual void setSTEDuration(Interval dur); /** Returns @c true if the manual dialed group call hang time is infinite. */ virtual bool infiniteManDialGroupCallHangTime() const; /** Returns the manual dial group call hang time. */ virtual Interval manDialGroupCallHangTime() const; /** Sets the manual dial group call hang time. */ virtual void setManDialGroupCallHangTime(Interval dur); /** Sets the manual dial group call hang time to infinite. */ virtual void setManDialGroupCallHangTimeInfinite(); /** Returns @c true if the manual dialed private call hang time is infinite. */ virtual bool infiniteManDialPrivateCallHangTime() const; /** Returns the manual dial private call hang time. */ virtual Interval manDialPrivateCallHangTime() const; /** Sets the manual dial private call hang time. */ virtual void setManDialPrivateCallHangTime(Interval dur); /** Sets the manual dial private call hang time to infinite. */ virtual void setManDialPrivateCallHangTimeInfinite(); AnytoneDisplaySettingsExtension::Color channelBNameColor() const; void setChannelBNameColor(AnytoneDisplaySettingsExtension::Color color); /** Returns the encryption mode. */ virtual AnytoneDMRSettingsExtension::EncryptionType encryption() const; /** Sets the encryption mode. */ virtual void setEncryption(AnytoneDMRSettingsExtension::EncryptionType mode); /** Returns @c true if the transmit timeout notification is enabled. */ virtual bool totNotification() const; /** Enables/disables transmit timeout notification. */ virtual void enableTOTNotification(bool enable); /** Returns @c true if the ATPC (Adaptiv Transmission Power Control) is enabled. */ virtual bool atpc() const; /** Enables/disables the ATPC (Adaptiv Transmission Power Control). */ virtual void enableATPC(bool enable); AnytoneDisplaySettingsExtension::Color zoneANameColor() const; void setZoneANameColor(AnytoneDisplaySettingsExtension::Color color); AnytoneDisplaySettingsExtension::Color zoneBNameColor() const; void setZoneBNameColor(AnytoneDisplaySettingsExtension::Color color); /** Returns @c true if the auto-shutdown timer is reset on a call. */ virtual bool resetAutoShutdownOnCall() const; /** Enables/disables reset on call of the auto-shutdown timer. */ virtual void enableResetAutoShutdownOnCall(bool enable); /** Returns @c true if the color code is shown. */ virtual bool showColorCode() const; /** Enables/disables display of color code. */ virtual void enableShowColorCode(bool enable); /** Returns @c true if the time slot is shown. */ virtual bool showTimeSlot() const; /** Enables/disables display of time slot. */ virtual void enableShowTimeSlot(bool enable); /** Returns @c true if the channel type is shown. */ virtual bool showChannelType() const; /** Enables/disables display of channel type. */ virtual void enableShowChannelType(bool enable); /** Returns @c true if the FM idle channel tone is enabled. */ virtual bool fmIdleTone() const; /** Enables/disables FM idle channel tone. */ virtual void enableFMIdleTone(bool enable); /** Returns the date format. */ virtual AnytoneDisplaySettingsExtension::DateFormat dateFormat() const; /** Sets the date format. */ virtual void setDateFormat(AnytoneDisplaySettingsExtension::DateFormat format); /** Returns the FM Mic gain [1,10]. */ virtual Level fmMicGain() const; /** Sets the analog mic gain [1,10]. */ virtual void setFMMicGain(Level gain); /** Returns @c true if the GPS roaming is enabled. */ virtual bool gpsRoaming() const; /** Enables/disables GPS roaming. */ virtual void enableGPSRoaming(bool enable); /** Returns the call-end tone melody. */ virtual void callEndToneMelody(Melody &melody) const; /** Sets the call-end tone melody. */ virtual void setCallEndToneMelody(const Melody &melody); /** Returns the all-call tone melody. */ virtual void allCallToneMelody(Melody &melody) const; /** Sets the all-call tone melody. */ virtual void setAllCallToneMelody(const Melody &melody); /** Encodes the settings from the config. */ virtual bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err); /** Update config from settings. */ virtual bool updateConfig(Context &ctx, const ErrorStack &err); /** Link config from settings extension. */ virtual bool linkConfig(Context &ctx, const ErrorStack &err); public: /** Some limits for the settings. */ struct Limit: AnytoneCodeplug::ExtendedSettingsElement::Limit { static constexpr unsigned int maxBluetoothPTTSleepDelay() { return 4; } ///< Maximum delay in minutes. static constexpr Range micGain() { return {0,4}; } }; protected: /** Internal used offset within the element. */ struct Offset: public AnytoneCodeplug::ExtendedSettingsElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int sendTalkerAlias() { return 0x0000; } static constexpr unsigned int talkerAliasDisplay() { return 0x001e; } static constexpr unsigned int talkerAliasEncoding() { return 0x001f; } static constexpr unsigned int btPTTLatch() { return 0x0020; } static constexpr unsigned int autoRepeaterUHF2OffsetIndex() { return 0x0022; } static constexpr unsigned int autoRepeaterVHF2OffsetIndex() { return 0x0023; } static constexpr unsigned int autoRepeaterVHF2MinFrequency() { return 0x0024; } static constexpr unsigned int autoRepeaterVHF2MaxFrequency() { return 0x0028; } static constexpr unsigned int autoRepeaterUHF2MinFrequency() { return 0x002c; } static constexpr unsigned int autoRepeaterUHF2MaxFrequency() { return 0x0030; } static constexpr unsigned int btPTTSleepDelay() { return 0x0034; } static constexpr unsigned int gpsMode() { return 0x0035; } static constexpr unsigned int steDuration() { return 0x0036; } static constexpr unsigned int manGrpCallHangTime() { return 0x0037; } static constexpr unsigned int manPrivCallHangTime() { return 0x0038; } static constexpr unsigned int channelBNameColor() { return 0x0039; } static constexpr unsigned int encryptionType() { return 0x003a; } static constexpr unsigned int totNotification() { return 0x003b; } static constexpr unsigned int atpc() { return 0x003c; } static constexpr unsigned int zoneANameColor() { return 0x003d; } static constexpr unsigned int zoneBNameColor() { return 0x003e; } static constexpr unsigned int autoShutdownMode() { return 0x003f; } static constexpr Bit displayColorCode() { return {0x0040, 2}; } static constexpr Bit displayTimeSlot() { return {0x0040, 1}; } static constexpr Bit displayChannelType() { return {0x0040, 0}; } static constexpr unsigned int fmIdleTone() { return 0x0041; } static constexpr unsigned int dateFormat() { return 0x0042; } static constexpr unsigned int analogMicGain() { return 0x0043; } static constexpr unsigned int gpsRoaming() { return 0x0044; } static constexpr unsigned int callEndTones() { return 0x0046; } static constexpr unsigned int callEndDurations() { return 0x0050; } static constexpr unsigned int allCallTones() { return 0x005a; } static constexpr unsigned int allCallDurations() { return 0x0064; } /// @endcond }; }; /** Represents the APRS settings within the binary D878UV codeplug. * * Memory layout of APRS settings (size 0x00f0 bytes): * @verbinclude d878uv_aprssetting.txt */ class APRSSettingsElement: public Element { protected: /** Hidden constructor. */ APRSSettingsElement(uint8_t *ptr, unsigned size); /** Possible settings for the FM APRS subtone type. */ enum class SignalingType { Off=0, CTCSS=1, DCS=2 }; public: /** Constructor. */ explicit APRSSettingsElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0100; } /** Resets the settings. */ void clear(); bool isValid() const; /** Returns the TX delay in ms. */ virtual Interval fmTXDelay() const; /** Sets the TX delay in ms. */ virtual void setFMTXDelay(Interval ms); /** Returns the sub tone settings. */ virtual SelectiveCall txTone() const; /** Sets the sub tone settings. */ virtual void setTXTone(const SelectiveCall &code); /** Returns the manual TX interval in seconds. */ virtual Interval manualTXInterval() const; /** Sets the manual TX interval in seconds. */ virtual void setManualTXInterval(Interval sec); /** Returns @c true if the auto transmit is enabled. */ virtual bool autoTX() const; /** Returns the auto TX interval in seconds. */ virtual Interval autoTXInterval() const; /** Sets the auto TX interval in seconds. */ virtual void setAutoTXInterval(Interval sec); /** Disables auto tx. */ virtual void disableAutoTX(); /** Returns @c true if a fixed location is sent. */ virtual bool fixedLocationEnabled() const; /** Returns the fixed location send. */ virtual QGeoCoordinate fixedLocation() const; /** Sets the fixed location to send. */ virtual void setFixedLocation(const QGeoCoordinate& loc); /** Disables sending a fixed location. */ virtual void enableFixedLocation(bool enable); /** Returns the destination call. */ virtual QString destination() const; /** Returns the destination SSID. */ virtual unsigned destinationSSID() const; /** Sets the destination call & SSID. */ virtual void setDestination(const QString &call, unsigned ssid); /** Returns the source call. */ virtual QString source() const; /** Returns the source SSID. */ virtual unsigned sourceSSID() const; /** Sets the source call & SSID. */ virtual void setSource(const QString &call, unsigned ssid); /** Returns the path string. */ virtual QString path() const; /** Sets the path string. */ virtual void setPath(const QString &path); /** Returns the APRS icon. */ virtual FMAPRSSystem::Icon icon() const; /** Sets the APRS icon. */ virtual void setIcon(FMAPRSSystem::Icon icon); /** Returns the transmit power. */ virtual Channel::Power power() const; /** Sets the transmit power. */ virtual void setPower(Channel::Power power); /** Returns the pre-wave delay in ms. */ virtual Interval fmPreWaveDelay() const; /** Sets the pre-wave delay in ms. */ virtual void setFMPreWaveDelay(Interval ms); /** Returns @c true if the channel points to the current/selected channel. */ virtual bool dmrChannelIsSelected(unsigned n) const; /** Returns the digital channel index for the n-th system. */ virtual unsigned dmrChannelIndex(unsigned n) const; /** Sets the digital channel index for the n-th system. */ virtual void setDMRChannelIndex(unsigned n, unsigned idx); /** Sets the channel to the current/selected channel. */ virtual void setDMRChannelSelected(unsigned n); /** Returns the destination contact for the n-th system. */ virtual unsigned dmrDestination(unsigned n) const; /** Sets the destination contact for the n-th system. */ virtual void setDMRDestination(unsigned n, unsigned idx); /** Returns the call type for the n-th system. */ virtual DMRContact::Type dmrCallType(unsigned n) const; /** Sets the call type for the n-th system. */ virtual void setDMRCallType(unsigned n, DMRContact::Type type); /** Returns @c true if the n-th system overrides the channel time-slot. */ virtual bool dmrTimeSlotOverride(unsigned n); /** Returns the time slot if overridden (only valid if @c timeSlot returns true). */ virtual DMRChannel::TimeSlot dmrTimeSlot(unsigned n) const; /** Overrides the time slot of the n-th selected channel. */ virtual void setDMRTimeSlot(unsigned n, DMRChannel::TimeSlot ts); /** Clears the time-slot override. */ virtual void clearDMRTimeSlotOverride(unsigned n); /** Returns @c true if the roaming is enabled. */ virtual bool dmrRoaming() const; /** Enables/disables roaming. */ virtual void enableDMRRoaming(bool enable); /** Returns the the repeater activation delay in ms. */ virtual Interval dmrPreWaveDelay() const; /** Sets the repeater activation delay in ms. */ virtual void setDMRPreWaveDelay(Interval ms); /** Returns @c true if a received APRS message is shown indefinitely. */ virtual bool infiniteDisplayTime() const; /** Returns the time, a received APRS message is shown. */ virtual Interval displayTime() const; /** Sets the time, a received APRS is shown. */ virtual void setDisplayTime(Interval dur); /** Sets the APRS display time to infinite. */ virtual void setDisplayTimeInifinite(); /** Returns the FM APRS channel width. */ virtual AnytoneFMAPRSSettingsExtension::Bandwidth fmChannelWidth() const; /** Sets the FM APRS channel width. */ virtual void setFMChannelWidth(AnytoneFMAPRSSettingsExtension::Bandwidth width); /** Returns @c true if the CRC check on received FM APRS messages is disabled. */ virtual bool fmPassAll() const; /** Enables/disables "pass all", that is the CRC check on FM APRS messages is disabled. */ virtual void enableFMPassAll(bool enable); /** Returns @c true if the n-th of 8 FM APRS frequencies is set. */ virtual bool fmFrequencySet(unsigned int n) const; /** Returns the n-th of 8 FM APRS frequencies. */ virtual Frequency fmFrequency(unsigned int n) const; /** Sets the n-th of 8 FM APRS frequencies. */ virtual void setFMFrequency(unsigned int n, Frequency f); /** Clears the n-th of 8 FM APRS frequencies. */ virtual void clearFMFrequency(unsigned int n); /** Returns @c true if the report position flag is set. */ virtual bool reportPosition() const; /** Enables/disables report position flag. */ virtual void enableReportPosition(bool enable); /** Returns @c true if the report Mic-E flag is set. */ virtual bool reportMicE() const; /** Enables/disables report Mic-E flag. */ virtual void enableReportMicE(bool enable); /** Returns @c true if the report object flag is set. */ virtual bool reportObject() const; /** Enables/disables report object flag. */ virtual void enableReportObject(bool enable); /** Returns @c true if the report item flag is set. */ virtual bool reportItem() const; /** Enables/disables report item flag. */ virtual void enableReportItem(bool enable); /** Returns @c true if the report message flag is set. */ virtual bool reportMessage() const; /** Enables/disables report message flag. */ virtual void enableReportMessage(bool enable); /** Returns @c true if the report weather flag is set. */ virtual bool reportWeather() const; /** Enables/disables report weather flag. */ virtual void enableReportWeather(bool enable); /** Returns @c true if the report NMEA flag is set. */ virtual bool reportNMEA() const; /** Enables/disables report NMEA flag. */ virtual void enableReportNMEA(bool enable); /** Returns @c true if the report status flag is set. */ virtual bool reportStatus() const; /** Enables/disables report status flag. */ virtual void enableReportStatus(bool enable); /** Returns @c true if the report other flag is set. */ virtual bool reportOther() const; /** Enables/disables report other flag. */ virtual void enableReportOther(bool enable); /** Encodes global APRS settings. */ virtual bool fromConfig(Context &ctx, const ErrorStack &err=ErrorStack()); /** Updates global APRS settings. */ virtual bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); /** Configures this APRS system from the given generic config. */ virtual bool fromFMAPRSSystem(const FMAPRSSystem *sys, Context &ctx, const ErrorStack &err=ErrorStack()); /** Constructs a generic APRS system configuration from this APRS system. */ virtual FMAPRSSystem *toFMAPRSSystem(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the transmit channel within the generic APRS system based on the transmit frequency * defined within this APRS system. */ virtual bool linkFMAPRSSystem(FMAPRSSystem *sys, Context &ctx); /** Constructs all GPS system from the generic configuration. */ virtual bool fromDMRAPRSSystems(Context &ctx); /** Encodes the given GPS system. */ virtual bool fromDMRAPRSSystemObj(unsigned int idx, DMRAPRSSystem *sys, Context &ctx); /** Constructs a generic GPS system from the idx-th encoded GPS system. */ virtual DMRAPRSSystem *toDMRAPRSSystemObj(int idx) const; /** Links the specified generic GPS system. */ virtual bool linkDMRAPRSSystem(int idx, DMRAPRSSystem *sys, Context &ctx) const; public: /** Some static limits for this element. */ struct Limit { /// Maximum length of call signs. static constexpr unsigned int callLength() { return 0x0006; } /// Maximum length of the repeater path string. static constexpr unsigned int pathLength() { return 0x0020; } /// Maximum number of DMR APRS systems. static constexpr unsigned int dmrSystems() { return 0x0008; } /// Maximum number of FM APRS frequencies. static constexpr unsigned int fmFrequencies() { return 0x0008; } }; protected: /** Internal used offsets within the codeplug element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int fmTXDelay() { return 0x0005; } static constexpr unsigned int fmSigType() { return 0x0006; } static constexpr unsigned int fmCTCSS() { return 0x0007; } static constexpr unsigned int fmDCS() { return 0x0008; } static constexpr unsigned int manualTXInterval() { return 0x000a; } static constexpr unsigned int autoTXInterval() { return 0x000b; } static constexpr unsigned int fmTXMonitor() { return 0x000c; } static constexpr unsigned int fixedLocation() { return 0x000d; } static constexpr unsigned int fixedLatDeg() { return 0x000e; } static constexpr unsigned int fixedLatMin() { return 0x000f; } static constexpr unsigned int fixedLatSec() { return 0x0010; } static constexpr unsigned int fixedLatSouth() { return 0x0011; } static constexpr unsigned int fixedLonDeg() { return 0x0012; } static constexpr unsigned int fixedLonMin() { return 0x0013; } static constexpr unsigned int fixedLonSec() { return 0x0014; } static constexpr unsigned int fixedLonWest() { return 0x0015; } static constexpr unsigned int destinationCall() { return 0x0016; } static constexpr unsigned int destinationSSID() { return 0x001c; } static constexpr unsigned int sourceCall() { return 0x001d; } static constexpr unsigned int sourceSSID() { return 0x0023; } static constexpr unsigned int path() { return 0x0024; } static constexpr unsigned int symbolTable() { return 0x0039; } static constexpr unsigned int symbol() { return 0x003a; } static constexpr unsigned int fmPower() { return 0x003b; } static constexpr unsigned int fmPrewaveDelay() { return 0x003c; } static constexpr unsigned int dmrChannelIndices() { return 0x0040; } static constexpr unsigned int betweenDMRChannelIndices() { return 0x0002; } static constexpr unsigned int dmrDestinations() { return 0x0050; } static constexpr unsigned int betweenDMRDestinations() { return 0x0004; } static constexpr unsigned int dmrCallTypes() { return 0x0070; } static constexpr unsigned int betweenDMRCallTypes() { return 0x0001; } static constexpr unsigned int roamingSupport() { return 0x0078; } static constexpr unsigned int dmrTimeSlots() { return 0x0079; } static constexpr unsigned int betweenDMRTimeSlots() { return 0x0001; } static constexpr unsigned int dmrPrewaveDelay() { return 0x0081; } static constexpr unsigned int displayInterval() { return 0x0082; } static constexpr unsigned int fixedHeight() { return 0x00a6; } static constexpr unsigned int reportPosition() { return 0x00a8; } static constexpr unsigned int reportMicE() { return 0x00a8; } static constexpr unsigned int reportObject() { return 0x00a8; } static constexpr unsigned int reportItem() { return 0x00a8; } static constexpr unsigned int reportMessage() { return 0x00a8; } static constexpr unsigned int reportWeather() { return 0x00a8; } static constexpr unsigned int reportNMEA() { return 0x00a8; } static constexpr unsigned int reportStatus() { return 0x00a8; } static constexpr unsigned int reportOther() { return 0x00a9; } static constexpr unsigned int fmWidth() { return 0x00aa; } static constexpr unsigned int passAll() { return 0x00ab; } static constexpr unsigned int fmFrequencies() { return 0x00ac; } static constexpr unsigned int betweenFMFrequencies() { return 0x0004; } /// @endcond }; }; /** Represents an (analog/FM) APRS message. */ class AnalogAPRSMessageElement: public Element { protected: /** Hidden constructor. */ AnalogAPRSMessageElement(uint8_t *ptr, size_t size); public: /** Constructor. */ AnalogAPRSMessageElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0040; } void clear(); /** Returns the message. */ virtual QString message() const; /** Sets the message. */ virtual void setMessage(const QString &msg); public: /** Some limits. */ struct Limit { static constexpr unsigned int length() { return 60; } ///< Maximum message length. }; }; /** Represents an analog APRS RX entry. * * Memory layout of analog APRS RX entry (size 0x0008 bytes): * @verbinclude d878uv_aprsrxentry.txt */ class AnalogAPRSRXEntryElement: public Element { protected: /** Hidden constructor. */ AnalogAPRSRXEntryElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ AnalogAPRSRXEntryElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0008; } /** Resets the entry. */ void clear(); /** Returns @c true if the APRS RX entry is valid. */ bool isValid() const; /** Returns the call sign. */ virtual QString call() const; /** Returns the SSID. */ virtual unsigned ssid() const; /** Sets the call, SSID and enables the entry. */ virtual void setCall(const QString &call, unsigned ssid); }; /** Implements the binary representation of a roaming channel within the codeplug. * * Memory layout of roaming channel (size 0x0020 bytes): * @verbinclude d878uv_roamingchannel.txt */ class RoamingChannelElement: public Element { protected: /** Hidden constructor. */ RoamingChannelElement(uint8_t *ptr, unsigned size); protected: /** Special values for the color code. */ enum ColorCodeValue { Disabled = 16 }; /** Encoded values for the time slot. */ enum TimeSlotValue { TS1 = 0, TS2 = 1 }; public: /** Constructor. */ RoamingChannelElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } /** Resets the roaming channel. */ void clear(); /** Returns the RX frequency in Hz. */ virtual unsigned rxFrequency() const; /** Sets the RX frequency in Hz. */ virtual void setRXFrequency(unsigned hz); /** Returns the TX frequency in Hz. */ virtual unsigned txFrequency() const; /** Sets the TX frequency in Hz. */ virtual void setTXFrequency(unsigned hz); /** Returns @c true if the color code is set. */ virtual bool hasColorCode() const; /** Returns the color code. */ virtual unsigned colorCode() const; /** Sets the color code. */ virtual void setColorCode(unsigned cc); /** Disables the color code for the roaming channel. */ virtual void disableColorCode(); /** Returns the time slot. */ virtual DMRChannel::TimeSlot timeSlot() const; /** Sets the time slot. */ virtual void setTimeSlot(DMRChannel::TimeSlot ts); /** Returns the name of the channel. */ virtual QString name() const; /** Sets the name of the channel. */ virtual void setName(const QString &name); /** Constructs a roaming channel from the given digital channel. */ virtual bool fromChannel(const RoamingChannel *ch); /** Constructs a @c RoamingChannel instance for this roaming channel. */ virtual RoamingChannel *toChannel(Context &ctx); public: /** Some limits. */ struct Limit { static constexpr unsigned int nameLength() { return 16; } ///< Maximum name length. }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int rxFrequency() { return 0x0000; } static constexpr unsigned int txFrequency() { return 0x0004; } static constexpr unsigned int colorCode() { return 0x0008; } static constexpr unsigned int timeSlot() { return 0x0009; } static constexpr unsigned int name() { return 0x000a; } /// @endcond }; }; /** Represents the bitmap, indicating which roaming channel is valid. */ class RoamingChannelBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ RoamingChannelBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ RoamingChannelBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } }; /** Represents a roaming zone within the binary codeplug. * * Memory layout of roaming zone (0x80byte): * @verbinclude d878uv_roamingzone.txt */ class RoamingZoneElement: public Element { protected: /** Hidden constructor. */ RoamingZoneElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ RoamingZoneElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0080; } /** Clears the roaming zone. */ void clear(); /** Returns @c true if the n-th member is set. */ virtual bool hasMember(unsigned n) const; /** Returns the n-th member index. */ virtual unsigned member(unsigned n) const; /** Sets the n-th member index. */ virtual void setMember(unsigned n, unsigned idx); /** Clears the n-th member. */ virtual void clearMember(unsigned n); /** Returns the name of the zone. */ virtual QString name() const; /** Sets the name of the zone. */ virtual void setName(const QString &name); /** Assembles a binary representation of the given RoamingZone instance.*/ virtual bool fromRoamingZone(RoamingZone *zone, Context& ctx, const ErrorStack &err=ErrorStack()); /** Constructs a @c RoamingZone instance from this configuration. */ virtual RoamingZone *toRoamingZone(Context& ctx, const ErrorStack &err=ErrorStack()) const; /** Links the given RoamingZone. */ virtual bool linkRoamingZone(RoamingZone *zone, Context& ctx, const ErrorStack& err=ErrorStack()); public: /** Some limits. */ struct Limit { static constexpr unsigned int nameLength() { return 16; } ///< Maximum name length. static constexpr unsigned int numMembers() { return 64; } ///< Maximum number of roaming channel in zone. }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int members() { return 0x0000; } static constexpr unsigned int betweenMembers() { return 0x0001; } static constexpr unsigned int name() { return 0x0040; } /// @endcond }; }; /** Represents the bitmap, indicating which roaming zone is valid. */ class RoamingZoneBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ RoamingZoneBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ RoamingZoneBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0010; } }; /** Represents an AES encryption key. * * Binary representation of a variable size AES key. The key size is between 4 and 256 bits. */ class AESEncryptionKeyElement: public Element { protected: /** Hidden constructor. */ AESEncryptionKeyElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ AESEncryptionKeyElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0040; } /** Resets the key. */ void clear(); /** Returns @c true if the key is set. */ bool isValid() const; /** Returns the key index. */ virtual unsigned index() const; /** Sets the key index. */ virtual void setIndex(unsigned idx); /** Returns the actual key. */ virtual QByteArray key() const; /** Sets the key. */ virtual void setKey(const QByteArray &key); public: /** Some limits of the key element. */ struct Limit: public Element::Limit { /// The maximum index. static constexpr unsigned int maxIndex() { return 254; } /// The maximum key length in bytes. static constexpr unsigned int keySize() { return 32; } }; protected: /** Some internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int index() { return 0x0000; } static constexpr unsigned int key() { return 0x0001; } static constexpr unsigned int size() { return 0x0022; } /// @endcond }; }; /** Represents the bitmap, indicating which AES key is valid. */ class AESEncryptionKeyBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ AESEncryptionKeyBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ AESEncryptionKeyBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } }; /** Represents an ARC4 encryption key. * * Encodes a 8bit ID and 40bit key. A smaller key might be encoded right-aligned. */ class ARC4EncryptionKeyElement: public Element { protected: /** Hidden constructor. */ ARC4EncryptionKeyElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ARC4EncryptionKeyElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0010; } void clear(); bool isValid() const; /** Returns the key index. */ virtual unsigned index() const; /** Sets the key index. */ virtual void setIndex(unsigned idx); /** Returns the actual key. */ virtual QByteArray key() const; /** Sets the key. */ virtual void setKey(const QByteArray &key); public: /** Some limits of the key element. */ struct Limit: public Element::Limit { /// The maximum index. static constexpr unsigned int maxIndex() { return 254; } /// The maximum key length in bytes. static constexpr unsigned int keySize() { return 5; } }; protected: /** Some internal offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int index() { return 0x0000; } static constexpr unsigned int key() { return 0x0001; } /// @endcond }; }; /** Represents the bitmap, indicating which ARC4 key is valid. */ class ARC4EncryptionKeyBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ ARC4EncryptionKeyBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ARC4EncryptionKeyBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } }; /** Encodes the bitmap, indicating which zone is hidden. */ class HiddenZoneBitmapElement: public BitmapElement { protected: /** Hidden constructor. */ HiddenZoneBitmapElement(uint8_t *ptr, size_t size); public: /** Constructor. */ HiddenZoneBitmapElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0020; } }; /** Encodes some information about the radio and firmware. * * Binary encoding of the info, size 0x0100 bytes: * @verbinclude d878uv_radioinfo.txt */ class RadioInfoElement: public Element { public: /** Possible frequency ranges for the AT-D878UV. */ enum FrequencyRange { RX_400_480_136_174_TX_400_480_136_174 = 0, RX_400_480_136_174_TX_400_480_136_174_STEP_12_5kHz = 1, RX_430_440_136_174_TX_430_440_136_174 = 2, RX_400_480_136_174_TX_430_440_144_146 = 3, RX_440_480_136_174_TX_440_480_136_174 = 4, RX_440_480_144_146_TX_440_480_144_146 = 5, RX_446_447_136_174_TX_446_447_136_174 = 6, RX_400_480_136_174_TX_420_450_136_174 = 7, RX_400_470_136_174_TX_400_470_136_174 = 8, RX_430_432_144_146_TX_430_432_144_146 = 9, RX_400_480_136_174_TX_430_450_144_148 = 10, RX_400_520_136_174_TX_400_520_136_174 = 11, RX_400_490_136_174_TX_400_490_136_174 = 12, RX_400_480_136_174_TX_403_470_136_174 = 13, RX_400_520_220_225_136_174_TX_400_520_220_225_136_174 = 14, RX_420_520_144_148_TX_420_520_144_148 = 15, RX_430_440_144_147_TX_430_440_144_147 = 16, RX_430_440_136_174_TX_136_174 = 17 }; protected: /** Hidden constructor. */ RadioInfoElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit RadioInfoElement(uint8_t *ptr); /** Resets the info. */ void clear(); /** Returns @c true if full test is enabled. * @warning Do not enable, may brick device! */ virtual bool fullTest() const; /** Returns the frequency range. */ virtual FrequencyRange frequencyRange() const; /** Sets the frequency range. */ virtual void setFrequencyRange(FrequencyRange range); /** Returns @c true if "international" is enabled. */ virtual bool international() const; /** Enables/disables "international". */ virtual void enableInternational(bool enable); /** Returns @c true if band select is enabled. */ virtual bool bandSelect() const; /** Enables/disables band select. */ virtual void enableBandSelect(bool enable); /** Returns the band-select password. */ virtual QString bandSelectPassword() const; /** Sets the band-select password. */ virtual void setBandSelectPassword(const QString &passwd); /** Returns the radio type. */ virtual QString radioType() const; /** Returns the program password. */ virtual QString programPassword() const; /** Sets the program password. */ virtual void setProgramPassword(const QString &passwd); /** Returns the area code. */ virtual QString areaCode() const; /** Returns the serial number. */ virtual QString serialNumber() const; /** Returns the production date. */ virtual QString productionDate() const; /** Returns the manufacturer code. */ virtual QString manufacturerCode() const; /** Returns the maintained date. */ virtual QString maintainedDate() const; /** Returns the dealer code. */ virtual QString dealerCode() const; /** Returns the stock date. */ virtual QString stockDate() const; /** Returns the sell date. */ virtual QString sellDate() const; /** Returns the seller. */ virtual QString seller() const; /** Returns the maintainer note. */ virtual QString maintainerNote() const; }; protected: /** Hidden constructor. */ explicit D878UVCodeplug(const QString &label, QObject *parent = nullptr); public: /** Empty constructor. */ explicit D878UVCodeplug(QObject *parent = nullptr); Config *preprocess(Config *config, const ErrorStack &err) const; protected: bool allocateBitmaps(); void setBitmaps(Context &ctx); void allocateForDecoding(); void allocateUpdated(); void allocateForEncoding(); bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createElements(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkElements(Context &ctx, const ErrorStack &err=ErrorStack()); void allocateChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); virtual void allocateZones(); virtual bool encodeZone(int i, Zone *zone, const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); virtual bool decodeZone(int i, Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); void allocateGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void allocateGPSSystems(); bool encodeGPSSystems(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGPSSystems(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGPSSystems(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates memory to store all roaming channels and zones. */ virtual void allocateRoaming(); /** Encodes the roaming channels and zones. */ virtual bool encodeRoaming(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Creates roaming channels and zones from codeplug. */ virtual bool createRoaming(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links roaming channels and zones. */ virtual bool linkRoaming(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates memory to encode/decode AES keys. */ virtual void allocateAESKeys(); /** Encode all AES keys. */ virtual bool encodeAESKeys(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decode AES keys from the codeplug. */ virtual bool createAESKeys(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates memory to encode/decode ARC4 keys. */ virtual void allocateARC4Keys(); /** Encode all ARC4 keys. */ virtual bool encodeARC4Keys(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decode ARC4 keys from the codeplug. */ virtual bool createARC4Keys(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: public D868UVCodeplug::Limit { static constexpr unsigned int analogAPRSRXEntries() { return 32; } ///< Maximum number of analog APRS RX entries. static constexpr unsigned int roamingChannels() { return 250; } ///< Maximum number of roaming channels. static constexpr unsigned int roamingZones() { return 64; } ///< Maximum number of roaming zones. static constexpr unsigned int aesKeys() { return 255; } ///< Maximum number of AES keys. static constexpr unsigned int arc4Keys() { return 255; } ///< Maximum number of ARC4 keys. }; protected: /** Internal offsets within the codeplug. */ struct Offset: public D868UVCodeplug::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int toChannelExtension() { return 0x00002000; } static constexpr unsigned int settingsExtension() { return 0x02501400; } static constexpr unsigned int aprsSettings() { return 0x02501000; } static constexpr unsigned int analogAPRSMessage() { return 0x02501200; } static constexpr unsigned int analogAPRSRXEntries() { return 0x02501800; } //static constexpr unsigned int fmAPRSFrequencyNames() { return 0x02502000; } static constexpr unsigned int hiddenZoneBitmap() { return 0x024c1360; } static constexpr unsigned int roamingChannelBitmap() { return 0x01042000; } static constexpr unsigned int roamingChannels() { return 0x01040000; } static constexpr unsigned int roamingZoneBitmap() { return 0x01042080; } static constexpr unsigned int roamingZones() { return 0x01043000; } static constexpr unsigned int aesKeys() { return 0x024C4000; } static constexpr unsigned int aesKeyBitmap() { return 0x024C8000; } static constexpr unsigned int primaryId() { return 0x02582000; } static constexpr unsigned int arc4Keys() { return 0x025C0C00; } static constexpr unsigned int arc4KeyBitmap() { return 0x025C1C00; } /// @endcond }; }; #endif // D878UVCODEPLUG_HH ================================================ FILE: lib/d878uv_filereader.cc ================================================ #include "d878uv_filereader.hh" D878UVFileReader::D878UVFileReader(Config *config, const uint8_t *data, size_t size, QString &message) :D868UVFileReader(config, data, size, message) { // pass... } ================================================ FILE: lib/d878uv_filereader.hh ================================================ #ifndef D878UVFILEREADER_HH #define D878UVFILEREADER_HH #include "d868uv_filereader.hh" /** Implements a file read for the AnyTone D878UV manufacturer CPS file. */ class D878UVFileReader: public D868UVFileReader { public: /** Constructor. */ D878UVFileReader(Config *config, const uint8_t *data, size_t size, QString &message); }; #endif // D878UVFILEREADER_HH ================================================ FILE: lib/d878uv_limits.cc ================================================ #include "d878uv_limits.hh" #include "d878uv_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingzone.hh" #include "anytone_satelliteconfig.hh" D878UVLimits::D878UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent) : AnytoneLimits(hardwareRevision, "V101", true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 200000; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(D878UVCodeplug::BootSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 250, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, 0, 128, new RadioLimitObject { { "name", new RadioLimitString(1, 15, RadioLimitString::ASCII) }, { "number", new RadioLimitString(1, 14, RadioLimitString::DTMF) } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1, 64) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 4000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRef(FMAPRSSystem::staticMetaObject)}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false)}, {"aprs", new RadioLimitObjRef(PositionReportingSystem::staticMetaObject, true)}, {"roaming", new RadioLimitObjRef(RoamingZone::staticMetaObject, true) }, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 250, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } })); /* Handle positioning systems. */ add("positioning", new RadioLimitList{ { DMRAPRSSystem::staticMetaObject, 0, 8, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, 1, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, FMChannel::staticMetaObject}, false) }, { "icon", new RadioLimitEnum{} }, { "message", new RadioLimitString(0, 60, RadioLimitString::ASCII) } ///@todo extend APRSSystem to expose other settings as properties. }} } ); /* Handle roaming zones. */ add("roaming", new RadioLimitList(RoamingZone::staticMetaObject, 0, 64, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "channels", new RadioLimitRefList(0, 64, DMRChannel::staticMetaObject) } } ) ); } ================================================ FILE: lib/d878uv_limits.hh ================================================ #ifndef D878UVLIMITS_HH #define D878UVLIMITS_HH #include "anytone_limits.hh" /** Implements the limits for the AnyTone AT-D878UV. * @ingroup d878uv */ class D878UVLimits: public AnytoneLimits { Q_OBJECT public: /** Constructor. */ D878UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent=nullptr); }; #endif // D878UVLIMITS_HH ================================================ FILE: lib/dfu_libusb.cc ================================================ #include "dfu_libusb.hh" #include #include "logger.hh" #include "utils.hh" // USB request types. #define REQUEST_TYPE_TO_HOST 0xA1 #define REQUEST_TYPE_TO_DEVICE 0x21 enum { REQUEST_DETACH = 0, REQUEST_DNLOAD = 1, REQUEST_UPLOAD = 2, REQUEST_GETSTATUS = 3, REQUEST_CLRSTATUS = 4, REQUEST_GETSTATE = 5, REQUEST_ABORT = 6, }; enum { appIDLE = 0, appDETACH = 1, dfuIDLE = 2, dfuDNLOAD_SYNC = 3, dfuDNBUSY = 4, dfuDNLOAD_IDLE = 5, dfuMANIFEST_SYNC = 6, dfuMANIFEST = 7, dfuMANIFEST_WAIT_RESET = 8, dfuUPLOAD_IDLE = 9, dfuERROR = 10, }; /* ********************************************************************************************* * * Implementation of DFUDevice::Descriptor * ********************************************************************************************* */ DFUDevice::Descriptor::Descriptor(const USBDeviceInfo &info, uint8_t bus, uint8_t device) : USBDeviceDescriptor(info, USBDeviceHandle(bus, device)) { // pass... } /* ********************************************************************************************* * * Implementation of DFUDevice * ********************************************************************************************* */ DFUDevice::DFUDevice(const USBDeviceDescriptor &descr, const ErrorStack &err, QObject *parent) : QObject(parent), _ctx(nullptr), _dev(nullptr) { if (USBDeviceInfo::Class::DFU != descr.interfaceClass()) { errMsg(err) << "Cannot connect to DFU device using a non DFU descriptor: " << descr.description() << "."; return; } int error = libusb_init(&_ctx); if (error < 0) { errMsg(err) << "Libusb init failed (" << error << "): " << libusb_strerror((enum libusb_error) error) << "."; return; } int num=0; libusb_device **lst; libusb_device *dev=nullptr; if (0 > (num = libusb_get_device_list(_ctx, &lst))) { errMsg(err) << "Cannot obtain list of USB devices."; libusb_exit(_ctx); _ctx = nullptr; return; } logDebug() << "Try to detect USB DFU interface " << descr.description() << "."; USBDeviceHandle addr = descr.device().value(); for (int i=0; (i libusb_get_device_descriptor(lst[i],&usb_descr)) continue; if (descr.vendorId() != usb_descr.idVendor) continue; if (descr.productId() != usb_descr.idProduct) continue; logDebug() << "Matching device found at bus " << addr.bus << ", device " << addr.device << " with vendor ID " << QString::number(usb_descr.idVendor, 16) << " and product ID " << QString::number(usb_descr.idProduct, 16) << "."; libusb_ref_device(lst[i]); dev = lst[i]; } // Unref all devices and free list, matching device was referenced earlier libusb_free_device_list(lst, 1); if (nullptr == dev) { errMsg(err) << "No matching device found: " << descr.description() << "."; libusb_exit(_ctx); _ctx = nullptr; return; } if (0 > (error = libusb_open(dev, &_dev))) { errMsg(err) << "Cannot open device " << descr.description() << ": " << libusb_strerror((enum libusb_error) error) << "."; libusb_unref_device(dev); libusb_exit(_ctx); _ctx = nullptr; return; } if (libusb_kernel_driver_active(_dev, 0) && libusb_detach_kernel_driver(_dev, 0)) { errMsg(err) << "Cannot detach kernel driver for device " << descr.description() << ". Interface claim will likely fail."; } if (0 > (error = libusb_claim_interface(_dev, 0))) { errMsg(err) << "Failed to claim USB interface " << descr.description() << ": " << libusb_strerror((enum libusb_error) error) << "."; libusb_close(_dev); _dev = nullptr; libusb_exit(_ctx); _ctx = nullptr; return; } logDebug() << "Connected to DFU device " << descr.description() << "."; } DFUDevice::~DFUDevice() { close(); } QList DFUDevice::detect(uint16_t vid, uint16_t pid) { QList res; int error, num; libusb_context *ctx; if (0 > (error = libusb_init(&ctx))) { logError() << "Libusb init failed (" << error << "): " << libusb_strerror((enum libusb_error) error) << "."; return res; } libusb_device **lst; if (0 == (num = libusb_get_device_list(ctx, &lst))) { logDebug() << "No USB devices found at all."; // unref devices and free list libusb_free_device_list(lst, 1); return res; } logDebug() << "Search for DFU devices matching VID:PID " << QString::number(vid, 16) << ":" << QString::number(pid, 16) << "."; for (int i=0; (i error) { errMsg(err) << "Cannot detach device: " << libusb_strerror((enum libusb_error) error) << "."; return error; } return 0; } int DFUDevice::get_status(const ErrorStack &err) { int error = libusb_control_transfer( _dev, REQUEST_TYPE_TO_HOST, REQUEST_GETSTATUS, 0, 0, (unsigned char*)&_status, 6, 0); if (0 > error) { errMsg(err) << "Cannot get status: " << libusb_strerror((enum libusb_error) error) << "."; return error; } return 0; } int DFUDevice::clear_status(const ErrorStack &err) { int error = libusb_control_transfer( _dev, REQUEST_TYPE_TO_DEVICE, REQUEST_CLRSTATUS, 0, 0, NULL, 0, 0); if (0 > error) { errMsg(err) << "Cannot clear status: " << libusb_strerror((enum libusb_error) error) << "."; return error; } return 0; } int DFUDevice::get_state(int &pstate, const ErrorStack &err) { unsigned char state; int error = libusb_control_transfer( _dev, REQUEST_TYPE_TO_HOST, REQUEST_GETSTATE, 0, 0, &state, 1, 0); pstate = state; if (error < 0) { errMsg(err) << "Cannot get state: " << libusb_strerror((enum libusb_error) error) << "."; return error; } return 0; } int DFUDevice::abort(const ErrorStack &err) { int error = libusb_control_transfer( _dev, REQUEST_TYPE_TO_DEVICE, REQUEST_ABORT, 0, 0, NULL, 0, 0); if (error < 0) { errMsg(err) << "Cannot abort: " << libusb_strerror((enum libusb_error) error) << "."; return error; } return 0; } int DFUDevice::wait_idle(const ErrorStack &err) { int state, error; for (;;) { if (0 > (error = get_state(state, err))) return 1; switch (state) { case dfuIDLE: return 0; case appIDLE: error = detach(1000, err); break; case dfuERROR: error = clear_status(err); break; case appDETACH: case dfuDNBUSY: case dfuMANIFEST_WAIT_RESET: usleep(100000); continue; default: error = abort(err); break; } if (error < 0) return 1; } } /* ********************************************************************************************* * * Implementation of DFUSEDevice * ********************************************************************************************* */ DFUSEDevice::DFUSEDevice(const USBDeviceDescriptor &descr, const ErrorStack &err, uint16_t blocksize, QObject *parent) : DFUDevice(descr, err, parent), _blocksize(blocksize) { // pass... } void DFUSEDevice::close() { leaveDFU(); DFUDevice::close(); } uint16_t DFUSEDevice::blocksize() const { return _blocksize; } bool DFUSEDevice::setAddress(uint32_t address, const ErrorStack &err) { uint8_t cmd[5] ={ 0x21, (uint8_t)address, (uint8_t)(address >> 8), (uint8_t)(address >> 16), (uint8_t)(address >> 24) }; if (int error = download(0, cmd, 5, err)) { errMsg(err) << "Cannot set address to " << QString::number(address, 16) << "."; return error; } if (wait_idle(err)) { errMsg(err) << "Set address command failed."; return false; } return true; } bool DFUSEDevice::readBlock(unsigned block, uint8_t *data, const ErrorStack &err) { return 0 == upload(block+2, data, _blocksize, err); } bool DFUSEDevice::writeBlock(unsigned block, const uint8_t *data, const ErrorStack &err) { if (download(block+2, (uint8_t *)data, _blocksize, err)) { return false; } if (wait_idle(err)) { return false; } return true; } bool DFUSEDevice::erasePage(uint32_t address, const ErrorStack &err) { uint8_t cmd[5] ={ 0x41, (uint8_t)address, (uint8_t)(address >> 8), (uint8_t)(address >> 16), (uint8_t)(address >> 24) }; if (int error = download(0, cmd, 5, err)) { errMsg(err) << "Cannot erase page at address " << QString::number(address, 16) << "."; return error; } if (wait_idle(err)) { errMsg(err) << "Erase page command failed."; return false; } return true; } bool DFUSEDevice::eraseAll(const ErrorStack &err) { uint8_t cmd[1] ={0x41}; if (int error = download(0, cmd, 1, err)) { errMsg(err) << "Cannot erase entire memory."; return error; } if (wait_idle(err)) { errMsg(err) << "Erase memory command failed."; return false; } return true; } bool DFUSEDevice::releaseReadLock(const ErrorStack &err) { uint8_t cmd[1] ={0x92}; if (int error = download(0, cmd, 1, err)) { errMsg(err) << "Cannot unlock memory."; return error; } if (wait_idle(err)) { errMsg(err) << "Unlock memory command failed."; return false; } return true; } bool DFUSEDevice::leaveDFU(const ErrorStack &err) { if (int error = download(0, nullptr, 0)) { errMsg(err) << "Cannot leave DFU mode."; return error; } return true; } ================================================ FILE: lib/dfu_libusb.hh ================================================ #ifndef DFU_LIBUSB_HH #define DFU_LIBUSB_HH #include #include #include "errorstack.hh" #include "radiointerface.hh" /** This class implements DFU protocol to access radios. * * Many manufactures use the standardized DFU protocol to program codeplugs and update the * firmware of their radios. This class implements this protocol, see * https://www.usb.org/sites/default/files/DFU_1.1.pdf for details. * * @ingroup rif */ class DFUDevice: public QObject { Q_OBJECT private: /** Status message from device. */ struct __attribute__((packed)) status_t { unsigned status : 8; unsigned poll_timeout : 24; unsigned state : 8; unsigned string_index : 8; }; public: /** Specialization to address a DFU device uniquely. */ class Descriptor: public USBDeviceDescriptor { public: /** Constructor from interface info, bus number and device address. */ Descriptor(const USBDeviceInfo &info, uint8_t bus, uint8_t device); }; public: /** Opens a connection to the USB-DFU device at vendor @c vid and product @c pid. */ DFUDevice(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Destructor. */ virtual ~DFUDevice(); /** Returns @c true if the DFU device interface is open. */ bool isOpen() const; /** Closes the DFU interface. */ void close(); /** Downloads some data to the device. */ int download(unsigned block, uint8_t *data, unsigned len, const ErrorStack &err=ErrorStack()); /** Uploads some data from the device. */ int upload(unsigned block, uint8_t *data, unsigned len, const ErrorStack &err=ErrorStack()); public: /** Finds all DFU interfaces with the specified VID/PID combination. */ static QList detect(uint16_t vid, uint16_t pid); protected: /** Internal used function to detach the device. */ int detach(int timeout, const ErrorStack &err=ErrorStack()); /** Internal used function to read the current status. */ int get_status(const ErrorStack &err=ErrorStack()); /** Internal used function to clear the status. */ int clear_status(const ErrorStack &err=ErrorStack()); /** Internal used function to read the state. */ int get_state(int &pstate, const ErrorStack &err=ErrorStack()); /** Internal used function to abort the current operation. */ int abort(const ErrorStack &err=ErrorStack()); /** Internal used function to busy-wait for a response from the device. */ int wait_idle(const ErrorStack &err=ErrorStack()); protected: /** USB context. */ libusb_context *_ctx; /** USB device object. */ libusb_device_handle *_dev; /** Device status. */ status_t _status; }; /** Implements the ST MCU extensions for the DFU protocol, aka DfuSe. * This class implements the extensions to the DFU protocaol used by ST for their MCUs. This is * also known as DfuSe. * * @ingroup rif */ class DFUSEDevice: public DFUDevice { public: /** Constructor, also connects to the specified VID/PID device found first. The @c blocksize * specifies the blocksize for every read and write operation. */ DFUSEDevice(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), uint16_t blocksize=32, QObject *parent=nullptr); /** Closes the connection. */ void close(); /** Returns the blocksize in bytes. */ uint16_t blocksize() const; /** Sets the read/write reference address. By default this is @c 0x08000000 (flash program * memory address on ST devices). */ bool setAddress(uint32_t address, const ErrorStack &err=ErrorStack()); /** Reads a block of data from the device. The address is computed as base address + * block*blocksize, where the base address is set using the @c setAddress method. */ bool readBlock(unsigned block, uint8_t *data, const ErrorStack &err=ErrorStack()); /** Writes a block of data to the device. The address is computed as base address + * block*blocksize, where the base address is set using the @c setAddress method. */ bool writeBlock(unsigned block, const uint8_t *data, const ErrorStack &err=ErrorStack()); /** Erases an entire page of memory at the specified address. A page is usually 0x10000 bytes * large. */ bool erasePage(uint32_t address, const ErrorStack &err=ErrorStack()); /** Erases the entire memory. Not reconmended. */ bool eraseAll(const ErrorStack &err=ErrorStack()); /** Releases the read lock. This usually also erases the entire memory. Not reconmended. */ bool releaseReadLock(const ErrorStack &err=ErrorStack()); /** Leaves the DFU mode, may boot into the application code. */ bool leaveDFU(const ErrorStack &err=ErrorStack()); protected: /** Holds the block size in bytes. */ uint16_t _blocksize; }; #endif // DFU_LIBUSB_HH ================================================ FILE: lib/dfufile.cc ================================================ #include "dfufile.hh" #include #include #include "crc32.hh" #include "logger.hh" typedef struct __attribute((packed)) { uint8_t signature[5]; ///< File signature = "DfuSe" uint8_t version; ///< File version = 0x01 uint32_t image_size; ///< Total file size in big-endian uint8_t n_targets; ///< Number of images. } file_prefix_t; typedef struct __attribute((packed)) { uint16_t device_id; ///< Device id in little endian uint16_t product_id; ///< Product id in little endian uint16_t vendor_id; ///< Vendor id in little endian uint8_t DFUlo; ///< Fixed 0x1A uint8_t DFUhi; ///< Fixed 0x01 uint8_t signature[3]; ///< Fixed "UFD", {0x44, 0x46, 0x55} uint8_t size; ///< Suffix size, fixed 16 uint32_t crc; ///< CRC over compltete file excluding the CRC in little endian. } file_suffix_t; typedef struct __attribute((packed)) { uint8_t signature[6]; ///< Target signature, fixed "Target" uint8_t alternate_setting; ///< Alternate setting for image. uint32_t is_named; ///< Bool, if target is named; uint8_t name[255]; ///< Target name (0-padded?). uint32_t size; ///< Size of complete image excl. prefix in big endian. uint32_t n_elements; ///< Number of elements in image in big endian. } image_prefix_t; typedef struct __attribute((packed)) { uint32_t address; ///< Target address of element in big endian. uint32_t size; ///< Element size in big endian; } element_prefix_t; /* ********************************************************************************************* * * Implementation of DFUFile * ********************************************************************************************* */ DFUFile::DFUFile(QObject *parent) : QObject(parent) { // pass... } uint32_t DFUFile::size() const { uint32_t size = sizeof(file_prefix_t); foreach (const Image &i, _images) { size += i.size(); } return size+sizeof(file_suffix_t); } uint32_t DFUFile::memSize() const { uint32_t size = 0; foreach (const Image &i, _images) { size += i.memSize(); } return size; } int DFUFile::numImages() const { return _images.size(); } const DFUFile::Image & DFUFile::image(int i) const { return _images[i]; } DFUFile::Image & DFUFile::image(int i) { return _images[i]; } void DFUFile::addImage(const QString &name, uint8_t altSettings) { _images.append(Image(name, altSettings)); } void DFUFile::addImage(const Image &img) { _images.append(img); } void DFUFile::remImage(int i) { _images.remove(i); } bool DFUFile::isAligned(unsigned blocksize) const { for (int i=0; i<_images.size(); i++) if (! _images.at(i).isAligned(blocksize)) return false; return true; } bool DFUFile::read(const QString &filename, const ErrorStack &err) { QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { errMsg(err) << "Cannot read DFU file '" << filename << "': " << file.errorString() << "."; return false; } if (! read(file, err)) { file.close(); return false; } return true; } bool DFUFile::read(QFile &file, const ErrorStack &err) { CRC32 crc; _images.clear(); file_prefix_t prefix; if (sizeof(file_prefix_t) != file.read((char *)&prefix, sizeof(file_prefix_t))) { errMsg(err) << "Cannot read suffix: " << file.errorString() << "."; errMsg(err) << "Cannot read DFU file '" << file.fileName() << "'."; return false; } // update crc crc.update((const uint8_t *)&prefix, sizeof(file_prefix_t)); if (memcmp(prefix.signature, "DfuSe", 5)) { errMsg(err) << "Invalid DFU file signature. Not a DFU file?"; errMsg(err) << "Cannot read DFU file '" << file.fileName() << "'."; return false; } uint32_t filesize = qFromLittleEndian(prefix.image_size); uint8_t n_images = prefix.n_targets; for (uint8_t i=0; i= _images.size()) return false; return image(img).isAllocated(offset); } unsigned char * DFUFile::data(uint32_t offset, uint32_t img) { if (int(img) >= _images.size()) return nullptr; return image(img).data(offset); } const unsigned char * DFUFile::data(uint32_t offset, uint32_t img) const { if (int(img) >= _images.size()) return nullptr; return image(img).data(offset); } void DFUFile::dump(QTextStream &stream) const { stream << "DFU file with " << _images.size() << " images:\n"; foreach (const Image &i, _images) { i.dump(stream); } } /* ********************************************************************************************* * * Implementation of DFUFile::Element * ********************************************************************************************* */ DFUFile::Element::Element() : _address(0), _data() { // pass... } DFUFile::Element::Element(uint32_t addr, uint32_t size) : _address(addr), _data(size, 0x00) { // pass... } DFUFile::Element::Element(const Element &other) : _address(other._address), _data(other._data) { // pass... } DFUFile::Element & DFUFile::Element::operator=(const Element &other) { _address = other._address; _data = other._data; return *this; } uint32_t DFUFile::Element::size() const { return sizeof(element_prefix_t) + _data.size(); } uint32_t DFUFile::Element::memSize() const { return _data.size(); } uint32_t DFUFile::Element::address() const { return _address; } void DFUFile::Element::setAddress(uint32_t addr) { _address = addr; } bool DFUFile::Element::isAligned(unsigned blocksize) const { return (0 == (_address % blocksize)) && (0 == (_data.size() % blocksize)); } const QByteArray & DFUFile::Element::data() const { return _data; } QByteArray & DFUFile::Element::data() { return _data; } bool DFUFile::Element::read(QFile &file, CRC32 &crc, QString &errorMessage) { // Read Element prefix: element_prefix_t prefix; if (sizeof(element_prefix_t) != file.read((char *)&prefix, sizeof(element_prefix_t))) { errorMessage = tr("Cannot read DFU file '%1': Cannot read element prefix: %2").arg(file.fileName()).arg(file.errorString()); return false; } crc.update((const uint8_t *) &prefix, sizeof(element_prefix_t)); _address = qFromLittleEndian(prefix.address); uint32_t size = qFromLittleEndian(prefix.size); _data.clear(); _data = file.read(size); if (size != uint32_t(_data.size())) { errorMessage = tr("Cannot read DFU file '%1': Cannot read element data: %2").arg(file.fileName()).arg(file.errorString()); return false; } crc.update(_data); return true; } bool DFUFile::Element::write(QFile &file, CRC32 &crc, QString &errorMessage) const { element_prefix_t prefix; prefix.address = qToLittleEndian(_address); prefix.size = qToLittleEndian(uint32_t(_data.size())); crc.update((uint8_t *) &prefix, sizeof(element_prefix_t)); if (sizeof(element_prefix_t) != file.write((const char *)&prefix, sizeof(element_prefix_t))) { errorMessage = tr("Cannot write element prefix to file '%1': %2") .arg(file.fileName()).arg(file.errorString()); return false; } crc.update(_data); if (_data.size() != file.write(_data)) { errorMessage = tr("Cannot write element data to file '%1': %2") .arg(file.fileName()).arg(file.errorString()); return false; } return true; } void DFUFile::Element::dump(QTextStream &stream) const { stream.setIntegerBase(16); stream << " Element @ 0x" << _address << ", size=0x" << _data.size() << "\n"; int nrow = _data.size()/16; uint8_t last_line[16]; memset(last_line, 0, 16); bool skipping = false; for (int i=0; i0) && (0==memcmp(last_line, _data.constData()+i*16, 16)) && skipping) continue; if ((i>0) && (0==memcmp(last_line, _data.constData()+i*16, 16))) { skipping = true; stream.setFieldAlignment(QTextStream::AlignRight); stream << qSetFieldWidth(8) << "*" << qSetFieldWidth(1) << "\n"; continue; } memcpy(last_line, _data.constData()+i*16, 16); skipping = false; stream << qSetFieldWidth(8) << (_address+i*16) << qSetFieldWidth(1) << " "; for (int j=(i*16); j<(i*16+8); j++) { stream << QString("%1").arg(uint8_t(_data.at(j)), 2, 16, QChar('0')) << qSetFieldWidth(1) << " "; } stream << " "; for (int j=(i*16+8); j<(i*16+16); j++) { stream << QString("%1").arg(uint8_t(_data.at(j)), 2, 16, QChar('0')) << qSetFieldWidth(1) << " "; } stream << " |"; for (int j=(i*16); j<(i*16+16); j++) { char c = _data.at(j); if ((c>=32) && (c<127)) stream << c; else stream << "."; } stream << "|\n"; } } /* ********************************************************************************************* * * Implementation of DFUFile::Image * ********************************************************************************************* */ DFUFile::Image::Image() : _alternate_settings(0), _name(), _elements(), _addressmap() { // pass... } DFUFile::Image::Image(const QString &name, uint8_t altSettings) : _alternate_settings(altSettings), _name(name), _elements(), _addressmap() { // pass... } DFUFile::Image::Image(const Image &other) : _alternate_settings(other._alternate_settings), _name(other._name), _elements(other._elements), _addressmap(other._addressmap) { // pass... } DFUFile::Image::~Image() { // pass... } DFUFile::Image & DFUFile::Image::operator=(const Image &other) { _alternate_settings = other._alternate_settings; _name = other._name; _elements = other._elements; _addressmap = other._addressmap; return *this; } uint32_t DFUFile::Image::size() const { uint32_t size = sizeof(image_prefix_t); foreach (const Element &e, _elements) size += e.size(); return size; } uint32_t DFUFile::Image::memSize() const { uint32_t size = 0; foreach (const Element &e, _elements) size += e.memSize(); return size; } uint8_t DFUFile::Image::alternateSettings() const { return _alternate_settings; } void DFUFile::Image::setAlternateSettings(uint8_t s) { _alternate_settings = s; } bool DFUFile::Image::isNamed() const { return ! _name.isEmpty(); } const QString & DFUFile::Image::name() const { return _name; } void DFUFile::Image::setName(const QString &name) { _name = name; } int DFUFile::Image::numElements() const { return _elements.size(); } const DFUFile::Element & DFUFile::Image::element(int i) const { return _elements[i]; } DFUFile::Element & DFUFile::Image::element(int i) { return _elements[i]; } void DFUFile::Image::addElement(uint32_t addr, uint32_t size, int index) { if ((0 > index) || (_elements.size() <= index)) { _elements.append(Element(addr, size)); _addressmap.add(addr, size); } else { _elements.insert(index, Element(addr, size)); _addressmap.add(addr, size, index); } } void DFUFile::Image::addElement(const Element &element) { _elements.append(element); _addressmap.add(element.address(), element.size()); } void DFUFile::Image::remElement(int i) { _elements.remove(i); _addressmap.rem(i); } bool DFUFile::Image::isAligned(unsigned blocksize) const { for (int i=0; i<_elements.count(); i++) if (! _elements.at(i).isAligned(blocksize)) return false; return true; } DFUFile::Image::iterator DFUFile::Image::begin() { return _elements.begin(); } DFUFile::Image::iterator DFUFile::Image::end() { return _elements.end(); } bool DFUFile::Image::read(QFile &file, CRC32 &crc, QString &errorMessage) { image_prefix_t prefix; if (sizeof(image_prefix_t) != file.read((char *)&prefix, sizeof(image_prefix_t))) { errorMessage = tr("Cannot read DFU file '%1': Cannot read image: %2").arg(file.fileName()).arg(file.errorString()); return false; } crc.update((const uint8_t *) &prefix, sizeof(image_prefix_t)); if (memcmp(prefix.signature, "Target", 6)) { errorMessage = tr("Cannot read DFU file '%1': Invalid image signature value.").arg(file.fileName()); return false; } _alternate_settings = prefix.alternate_setting; if (0x01 ==qFromLittleEndian(prefix.is_named)) { char tmp[256]; tmp[255]=0; memcpy(tmp, prefix.name, 255); _name = tmp; } uint32_t size = qFromLittleEndian(prefix.size); uint32_t n_elements = qFromLittleEndian(prefix.n_elements); for (uint32_t i=0; iaddElement(element); } // verify size: if (size != (this->size()-sizeof(image_prefix_t))) { errorMessage = tr("Cannot read DFU file '%1': Invalid image size %2b specified, expected %3b.") .arg(file.fileName()).arg(size).arg(this->size()-sizeof(image_prefix_t)); return false; } return true; } bool DFUFile::Image::write(QFile &file, CRC32 &crc, QString &errorMessage) const { image_prefix_t prefix; memcpy(prefix.signature, "Target", 6); prefix.alternate_setting = _alternate_settings; prefix.is_named = qToLittleEndian(uint32_t(_name.isEmpty() ? 0 : 1)); memset(prefix.name, 0, 255); if (! _name.isEmpty()) memcpy(prefix.name, _name.toLocal8Bit().constData(), std::min(qsizetype(255), _name.size())); prefix.size = qToLittleEndian(uint32_t(size()-sizeof(image_prefix_t))); prefix.n_elements = qToLittleEndian(uint32_t(_elements.size())); crc.update((uint8_t *)&prefix, sizeof(image_prefix_t)); if (sizeof(image_prefix_t) != file.write((char *)&prefix, sizeof(image_prefix_t))) { errorMessage = tr("Cannot write image prefix to '%1': %2.") .arg(file.fileName()).arg(file.errorString()); return false; } foreach (const Element &e, _elements) { if (! e.write(file, crc, errorMessage)) return false; } return true; } void DFUFile::Image::sort() { std::stable_sort(_elements.begin(), _elements.end(), [](const Element &first, const Element &second) { return first.address() idx) { logFatal() << "Cannot resolve offset " << QString::number(offset, 16) << "h."; return nullptr; } return (unsigned char *)(element(idx).data().data()+ (offset-element(idx).address())); } const unsigned char * DFUFile::Image::data(uint32_t offset) const { int idx = _addressmap.find(offset); if (0 > idx) { logFatal() << "Cannot resolve offset " << QString::number(offset, 16) << "h."; return nullptr; } return (unsigned char *)(element(idx).data().data()+ (offset-element(idx).address())); } ================================================ FILE: lib/dfufile.hh ================================================ #ifndef DFUFILE_HH #define DFUFILE_HH #include #include #include #include #include #include "addressmap.hh" #include "errorstack.hh" class CRC32; /** A collection of images, each consisting of one or more memory sections. * * This class forms the base of all binary encoded codeplugs and call-sign DBs. To this end, it * represents the codeplug or call-sign DB memory as written and stored inside the radio. It also * implements the DFU file format (hence the name) and thus allows to store and load binary * codeplugs or call-sign DBs in files. Please note, that binary codeplugs and call-sign DBs are * highly vendor and device specific. Consequently, they should be be used to exchange codeplugs. * * DFU File consists of a file prefix followed by several images and a final file suffix. The file * prefix consists of a file signature of 5 bytes just consisting of the ASCII string "DfuSe", * followed by a single byte indicating the version number (V), here 0x01. The next field is a * uint32_t containing the file-size excluding the suffix in little endian. Finally, there is a * single byte (N) holding the number of images. * * @code * +---+---+---+---+---+---+---+---+---+---+---+ * | "DfuSe" | V | file size | N | * +---+---+---+---+---+---+---+---+---+---+---+ * @endcode * * The file suffix consists of the device, product and vendor IDs as uint16_t followed by a * fixed signature uin16_t 0x011a (little endian) followed by another fixed signature containing the * ASCII string "UFD". The next field (S) contains the size of the suffix, that is 16. Finally there * is a CRC32 field computed over the entire file excluding the CRC itself and stored in little * endian. * * @code * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ * | dev | prod | vend | Sig | UFD | S | CRC32 | * +---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+---+ * @endcode * * Each image section consists of a image prefix followed by several element sections. The image * prefix consists of a 6byte signature containing the ASCII string "Target" followed by a * single byte indicating the so-called "alternate settings" field, usually 0x01. The 32bit field * "is named" just indicates that the 255 byte name field is set (i.e., 0x01 in little endian). * The next field contains the 32 bit size of the image excluding the image prefix in little endian. * Finally the last 32bit field contains the number of elements, the image consists of in little * endian too. * * @code * +---+---+---+---+---+---+---+---+---+---+---+---+...+---+---+---+---+---+---+---+---+---+ * | "Target" | A | is named | 255b name | size | N Elements | * +---+---+---+---+---+---+---+---+---+---+---+---+...+---+---+---+---+---+---+---+---+---+ * @endcode * * Finally, each element of an image is prefixed by a header containing the target address and size * of the element in little endian. * * @code * +---+---+---+---+---+---+---+---+---+...+---+ * | address | size | el. data | * +---+---+---+---+---+---+---+---+---+...+---+ * @endcode * * @ingroup util */ class DFUFile: public QObject { Q_OBJECT public: /** Represents a single element within a @c Image. */ class Element { public: /** Empty constructor. */ Element(); /** Constructs an element for the given address and of the given size. */ Element(uint32_t addr, uint32_t size); /** Copy constructor. */ Element(const Element &other); /** Copying assignment. */ Element &operator= (const Element &other); /** Returns the address of the element. */ uint32_t address() const; /** Sets the address of the element. */ void setAddress(uint32_t addr); /** Returns the size of the element (including headers). */ uint32_t size() const; /** Returns the memory size of the element. */ uint32_t memSize() const; /** Checks if the element address and size is aligned with the given block size. */ bool isAligned(unsigned blocksize) const; /** Returns a reference to the data. */ const QByteArray &data() const; /** Returns a reference to the data. */ QByteArray &data(); /** Reads an element from the given file and updates the CRC. */ bool read(QFile &file, CRC32 &crc, QString &errorMessage); /** Writes an element to the given file and updates the CRC. */ bool write(QFile &file, CRC32 &crc, QString &errorMessage) const; /** Dumps a textual representation of the element. */ void dump(QTextStream &stream) const; protected: /** The address of the element. */ uint32_t _address; /** The data of the element. */ QByteArray _data; }; /** Represents a single image within a @c DFUFile. */ class Image { public: /** Iterator type over elements. */ typedef QVector::iterator iterator; public: /** Default constructor. * Constructs an empty image. */ Image(); /** Constructs an image with the given name and optional "alternative settings". */ Image(const QString &name, uint8_t altSettings=0); /** Copy constructor. */ Image(const Image &other); /** Destructor. */ virtual ~Image(); /** Copying assignment. */ Image &operator=(const Image &other); /** Returns the alternate settings byte. */ uint8_t alternateSettings() const; /** Sets the alternate settings byte. */ void setAlternateSettings(uint8_t s); /** Returns @c true if the image is named. */ bool isNamed() const; /** Returns the name of the image. */ const QString &name() const; /** Sets the name of the image. */ void setName(const QString &name); /** Returns the total size of the image (including headers). */ uint32_t size() const; /** Returns the memory size stored in the image. */ uint32_t memSize() const; /** Returns the number of elements of this image. */ int numElements() const; /** Returns a reference to the i-th element of the image. */ const Element &element(int i) const; /** Returns a reference to the i-th element of the image. */ Element &element(int i); /** Adds an element to the image with the given address and size at the specified index. * If the index is negative, the element gets appended. */ void addElement(uint32_t addr, uint32_t size, int index=-1); /** Adds an element to the image. */ void addElement(const Element &element); /** Removes the i-th element from this image. */ void remElement(int i); /** Checks if all element addresses and sizes is aligned with the given block size. */ bool isAligned(unsigned blocksize) const; /** Retruns a pointer to the first element. */ iterator begin(); /** Returns a pointer after the last element. */ iterator end(); /** Reads an image from the given file and updates the CRC. */ bool read(QFile &file, CRC32 &crc, QString &errorMessage); /** Writes this image to the given file and updates the CRC. */ bool write(QFile &file, CRC32 &crc, QString &errorMessage) const; /** Prints a textual representation of the image into the given stream. */ void dump(QTextStream &stream) const; /** Returns @c true if the specified address is allocated. */ virtual bool isAllocated(uint32_t offset) const; /** Returns a pointer to the encoded raw data at the specified offset. */ virtual unsigned char *data(uint32_t offset); /** Returns a const pointer to the encoded raw data at the specified offset. */ virtual const unsigned char *data(uint32_t offset) const; /** Sorts all elements with respect to their addresses. */ void sort(); protected: /** Alternate settings byte. */ uint8_t _alternate_settings; /** Optional image name. */ QString _name; /** The elements of the image. */ QVector _elements; /** Maps an address range to element index. */ AddressMap _addressmap; }; public: /** Constructs an empty DFU file object. */ DFUFile(QObject *parent=nullptr); /** Returns the total size of the DFU file. */ uint32_t size() const; /** Returns the total memory size stored in the DFU file. */ uint32_t memSize() const; /** Returns the number of images within the DFU file. */ int numImages() const; /** Returns a reference to the @c i-th image of the file. */ const Image &image(int i) const; /** Returns a reference to the @c i-th image of the file. */ Image &image(int i); /** Adds a new image to the file. */ void addImage(const QString &name, uint8_t altSettings=1); /** Adds an image to the file. */ void addImage(const Image &img); /** Deletes the @c i-th image from the file. */ void remImage(int i); /** Checks if all image addresses and sizes is aligned with the given block size. */ bool isAligned(unsigned blocksize) const; /** Reads the specified DFU file. * @return @c false on error. */ bool read(const QString &filename, const ErrorStack &err=ErrorStack()); /** Reads the specified DFU file. * @returns @c false on error. */ bool read(QFile &file, const ErrorStack &err=ErrorStack()); /** Writes to the specified file. * @returns @c false on error. */ bool write(const QString &filename, const ErrorStack &err=ErrorStack()); /** Writes to the specified file. * @returns @c false on error. */ bool write(QFile &file, const ErrorStack &err=ErrorStack()); /** Dumps a text representation of the DFU file structure to the specified text stream. */ void dump(QTextStream &stream) const; /** Returns @c true if the specified address (and image) is allocated. */ virtual bool isAllocated(uint32_t offset, uint32_t img=0) const; /** Returns a pointer to the encoded raw data at the specified offset. */ virtual unsigned char *data(uint32_t offset, uint32_t img=0); /** Returns a const pointer to the encoded raw data at the specified offset. */ virtual const unsigned char *data(uint32_t offset, uint32_t img=0) const; protected: /// The list of images. QVector _images; }; #endif // DFUFILE_HH ================================================ FILE: lib/dm1701.cc ================================================ #include "dm1701.hh" #include "dm1701_limits.hh" #include "config.hh" #include "logger.hh" #include "utils.hh" RadioLimits *DM1701::_limits = nullptr; DM1701::DM1701(TyTInterface *device, QObject *parent) : TyTRadio(device, parent), _name("Baofeng DM-1701"), _codeplug() { // pass... } const QString & DM1701::name() const { return _name; } const RadioLimits & DM1701::limits() const { if (nullptr == _limits) _limits = new DM1701Limits(); return *_limits; } RadioInfo DM1701::defaultRadioInfo() { return RadioInfo( RadioInfo::DM1701, "dm1701", "DM-1701", "Baofeng", {TyTInterface::interfaceInfo()}, QList{ RadioInfo(RadioInfo::RT84, "rt84", "RT84", "Retevis", {TyTInterface::interfaceInfo()}) }); } const Codeplug & DM1701::codeplug() const { return _codeplug; } Codeplug & DM1701::codeplug() { return _codeplug; } const CallsignDB * DM1701::callsignDB() const { return &_callsigndb; } CallsignDB * DM1701::callsignDB() { return &_callsigndb; } ================================================ FILE: lib/dm1701.hh ================================================ /** @defgroup dm1701 Baofeng DM-1701, Retevis RT84 * Device specific classes for Baofeng DM-1701 and Retevis RT84. * * \image html dm1701.png "DM-1701" width=200px * \image latex dm1701.png "DM-1701" width=200px * * The Baofeng DM-1701 and the identical Retevis RT84. This implementation supports CPS version 1.5 * and firmware version 2.3. * * Features: * - VHF/UHF, 136-174 MHz and 400-480 MHz * - Pout = 1, 3 and 5W * - 3000 channels * - 10000 contacts * - 250 RX group lists with ?? contacts each * - 250 zones, 64 channels each (A & B) * - 250 scan lists, 31 channels each * - ... * @ingroup tyt */ #ifndef DM1701_HH #define DM1701_HH #include "radio.hh" #include "tyt_radio.hh" #include "tyt_codeplug.hh" #include "tyt_callsigndb.hh" #include "dm1701_codeplug.hh" #include "dm1701_callsigndb.hh" /** Implements an USB interface to the Baofeng DM-1701 and Retevis RT84 VHF/UHF 5W DMR (Tier I&II) radios. * * The Baofeng DM-1701 and Retevis RT84 radios use a DFU-style communication protocol to read and write * codeplugs onto the radio (see @c DFUDevice). This class implements the communication details * using DFU protocol. * * @ingroup dm1701 */ class DM1701: public TyTRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit DM1701(TyTInterface *device=nullptr, QObject *parent=nullptr); const QString &name() const; const RadioLimits &limits() const; const Codeplug &codeplug() const; Codeplug &codeplug(); const CallsignDB *callsignDB() const; CallsignDB *callsignDB(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); protected: /** The device identifier. */ QString _name; /** The actual binary codeplug representation. */ DM1701Codeplug _codeplug; /** The callsign DB object. */ DM1701CallsignDB _callsigndb; private: /** Holds the singleton instance of the radio limits. */ static RadioLimits *_limits; }; #endif // DM1701_HH ================================================ FILE: lib/dm1701_callsigndb.cc ================================================ #include "dm1701_callsigndb.hh" DM1701CallsignDB::DM1701CallsignDB(QObject *parent) : TyTCallsignDB(parent) { image(0).setName("BTECH DM-1701 Callsign database."); } DM1701CallsignDB::~DM1701CallsignDB() { // pass... } ================================================ FILE: lib/dm1701_callsigndb.hh ================================================ #ifndef DM1701_CALLSIGNDB_HH #define DM1701_CALLSIGNDB_HH #include "tyt_callsigndb.hh" /** Device specific implementation of the call-sign DB for the BTECH DM1701 and Retevis RT84. * * In fact this callsign DB is identical to the generic @c TyTCallsignDB. * * @ingroup dm1701 */ class DM1701CallsignDB : public TyTCallsignDB { Q_OBJECT public: /** Constructor. */ explicit DM1701CallsignDB(QObject *parent=nullptr); /** Destructor. */ virtual ~DM1701CallsignDB(); }; #endif // DM1701_CALLSIGNDB_HH ================================================ FILE: lib/dm1701_codeplug.cc ================================================ #include "dm1701_codeplug.hh" #include "logger.hh" #include "config.hh" #include #define NUM_CHANNELS 3000 #define ADDR_CHANNELS 0x110000 #define CHANNEL_SIZE 0x000040 #define NUM_CONTACTS 10000 #define ADDR_CONTACTS 0x140000 #define CONTACT_SIZE 0x000024 #define NUM_ZONES 250 #define ADDR_ZONES 0x0149e0 #define ZONE_SIZE 0x000040 #define ADDR_ZONEEXTS 0x031000 #define ZONEEXT_SIZE 0x0000e0 #define NUM_GROUPLISTS 250 #define ADDR_GROUPLISTS 0x00ec20 #define GROUPLIST_SIZE 0x000060 #define NUM_SCANLISTS 250 #define ADDR_SCANLISTS 0x018860 #define SCANLIST_SIZE 0x000068 #define ADDR_TIMESTAMP 0x002000 #define ADDR_SETTINGS 0x002040 #define SETTINGS_SIZE 0x0000b0 #define ADDR_BOOTSETTINGS 0x02f000 #define ADDR_MENUSETTINGS 0x0020f0 #define ADDR_BUTTONSETTINGS 0x002100 #define BUTTONSETTINGS_SIZE 0x000014 #define ADDR_PRIVACY_KEYS 0x0059c0 #define NUM_GPSSYSTEMS 16 #define ADDR_GPSSYSTEMS 0x03ec40 #define GPSSYSTEM_SIZE 0x000010 #define ADDR_EMERGENCY_SETTINGS 0x005a50 #define NUM_EMERGENCY_SYSTEMS 32 #define ADDR_EMERGENCY_SYSTEMS 0x005a60 #define EMERGENCY_SYSTEM_SIZE 0x000028 #define ADDR_VFO_CHANNEL_A 0x02ef00 #define ADDR_VFO_CHANNEL_B 0x02ef40 /* ********************************************************************************************* * * Implementation of DM1701Codeplug::ChannelElement * ********************************************************************************************* */ DM1701Codeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : TyTCodeplug::ChannelElement::ChannelElement(ptr, size) { // pass... } DM1701Codeplug::ChannelElement::ChannelElement(uint8_t *ptr) : TyTCodeplug::ChannelElement(ptr, CHANNEL_SIZE) { // pass... } void DM1701Codeplug::ChannelElement::clear() { TyTCodeplug::ChannelElement::clear(); enableTightSquelch(false); enableReverseBurst(true); setPower(Channel::Power::High); setBit(0x0003, 6, true); setUInt8(0x0005, 0xc3); setUInt8(0x000f, 0xff); } bool DM1701Codeplug::ChannelElement::tightSquelchEnabled() const { return !getBit(0x0000, 5); } void DM1701Codeplug::ChannelElement::enableTightSquelch(bool enable) { setBit(0x0000, 5, !enable); } bool DM1701Codeplug::ChannelElement::reverseBurst() const { return getBit(0x0004, 2); } void DM1701Codeplug::ChannelElement::enableReverseBurst(bool enable) { setBit(0x0004, 2, enable); } Channel::Power DM1701Codeplug::ChannelElement::power() const { if (getBit(0x0004, 5)) return Channel::Power::High; return Channel::Power::Low; } void DM1701Codeplug::ChannelElement::setPower(Channel::Power pwr) { switch (pwr) { case Channel::Power::Min: case Channel::Power::Low: case Channel::Power::Mid: setBit(0x0004, 5, false); break; case Channel::Power::High: case Channel::Power::Max: setBit(0x0004, 5, true); } } Channel * DM1701Codeplug::ChannelElement::toChannelObj(const ErrorStack &err) const { Channel *ch = TyTCodeplug::ChannelElement::toChannelObj(err); if (nullptr == ch) return ch; ch->setPower(power()); if (auto fmc = ch->as()) fmc->extended()->enableReverseBurst(reverseBurst()); // Apply extension if (ch->tytChannelExtension()) { ch->tytChannelExtension()->enableTightSquelch(tightSquelchEnabled()); } return ch; } void DM1701Codeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx) { TyTCodeplug::ChannelElement::fromChannelObj(c, ctx); setPower(c->power()); if (auto fmc = c->as()) enableReverseBurst(fmc->extended()->reverseBurst()); // apply extensions (extension will be created in TyTCodeplug::ChannelElement::fromChannelObj) if (TyTChannelExtension *ex = c->tytChannelExtension()) { enableTightSquelch(ex->tightSquelch()); } } /* ********************************************************************************************* * * Implementation of DM1701Codeplug::VFOChannelElement * ********************************************************************************************* */ DM1701Codeplug::VFOChannelElement::VFOChannelElement(uint8_t *ptr, size_t size) : ChannelElement(ptr, size) { // pass... } DM1701Codeplug::VFOChannelElement::VFOChannelElement(uint8_t *ptr) : ChannelElement(ptr, CHANNEL_SIZE) { // pass... } DM1701Codeplug::VFOChannelElement::~VFOChannelElement() { // pass... } QString DM1701Codeplug::VFOChannelElement::name() const { return ""; } void DM1701Codeplug::VFOChannelElement::setName(const QString &txt) { Q_UNUSED(txt) // pass... } unsigned DM1701Codeplug::VFOChannelElement::stepSize() const { return (getUInt8(32)+1)*2500; } void DM1701Codeplug::VFOChannelElement::setStepSize(unsigned ss_Hz) { ss_Hz = std::min(50000U, std::max(ss_Hz, 2500U)); setUInt8(32, ss_Hz/2500-1); setUInt8(33, 0xff); } /* ********************************************************************************************* * * Implementation of DM1701Codeplug::GeneralSettingsElement * ********************************************************************************************* */ DM1701Codeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, size_t size) : TyTCodeplug::GeneralSettingsElement(ptr, size) { // pass... } DM1701Codeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : TyTCodeplug::GeneralSettingsElement(ptr, SETTINGS_SIZE) { // pass... } void DM1701Codeplug::GeneralSettingsElement::clear() { TyTCodeplug::GeneralSettingsElement::clear(); enableChannelModeA(true); enableChannelModeB(true); enableChannelMode(true); enableGroupCallMatch(true); enablePrivateCallMatch(true); setTimeZone(QTimeZone::systemTimeZone()); setChannelHangTime(3000); memset(_data+0x91, 0xff, 0x1f); } bool DM1701Codeplug::GeneralSettingsElement::channelModeA() const { return getBit(0x43,3); } void DM1701Codeplug::GeneralSettingsElement::enableChannelModeA(bool enable) { setBit(0x43,3, enable); } bool DM1701Codeplug::GeneralSettingsElement::channelModeB() const { return getBit(0x43,7); } void DM1701Codeplug::GeneralSettingsElement::enableChannelModeB(bool enable) { setBit(0x43,7, enable); } bool DM1701Codeplug::GeneralSettingsElement::channelMode() const { return 0xff == getUInt8(0x57); } void DM1701Codeplug::GeneralSettingsElement::enableChannelMode(bool enable) { setUInt8(0x57, enable ? 0xff : 0x00); } bool DM1701Codeplug::GeneralSettingsElement::groupCallMatch() const { return getBit(0x6b, 0); } void DM1701Codeplug::GeneralSettingsElement::enableGroupCallMatch(bool enable) { setBit(0x6b, 0, enable); } bool DM1701Codeplug::GeneralSettingsElement::privateCallMatch() const { return getBit(0x6b, 1); } void DM1701Codeplug::GeneralSettingsElement::enablePrivateCallMatch(bool enable) { setBit(0x6b, 1, enable); } QTimeZone DM1701Codeplug::GeneralSettingsElement::timeZone() const { return QTimeZone((int(getUInt5(0x6b, 3))-12)*3600); } void DM1701Codeplug::GeneralSettingsElement::setTimeZone(const QTimeZone &zone) { int idx = (zone.standardTimeOffset(QDateTime::currentDateTime())/3600)+12; setUInt5(0x6b, 3, uint8_t(idx)); } unsigned DM1701Codeplug::GeneralSettingsElement::channelHangTime() const { return unsigned(getUInt8(0x90))*100; } void DM1701Codeplug::GeneralSettingsElement::setChannelHangTime(unsigned dur) { setUInt8(0x90, dur/100); } bool DM1701Codeplug::GeneralSettingsElement::fromConfig(const Config *config) { if (! TyTCodeplug::GeneralSettingsElement::fromConfig(config)) return false; setTimeZone(QTimeZone::systemTimeZone()); enablePrivateCallMatch(config->settings()->dmr()->privateCallMatchEnabled()); enableGroupCallMatch(config->settings()->dmr()->groupCallMatchEnabled()); // apply extension if (TyTSettingsExtension *ex = config->settings()->tytExtension()) { enableChannelMode(ex->channelMode()); enableChannelModeA(ex->channelModeA()); enableChannelModeB(ex->channelModeB()); setChannelHangTime(ex->channelHangTime()); } return true; } bool DM1701Codeplug::GeneralSettingsElement::updateConfig(Config *config) { if (! TyTCodeplug::GeneralSettingsElement::updateConfig(config)) return false; config->settings()->dmr()->enablePrivateCallMatch(privateCallMatch()); config->settings()->dmr()->enableGroupCallMatch(groupCallMatch()); // Update extension if set. if (TyTSettingsExtension *ex = config->settings()->tytExtension()) { ex->enableChannelMode(channelMode()); ex->enableChannelModeA(channelModeA()); ex->enableChannelModeB(channelModeB()); ex->setChannelHangTime(channelHangTime()); } return true; } /* ********************************************************************************************* * * Implementation of DM1701Codeplug::ButtonSettingsElement * ********************************************************************************************* */ DM1701Codeplug::ButtonSettingsElement::ButtonSettingsElement(uint8_t *ptr, size_t size) : TyTCodeplug::ButtonSettingsElement(ptr, size) { // pass... } DM1701Codeplug::ButtonSettingsElement::ButtonSettingsElement(uint8_t *ptr) : TyTCodeplug::ButtonSettingsElement(ptr, BUTTONSETTINGS_SIZE) { // pass... } void DM1701Codeplug::ButtonSettingsElement::clear() { setSideButton3Short(ButtonAction::Disabled); setSideButton3Long(ButtonAction::Disabled); setProgButton1Short(ButtonAction::Disabled); setProgButton1Long(ButtonAction::Disabled); setProgButton2Short(ButtonAction::Disabled); setProgButton2Long(ButtonAction::Disabled); } TyTCodeplug::ButtonSettingsElement::ButtonAction DM1701Codeplug::ButtonSettingsElement::sideButton3Short() const { return ButtonAction(getUInt8(0x06)); } void DM1701Codeplug::ButtonSettingsElement::setSideButton3Short(ButtonAction action) { setUInt8(0x06, action); } TyTCodeplug::ButtonSettingsElement::ButtonAction DM1701Codeplug::ButtonSettingsElement::sideButton3Long() const { return ButtonAction(getUInt8(0x07)); } void DM1701Codeplug::ButtonSettingsElement::setSideButton3Long(ButtonAction action) { setUInt8(0x07, action); } TyTCodeplug::ButtonSettingsElement::ButtonAction DM1701Codeplug::ButtonSettingsElement::progButton1Short() const { return ButtonAction(getUInt8(0x08)); } void DM1701Codeplug::ButtonSettingsElement::setProgButton1Short(ButtonAction action) { setUInt8(0x08, action); } TyTCodeplug::ButtonSettingsElement::ButtonAction DM1701Codeplug::ButtonSettingsElement::progButton1Long() const { return ButtonAction(getUInt8(0x09)); } void DM1701Codeplug::ButtonSettingsElement::setProgButton1Long(ButtonAction action) { setUInt8(0x09, action); } TyTCodeplug::ButtonSettingsElement::ButtonAction DM1701Codeplug::ButtonSettingsElement::progButton2Short() const { return ButtonAction(getUInt8(0x0a)); } void DM1701Codeplug::ButtonSettingsElement::setProgButton2Short(ButtonAction action) { setUInt8(0x0a, action); } TyTCodeplug::ButtonSettingsElement::ButtonAction DM1701Codeplug::ButtonSettingsElement::progButton2Long() const { return ButtonAction(getUInt8(0x0b)); } void DM1701Codeplug::ButtonSettingsElement::setProgButton2Long(ButtonAction action) { setUInt8(0x0b, action); } bool DM1701Codeplug::ButtonSettingsElement::fromConfig(const Config *config) { if (! TyTCodeplug::ButtonSettingsElement::fromConfig(config)) return false; if (config->tytExtension()) { TyTButtonSettings *ex = config->tytExtension()->buttonSettings(); setSideButton3Short(ex->sideButton3Short()); setSideButton3Long(ex->sideButton3Long()); setProgButton1Short(ex->progButton1Short()); setProgButton1Long(ex->progButton1Long()); setProgButton2Short(ex->progButton2Short()); setProgButton2Long(ex->progButton2Long()); } return true; } bool DM1701Codeplug::ButtonSettingsElement::updateConfig(Config *config) { if (! TyTCodeplug::ButtonSettingsElement::updateConfig(config)) return false; if (config->tytExtension()) { TyTButtonSettings *ex = config->tytExtension()->buttonSettings(); ex->setSideButton3Short(sideButton3Short()); ex->setSideButton3Long(sideButton3Long()); ex->setProgButton1Short(progButton1Short()); ex->setProgButton1Long(progButton1Long()); ex->setProgButton2Short(progButton2Short()); ex->setProgButton2Long(progButton2Long()); } return true; } /* ******************************************************************************************** * * Implementation of UV390Codeplug::ZoneElement * ******************************************************************************************** */ DM1701Codeplug::ZoneExtElement::ZoneExtElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } DM1701Codeplug::ZoneExtElement::ZoneExtElement(uint8_t *ptr) : Codeplug::Element(ptr, ZONEEXT_SIZE) { // pass... } DM1701Codeplug::ZoneExtElement::~ZoneExtElement() { // pass... } void DM1701Codeplug::ZoneExtElement::clear() { memset(_data, 0x00, 0xe0); } bool DM1701Codeplug::ZoneExtElement::hasMemberIndexA(unsigned n) const { return 0 != memberIndexA(n); } uint16_t DM1701Codeplug::ZoneExtElement::memberIndexA(unsigned n) const { return getUInt16_le(0x00 + 2*n); } void DM1701Codeplug::ZoneExtElement::setMemberIndexA(unsigned n, uint16_t idx) { setUInt16_le(0x00 + 2*n, idx); } bool DM1701Codeplug::ZoneExtElement::hasMemberIndexB(unsigned n) const { return 0 != memberIndexB(n); } uint16_t DM1701Codeplug::ZoneExtElement::memberIndexB(unsigned n) const { return getUInt16_le(0x60 + 2*n); } void DM1701Codeplug::ZoneExtElement::setMemberIndexB(unsigned n, uint16_t idx) { setUInt16_le(0x60 + 2*n, idx); } bool DM1701Codeplug::ZoneExtElement::fromZoneObj(const Zone *zone, Context &ctx) { // Store remaining channels from list A for (int i=16; i<64; i++) { if (i < zone->A()->count()) { int idx = ctx.index(zone->A()->get(i)); setMemberIndexA(i-16, idx); } else setMemberIndexA(i-16, 0); } // Store channel from list B for (int i=0; i<64; i++) { if (i < zone->B()->count()) setMemberIndexB(i, ctx.index(zone->B()->get(i))); else setMemberIndexB(i, 0); } return true; } bool DM1701Codeplug::ZoneExtElement::linkZoneObj(Zone *zone, Context &ctx) { for (int i=0; (i<48) && hasMemberIndexA(i); i++) { if (! ctx.has(memberIndexA(i))) { logWarn() << "Cannot link zone extension: Channel index " << memberIndexA(i) << " not defined."; continue; } zone->A()->add(ctx.get(memberIndexA(i))); } for (int i=0; (i<64) && hasMemberIndexB(i); i++) { if (! ctx.has(memberIndexB(i))) { logWarn() << "Cannot link zone extension: Channel index " << memberIndexB(i) << " not defined."; continue; } zone->B()->add(ctx.get(memberIndexB(i))); } return true; } /* ********************************************************************************************* * * Implementation of DM1701Codeplug * ********************************************************************************************* */ DM1701Codeplug::DM1701Codeplug(QObject *parent) : TyTCodeplug(parent) { addImage("Baofeng DM-1701 Codeplug"); image(0).addElement(0x002000, 0x3e000); image(0).addElement(0x110000, 0x90000); // Clear entire codeplug clear(); } DM1701Codeplug::~DM1701Codeplug() { // pass... } void DM1701Codeplug::clearTimestamp() { TimestampElement(data(ADDR_TIMESTAMP)).clear(); } bool DM1701Codeplug::encodeTimestamp() { TimestampElement ts(data(ADDR_TIMESTAMP)); ts.setTimestamp(QDateTime::currentDateTime()); return true; } void DM1701Codeplug::clearGeneralSettings() { GeneralSettingsElement(data(ADDR_SETTINGS)).clear(); } bool DM1701Codeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err) return GeneralSettingsElement(data(ADDR_SETTINGS)).fromConfig(ctx.config()); } bool DM1701Codeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return GeneralSettingsElement(data(ADDR_SETTINGS)).updateConfig(ctx.config()); } void DM1701Codeplug::clearChannels() { // Clear channels for (int i=0; i()) { chan.fromChannelObj(ctx.get(i+1), ctx); } else { chan.clear(); } } return true; } bool DM1701Codeplug::createChannels(Context &ctx, const ErrorStack &err) { for (int i=0; ichannelList()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid channel at index %" << i << "."; return false; } } return true; } bool DM1701Codeplug::linkChannels(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link channel at index " << i << "."; return false; } } return true; } void DM1701Codeplug::clearContacts() { // Clear contacts for (int i=0; i()) cont.fromContactObj(ctx.get(i+1)); else cont.clear(); } return true; } bool DM1701Codeplug::createContacts(Context &ctx, const ErrorStack &err) { for (int i=0; icontacts()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid contact at index " << i << "."; return false; } } return true; } void DM1701Codeplug::clearZones() { // Clear zones & zone extensions for (int i=0; izones()->count()) { zone.fromZoneObj(ctx.config()->zones()->zone(i), ctx); if (ctx.config()->zones()->zone(i)->B()->count() || (16 < ctx.config()->zones()->zone(i)->A()->count())) ext.fromZoneObj(ctx.config()->zones()->zone(i), ctx); } } return true; } bool DM1701Codeplug::createZones(Context &ctx, const ErrorStack &err) { for (int i=0; izones()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid zone at index " << i << "."; return false; } } return true; } bool DM1701Codeplug::linkZones(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } ZoneExtElement zoneext(data(ADDR_ZONEEXTS + i*ZONEEXT_SIZE)); if (! zoneext.linkZoneObj(ctx.get(i+1), ctx)) { errMsg(err) << "Cannot link zone extension at index " << i << "."; return false; } } return true; } void DM1701Codeplug::clearGroupLists() { for (int i=0; irxGroupLists()->count()) glist.fromGroupListObj(ctx.config()->rxGroupLists()->list(i), ctx); else glist.clear(); } return true; } bool DM1701Codeplug::createGroupLists(Context &ctx, const ErrorStack &err) { for (int i=0; irxGroupLists()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid group list at index " << i << "."; return false; } } return true; } bool DM1701Codeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link group list at index " << i << "."; return false; } } return true; } void DM1701Codeplug::clearScanLists() { // Clear scan lists for (int i=0; iscanlists()->count()) scan.fromScanListObj(ctx.config()->scanlists()->scanlist(i), ctx); else scan.clear(); } return true; } bool DM1701Codeplug::createScanLists(Context &ctx, const ErrorStack &err) { for (int i=0; iscanlists()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid scanlist at index " << i << "."; return false; } } return true; } bool DM1701Codeplug::linkScanLists(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link scan list at index" << i << "."; return false; } } return true; } void DM1701Codeplug::clearMenuSettings() { MenuSettingsElement(data(ADDR_MENUSETTINGS)).clear(); } void DM1701Codeplug::clearButtonSettings() { ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).clear(); } bool DM1701Codeplug::encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err) // Encode settings return ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).fromConfig(ctx.config()); } bool DM1701Codeplug::decodeButtonSetttings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).updateConfig(ctx.config()); } void DM1701Codeplug::clearPrivacyKeys() { EncryptionElement(data(ADDR_PRIVACY_KEYS)).clear(); } bool DM1701Codeplug::encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // First, reset keys clearPrivacyKeys(); // Get keys EncryptionElement keys(data(ADDR_PRIVACY_KEYS)); return keys.fromCommercialExt(ctx.config()->commercialExtension(), ctx); } bool DM1701Codeplug::decodePrivacyKeys(Context &ctx, const ErrorStack &err) { // Get keys EncryptionElement keys(data(ADDR_PRIVACY_KEYS)); // Decode element if (! keys.updateCommercialExt(ctx)) { errMsg(err) << "Cannot create encryption extension."; return false; } return true; } void DM1701Codeplug::clearTextMessages() { MessageBankElement(data(Offset::messages())).clear(); } bool DM1701Codeplug::encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).encode(ctx, flags, err); } bool DM1701Codeplug::decodeTextMessages(Context &ctx, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).decode(ctx, err); } void DM1701Codeplug::clearEmergencySystems() { EmergencySettingsElement(data(ADDR_EMERGENCY_SETTINGS)).clear(); for (int i=0; i * Start End Size Content * First segment 0x002000-0x040000 * 0x002000 0x00200c 0x0000c Timestamp see @c TyTCodeplug::TimestampElement. * 0x00200c 0x002040 0x00034 Reserved, filled with 0xff. * 0x002040 0x0020f0 0x000b0 General settings see @c DM1701Codeplug::GeneralSettingsElement. * 0x0020f0 0x002100 0x00010 Menu settings, see @c TyTCodeplug::MenuSettingsElement * 0x002100 0x002140 0x00040 Button config, see @c DM1701Codeplug::ButtonSettingsElement. * 0x002140 0x002180 0x00040 Reserved, filled with 0xff. * 0x002180 0x0059c0 0x03840 50 Text messages @ 0x120 bytes each. * 0x0059c0 0x005a70 0x000b0 Privacy keys, see @c TyTCodeplug::EncryptionElement. * 0x005a70 0x005a80 0x00010 Emergency system settings, see @c TyTCodeplug::EmergencySettingsElement. * 0x005a80 0x005f80 0x00500 Emergency systems, see @c TyTCodeplug::EmergencySystemElement. * 0x005f80 0x00ec20 0x08ca0 Reserved, filled with 0xff. * 0x00ec20 0x0149e0 0x05dc0 250 RX Group lists @ 0x60 bytes each, see @c TyTCodeplug::GroupListElement. * 0x0149e0 0x018860 0x03e80 250 Zones @ 0x40 bytes each, see @c TyTCodeplug::ZoneElement. * 0x018860 0x01edf0 0x06590 250 Scanlists @ 0x68 bytes each, see @c TyTCodeplug::ScanListElement. * 0x01edf0 0x02ef00 0x10110 Reserved, filled with @c 0xff. * 0x02ef00 0x02ef40 0x00040 VFO A channel, see @c DM1701Codeplug::VFOChannelElement. * 0x02ef40 0x02ef80 0x00040 VFO B channel, see @c DM1701Codeplug::VFOChannelElement. * 0x02ef80 0x031000 0x02080 Reserved, filled with @c 0xff. * 0x031000 0x03eac0 0x0dac0 250 Zone-extensions @ 0xe0 bytes each, see @c DM1701Codeplug::ZoneExtElement. * 0x03eac0 0x03ec40 0x00180 Reserved, filled with @c 0xff. * 0x03ec40 0x03ed40 0x00100 16 GPS systems @ 0x10 bytes each, see @c TyTCodeplug::GPSSystemElement. * 0x03ed40 0x040000 0x012c0 Reserved, filled with @c 0xff. * Second segment 0x110000-0x1a0000 * 0x110000 0x13ee00 0x2ee00 3000 Channels @ 0x40 bytes each, see @c DM1701Codeplug::ChannelElement. * 0x13ee00 0x140000 0x01200 Reserved, filled with @c 0xff. * 0x140000 0x197e40 0x57e40 10000 Contacts @ 0x24 bytes each, see @c TyTCodeplug::ContactElement. * 0x197e40 0x1a0000 0x081c0 Reserved, filled with @c 0xff. * * * @ingroup dm1701 */ class DM1701Codeplug : public TyTCodeplug { Q_OBJECT public: /** Extends the common @c TyTCodeplug::ChannelElement to implement the DM-1701 specific settings. * * Memory layout of the channel (size 0x0040 bytes): * @verbinclude dm1701_channel.txt */ class ChannelElement: public TyTCodeplug::ChannelElement { protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit ChannelElement(uint8_t *ptr); void clear(); /** Returns @c true if the squelch is 'tight'. */ virtual bool tightSquelchEnabled() const; /** Enables/disables tight squelch. */ virtual void enableTightSquelch(bool enable); /** Returns @c true if the reversed burst is enabled. */ virtual bool reverseBurst() const; /** Enables/disables reverse burst. */ virtual void enableReverseBurst(bool enable); /** Returns the power of this channel. */ virtual Channel::Power power() const; /** Sets the power of this channel. */ virtual void setPower(Channel::Power pwr); /** Constructs a generic @c Channel object from the codeplug channel. */ Channel *toChannelObj(const ErrorStack &err=ErrorStack()) const; /** Initializes this codeplug channel from the given generic configuration. */ void fromChannelObj(const Channel *c, Context &ctx); }; /** Extends the @c ChannelElement to implement the VFO channel settings for the DM-1701. * This class is an extension of the normal @c ChannelElement that only implements the step-size * feature and encodes it where the name used to be. Thus the memory layout and size is identical * to the normal channel. */ class VFOChannelElement: public ChannelElement { protected: /** Constructor from pointer to memory. */ VFOChannelElement(uint8_t *ptr, size_t size); public: /** Constructor from pointer to memory. */ VFOChannelElement(uint8_t *ptr); /** Destructor. */ virtual ~VFOChannelElement(); QString name() const; void setName(const QString &txt); /** Returns the step-size for the VFO channel. */ virtual unsigned stepSize() const; /** Sets the step-size for the VFO channel in Hz. */ virtual void setStepSize(unsigned ss_hz); }; /** Extends the common @c TyTCodeplug::GeneralSettingsElement to implement the DM-1701 specific * settings. * * Memory layout of the settings (size 0x00b0 bytes): * @verbinclude dm1701_settings.txt */ class GeneralSettingsElement: public TyTCodeplug::GeneralSettingsElement { protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GeneralSettingsElement(uint8_t *ptr); void clear(); /** Returns @c true, if VFO A is in channel mode. */ virtual bool channelModeA() const; /** Enables/disables the channel mode for VFO A. */ virtual void enableChannelModeA(bool enable); /** Returns @c true, if VFO B is in channel mode. */ virtual bool channelModeB() const; /** Enables/disables the channel mode for VFO B. */ virtual void enableChannelModeB(bool enable); /** Returns @c true, if the radio is in channel (and not VFO) mode. */ virtual bool channelMode() const; /** Enable/disable channel mode. */ virtual void enableChannelMode(bool enable); /** Returns @c true if group-call match is enabled. */ virtual bool groupCallMatch() const; /** Enables/disables group-call match. */ virtual void enableGroupCallMatch(bool enable); /** Returns @c true if private-call match is enabled. */ virtual bool privateCallMatch() const; /** Enables/disables private-call match. */ virtual void enablePrivateCallMatch(bool enable); /** Returns the time-zone. */ virtual QTimeZone timeZone() const; /** Sets the time-zone. */ virtual void setTimeZone(const QTimeZone &zone); /** Returns the channel hang time in ms. */ virtual unsigned channelHangTime() const; /** Sets the channel hang time in ms. */ virtual void setChannelHangTime(unsigned dur); /** Encodes the general settings. */ virtual bool fromConfig(const Config *config); /** Updates config from general settings. */ virtual bool updateConfig(Config *config); }; /** Extens the common @c TyTCodeplug::ButtonSettingsElement to implement the DM-1701 specific * settings. * * Memory layout of the setting (size 0x0014 bytes): * @verbinclude dm1701_buttonsettings.txt */ class ButtonSettingsElement: public TyTCodeplug::ButtonSettingsElement { protected: /** Hidden constructor. */ ButtonSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit ButtonSettingsElement(uint8_t *ptr); void clear(); /** Returns the action for a short press on side button 3. */ virtual ButtonAction sideButton3Short() const; /** Sets the action for a short press on side button 3. */ virtual void setSideButton3Short(ButtonAction action); /** Returns the action for a long press on side button 3. */ virtual ButtonAction sideButton3Long() const; /** Sets the action for a long press on side button 3. */ virtual void setSideButton3Long(ButtonAction action); /** Returns the action for a short press on programmable button 1. */ virtual ButtonAction progButton1Short() const; /** Sets the action for a short press on programmable button 1. */ virtual void setProgButton1Short(ButtonAction action); /** Returns the action for a long press on programmable button 1. */ virtual ButtonAction progButton1Long() const; /** Sets the action for a long press on programmable button 1. */ virtual void setProgButton1Long(ButtonAction action); /** Returns the action for a short press on programmable button 2. */ virtual ButtonAction progButton2Short() const; /** Sets the action for a short press on programmable button 2. */ virtual void setProgButton2Short(ButtonAction action); /** Returns the action for a long press on programmable button 2. */ virtual ButtonAction progButton2Long() const; /** Sets the action for a long press on programmable button 2. */ virtual void setProgButton2Long(ButtonAction action); /** Encodes the button settings. */ virtual bool fromConfig(const Config *config); /** Updates config from button settings. */ virtual bool updateConfig(Config *config); }; /** Extended zone data for the DM-1701. * The zone definition @c ZoneElement contains only a single set of 16 channels. For each zone * definition, there is a zone extension which extends a zone to zwo sets of 64 channels each. * * Memory layout of encoded zone extension: * @verbinclude dm1701_zoneext.txt */ class ZoneExtElement: public Codeplug::Element { protected: /** Constructor. */ ZoneExtElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ZoneExtElement(uint8_t *ptr); /** Destructor. */ virtual ~ZoneExtElement(); void clear(); /** Returns @c true if the n-th member index for list A is set. */ virtual bool hasMemberIndexA(unsigned n) const; /** Returns the n-th member index of the channel list for A. */ virtual uint16_t memberIndexA(unsigned n) const; /** Sets the n-th member index of the channel list for A. */ virtual void setMemberIndexA(unsigned n, uint16_t idx); /** Returns @c true if the n-th member index for list B is set. */ virtual bool hasMemberIndexB(unsigned n) const; /** Returns the n-th member index of the channel list for B. */ virtual uint16_t memberIndexB(unsigned n) const; /** Returns the n-th member index of the channel list for B. */ virtual void setMemberIndexB(unsigned n, uint16_t idx); /** Encodes the given zone. */ virtual bool fromZoneObj(const Zone *zone, Context &ctx); /** Links the given zone object. * That is, extends channel list A and populates channel list B. */ virtual bool linkZoneObj(Zone *zone, Context &ctx); }; public: /** Constructor. */ explicit DM1701Codeplug(QObject *parent = nullptr); /** Destructor. */ virtual ~DM1701Codeplug(); public: void clearTimestamp(); bool encodeTimestamp(); void clearGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearZones(); bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); void clearGroupLists(); bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearScanLists(); bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearButtonSettings(); bool encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeButtonSetttings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearPositioningSystems(); bool encodePositioningSystems(const Flags &flags, Context &ctx, const ErrorStack &err); bool createPositioningSystems(Context &ctx, const ErrorStack &err); bool linkPositioningSystems(Context &ctx, const ErrorStack &err); void clearPrivacyKeys(); bool encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err); bool decodePrivacyKeys(Context &ctx, const ErrorStack &err); void clearTextMessages(); bool encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err); bool decodeTextMessages(Context &ctx, const ErrorStack &err); void clearMenuSettings(); void clearEmergencySystems(); /** Resets VFO settings. */ virtual void clearVFOSettings(); protected: /** Some internal offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int messages() { return 0x002180; } /// @endcond }; }; #endif // DM1701_CODEPLUG_HH ================================================ FILE: lib/dm1701_filereader.cc ================================================ #include "dm1701_filereader.hh" #include #include #define SEGMENT0_FILE_ADDR 0x00002225 #define SEGMENT0_TARGET_ADDR 0x00002000 #define SEGMENT0_SIZE 0x0003e000 #define SEGMENT1_FILE_ADDR 0x00040235 #define SEGMENT1_TARGET_ADDR 0x00110000 #define SEGMENT1_SIZE 0x00090000 bool DM1701FileReader::read(const QString &filename, DM1701Codeplug *codeplug, const ErrorStack &err) { // Check file properties QFileInfo info(filename); if (! info.exists()) { errMsg(err) << "Cannot open file '" << filename << "': File does not exisist."; return false; } if (852533 != info.size()) { errMsg(err) << "Cannot read codeplug file '" << filename << "': File size is not 852533 bytes."; return false; } // Open file QFile file(filename); if (! file.open(QFile::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; return false; } // Read file content if (! file.seek(SEGMENT0_FILE_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } char *ptr = (char *)codeplug->data(SEGMENT0_TARGET_ADDR); size_t n = SEGMENT0_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } if (! file.seek(SEGMENT1_FILE_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } ptr = (char *)codeplug->data(SEGMENT1_TARGET_ADDR); n = SEGMENT1_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } return true; } ================================================ FILE: lib/dm1701_filereader.hh ================================================ #ifndef DM1701_FILEREADER_HH #define DM1701_FILEREADER_HH #include "dm1701_codeplug.hh" /** Methods to read manufacturer codeplug files. * * The file format of the stock CPS is still pretty simple. The first part of the file consists of * a mal-formed DFU file. This contains a single image with a single element containing the * first section of the memory written to the device. The second section is then added as-is * to the end of the file. Due to the DFU header/footer, the file and memory offsets differ. * * * * * *
File Start Memory Start Size
0x002225 0x002000 0x03e000
0x040235 0x110000 0x090000
* * @ingroup dm1701 */ class DM1701FileReader { public: /** Reads manufacturer codeplug file into given codeplug object. * @param filename Specifies the file to read. * @param codeplug Specifies the codeplug object to store read codeplug. * @param err Error stack. * @returns @c true on success and @c false on error. */ static bool read(const QString &filename, DM1701Codeplug *codeplug, const ErrorStack &err=ErrorStack()); }; #endif // DM1701_FILEREADER_HH ================================================ FILE: lib/dm1701_limits.cc ================================================ #include "dm1701_limits.hh" #include "dm1701_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "scanlist.hh" #include "zone.hh" #include "gpssystem.hh" #include "roamingzone.hh" DM1701Limits::DM1701Limits(QObject *parent) : RadioLimits(true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 122197; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; add("settings", new RadioLimitItem { { "introLine1", new RadioLimitString(-1, 10, RadioLimitString::Unicode) }, { "introLine2", new RadioLimitString(-1, 10, RadioLimitString::Unicode) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High) } }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(DM1701Codeplug::GeneralSettingsElement::Limit::bootPasswordLength(), RadioLimitIssue::Critical) } } } /// @todo check default radio ID. } ); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList{ { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } } ); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum { (unsigned) DMRContact::PrivateCall, (unsigned) DMRContact::GroupCall, (unsigned) DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, -1, -1, new RadioLimitIgnored() } } ); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "contacts", new RadioLimitGroupCallRefList(1, 32) } }) ); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 3000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef({ScanList::staticMetaObject})}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored(nullptr, RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), }}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored()}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } ) ); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, // 16 ASCII chars in name { "A", new RadioLimitRefList(0, 64, Channel::staticMetaObject) }, { "B", new RadioLimitRefList(0, 64, Channel::staticMetaObject) }, { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions } ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } }) ); /* Define limits for positioning systems. */ add("positioning", new RadioLimitList({ { DMRAPRSSystem::staticMetaObject, 0, 16, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, -1, new RadioLimitIgnored() } } ) ); /* Check encryption keys. */ add("commercial", new RadioLimitItem { {"encryptionKeys", new RadioLimitList { {BasicEncryptionKey::staticMetaObject, 0, TyTCodeplug::EncryptionElement::Limit::basicKeys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{4}")} }}, {AESEncryptionKey::staticMetaObject, 0, TyTCodeplug::EncryptionElement::Limit::advancedKeys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{32}")} }} } } }); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); } ================================================ FILE: lib/dm1701_limits.hh ================================================ #ifndef DM1701LIMITS_HH #define DM1701LIMITS_HH #include "radiolimits.hh" /** Implements the configuration limits for the Baofeng DM-1701. * @ingroup dm1701 */ class DM1701Limits : public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit DM1701Limits(QObject *parent=nullptr); }; #endif // UV390LIMITS_HH ================================================ FILE: lib/dm32uv.cc ================================================ #include "dm32uv.hh" #include "dm32uv_limits.hh" #include "dm32uv_interface.hh" #include "logger.hh" /* ********************************************************************************************* * * Implementation of Baofeng DM32UV weird address mapping * ********************************************************************************************* */ DM32UV::AddressMap::AddressMap() : _toVirtual(), _toPhysical() { // pass... } void DM32UV::AddressMap::map(uint32_t phys, uint32_t virt) { phys >>= 12; virt >>= 12; _toVirtual[phys] = virt; _toPhysical[virt] = phys; } bool DM32UV::AddressMap::virtualIsMapped(uint32_t addr) const { return _toPhysical.contains(addr>>12); } bool DM32UV::AddressMap::physicalIsMapped(uint32_t addr) const { return _toVirtual.contains(addr>>12); } uint32_t DM32UV::AddressMap::toPhysical(uint32_t virt) const { uint32_t offset = virt & 0xfff; if (! _toPhysical.contains(virt>>12)) return std::numeric_limits::max(); return (_toPhysical[virt>>12]<<12) | offset; } uint32_t DM32UV::AddressMap::toVirtual(uint32_t phys) const { uint32_t offset = phys & 0xfff; if (! _toVirtual.contains(phys>>12)) return std::numeric_limits::max(); return (_toVirtual[phys>>12]<<12) | offset; } /* ********************************************************************************************* * * Implementation of Baofeng DM32UV * ********************************************************************************************* */ RadioLimits *DM32UV::_limits = nullptr; DM32UV::DM32UV(DM32UVInterface *dev, QObject *parent) : Radio{parent}, _dev(dev), _radioName("Baofeng DM-32UV"), _codeplug() { // pass... } RadioInfo DM32UV::defaultRadioInfo() { return RadioInfo( RadioInfo::DM32UV, "dm32uv", "DM-32UV", "Baofeng", {DM32UVInterface::interfaceInfo()}); } const QString & DM32UV::name() const { return _radioName; } const RadioLimits & DM32UV::limits() const { if (nullptr == _limits) _limits = new DM32UVLimits(); return *_limits; } const Codeplug & DM32UV::codeplug() const { return _codeplug; } Codeplug & DM32UV::codeplug() { return _codeplug; } bool DM32UV::startDownload(const TransferFlags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; _task = StatusDownload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); start(); return true; } bool DM32UV::startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; if (! (_config = config)) return false; _codeplugFlags = flags; _task = StatusUpload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); start(); return true; } bool DM32UV::startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err) { _callsigns.encode(db, selection); _task = StatusUploadCallsigns; _errorStack = err; if (selection.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); start(); return true; } bool DM32UV::startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(flags); errMsg(err) << "Satellite config upload is not implemented yet."; return false; } void DM32UV::run() { if (StatusDownload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit downloadError(this); return; } emit downloadStarted(); if (! download(_errorStack)) { _dev->read_finish(); _dev->reboot(); _dev->close(); _task = StatusError; emit downloadError(this); return; } _task = StatusIdle; _dev->reboot(); _dev->close(); emit downloadFinished(this, &codeplug()); //_config = nullptr; } else if (StatusUpload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } emit uploadStarted(); if (! upload()) { _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadCallsigns == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } emit uploadStarted(); if (! uploadCallsigns()) { _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } } bool DM32UV::download(const ErrorStack &err) { // First, we need to get the codeplug address map DM32UV::AddressMap addressMap; if (! _dev->getAddressMap(addressMap, err)) { errMsg(err) << "Cannot read codeplug from device: Cannot get address map."; return false; } // Allocate all mapped memory regions: static Range codeplugMemoryRange = {0x3000, 0x68000}; foreach (uint32_t virtualBlockAddress, addressMap.mappedVirtual()) { if (codeplugMemoryRange.contains(virtualBlockAddress)) _codeplug.image(0).addElement(virtualBlockAddress, Offset::blockSize()); } if (! _dev->read_start(0, 0, err)) { errMsg(err) << "Cannot start reading codeplug."; return false; } // Read all allocated memory regions (sorted by physical address). uint32_t blockCount = 0; foreach (uint32_t physicalBlockAddress, addressMap.mappedPhysical()) { auto progress = (100*++blockCount)/addressMap.mappedPhysical().count(); emit downloadProgress(progress); uint32_t virtualBlockAddress = addressMap.toVirtual(physicalBlockAddress); if (! codeplugMemoryRange.contains(virtualBlockAddress)) continue; if (! _dev->read(0, physicalBlockAddress, _codeplug.data(virtualBlockAddress), Offset::blockSize())) { errMsg(err) << "Cannot read codeplug block from " << Qt::hex << physicalBlockAddress << "h (virt. " << Qt::hex << virtualBlockAddress << "h)."; return false; } } if (! _dev->read_finish(err)) { errMsg(err) << "Cannot finish codeplug download."; return false; } return true; } bool DM32UV::upload(const ErrorStack &err) { // First, we need to get the codeplug address map DM32UV::AddressMap addressMap; if (! _dev->getAddressMap(addressMap, err)) { errMsg(err) << "Cannot read codeplug from device: Cannot get address map."; return false; } // Allocate all mapped memory regions: static Range codeplugMemoryRange = {0x3000, 0x68000}; foreach (uint32_t virtualBlockAddress, addressMap.mappedVirtual()) { if (codeplugMemoryRange.contains(virtualBlockAddress)) _codeplug.image(0).addElement(virtualBlockAddress, Offset::blockSize()); } if (! _dev->read_start(0, 0, err)) { errMsg(err) << "Cannot start reading codeplug."; return false; } // Read all allocated memory regions (sorted by physical address). uint32_t blockCount = 0; foreach (uint32_t physicalBlockAddress, addressMap.mappedPhysical()) { emit uploadProgress((50*++blockCount)/addressMap.mappedPhysical().count()); uint32_t virtualBlockAddress = addressMap.toVirtual(physicalBlockAddress); if (! codeplugMemoryRange.contains(virtualBlockAddress)) continue; if (! _dev->read(0, physicalBlockAddress, _codeplug.data(virtualBlockAddress), Offset::blockSize())) { errMsg(err) << "Cannot read codeplug block from " << Qt::hex << physicalBlockAddress << "h (virt. " << Qt::hex << virtualBlockAddress << "h)."; return false; } } if (! _dev->read_finish(err)) { errMsg(err) << "Cannot finish codeplug read."; return false; } // Now, encode codeplug in physical address space, may override large portions and may also add // new portions to the codeplug. if (! _codeplug.encode(_config, _codeplugFlags, err)) { errMsg(err) << "Cannot encode codeplug from config."; return false; } if (! _dev->write_start(0,0,err)) { errMsg(err) << "Cannot start codeplug write´."; return false; } // Now mark all blocks with their virtual address // and write them to the associated pyhsical address or to 0xff000 if not yet allocated. for (unsigned int blk=0; blk<(unsigned int)_codeplug.image(0).numElements(); blk++) { emit uploadProgress(50 + (50*blk)/_codeplug.image(0).numElements()); auto element = _codeplug.image(0).element(blk); auto virtualBlockAddress = element.address(); element.data()[Offset::blockSize()-1] = (virtualBlockAddress>>12); auto physicalBlockAddress = addressMap.virtualIsMapped(virtualBlockAddress) ? addressMap.toPhysical(virtualBlockAddress) : 0xff<<12; if (! _dev->write(0, physicalBlockAddress, _codeplug.data(virtualBlockAddress), Offset::blockSize())) { errMsg(err) << "Cannot write codeplug block to " << Qt::hex << physicalBlockAddress << "h (virt. " << Qt::hex << virtualBlockAddress << "h)."; return false; } } if (! _dev->write_finish(err)) { errMsg(err) << "Cannot finish codeplug write."; return false; } return true; } bool DM32UV::uploadCallsigns(const ErrorStack &err) { if (! _dev->write_start(0,0,err)) { errMsg(err) << "Cannot start codeplug write´."; return false; } // Now mark all blocks with their virtual address // and write them to the associated pyhsical address or to 0xff000 if not yet allocated. for (unsigned int blk=0; blk<(unsigned int)_callsigns.image(0).numElements(); blk++) { emit uploadProgress(100*blk/_callsigns.image(0).numElements()); auto element = _callsigns.image(0).element(blk); auto blockAddress = element.address(); if (! _dev->write(0, blockAddress, _callsigns.data(blockAddress), Offset::blockSize())) { errMsg(err) << "Cannot write codeplug block to " << Qt::hex << blockAddress << "h."; return false; } } if (! _dev->write_finish(err)) { errMsg(err) << "Cannot finish codeplug write."; return false; } return true; } ================================================ FILE: lib/dm32uv.hh ================================================ /** @defgroup dm32uv Baofeng DM32UV * @ingroup dsc */ #ifndef DM32UV_HH #define DM32UV_HH #include "radio.hh" #include "dm32uv_codeplug.hh" #include "dm32uv_callsigndb.hh" #include #include // Forward declaration class DM32UVInterface; /** Represents a Baofeng DM32UV. * @ingroup dm32uv */ class DM32UV : public Radio { Q_OBJECT public: /** Implements a memory map between physical and virtual codeplug addresses. * The addresses, the codeplug is encoded in, are virtual. These codeplug blocks are then written * to different physical addresses. */ class AddressMap { public: /** Default constructor. */ AddressMap(); /** Default copy constructor. */ AddressMap(const AddressMap &other) = default; /** Adds an address mapping. */ void map(uint32_t phys, uint32_t virt); /** Returns @c true, if the virtual address is mapped. */ bool virtualIsMapped(uint32_t virt) const; /** Returns @c true, if the physical address is mapped. */ bool physicalIsMapped(uint32_t virt) const; /** Maps the given physical address to a virtual codeplug address. * If physical address is unknown -- that is, not mapped -- * @c std::numeric_limits::max() gets returned. */ uint32_t toVirtual(uint32_t phys) const; /** Maps the given virtual codeplug address to a physical memory address. * If virtual address is unknown -- that is, not mapped -- * @c std::numeric_limits::max() gets returned. */ uint32_t toPhysical(uint32_t virt) const; /** Returns the map from physical to virtual addresses, sorted by physical addresses. */ inline QList mappedPhysical() const { QList addrs; for (auto prefix: _toVirtual.keys()) addrs.append(prefix<<12); return addrs; } /** Returns the map from virtual to physical addresses, sorted by virtual addresses. */ inline QList mappedVirtual() const { QList addrs; for (auto prefix: _toPhysical.keys()) addrs.append(prefix<<12); return addrs; } protected: /** Holds only the prefix address mapping from physical to virtual addresses. That is * @c addr>>12. */ QMap _toVirtual; /** Holds only the prefix address mapping from virtual to physical addresses. That is * @c addr>>12. */ QMap _toPhysical; }; public: /** Default constructor. */ explicit DM32UV(DM32UVInterface *device=nullptr, QObject *parent=nullptr); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); const QString &name() const override; const RadioLimits &limits() const override; const Codeplug &codeplug() const override; Codeplug &codeplug() override; bool startDownload(const TransferFlags &flags, const ErrorStack &err=ErrorStack()) override; bool startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err) override; bool startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err) override; bool startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err) override; public: /** Returns a set of supported firmware versions. */ static inline QSet supportedFirmwareVersions() { return {"DM32.01.L01.048"}; } public: /** Some constants. */ struct Offset { /** Size for each codeplug block. */ static constexpr uint32_t blockSize() { return 0x1000; } }; private: virtual bool download(const ErrorStack &err=ErrorStack()); virtual bool upload(const ErrorStack &err=ErrorStack()); virtual bool uploadCallsigns(const ErrorStack &err=ErrorStack()); protected: /** Thread main routine, performs all blocking IO operations for codeplug up- and download. */ void run() override; protected: /** The interface to the radio. */ DM32UVInterface *_dev; /** The name of the device. */ QString _radioName; /** Holds the flags to control assembly and upload of code-plugs. */ Codeplug::Flags _codeplugFlags; /** Encoded / read codeplug. */ DM32UVCodeplug _codeplug; /** Encoded call-sign db. */ DM32UVCallsignDB _callsigns; /** The generic configuration. */ Config *_config; /** A weak reference to the user-database. */ UserDatabase *_userDB; private: /** Holds the singleton instance of the radio limits. */ static RadioLimits *_limits; }; #endif // DM32UV_HH ================================================ FILE: lib/dm32uv_callsigndb.cc ================================================ #include "dm32uv_callsigndb.hh" /* ********************************************************************************************* * * Implementation of call sign DB header * ********************************************************************************************* */ DM32UVCallsignDB::HeaderElement::HeaderElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void DM32UVCallsignDB::HeaderElement::clear() { memset(_data, 0, size()); } unsigned int DM32UVCallsignDB::HeaderElement::count() const { return getUInt32_le(Offset::count()); } void DM32UVCallsignDB::HeaderElement::setCount(unsigned int count) { setUInt32_le(Offset::count(), count); } /* ********************************************************************************************* * * Implementation of call sign DB entry * ********************************************************************************************* */ DM32UVCallsignDB::EntryElement::EntryElement(uint8_t *ptr) : Codeplug::Element(ptr, size()) { // pass... } void DM32UVCallsignDB::EntryElement::clear() { memset(_data, 0xff, size()); setId(0); } bool DM32UVCallsignDB::EntryElement::isValid() const { return 0 != id(); } QString DM32UVCallsignDB::EntryElement::name() const { return readASCII(Offset::name(), Limit::name(), 0x00); } void DM32UVCallsignDB::EntryElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::name(), 0x00); } unsigned int DM32UVCallsignDB::EntryElement::id() const { return getUInt32_le(Offset::id()); } void DM32UVCallsignDB::EntryElement::setId(unsigned int id) { setUInt32_le(Offset::id(), id); } QString DM32UVCallsignDB::EntryElement::callsign() const { return readASCII(Offset::callsign(), Limit::callsign(), 0xff); } void DM32UVCallsignDB::EntryElement::setCallsign(const QString &cal) { writeASCII(Offset::callsign(), cal, Limit::callsign(), 0xff); } QString DM32UVCallsignDB::EntryElement::city() const { return readASCII(Offset::city(), Limit::city(), 0xff); } void DM32UVCallsignDB::EntryElement::setCity(const QString &city) { writeASCII(Offset::city(), city, Limit::city(), 0xff); } QString DM32UVCallsignDB::EntryElement::province() const { return readASCII(Offset::province(), Limit::province(), 0xff); } void DM32UVCallsignDB::EntryElement::setProvince(const QString &province) { writeASCII(Offset::province(), province, Limit::province(), 0xff); } QString DM32UVCallsignDB::EntryElement::country() const { return readASCII(Offset::country(), Limit::country(), 0xff); } void DM32UVCallsignDB::EntryElement::setCountry(const QString &country) { writeASCII(Offset::country(), country, Limit::country(), 0xff); } QString DM32UVCallsignDB::EntryElement::remark() const { return readASCII(Offset::remark(), Limit::remark(), 0xff); } void DM32UVCallsignDB::EntryElement::setRemark(const QString &remark) { writeASCII(Offset::remark(), remark, Limit::remark(), 0xff); } bool DM32UVCallsignDB::EntryElement::encode(const UserDatabase::User &user, const ErrorStack &err) { Q_UNUSED(err); setName(user.name); setCallsign(user.call); setId(user.id); setCity(user.city); setCountry(user.country); setRemark(user.comment); return true; } /* ********************************************************************************************* * * Implementation of call sign DB * ********************************************************************************************* */ DM32UVCallsignDB::DM32UVCallsignDB(QObject *parent) : CallsignDB{parent} { addImage("Call-sign DB for Baofeng DM-32UV"); } bool DM32UVCallsignDB::encode(UserDatabase *db, const Flags &selection, const ErrorStack &err) { // Limit number of entries. unsigned int count = std::min(Limit::entries(), (unsigned int)db->count()); if (selection.hasCountLimit()) count = std::min(count, (unsigned int)selection.countLimit()); // Allocate first block if (! isAllocated(Offset::count())) image(0).addElement(Offset::count(), Offset::betweenBlocks()); // Store count HeaderElement(data(Offset::count())).setCount(count); // Encode first count entries. for (unsigned int i=0; iuser(i))) { errMsg(err) << "Cannot encode DB entry at index " << i << "."; return false; } } return true; } ================================================ FILE: lib/dm32uv_callsigndb.hh ================================================ #ifndef DM32UV_CALLSIGNDB_HH #define DM32UV_CALLSIGNDB_HH #include "callsigndb.hh" #include "codeplug.hh" #include "userdatabase.hh" /** Implements the call-sign DB for the DM32UV. * * @ingroup dm32uv */ class DM32UVCallsignDB : public CallsignDB { Q_OBJECT public: class HeaderElement: public Codeplug::Element { public: /** Constructor. */ explicit HeaderElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0010; } void clear() override; /** Returns the number of entries. */ virtual unsigned int count() const; /** Sets the number of entries. */ virtual void setCount(unsigned int count); protected: /** Some internal offsets. */ struct Offset: Codeplug::Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } /// @endcond }; }; class EntryElement: public Codeplug::Element { public: /** Constructor. */ explicit EntryElement(uint8_t *ptr); /** Size of the element. */ static unsigned int size() { return 0x005c; } void clear() override; bool isValid() const override; /** Returns the name of the entry. */ virtual QString name() const; /** Sets the name of the entry. */ virtual void setName(const QString &name); /** Returns the DMR id of the entry. */ virtual unsigned int id() const; /** Sets the DMR id of the entry. */ virtual void setId(unsigned int id); /** Retruns the callsign field. */ virtual QString callsign() const; /** Sets the callsign field. */ virtual void setCallsign(const QString &cal); /** Retruns the city field. */ virtual QString city() const; /** Sets the city field. */ virtual void setCity(const QString &city); /** Retruns the province field. */ virtual QString province() const; /** Sets the province field. */ virtual void setProvince(const QString &provice); /** Retruns the country field. */ virtual QString country() const; /** Sets the country field. */ virtual void setCountry(const QString &country); /** Retruns the remark field. */ virtual QString remark() const; /** Sets the remark field. */ virtual void setRemark(const QString &remark); /** Encode entry. */ virtual bool encode(const UserDatabase::User &user, const ErrorStack &err=ErrorStack()); public: /** Some limits for the entry. */ struct Limit: Codeplug::Element::Limit { /// Maximum name length. static constexpr unsigned int name() { return 16; } static constexpr unsigned int callsign() { return 8; } static constexpr unsigned int city() { return 16; } static constexpr unsigned int province() { return 16; } static constexpr unsigned int country() { return 16; } static constexpr unsigned int remark() { return 16; } }; protected: /** Some internal offsets. */ struct Offset: Codeplug::Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int id() { return 0x0010; } static constexpr unsigned int callsign() { return 0x0014; } static constexpr unsigned int city() { return 0x001c; } static constexpr unsigned int province() { return 0x002c; } static constexpr unsigned int country() { return 0x003c; } static constexpr unsigned int remark() { return 0x004c; } /// @endcond }; }; public: explicit DM32UVCallsignDB(QObject *parent = nullptr); bool encode(UserDatabase *db, const Flags &selection,const ErrorStack &err=ErrorStack()); public: /** Some limits for the DB. */ struct Limit: Codeplug::Element::Limit { /// Maximum number of db entries. static constexpr unsigned int entries() { return 50000; } // Number of entires per block. static constexpr unsigned int entriesPerBlock() { return 44; } }; protected: /** Some internal offsets. */ struct Offset: Codeplug::Element::Limit { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x278000; } static constexpr unsigned int block0() { return 0x278010; } static constexpr unsigned int block1() { return 0x279000; } static constexpr unsigned int betweenBlocks() { return 0x1000; } /// @endcond }; }; #endif // DM32UV_CALLSIGNDB_HH ================================================ FILE: lib/dm32uv_codeplug.cc ================================================ #include "dm32uv_codeplug.hh" #include "intermediaterepresentation.hh" #include "config.hh" #include "logger.hh" #include /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ChannelElement * ******************************************************************************************** */ DM32UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DM32UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void DM32UVCodeplug::ChannelElement::clear() { fill(0x00); } QString DM32UVCodeplug::ChannelElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DM32UVCodeplug::ChannelElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } Frequency DM32UVCodeplug::ChannelElement::rxFrequency() const { return Frequency::fromHz(getBCD8_le(Offset::rxFrequency())*10); } void DM32UVCodeplug::ChannelElement::setRXFrequency(const Frequency &freq) { setBCD8_le(Offset::rxFrequency(), freq.inHz()/10); } bool DM32UVCodeplug::ChannelElement::validTXFrequency() const { return std::numeric_limits::max() != getUInt32_le(Offset::txFrequency()); } Frequency DM32UVCodeplug::ChannelElement::txFrequency() const { if (validTXFrequency()) return Frequency::fromHz(getBCD8_le(Offset::txFrequency())*10); return Frequency(); } void DM32UVCodeplug::ChannelElement::setTXFrequency(const Frequency &freq) { setBCD8_le(Offset::txFrequency(), freq.inHz()/10); } void DM32UVCodeplug::ChannelElement::clearTXFrequency() { setUInt32_le(Offset::txFrequency(), std::numeric_limits::max()); } DM32UVCodeplug::ChannelElement::ChannelType DM32UVCodeplug::ChannelElement::channelType() const { return (ChannelType)getUInt2(Offset::channelType()); } void DM32UVCodeplug::ChannelElement::setChannelType(ChannelType type) { setUInt2(Offset::channelType(), (unsigned int)type); } Channel::Power DM32UVCodeplug::ChannelElement::power() const { switch ((Power)getUInt2(Offset::power())) { case Power::Low: return Channel::Power::Low; case Power::Medium: return Channel::Power::Mid; case Power::High: return Channel::Power::High; } return Channel::Power::Low; } void DM32UVCodeplug::ChannelElement::setPower(Channel::Power power) { switch (power) { case Channel::Power::Min: case Channel::Power::Low: setUInt2(Offset::power(), (unsigned int)Power::Low); break; case Channel::Power::Mid: setUInt2(Offset::power(), (unsigned int)Power::Medium); break; case Channel::Power::High: case Channel::Power::Max: setUInt2(Offset::power(), (unsigned int)Power::High); break; } } bool DM32UVCodeplug::ChannelElement::loneWorkerEnabled() const { return getBit(Offset::loneWorker()); } void DM32UVCodeplug::ChannelElement::enableLoneWorker(bool enable) { setBit(Offset::loneWorker(), enable); } FMChannel::Bandwidth DM32UVCodeplug::ChannelElement::bandwidth() const { if (getBit(Offset::bandwidth())) { return FMChannel::Bandwidth::Wide; } return FMChannel::Bandwidth::Narrow; } void DM32UVCodeplug::ChannelElement::setBandwidth(FMChannel::Bandwidth bw) { switch (bw) { case FMChannel::Bandwidth::Narrow: clearBit(Offset::bandwidth()); break; case FMChannel::Bandwidth::Wide: setBit(Offset::bandwidth()); break; } } bool DM32UVCodeplug::ChannelElement::validScanListIndex() const { return 0 != getUInt5(Offset::scanListIndex()); } unsigned int DM32UVCodeplug::ChannelElement::scanListIndex() const { return getUInt5(Offset::scanListIndex())-1; } void DM32UVCodeplug::ChannelElement::setScanListIndex(unsigned int idx) { setUInt5(Offset::scanListIndex(), idx+1); } void DM32UVCodeplug::ChannelElement::clearScanListIndex() { setUInt5(Offset::scanListIndex(), 0); } bool DM32UVCodeplug::ChannelElement::talkaroundEnabled() const { return !getBit(Offset::preventTalkaround()); } void DM32UVCodeplug::ChannelElement::enableTalkaround(bool enabled) { setBit(Offset::preventTalkaround(), !enabled); } DM32UVCodeplug::ChannelElement::Admit DM32UVCodeplug::ChannelElement::admitCriterion() const { return (DM32UVCodeplug::ChannelElement::Admit) getUInt2(Offset::admitCriterion()); } void DM32UVCodeplug::ChannelElement::setAdmitCriterion(Admit admit) { setUInt2(Offset::admitCriterion(), (unsigned int)admit); } bool DM32UVCodeplug::ChannelElement::rxDMRAPRSEnabled() const { return getBit(Offset::rxDMRAPRS()); } void DM32UVCodeplug::ChannelElement::enableRXDMRAPRS(bool enable) { setBit(Offset::rxDMRAPRS(), enable); } bool DM32UVCodeplug::ChannelElement::emergencyNotificationEnabled() const { return getBit(Offset::emergencyNotification()); } void DM32UVCodeplug::ChannelElement::enableEmergencyNotification(bool enable) { setBit(Offset::emergencySystemIndex(), enable); } bool DM32UVCodeplug::ChannelElement::emergencyACKEnabled() const { return getBit(Offset::emergencyACK()); } void DM32UVCodeplug::ChannelElement::enableEmergencyACK(bool enable) { setBit(Offset::emergencyACK(), enable); } bool DM32UVCodeplug::ChannelElement::validEmergencySystemIndex() const { return 0 != getUInt3(Offset::emergencySystemIndex()); } unsigned int DM32UVCodeplug::ChannelElement::emergencySystemIndex() const { return getUInt3(Offset::emergencySystemIndex())-1; } void DM32UVCodeplug::ChannelElement::setEmergencySystemIndex(unsigned int idx) { setUInt3(Offset::emergencySystemIndex(), idx+1); } void DM32UVCodeplug::ChannelElement::clearEmergencySystemIndex() { setUInt3(Offset::emergencySystemIndex(), 0); } Level DM32UVCodeplug::ChannelElement::squelchLevel() const { return Level::fromValue(getUInt4(Offset::squelchLevel()), Limit::squelchLevel()); } void DM32UVCodeplug::ChannelElement::setSquelchLevel(Level level) { setUInt4(Offset::squelchLevel(), level.mapTo(Limit::squelchLevel())); } bool DM32UVCodeplug::ChannelElement::rxOnlyEnabled() const { return getBit(Offset::rxOnly()); } void DM32UVCodeplug::ChannelElement::enableRXOnly(bool enable) { setBit(Offset::rxOnly(), enable); } bool DM32UVCodeplug::ChannelElement::dmrAPRSEnabled() const { return getBit(Offset::dmrAPRS()); } void DM32UVCodeplug::ChannelElement::enableDMRAPRS(bool enable) { return setBit(Offset::dmrAPRS(), enable); } bool DM32UVCodeplug::ChannelElement::privateCallACKEnabled() const { return getBit(Offset::privateCallACK()); } void DM32UVCodeplug::ChannelElement::enablePrivateCallACK(bool enable) { return setBit(Offset::privateCallACK(), enable); } bool DM32UVCodeplug::ChannelElement::dataACKEnabled() const { return getBit(Offset::dataACK()); } void DM32UVCodeplug::ChannelElement::enableDataACK(bool enable) { return setBit(Offset::dataACK(), enable); } bool DM32UVCodeplug::ChannelElement::dcdmEnabled() const { return getBit(Offset::dcdm()); } void DM32UVCodeplug::ChannelElement::enableDCDM(bool enable) { return setBit(Offset::dcdm(), enable); } DMRChannel::TimeSlot DM32UVCodeplug::ChannelElement::timeslot() const { return getBit(Offset::timeslot()) ? DMRChannel::TimeSlot::TS2 : DMRChannel::TimeSlot::TS1; } void DM32UVCodeplug::ChannelElement::setTimeslot(DMRChannel::TimeSlot ts) { setBit(Offset::timeslot(), DMRChannel::TimeSlot::TS2 == ts); } unsigned int DM32UVCodeplug::ChannelElement::colorCode() const { return getUInt4(Offset::colorcode()); } void DM32UVCodeplug::ChannelElement::setColorCode(unsigned int cc) { setUInt4(Offset::colorcode(), cc); } bool DM32UVCodeplug::ChannelElement::encryptionEnabled() const { return getBit(Offset::encryptionEnable()); } void DM32UVCodeplug::ChannelElement::enableEncryption(bool enable) { setBit(Offset::encryptionEnable(), enable); } bool DM32UVCodeplug::ChannelElement::validGroupListIndex() const { return 0 != getUInt6(Offset::groupListIndex()); } unsigned int DM32UVCodeplug::ChannelElement::groupListIndex() const { return getUInt6(Offset::groupListIndex())-1; } void DM32UVCodeplug::ChannelElement::setGroupListIndex(unsigned int idx) { setUInt6(Offset::groupListIndex(), idx+1); } void DM32UVCodeplug::ChannelElement::clearGroupListIndex() { setUInt6(Offset::groupListIndex(), 0); } unsigned int DM32UVCodeplug::ChannelElement::dmrAPRSChannelIndex() const { return getUInt4(Offset::dmrAPRSChannelIndex()); } void DM32UVCodeplug::ChannelElement::setDMRAPRSChannelIndex(unsigned int idx) { setUInt4(Offset::dmrAPRSChannelIndex(), idx); } SelectiveCall DM32UVCodeplug::ChannelElement::rxTone() const { return decodeSelectiveCall(getUInt16_le(Offset::rxTone())); } void DM32UVCodeplug::ChannelElement::setRXTone(const SelectiveCall &tone) { setUInt16_le(Offset::rxTone(), encodeSelectiveCall(tone)); } SelectiveCall DM32UVCodeplug::ChannelElement::txTone() const { return decodeSelectiveCall(getUInt16_le(Offset::txTone())); } void DM32UVCodeplug::ChannelElement::setTXTone(const SelectiveCall &tone) { setUInt16_le(Offset::txTone(), encodeSelectiveCall(tone)); } bool DM32UVCodeplug::ChannelElement::voxEnabled() const { return getBit(Offset::vox()); } void DM32UVCodeplug::ChannelElement::enableVOX(bool enable) { return setBit(Offset::vox(), enable); } unsigned int DM32UVCodeplug::ChannelElement::dmrIdIndex() const { return getUInt8(Offset::dmrIdIndex()); } void DM32UVCodeplug::ChannelElement::setDMRIdIndex(unsigned int id) { setUInt8(Offset::dmrIdIndex(), id); } Channel * DM32UVCodeplug::ChannelElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); Channel *ch = nullptr; if ((ChannelType::FM == channelType()) || (ChannelType::FMFixed == channelType())) { if ((rxFrequency().inMHz() >= 118) && (rxFrequency().inMHz() <= 137)) { AMChannel *am = new AMChannel(); ch = am; am->setTXFrequency(Frequency()); am->setSquelch(squelchLevel()); } else { FMChannel *fm = new FMChannel(); ch = fm; fm->setBandwidth(bandwidth()); switch (admitCriterion()) { case Admit::Always: fm->setAdmit(FMChannel::Admit::Always); break; case Admit::ChannelFree: case Admit::ToneOrCCMatch: fm->setAdmit(FMChannel::Admit::Free); break; case Admit::ToneMismatch: fm->setAdmit(FMChannel::Admit::Tone); break; } fm->setSquelch(squelchLevel()); fm->setRXTone(rxTone()); fm->setTXTone(txTone()); fm->extended()->enableTalkaround(talkaroundEnabled()); } } else if ((ChannelType::DMR == channelType()) || (ChannelType::DMRFixed == channelType())) { DMRChannel *dmr = new DMRChannel(); ch = dmr; switch (admitCriterion()) { case Admit::Always: dmr->setAdmit(DMRChannel::Admit::Always); break; case Admit::ToneOrCCMatch: dmr->setAdmit(DMRChannel::Admit::ColorCode); break; case Admit::ChannelFree: dmr->setAdmit(DMRChannel::Admit::Free); break; default: dmr->setAdmit(DMRChannel::Admit::Always); break; } dmr->setTimeSlot(timeslot()); dmr->setColorCode(colorCode()); dmr->extended()->enableTalkaround(talkaroundEnabled()); dmr->extended()->enableDCDM(dcdmEnabled()); dmr->extended()->enableLoneWorker(loneWorkerEnabled()); dmr->extended()->enablePrivateCallConfirm(privateCallACKEnabled()); dmr->extended()->enableDataConfirm(dataACKEnabled()); } else { errMsg(err) << "Unknown channel type " << (unsigned int)channelType() << "."; return nullptr; } ch->setName(name()); ch->setRXFrequency(rxFrequency()); ch->setTXFrequency(validTXFrequency() ? txFrequency() : Frequency()); ch->setPower(power()); ch->setRXOnly(rxOnlyEnabled()); if (voxEnabled()) ch->setVOXDefault(); else ch->disableVOX(); return ch; } bool DM32UVCodeplug::ChannelElement::link(Channel *channel, Context &ctx, const ErrorStack &err) const { // Link scan list if (validScanListIndex()) { if (! ctx.has(scanListIndex())) { errMsg(err) << "Cannot link channel: Scan list with index " << scanListIndex() << " not defined."; return false; } channel->setScanList(ctx.get(scanListIndex())); } if (channel->is()) { auto dmr = channel->as(); // Link group list if (validGroupListIndex()) { if (! ctx.has(groupListIndex())) { errMsg(err) << "Cannot resolve group list index " << groupListIndex() << "."; return false; } dmr->setGroupList(ctx.get(groupListIndex())); } // Link DMR ID if (! ctx.has(dmrIdIndex())) { errMsg(err) << "Cannot resolve radio id index " << dmrIdIndex() << "."; return false; } if (0 == dmrIdIndex()) dmr->setRadioId(DefaultRadioID::get()); else dmr->setRadioId(ctx.get(dmrIdIndex())); if (dmrAPRSEnabled() && ctx.has(dmrAPRSChannelIndex())) { dmr->setAPRS(ctx.get(dmrAPRSChannelIndex())); } } return true; } bool DM32UVCodeplug::ChannelElement::encode(const Channel *channel, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); clear(); setName(channel->name()); setRXFrequency(channel->rxFrequency()); if (channel->txFrequency().isZero()) clearTXFrequency(); else setTXFrequency(channel->txFrequency()); // AM channels are encoded as FM with rx frequency within the air band setChannelType(channel->is() ? ChannelType::DMR : ChannelType::FM); setPower(channel->power()); enableRXOnly(channel->rxOnly()); if (channel->defaultVOX()) enableVOX(ctx.config()->settings()->audio()->voxEnabled()); else enableVOX(! channel->voxDisabled()); if (! channel->scanListRef()->isNull()) setScanListIndex(ctx.index(channel->scanList())); else clearScanListIndex(); if (channel->is()) { auto dmr = channel->as(); setBandwidth(FMChannel::Bandwidth::Narrow); switch (dmr->admit()) { case DMRChannel::Admit::Always: setAdmitCriterion(Admit::Always); break; case DMRChannel::Admit::Free: setAdmitCriterion(Admit::ChannelFree); break; case DMRChannel::Admit::ColorCode: setAdmitCriterion(Admit::ToneOrCCMatch); break; } setSquelchLevel(ctx.config()->settings()->audio()->squelch()); setTimeslot(dmr->timeSlot()); setColorCode(dmr->colorCode()); if (dmr->commercialExtension()) { enableEncryption(! dmr->commercialExtension()->encryptionKeyRef()->isNull()); if (! dmr->commercialExtension()->encryptionKeyRef()->isNull()) { // reverse engineer encryption key index! } } clearGroupListIndex(); if (! dmr->groupListRef()->isNull()) { setGroupListIndex(ctx.index(dmr->groupList())); } if (dmr->radioIdRef()->is()) { setDMRIdIndex(ctx.index(ctx.config()->settings()->defaultId())); } else { setDMRIdIndex(ctx.index(dmr->radioId())); } enableTalkaround(dmr->extended()->talkaround()); enableDCDM(dmr->extended()->dcdm()); enableLoneWorker(dmr->extended()->loneWorker()); enablePrivateCallACK(dmr->extended()->privateCallConfirm()); enableDataACK(dmr->extended()->dataConfirm()); if (!dmr->aprsRef()->isNull() && dmr->aprsRef()->is()) { enableDMRAPRS(true); setDMRAPRSChannelIndex(ctx.index(dmr->aprsRef()->as())); } else { enableDMRAPRS(false); } } else if (channel->is()) { auto fm = channel->as(); setBandwidth(fm->bandwidth()); switch (fm->admit()) { case FMChannel::Admit::Always: setAdmitCriterion(Admit::Always); break; case FMChannel::Admit::Free: setAdmitCriterion(Admit::ChannelFree); break; case FMChannel::Admit::Tone: setAdmitCriterion(Admit::ToneOrCCMatch); break; } setSquelchLevel(fm->defaultSquelch() ? ctx.config()->settings()->audio()->squelch() : fm->squelch()); setRXTone(fm->rxTone()); setTXTone(fm->txTone()); enableTalkaround(fm->extended()->talkaround()); } else if (channel->is()) { auto am = channel->as(); clearTXFrequency(); setSquelchLevel(am->defaultSquelch() ? ctx.config()->settings()->audio()->squelch() : am->squelch()); enableRXOnly(true); } return true; } SelectiveCall DM32UVCodeplug::ChannelElement::decodeSelectiveCall(uint16_t code) { if (0xffff == code) return SelectiveCall(); uint8_t type = code >> 14; code &= 0x3fff; if (0 == type) { return SelectiveCall(double((code>>8)&0xf)*10 + double((code>>4)&0xf)*1 + double(code & 0xf)/10); } else if ((1 == type) || (2 == type)) { return SelectiveCall(code, 2 == type); } return SelectiveCall(); } uint16_t DM32UVCodeplug::ChannelElement::encodeSelectiveCall(const SelectiveCall &tone) { if (! tone.isValid()) return 0xffff; if (tone.isCTCSS()) return (((tone.mHz()/10000) % 10) << 8) | (((tone.mHz()/1000) % 10) << 4) | ((tone.mHz()/100) % 10); if (tone.isDCS()) return ((tone.isInverted() ? 2 : 1) << 14) | tone.octalCode(); return 0xffff; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ChannelBankElement * ******************************************************************************************** */ DM32UVCodeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } unsigned int DM32UVCodeplug::ChannelBankElement::channelCount() const { return getUInt16_le(Offset::channelCount()); } void DM32UVCodeplug::ChannelBankElement::setChannelCount(unsigned int n) { setUInt16_le(Offset::channelCount(), n); } unsigned int DM32UVCodeplug::ChannelBankElement::channelBank(unsigned int index) { if (index < Limit::channelsInBlock0()) return 0; index -= Limit::channelsInBlock0(); return 1 + index/Limit::channelsPerBlock(); } unsigned int DM32UVCodeplug::ChannelBankElement::indexInBank(unsigned int index) { if (index < Limit::channelsInBlock0()) return index; index -= Limit::channelsInBlock0(); return index % Limit::channelsPerBlock(); } unsigned int DM32UVCodeplug::ChannelBankElement::bankCount(unsigned int channelCount) { if (channelCount <= Limit::channelsInBlock0()) return 1; channelCount -= Limit::channelsInBlock0(); return 1 + channelCount / Limit::channelsPerBlock() + ((0 != channelCount % Limit::channelsPerBlock()) ? 1 : 0); } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ChannelExtensionElement * ******************************************************************************************** */ DM32UVCodeplug::ChannelExtensionElement::ChannelExtensionElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void DM32UVCodeplug::ChannelExtensionElement::clear() { setUInt16_le(0x0000, 0x0001); clearContactIndex(); } bool DM32UVCodeplug::ChannelExtensionElement::hasContactIndex() const { unsigned int contactIndex = (((unsigned int)getUInt4(Offset::indexMSN()))<<8) | (unsigned int) getUInt8(Offset::indexLSB()); return 0x0000 != contactIndex; } unsigned int DM32UVCodeplug::ChannelExtensionElement::contactIndex() const { unsigned int contactIndex = (((unsigned int)getUInt4(Offset::indexMSN()))<<8) | (unsigned int) getUInt8(Offset::indexLSB()); return contactIndex - 1; } void DM32UVCodeplug::ChannelExtensionElement::setContactIndex(unsigned int idx) { idx = std::min(ContactBankElement::Limit::contacts(), idx+1); uint8_t msn = (idx >> 8), lsb = (idx & 0xff); setUInt4(Offset::indexMSN(), msn); setUInt8(Offset::indexLSB(), lsb); } void DM32UVCodeplug::ChannelExtensionElement::clearContactIndex() { setUInt4(Offset::indexMSN(), 0); setUInt8(Offset::indexLSB(), 0); } bool DM32UVCodeplug::ChannelExtensionElement::decode(Channel *ch, Context &ctx, const ErrorStack &err) const { Q_UNUSED(ch); Q_UNUSED(ctx); Q_UNUSED(err); return true; } bool DM32UVCodeplug::ChannelExtensionElement::link(Channel *ch, Context &ctx, const ErrorStack &err) const { if (!ch->is() || !hasContactIndex()) return true; if (! ctx.has(contactIndex())) { errMsg(err) << "Cannot resolve contact index " << contactIndex() << "."; return false; } ch->as()->setContact(ctx.get(contactIndex())); return true; } bool DM32UVCodeplug::ChannelExtensionElement::encode(const Channel *ch, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); clear(); if (! ch->is()) return true; auto dch = ch->as(); if (dch->contactRef()->isNull()) clearContactIndex(); else setContactIndex(ctx.index(dch->contact())); return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ContactElement * ******************************************************************************************** */ DM32UVCodeplug::ContactElement::ContactElement(uint8_t *data, size_t size) : Element{data,size} { // pass... } DM32UVCodeplug::ContactElement::ContactElement(uint8_t *data) : Element{data, size()} { // pass... } QString DM32UVCodeplug::ContactElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DM32UVCodeplug::ContactElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } DMRContact::Type DM32UVCodeplug::ContactElement::callType() const { switch ((Type)getUInt8(Offset::callType())) { case Type::Private: return DMRContact::Type::PrivateCall; case Type::Group: return DMRContact::Type::GroupCall; case Type::All: return DMRContact::Type::AllCall; } return DMRContact::Type::PrivateCall; } void DM32UVCodeplug::ContactElement::setCallType(DMRContact::Type type) { switch (type) { case DMRContact::Type::PrivateCall: setUInt8(Offset::callType(), (unsigned int)Type::Private); break; case DMRContact::Type::GroupCall: setUInt8(Offset::callType(), (unsigned int)Type::Group); break; case DMRContact::Type::AllCall: setUInt8(Offset::callType(), (unsigned int)Type::All); break; } } unsigned int DM32UVCodeplug::ContactElement::dmrId() const { return getUInt24_le(Offset::dmrId()); } void DM32UVCodeplug::ContactElement::setDMRId(unsigned int id) { setUInt24_le(Offset::dmrId(), id); } DMRContact * DM32UVCodeplug::ContactElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); return new DMRContact(callType(), name(), dmrId()); } bool DM32UVCodeplug::ContactElement::encode(const DMRContact *contact, const ErrorStack &err) { Q_UNUSED(err); setCallType(contact->type()); setName(contact->name()); setDMRId(contact->number()); return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ContactIndexElement::ContactBitmapElement * ******************************************************************************************** */ DM32UVCodeplug::ContactIndexElement::ContactBitmapElement::ContactBitmapElement(uint8_t *ptr) : Codeplug::InvertedBitmapElement(ptr, size()) { // pass... } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ContactIndexElement::EntryElement * ******************************************************************************************** */ DM32UVCodeplug::ContactIndexElement::EntryElement::EntryElement(uint8_t *ptr, size_t size) : Element{ptr, size} { // pass... } DM32UVCodeplug::ContactIndexElement::EntryElement::EntryElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } void DM32UVCodeplug::ContactIndexElement::EntryElement::clear() { setUInt16_le(0x0000, 0xffff); } bool DM32UVCodeplug::ContactIndexElement::EntryElement::isValid() const { return 0xffff != getUInt16_le(0x0000); } DMRContact::Type DM32UVCodeplug::ContactIndexElement::EntryElement::callType() const { switch ((Type)(getUInt16_le(0x0000) >> 12)) { case Type::Private: return DMRContact::Type::PrivateCall; case Type::Group: return DMRContact::Type::GroupCall; case Type::All: return DMRContact::Type::AllCall; } return DMRContact::Type::PrivateCall; } void DM32UVCodeplug::ContactIndexElement::EntryElement::setCallType(DMRContact::Type type) { uint16_t value = (getUInt16_le(0x0000) & 0x0fff); switch (type) { case DMRContact::Type::PrivateCall: value |= ((unsigned int)Type::Private)<<12; break; case DMRContact::Type::GroupCall: value |= ((unsigned int)Type::Group)<<12; break; case DMRContact::Type::AllCall: value |= ((unsigned int)Type::All)<<12; break; } setUInt16_le(0x0000, value); } unsigned int DM32UVCodeplug::ContactIndexElement::EntryElement::index() const { return (getUInt16_le(0x0000) & 0x0fff)-1; } void DM32UVCodeplug::ContactIndexElement::EntryElement::setIndex(unsigned int idx) { setUInt16_le(0x0000, (getUInt16_le(0x0000) & 0xf000) | ((idx+1) & 0x0fff)); } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ContactIndexElement * ******************************************************************************************** */ DM32UVCodeplug::ContactIndexElement::ContactIndexElement(uint8_t *ptr, size_t size) : Element{ptr, size} { // pass... } DM32UVCodeplug::ContactIndexElement::ContactIndexElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } unsigned int DM32UVCodeplug::ContactIndexElement::contactCount() const { return getUInt16_le(Offset::contactCount()); } void DM32UVCodeplug::ContactIndexElement::setContactCount(unsigned int n) { setUInt16_le(Offset::contactCount(), n); } unsigned int DM32UVCodeplug::ContactIndexElement::groupCallCount() const { return getUInt16_le(Offset::groupCount()); } void DM32UVCodeplug::ContactIndexElement::setGroupCallCount(unsigned int n) { setUInt16_le(Offset::groupCount(), n); } unsigned int DM32UVCodeplug::ContactIndexElement::privateCallCount() const { return getUInt16_le(Offset::privateCount()); } void DM32UVCodeplug::ContactIndexElement::setPrivateCallCount(unsigned int n) { setUInt16_le(Offset::privateCount(), n); } DM32UVCodeplug::ContactIndexElement::ContactBitmapElement DM32UVCodeplug::ContactIndexElement::bitmap() const { return ContactBitmapElement(_data + Offset::bitmap()); } DM32UVCodeplug::ContactIndexElement::EntryElement DM32UVCodeplug::ContactIndexElement::indexEntry(unsigned int n) { return EntryElement(_data + Offset::index() + n*EntryElement::size()); } DM32UVCodeplug::ContactIndexElement::EntryElement DM32UVCodeplug::ContactIndexElement::sortedIndexEntry(unsigned int n) { return EntryElement(_data + Offset::sorted() + n*EntryElement::size()); } bool DM32UVCodeplug::ContactIndexElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setContactCount(ctx.count()); unsigned int privatCallCount = 0, groupCallCount = 0; unsigned int cc = contactCount(); QVector indices; indices.reserve(cc); for (unsigned int idx=0; idx(idx)->type()) privatCallCount++; else if (DMRContact::Type::GroupCall == ctx.get(idx)->type()) groupCallCount++; indexEntry(idx).setCallType(ctx.get(idx)->type()); indexEntry(idx).setIndex(idx); indices.append(idx); } setPrivateCallCount(privatCallCount); setGroupCallCount(groupCallCount); bitmap().enableFirst(ctx.count()); // Sort indices w.r.t. associated DMR id: std::sort(indices.begin(), indices.end(), [&ctx](unsigned ia, unsigned ib)->bool { return ctx.get(ia)->number() < ctx.get(ib)->number(); }); for (unsigned int sidx=0; sidx(indices[sidx])->type()); sortedIndexEntry(sidx).setIndex(indices[sidx]); } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::GroupListElement * ******************************************************************************************** */ DM32UVCodeplug::GroupListElement::GroupListElement(uint8_t *ptr, size_t size) : Element{ptr, size} { // pass... } DM32UVCodeplug::GroupListElement::GroupListElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } QString DM32UVCodeplug::GroupListElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DM32UVCodeplug::GroupListElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } bool DM32UVCodeplug::GroupListElement::validId(unsigned int n) { return 0 != getUInt24_le(Offset::ids() + n*Offset::betweenIds()); } unsigned int DM32UVCodeplug::GroupListElement::id(unsigned int n) { return getUInt24_le(Offset::ids() + n*Offset::betweenIds()); } void DM32UVCodeplug::GroupListElement::setId(unsigned int n, unsigned int idx) { setUInt24_le(Offset::ids() + n*Offset::betweenIds(), idx); } void DM32UVCodeplug::GroupListElement::clearId(unsigned int n) { setUInt24_le(Offset::ids() + n*Offset::betweenIds(), 0); } RXGroupList * DM32UVCodeplug::GroupListElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); Q_UNUSED(ctx); return new RXGroupList(name()); } bool DM32UVCodeplug::GroupListElement::link(RXGroupList *gl, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); for (unsigned int i=0; i(); j++) { if (id(i) == ctx.get(j)->number()) { contact = ctx.get(j); break; } } if (nullptr == contact) { contact = new DMRContact(DMRContact::GroupCall, "Group Call", id(i)); ctx.config()->contacts()->add(contact); logInfo() << "Cannot find DMR contact with ID " << id(i) << ", create one."; } else if (DMRContact::Type::GroupCall != contact->type()) { logWarn() << "Cannot add non-group-call contact to group list."; continue; } gl->addContact(contact); } return true; } bool DM32UVCodeplug::GroupListElement::encode(const RXGroupList *gl, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); setName(gl->name()); for (unsigned int i=0; icount(), Limit::contacts()); i++) { setId(i, gl->contacts()->get(i)->as()->number()); } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::GroupListBankElement::GroupListBitmapElement * ******************************************************************************************** */ DM32UVCodeplug::GroupListBankElement::GroupListBitmapElement::GroupListBitmapElement(uint8_t *ptr) : BitmapElement(ptr, 4) { // pass... } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::GroupListBankElement * ******************************************************************************************** */ DM32UVCodeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr, size_t size) : Element{ptr, size} { // pass... } DM32UVCodeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } DM32UVCodeplug::GroupListBankElement::GroupListBitmapElement DM32UVCodeplug::GroupListBankElement::bitmap() const { return GroupListBitmapElement(_data + Offset::bitmap()); } DM32UVCodeplug::GroupListElement DM32UVCodeplug::GroupListBankElement::groupList(unsigned int n) const { return GroupListElement(_data + Offset::groupLists() + n*GroupListElement::size()); } bool DM32UVCodeplug::GroupListBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0,c=0; irxGroupLists()->add(gl); } return true; } bool DM32UVCodeplug::GroupListBankElement::link(Context &ctx, const ErrorStack &err) { for (unsigned int i=0,c=0; i(c++); if (! groupList(i).link(gl, ctx, err)) { errMsg(err) << "Cannot link " << i << "-th group list."; return false; } } return true; } bool DM32UVCodeplug::GroupListBankElement::encode(Context &ctx, const ErrorStack &err) { bitmap().enableFirst(ctx.count()); for (unsigned int i=0; i(); i++) { auto gl = ctx.get(i); if (! groupList(i).encode(gl, ctx, err)) { errMsg(err) << "Cannot encode " << i << "-th group list."; return false; } } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::RadioIdElement * ******************************************************************************************** */ DM32UVCodeplug::RadioIdElement::RadioIdElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } unsigned int DM32UVCodeplug::RadioIdElement::id() const { return getUInt24_le(Offset::id()); } void DM32UVCodeplug::RadioIdElement::setId(unsigned int id) { setUInt24_le(Offset::id(), id); } QString DM32UVCodeplug::RadioIdElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DM32UVCodeplug::RadioIdElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } DMRRadioID * DM32UVCodeplug::RadioIdElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return new DMRRadioID(name(), id()); } bool DM32UVCodeplug::RadioIdElement::encode(const DMRRadioID *id, const ErrorStack &err) { Q_UNUSED(err); setName(id->name()); setId(id->number()); return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::RadioIdBankElement * ******************************************************************************************** */ DM32UVCodeplug::RadioIdBankElement::RadioIdBankElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } unsigned int DM32UVCodeplug::RadioIdBankElement::idCount() const { return getUInt8(Offset::count()); } void DM32UVCodeplug::RadioIdBankElement::setIdCount(unsigned int n) { setUInt8(Offset::count(), n); } DM32UVCodeplug::RadioIdElement DM32UVCodeplug::RadioIdBankElement::id(unsigned int n) const { return RadioIdElement(_data + Offset::ids() + n*Offset::betweenIds()); } bool DM32UVCodeplug::RadioIdBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; iradioIDs()->add(rid); } return true; } bool DM32UVCodeplug::RadioIdBankElement::encode(Context &ctx, const ErrorStack &err) { setIdCount(ctx.count()); for (unsigned int i=0; i(), Limit::ids()); i++) { if (! id(i).encode(ctx.get(i), err)) { errMsg(err) << "Cannot encode DMR radio ID '" << ctx.get(i)->name() << "' at idx " << i << "."; return false; } } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ZoneElement * ******************************************************************************************** */ DM32UVCodeplug::ZoneElement::ZoneElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } QString DM32UVCodeplug::ZoneElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void DM32UVCodeplug::ZoneElement::setName(const QString &name) { return writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } unsigned int DM32UVCodeplug::ZoneElement::channelCount() const { return getUInt8(Offset::channelCount()); } void DM32UVCodeplug::ZoneElement::setChannelCount(unsigned int n) { setUInt8(Offset::channelCount(), n); } bool DM32UVCodeplug::ZoneElement::channelIndexValid(unsigned int n) const { return 0 != getUInt16_le(Offset::channels() + n*sizeof(uint16_t)); } unsigned int DM32UVCodeplug::ZoneElement::channelIndex(unsigned int n) const { return getUInt16_le(Offset::channels() + n*sizeof(uint16_t)) - 1; } void DM32UVCodeplug::ZoneElement::setChannelIndex(unsigned int n, unsigned int idx) { setUInt16_le(Offset::channels() + n*sizeof(uint16_t), idx+1); } void DM32UVCodeplug::ZoneElement::clearChannelIndex(unsigned int n) { setUInt16_le(Offset::channels() + n*sizeof(uint16_t), 0); } Zone * DM32UVCodeplug::ZoneElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return new Zone(name()); } bool DM32UVCodeplug::ZoneElement::link(Zone *zone, Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(channelIndex(i))) { errMsg(err) << "Cannot link zone, " << i << "-th channel index " << channelIndex(i) << " not defined."; return false; } zone->A()->add(ctx.get(channelIndex(i))); } return true; } bool DM32UVCodeplug::ZoneElement::encode(const Zone *zone, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setName(zone->name()); setChannelCount(std::min(Limit::channels(), (unsigned int)zone->A()->count())); for (unsigned int i=0; iA()->get(i)->as())); } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ZoneBankElement * ******************************************************************************************** */ DM32UVCodeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } unsigned int DM32UVCodeplug::ZoneBankElement::count() const { return getUInt8(Offset::count()); } void DM32UVCodeplug::ZoneBankElement::setCount(unsigned int n) { return setUInt8(Offset::count(), n); } bool DM32UVCodeplug::ZoneBankElement::channelIndexAValid() const { return 0 != getUInt8(Offset::channelIndexA()); } unsigned int DM32UVCodeplug::ZoneBankElement::channelIndexA() const { return getUInt8(Offset::channelIndexA())-1; } void DM32UVCodeplug::ZoneBankElement::setChannelIndexA(unsigned int idx) { setUInt8(Offset::channelIndexA(), idx+1); } void DM32UVCodeplug::ZoneBankElement::clearChannelIndexA() { setUInt8(Offset::channelIndexA(), 0); } bool DM32UVCodeplug::ZoneBankElement::channelIndexBValid() const { return 0 != getUInt8(Offset::channelIndexB()); } unsigned int DM32UVCodeplug::ZoneBankElement::channelIndexB() const { return getUInt8(Offset::channelIndexB())-1; } void DM32UVCodeplug::ZoneBankElement::setChannelIndexB(unsigned int idx) { setUInt8(Offset::channelIndexB(), idx+1); } void DM32UVCodeplug::ZoneBankElement::clearChannelIndexB() { setUInt8(Offset::channelIndexB(), 0); } bool DM32UVCodeplug::ZoneBankElement::zoneIndexAValid() const { return 0 != getUInt8(Offset::zoneIndexA()); } unsigned int DM32UVCodeplug::ZoneBankElement::zoneIndexA() const { return getUInt8(Offset::zoneIndexA())-1; } void DM32UVCodeplug::ZoneBankElement::setZoneIndexA(unsigned int idx) { setUInt8(Offset::zoneIndexA(), idx+1); } void DM32UVCodeplug::ZoneBankElement::clearZoneIndexA() { setUInt8(Offset::zoneIndexA(), 0); } bool DM32UVCodeplug::ZoneBankElement::zoneIndexBValid() const { return 0 != getUInt8(Offset::zoneIndexB()); } unsigned int DM32UVCodeplug::ZoneBankElement::zoneIndexB() const { return getUInt8(Offset::zoneIndexB())-1; } void DM32UVCodeplug::ZoneBankElement::setZoneIndexB(unsigned int idx) { setUInt8(Offset::zoneIndexB(), idx+1); } void DM32UVCodeplug::ZoneBankElement::clearZoneIndexB() { setUInt8(Offset::zoneIndexB(), 0); } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ScanListElement * ******************************************************************************************** */ DM32UVCodeplug::ScanListElement::ScanListElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } QString DM32UVCodeplug::ScanListElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DM32UVCodeplug::ScanListElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } unsigned int DM32UVCodeplug::ScanListElement::channelCount() const { return getUInt8(Offset::channelCount()); } void DM32UVCodeplug::ScanListElement::setChannelCount(unsigned int n) { setUInt8(Offset::channelCount(), n); } DM32UVCodeplug::ScanListElement::TransmitMode DM32UVCodeplug::ScanListElement::transmitMode() const { return (TransmitMode)getUInt4(Offset::transmitMode()); } void DM32UVCodeplug::ScanListElement::setTransmitMode(TransmitMode mode) { setUInt4(Offset::transmitMode(), (unsigned int)mode); } DM32UVCodeplug::ScanListElement::ToneDetectionMode DM32UVCodeplug::ScanListElement::toneDetectionMode() const { return (ToneDetectionMode) getUInt4(Offset::toneDetection()); } void DM32UVCodeplug::ScanListElement::setToneDetectionMode(ToneDetectionMode mode) { setUInt4(Offset::toneDetection(), (unsigned int) mode); } Interval DM32UVCodeplug::ScanListElement::hangTime() const { return Interval::fromMilliseconds(500 * getUInt4(Offset::hangTime())); } void DM32UVCodeplug::ScanListElement::setHangTime(const Interval &dur) { setUInt4(Offset::hangTime(), dur.milliseconds()/500); } bool DM32UVCodeplug::ScanListElement::secondaryChannelIndexValid() const { return 0 != getUInt4(Offset::secondaryChannel()); } unsigned int DM32UVCodeplug::ScanListElement::secondaryChannelIndex() const { return getUInt4(Offset::secondaryChannel())-1; } void DM32UVCodeplug::ScanListElement::setSecondaryChannelIndex(unsigned int idx) { setUInt4(Offset::secondaryChannel(), idx+1); } void DM32UVCodeplug::ScanListElement::clearSecondaryChannelIndex() { setUInt4(Offset::secondaryChannel(), 0); } bool DM32UVCodeplug::ScanListElement::primaryChannelIndexValid() const { return 0 != getUInt4(Offset::primaryChannel()); } unsigned int DM32UVCodeplug::ScanListElement::primaryChannelIndex() const { return getUInt4(Offset::primaryChannel())-1; } void DM32UVCodeplug::ScanListElement::setPrimaryChannelIndex(unsigned int idx) { setUInt4(Offset::primaryChannel(), idx+1); } void DM32UVCodeplug::ScanListElement::clearPrimaryChannelIndex() { setUInt4(Offset::primaryChannel(), 0); } bool DM32UVCodeplug::ScanListElement::revertChannelIndexValid() const { return 0 != getUInt16_le(Offset::revertChannel()); } unsigned int DM32UVCodeplug::ScanListElement::revertChannelIndex() const { return getUInt16_le(Offset::revertChannel())-1; } void DM32UVCodeplug::ScanListElement::setRevertChannelIndex(unsigned int idx) { setUInt16_le(Offset::revertChannel(), idx+1); } void DM32UVCodeplug::ScanListElement::clearRevertChannelIndex() { setUInt16_le(Offset::revertChannel(), 0); } Interval DM32UVCodeplug::ScanListElement::prioritySweepTime() const { return Interval::fromMilliseconds(500 + 300*getUInt6(Offset::prioritySweepTime())); } void DM32UVCodeplug::ScanListElement::setPrioritySweepTime(const Interval &dur) { if (dur.milliseconds() >= 500) { setUInt6(Offset::prioritySweepTime(), (dur.milliseconds()-500)/300); } else { setUInt6(Offset::prioritySweepTime(), 0); } } bool DM32UVCodeplug::ScanListElement::isCurrentChannel(unsigned int n) const { return 0 == getUInt16_le(Offset::channels() + n*sizeof(uint16_t)); } unsigned int DM32UVCodeplug::ScanListElement::channelIndex(unsigned int n) const { return getUInt16_le(Offset::channels() + n*sizeof(uint16_t)) - 1; } void DM32UVCodeplug::ScanListElement::setChannelIndex(unsigned int n, unsigned int idx) { setUInt16_le(Offset::channels() + n*sizeof(uint16_t), idx+1); } void DM32UVCodeplug::ScanListElement::setCurrentChannel(unsigned int n) { setUInt16_le(Offset::channels() + n*sizeof(uint16_t), 0); } ScanList * DM32UVCodeplug::ScanListElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return new ScanList(name()); } bool DM32UVCodeplug::ScanListElement::link(ScanList *lst, Context &ctx, const ErrorStack &err) { // Link primary, secondary and revert channels if set if (primaryChannelIndexValid()) { if (! ctx.has(primaryChannelIndex())) { errMsg(err) << "Cannot link primary priority channel for scan list '" << lst->name() << "': Channel with index " << primaryChannelIndex() << " not defined."; return false; } lst->setPrimaryChannel(ctx.get(primaryChannelIndex())); } if (secondaryChannelIndexValid()) { if (! ctx.has(secondaryChannelIndex())) { errMsg(err) << "Cannot link secondary priority channel for scan list '" << lst->name() << "': Channel with index " << secondaryChannelIndex() << " not defined."; return false; } lst->setSecondaryChannel(ctx.get(secondaryChannelIndex())); } if (revertChannelIndexValid()) { if (! ctx.has(revertChannelIndex())) { errMsg(err) << "Cannot link revert channel for scan list '" << lst->name() << "': Channel with index " << revertChannelIndex() << " not defined."; return false; } lst->setRevertChannel(ctx.get(revertChannelIndex())); } for (unsigned int i=0; iaddChannel(SelectedChannel::get()); } else if (! ctx.has(channelIndex(i))) { errMsg(err) << "Cannot link scan list '" << lst->name() << "': Channel with index " << channelIndex(i) << "not defined"; return false; } lst->addChannel(ctx.get(channelIndex(i))); } return true; } bool DM32UVCodeplug::ScanListElement::encode(const ScanList *lst, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setName(lst->name()); if (! lst->primaryChannelRef()->isNull()) setPrimaryChannelIndex(ctx.index(lst->primaryChannel())); if (! lst->secondaryChannelRef()->isNull()) setSecondaryChannelIndex(ctx.index(lst->secondaryChannel())); if (! lst->revertChannelRef()->isNull()) setRevertChannelIndex(ctx.index(lst->revertChannel())); setChannelCount(std::min(Limit::channels(), (unsigned int)lst->count())); for (unsigned int i=0; ichannel(i)) setCurrentChannel(i); else setChannelIndex(i, ctx.index(lst->channel(i))); } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::ScanListBankElement * ******************************************************************************************** */ DM32UVCodeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } unsigned int DM32UVCodeplug::ScanListBankElement::count() const { return getUInt8(Offset::count()); } void DM32UVCodeplug::ScanListBankElement::setCount(unsigned int n) { setUInt8(Offset::count(), n); } DM32UVCodeplug::ScanListElement DM32UVCodeplug::ScanListBankElement::scanList(unsigned int n) const { return ScanListElement(_data + Offset::scanLists() + n*ScanListElement::size()); } DM32UVCodeplug::ScanListBankElement::ScanMode DM32UVCodeplug::ScanListBankElement::scanMode() const { return (ScanMode)getUInt8(Offset::scanMode()); } void DM32UVCodeplug::ScanListBankElement::setScanMode(ScanMode mode) { setUInt8(Offset::scanMode(), (unsigned int)mode); } FrequencyRange DM32UVCodeplug::ScanListBankElement::vhfRange() const { return {Frequency::fromMHz(getBCD4_le(Offset::vhfLower())), Frequency::fromMHz(getBCD4_le(Offset::vhfUpper())) }; } void DM32UVCodeplug::ScanListBankElement::setVHFRange(const FrequencyRange &range) { setBCD4_le(Offset::vhfLower(), range.lower.inMHz()); setBCD4_le(Offset::vhfUpper(), range.upper.inMHz()); } FrequencyRange DM32UVCodeplug::ScanListBankElement::uhfRange() const { return {Frequency::fromMHz(getBCD4_le(Offset::uhfLower())), Frequency::fromMHz(getBCD4_le(Offset::uhfUpper())) }; } void DM32UVCodeplug::ScanListBankElement::setUHFRange(const FrequencyRange &range) { setBCD4_le(Offset::uhfLower(), range.lower.inMHz()); setBCD4_le(Offset::uhfUpper(), range.upper.inMHz()); } bool DM32UVCodeplug::ScanListBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; iscanlists()->add(lst); } return true; } bool DM32UVCodeplug::ScanListBankElement::link(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(i); if (! scanList(i).link(lst, ctx, err)) { errMsg(err) << "Cannot link scan list at index " << i << "."; return false; } } return true; } bool DM32UVCodeplug::ScanListBankElement::encode(Context &ctx, const ErrorStack &err) { setCount(std::min(Limit::scanLists(), ctx.count())); for (unsigned int i=0; i(i), ctx, err)) { errMsg(err) << "Cannot encode scan list at index " << i << "."; return false; } } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::RoamingChannelElement * ******************************************************************************************** */ DM32UVCodeplug::RoamingChannelElement::RoamingChannelElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } QString DM32UVCodeplug::RoamingChannelElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DM32UVCodeplug::RoamingChannelElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } Frequency DM32UVCodeplug::RoamingChannelElement::rxFrequency() const { return Frequency::fromHz((unsigned long long)getBCD8_le(Offset::rxFrequency()) * 10); } void DM32UVCodeplug::RoamingChannelElement::setRXFrequency(const Frequency &f) { setBCD8_le(Offset::rxFrequency(), f.inHz()/10); } Frequency DM32UVCodeplug::RoamingChannelElement::txFrequency() const { return Frequency::fromHz((unsigned long long)getBCD8_le(Offset::txFrequency()) * 10); } void DM32UVCodeplug::RoamingChannelElement::setTXFrequency(const Frequency &f) { setBCD8_le(Offset::txFrequency(), f.inHz()/10); } unsigned int DM32UVCodeplug::RoamingChannelElement::colorCode() const { return getUInt8(Offset::colorCode()); } void DM32UVCodeplug::RoamingChannelElement::setColorCode(unsigned int cc) { setUInt8(Offset::colorCode(), std::min(cc, 15u)); } DMRChannel::TimeSlot DM32UVCodeplug::RoamingChannelElement::timeSlot() const { switch ((TimeSlot) getUInt8(Offset::timeSlot())) { case TimeSlot::TS1: return DMRChannel::TimeSlot::TS1; case TimeSlot::TS2: return DMRChannel::TimeSlot::TS2; } return DMRChannel::TimeSlot::TS1; } void DM32UVCodeplug::RoamingChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { switch (ts) { case DMRChannel::TimeSlot::TS1: setUInt8(Offset::timeSlot(), (unsigned int)TimeSlot::TS1); break; case DMRChannel::TimeSlot::TS2: setUInt8(Offset::timeSlot(), (unsigned int)TimeSlot::TS2); break; } } RoamingChannel * DM32UVCodeplug::RoamingChannelElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); auto rc = new RoamingChannel(); rc->setName(name()); rc->setRXFrequency(rxFrequency()); rc->setTXFrequency(txFrequency()); rc->overrideColorCode(true); rc->setColorCode(colorCode()); rc->overrideTimeSlot(true); rc->setTimeSlot(timeSlot()); return rc; } bool DM32UVCodeplug::RoamingChannelElement::encode(const RoamingChannel *ch, const ErrorStack &err) { Q_UNUSED(err); setName(ch->name()); setRXFrequency(ch->rxFrequency()); setTXFrequency(ch->txFrequency()); setColorCode(ch->colorCode()); setTimeSlot(ch->timeSlot()); return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::RoamingChannelBankElement * ******************************************************************************************** */ DM32UVCodeplug::RoamingChannelBankElement::RoamingChannelBankElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } unsigned int DM32UVCodeplug::RoamingChannelBankElement::count() const { return getUInt8(Offset::count()); } void DM32UVCodeplug::RoamingChannelBankElement::setCount(unsigned int n) { setUInt8(Offset::count(), std::min(n, Limit::channels())); } DM32UVCodeplug::RoamingChannelElement DM32UVCodeplug::RoamingChannelBankElement::channel(unsigned int n) { return RoamingChannelElement(_data + Offset::channels() + n*Offset::betweenChannels()); } bool DM32UVCodeplug::RoamingChannelBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; iroamingChannels()->add(rc); } return true; } bool DM32UVCodeplug::RoamingChannelBankElement::encode(Context &ctx, const ErrorStack &err) { setCount(std::min(Limit::channels(), ctx.count())); for (unsigned int i=0; i(i), err)) { errMsg(err) << "Cannot encode roaming channel at index " << i << "."; return false; } } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::RoamingZoneElement * ******************************************************************************************** */ DM32UVCodeplug::RoamingZoneElement::RoamingZoneElement(uint8_t *ptr) : Element{ptr, size()} { // pass.. } QString DM32UVCodeplug::RoamingZoneElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DM32UVCodeplug::RoamingZoneElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } unsigned int DM32UVCodeplug::RoamingZoneElement::count() const { return getUInt8(Offset::channelCount()); } void DM32UVCodeplug::RoamingZoneElement::setCount(unsigned int n) { setUInt8(Offset::channelCount(), std::min(n, Limit::channels())); } bool DM32UVCodeplug::RoamingZoneElement::channelIndexValid(unsigned int n) { return 0 != getUInt8(Offset::channels() + n*Offset::betweenChannels()); } unsigned int DM32UVCodeplug::RoamingZoneElement::channelIndex(unsigned int n) { return getUInt8(Offset::channels() + n*Offset::betweenChannels())-1; } void DM32UVCodeplug::RoamingZoneElement::setChannelIndex(unsigned int n, unsigned int idx) { setUInt8(Offset::channels() + n*Offset::betweenChannels(), idx+1); } void DM32UVCodeplug::RoamingZoneElement::clearChannelIndex(unsigned int n) { setUInt8(Offset::channels() + n*Offset::betweenChannels(), 0); } RoamingZone * DM32UVCodeplug::RoamingZoneElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return new RoamingZone(name()); } bool DM32UVCodeplug::RoamingZoneElement::link(RoamingZone *zone, Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(channelIndex(i))) { errMsg(err) << "Cannot resolve " << i << "-th channel index " << channelIndex(i) << ": Not defined."; return false; } zone->addChannel(ctx.get(channelIndex(i))); } return true; } bool DM32UVCodeplug::RoamingZoneElement::encode(const RoamingZone *zone, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setName(zone->name()); setCount(std::min(Limit::channels(), (unsigned int)zone->count())); for (unsigned int i=0; ichannel(i))); } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::RoamingZoneBankElement * ******************************************************************************************** */ DM32UVCodeplug::RoamingZoneBankElement::RoamingZoneBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } unsigned int DM32UVCodeplug::RoamingZoneBankElement::count() const { return getUInt8(Offset::count()); } void DM32UVCodeplug::RoamingZoneBankElement::setCount(unsigned int n) { setUInt8(Offset::count(), std::min(n, Limit::zones())); } bool DM32UVCodeplug::RoamingZoneBankElement::autoRoamEnabled() const { return 0x01 == getUInt8(Offset::autoRoam()); } void DM32UVCodeplug::RoamingZoneBankElement::enableAutoRoam(bool enable) { setUInt8(Offset::autoRoam(), enable ? 0x01 : 0x00); } Interval DM32UVCodeplug::RoamingZoneBankElement::roamingDelay() const { return Interval::fromMinutes(getUInt8(Offset::roamingDelay())); } void DM32UVCodeplug::RoamingZoneBankElement::setRoamingDelay(const Interval &delay) { setUInt8(Offset::roamingDelay(), delay.minutes()); } bool DM32UVCodeplug::RoamingZoneBankElement::defaultRoamingZoneIndexValid() const { return 0 != getUInt8(Offset::defaultRoamingZone()); } unsigned int DM32UVCodeplug::RoamingZoneBankElement::defaultRoamingZoneIndex() const { return getUInt8(Offset::defaultRoamingZone())-1; } void DM32UVCodeplug::RoamingZoneBankElement::setDefaultRoamingZoneIndex(unsigned int idx) { return setUInt8(Offset::defaultRoamingZone(), idx+1); } void DM32UVCodeplug::RoamingZoneBankElement::clearDefaultRoamingZoneIndex() { setUInt8(Offset::defaultRoamingZone(), 0); } DM32UVCodeplug::RoamingZoneElement DM32UVCodeplug::RoamingZoneBankElement::zone(unsigned int n) { return RoamingZoneElement(_data + Offset::zones() + n*Offset::betweenZones()); } bool DM32UVCodeplug::RoamingZoneBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; iroamingZones()->add(z); } return true; } bool DM32UVCodeplug::RoamingZoneBankElement::link(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(i); if (nullptr == z) { errMsg(err) << "Cannot link zone at index " << i << ": Cannot resolve index."; return false; } if (! zone(i).link(z, ctx, err)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } } return true; } bool DM32UVCodeplug::RoamingZoneBankElement::encode(Context &ctx, const ErrorStack &err) { setCount(std::min(Limit::zones(), ctx.count())); for (unsigned int i=0; i(i), ctx, err)) { errMsg(err) << "Cannot encode roaming zone at index " << i << "."; return false; } } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::SMSTemplateElement * ******************************************************************************************** */ DM32UVCodeplug::SMSTemplateElement::SMSTemplateElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } QString DM32UVCodeplug::SMSTemplateElement::message() const { uint8_t len = getUInt8(Offset::length()); return readASCII(Offset::message(), len, 0x00); } void DM32UVCodeplug::SMSTemplateElement::setMessage(const QString &msg) { unsigned int len = std::min((unsigned int)msg.length(), Limit::messageLength()); setUInt8(Offset::length(), len); writeASCII(Offset::message(), msg, Limit::messageLength(), 0x00); } SMSTemplate * DM32UVCodeplug::SMSTemplateElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); auto tmpl = new SMSTemplate(); tmpl->setMessage(message()); return tmpl; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::SMSTemplateBankElement * ******************************************************************************************** */ DM32UVCodeplug::SMSTemplateBankElement::SMSTemplateBankElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } unsigned int DM32UVCodeplug::SMSTemplateBankElement::count() const { return getUInt8(Offset::count()); } void DM32UVCodeplug::SMSTemplateBankElement::setCount(unsigned int n) { n = std::min(n, Limit::messages()); setUInt8(Offset::count(), n); } DM32UVCodeplug::SMSTemplateElement DM32UVCodeplug::SMSTemplateBankElement::message(unsigned int n) const { return SMSTemplateElement(_data + Offset::messages() + n*Offset::betweenMessages()); } bool DM32UVCodeplug::SMSTemplateBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; ismsExtension()->smsTemplates()->add(tmpl); } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::GeneralSettingsElement::Color * ******************************************************************************************** */ unsigned int DM32UVCodeplug::GeneralSettingsElement::Color::encode(Code name) { return (unsigned int) name; } DM32UVCodeplug::GeneralSettingsElement::Color::Code DM32UVCodeplug::GeneralSettingsElement::Color::decode(unsigned int code) { return (Code)code; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::GeneralSettingsElement::Function * ******************************************************************************************** */ unsigned int DM32UVCodeplug::GeneralSettingsElement::KeyFunction::encode(Function func) { return (unsigned int) func; } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::KeyFunction::decode(unsigned int code) { return (Function)code; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::GeneralSettingsElement * ******************************************************************************************** */ DM32UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } BootSettings::BootDisplay DM32UVCodeplug::GeneralSettingsElement::bootDisplay() const { switch ((BootDisplay) getUInt8(Offset::bootDisplay())) { case BootDisplay::Message: return BootSettings::BootDisplay::Text; case BootDisplay::Image: return BootSettings::BootDisplay::Image; default: break; } return BootSettings::BootDisplay::Logo; } void DM32UVCodeplug::GeneralSettingsElement::setBootDisplay(BootSettings::BootDisplay dis) { switch (dis) { case BootSettings::BootDisplay::Text: setUInt8(Offset::bootDisplay(), (unsigned int)BootDisplay::Message); break; case BootSettings::BootDisplay::Logo: case BootSettings::BootDisplay::Image: setUInt8(Offset::bootDisplay(), (unsigned int)BootDisplay::Image); break; } } QString DM32UVCodeplug::GeneralSettingsElement::bootMessage1() const { return readASCII(Offset::bootMessage1(), Limit::bootMessageLength(), 0x00); } void DM32UVCodeplug::GeneralSettingsElement::setBootMessage1(const QString &msg) { writeASCII(Offset::bootMessage1(), msg, Limit::bootMessageLength(), 0x00); } QString DM32UVCodeplug::GeneralSettingsElement::bootMessage2() const { return readASCII(Offset::bootMessage2(), Limit::bootMessageLength(), 0x00); } void DM32UVCodeplug::GeneralSettingsElement::setBootMessage2(const QString &msg) { writeASCII(Offset::bootMessage2(), msg, Limit::bootMessageLength(), 0x00); } bool DM32UVCodeplug::GeneralSettingsElement::mcuResetEnabled() const { return 0x01 == getUInt8(Offset::mcuReset()); } void DM32UVCodeplug::GeneralSettingsElement::enableMCUReset(bool enable) { setUInt8(Offset::mcuReset(), enable ? 0x01 : 0x00); } Interval DM32UVCodeplug::GeneralSettingsElement::autoPowerOffDelay() const { switch ((AutoPowerOffDelay)getUInt8(Offset::autoPowerOffDelay())) { case AutoPowerOffDelay::Off: return Interval::infinity(); case AutoPowerOffDelay::T30Min: return Interval::fromMinutes(30); case AutoPowerOffDelay::T60Min: return Interval::fromMinutes(60); case AutoPowerOffDelay::T2h: return Interval::fromMinutes(120); case AutoPowerOffDelay::T4h: return Interval::fromMinutes(240); case AutoPowerOffDelay::T8h: return Interval::fromMinutes(480); } return Interval::infinity(); } void DM32UVCodeplug::GeneralSettingsElement::setAutoPowerOffDelay(const Interval &delay) { if (delay.isNull() || delay.isInfinite()) setUInt8(Offset::autoPowerOffDelay(), (unsigned int)AutoPowerOffDelay::Off); else if (delay.minutes() <= 30) setUInt8(Offset::autoPowerOffDelay(), (unsigned int)AutoPowerOffDelay::T30Min); else if (delay.minutes() <= 60) setUInt8(Offset::autoPowerOffDelay(), (unsigned int)AutoPowerOffDelay::T60Min); else if (delay.minutes() <= 120) setUInt8(Offset::autoPowerOffDelay(), (unsigned int)AutoPowerOffDelay::T2h); else if (delay.minutes() <= 240) setUInt8(Offset::autoPowerOffDelay(), (unsigned int)AutoPowerOffDelay::T4h); else if (delay.minutes() <= 480) setUInt8(Offset::autoPowerOffDelay(), (unsigned int)AutoPowerOffDelay::T8h); else setUInt8(Offset::autoPowerOffDelay(), (unsigned int)AutoPowerOffDelay::Off); } bool DM32UVCodeplug::GeneralSettingsElement::radioSilentEnabled() const { return getBit(Offset::radioSilent()); } void DM32UVCodeplug::GeneralSettingsElement::enableRadioSilent(bool enable) { setBit(Offset::radioSilent(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::keyToneEnabled() const { return getBit(Offset::keyTone()); } void DM32UVCodeplug::GeneralSettingsElement::enableKeyTone(bool enable) { setBit(Offset::keyTone(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::smsToneEnabled() const { return getBit(Offset::smsTone()); } void DM32UVCodeplug::GeneralSettingsElement::enableSMSTone(bool enable) { setBit(Offset::smsTone(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::groupCallToneEnabled() const { return getBit(Offset::groupCallTone()); } void DM32UVCodeplug::GeneralSettingsElement::enableGroupCallTone(bool enable) { setBit(Offset::groupCallTone(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::privateCallToneEnabled() const { return getBit(Offset::privateCallTone()); } void DM32UVCodeplug::GeneralSettingsElement::enablePrivateCallTone(bool enable) { setBit(Offset::privateCallTone(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::eotToneEnabled() const { return getBit(Offset::eotTone()); } void DM32UVCodeplug::GeneralSettingsElement::enableEOTTone(bool enable) { setBit(Offset::eotTone(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::talkPermitToneEnabled() const { return getBit(Offset::talkPermitTone()); } void DM32UVCodeplug::GeneralSettingsElement::enableTalkPermitTone(bool enable) { setBit(Offset::talkPermitTone(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::bootToneEnabled() const { return getBit(Offset::bootTone()); } void DM32UVCodeplug::GeneralSettingsElement::enableBootTone(bool enable) { setBit(Offset::bootTone(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::voicePromptEnabled() const { return getBit(Offset::voicePrompt()); } void DM32UVCodeplug::GeneralSettingsElement::enableVoicePrompt(bool enable) { setBit(Offset::voicePrompt(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::lowBatteryToneEnabled() const { return getBit(Offset::lowBatteryTone()); } void DM32UVCodeplug::GeneralSettingsElement::enableLowBatteryTone(bool enable) { setBit(Offset::lowBatteryTone(), enable); } DM32UVCodeplug::GeneralSettingsElement::FMRogerTone DM32UVCodeplug::GeneralSettingsElement::fmRogerTone() const { return (FMRogerTone)getUInt2(Offset::fmRogerTone()); } void DM32UVCodeplug::GeneralSettingsElement::setFMRogerTone(FMRogerTone tone) { setUInt2(Offset::fmRogerTone(), (unsigned int)tone); } unsigned int DM32UVCodeplug::GeneralSettingsElement::displayBrightness() const { return (getUInt8(Offset::displayBrightness())*10)/Limit::maxBrightness(); } void DM32UVCodeplug::GeneralSettingsElement::setDisplayBrightness(unsigned int n) { setUInt8(Offset::displayBrightness(), (n*Limit::maxBrightness())/10); } Interval DM32UVCodeplug::GeneralSettingsElement::backlightDuration() const { switch ((BacklightDuration)getUInt8(Offset::backlightDuration())) { case BacklightDuration::Infinity: return Interval::infinity(); case BacklightDuration::T5s: return Interval::fromSeconds(5); case BacklightDuration::T10s: return Interval::fromSeconds(10); case BacklightDuration::T15s: return Interval::fromSeconds(15); case BacklightDuration::T20s: return Interval::fromSeconds(20); case BacklightDuration::T25s: return Interval::fromSeconds(25); case BacklightDuration::T30s: return Interval::fromSeconds(30); case BacklightDuration::T1min: return Interval::fromMinutes(1); case BacklightDuration::T2min: return Interval::fromMinutes(2); case BacklightDuration::T3min: return Interval::fromMinutes(3); case BacklightDuration::T4min: return Interval::fromMinutes(4); case BacklightDuration::T5min: return Interval::fromMinutes(5); } return Interval::infinity(); } void DM32UVCodeplug::GeneralSettingsElement::setBacklightDuration(Interval duration) { if (duration.isNull() || duration.isInfinite()) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::Infinity); } else if (duration.seconds() <= 5) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T5s); } else if (duration.seconds() <= 10) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T10s); } else if (duration.seconds() <= 15) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T15s); } else if (duration.seconds() <= 20) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T20s); } else if (duration.seconds() <= 25) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T25s); } else if (duration.seconds() <= 30) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T30s); } else if (duration.minutes() <= 1) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T1min); } else if (duration.minutes() <= 2) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T2min); } else if (duration.minutes() <= 3) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T3min); } else if (duration.minutes() <= 4) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T4min); } else if (duration.minutes() <= 5) { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::T5min); } else { setUInt8(Offset::backlightDuration(), (unsigned int)BacklightDuration::Infinity); } } Interval DM32UVCodeplug::GeneralSettingsElement::menuDuration() const { if (0 == getUInt8(Offset::menuDuration())) { return Interval::infinity(); } return Interval::fromSeconds(5 * getUInt8(Offset::menuDuration())); } void DM32UVCodeplug::GeneralSettingsElement::setMenuDuration(Interval duration) { if (duration.isNull() || duration.isInfinite()) { setUInt8(Offset::menuDuration(), 0); } else { setUInt8(Offset::menuDuration(), duration.seconds()/5); } } bool DM32UVCodeplug::GeneralSettingsElement::showVolmueChange() const { return getBit(Offset::showVolumeChange()); } void DM32UVCodeplug::GeneralSettingsElement::enableShowVolumeChange(bool enable) { setBit(Offset::showVolumeChange(), enable); } DM32UVCodeplug::GeneralSettingsElement::DateFormat DM32UVCodeplug::GeneralSettingsElement::dateFormat() const { return getBit(Offset::dateFormat()) ? DateFormat::DDMMYYYY : DateFormat::YYYYMMDD; } void DM32UVCodeplug::GeneralSettingsElement::setDateFormat(DateFormat format) { setBit(Offset::dateFormat(), DateFormat::DDMMYYYY == format); } bool DM32UVCodeplug::GeneralSettingsElement::showClock() const { return getBit(Offset::showClock()); } void DM32UVCodeplug::GeneralSettingsElement::enableShowClock(bool enable) { setBit(Offset::showClock(), enable); } DM32UVCodeplug::GeneralSettingsElement::Color::Code DM32UVCodeplug::GeneralSettingsElement::callColor() const { return Color::decode(getUInt8(Offset::callColor())); } void DM32UVCodeplug::GeneralSettingsElement::setCallColor(Color::Code c) { setUInt8(Offset::callColor(), Color::encode(c)); } DM32UVCodeplug::GeneralSettingsElement::Color::Code DM32UVCodeplug::GeneralSettingsElement::standbyColor() const { return Color::decode(getUInt8(Offset::standbyColor())); } void DM32UVCodeplug::GeneralSettingsElement::setStandbyColor(Color::Code c) { setUInt8(Offset::standbyColor(), Color::encode(c)); } DM32UVCodeplug::GeneralSettingsElement::Color::Code DM32UVCodeplug::GeneralSettingsElement::channelNameAColor() const { return Color::decode(getUInt8(Offset::channelNameAColor())); } void DM32UVCodeplug::GeneralSettingsElement::setChannelNameAColor(Color::Code c) { setUInt8(Offset::channelNameAColor(), Color::encode(c)); } DM32UVCodeplug::GeneralSettingsElement::Color::Code DM32UVCodeplug::GeneralSettingsElement::channelNameBColor() const { return Color::decode(getUInt8(Offset::channelNameBColor())); } void DM32UVCodeplug::GeneralSettingsElement::setChannelNameBColor(Color::Code c) { setUInt8(Offset::channelNameBColor(), Color::encode(c)); } DM32UVCodeplug::GeneralSettingsElement::Color::Code DM32UVCodeplug::GeneralSettingsElement::zoneNameAColor() const { return Color::decode(getUInt8(Offset::zoneNameAColor())); } void DM32UVCodeplug::GeneralSettingsElement::setZoneNameAColor(Color::Code c) { setUInt8(Offset::zoneNameAColor(), Color::encode(c)); } DM32UVCodeplug::GeneralSettingsElement::Color::Code DM32UVCodeplug::GeneralSettingsElement::zoneNameBColor() const { return Color::decode(getUInt8(Offset::zoneNameBColor())); } void DM32UVCodeplug::GeneralSettingsElement::setZoneNameBColor(Color::Code c) { setUInt8(Offset::zoneNameBColor(), Color::encode(c)); } DM32UVCodeplug::GeneralSettingsElement::PositionFormat DM32UVCodeplug::GeneralSettingsElement::positionFormat() const { return getBit(Offset::positionFormat()) ? PositionFormat::DMS : PositionFormat::DD; } void DM32UVCodeplug::GeneralSettingsElement::setPositionFormat(PositionFormat format) { setBit(Offset::positionFormat(), PositionFormat::DMS == format); } GNSSSettings::Systems DM32UVCodeplug::GeneralSettingsElement::gnss() const { switch ((GNSSMode)getUInt2(Offset::gnssMode())) { case GNSSMode::GPS: return GNSSSettings::System::GPS; case GNSSMode::Beidou: return GNSSSettings::System::Beidou; case GNSSMode::Both: return GNSSSettings::System::GPS | GNSSSettings::System::Beidou; } return GNSSSettings::System::GPS; } void DM32UVCodeplug::GeneralSettingsElement::setGNSS(GNSSSettings::Systems mode) { if (mode.testFlag(GNSSSettings::System::GPS)) setUInt2(Offset::gnssMode(), (unsigned int)GNSSMode::GPS); if (mode.testFlag(GNSSSettings::System::Beidou)) setUInt2(Offset::gnssMode(), (unsigned int)GNSSMode::Beidou); if (mode.testFlags(GNSSSettings::System::GPS|GNSSSettings::System::Beidou)) setUInt2(Offset::gnssMode(), (unsigned int)GNSSMode::Both); } bool DM32UVCodeplug::GeneralSettingsElement::gnssEnabled() const { return getBit(Offset::enableGNSS()); } void DM32UVCodeplug::GeneralSettingsElement::enableGNSS(bool enable) { setBit(Offset::enableGNSS(), enable); } QTimeZone DM32UVCodeplug::GeneralSettingsElement::timeZone() const { return QTimeZone( (((int)getUInt8(Offset::timeZone()))-11) * 3600); } void DM32UVCodeplug::GeneralSettingsElement::setTimeZone(const QTimeZone &timeZone) { setUInt8(Offset::timeZone(), timeZone.offsetFromUtc(QDateTime::currentDateTime())/3600+11); } Interval DM32UVCodeplug::GeneralSettingsElement::posUpdatePeriod() const { return Interval::fromMinutes(getUInt8(Offset::posUpdatePeriod())); } void DM32UVCodeplug::GeneralSettingsElement::setPosUpdatePeriod(const Interval &period) { setUInt8(Offset::posUpdatePeriod(), period.minutes()); } DM32UVCodeplug::GeneralSettingsElement::RecordMode DM32UVCodeplug::GeneralSettingsElement::recordMode() const { return (RecordMode) getUInt2(Offset::recordMode()); } void DM32UVCodeplug::GeneralSettingsElement::setRecordMode(RecordMode mode) { setUInt2(Offset::recordMode(), (unsigned int)mode); } bool DM32UVCodeplug::GeneralSettingsElement::recordingEnabled() const { return getBit(Offset::enableRecording()); } void DM32UVCodeplug::GeneralSettingsElement::enableRecording(bool enable) { setBit(Offset::enableRecording(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::groupCallMatchEnabled() const { return getBit(Offset::groupCallMatch()); } void DM32UVCodeplug::GeneralSettingsElement::enableGroupCallMatch(bool enable) { setBit(Offset::groupCallMatch(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::privateCallMatchEnabled() const { return getBit(Offset::privateCallMatch()); } void DM32UVCodeplug::GeneralSettingsElement::enablePrivateCallMatch(bool enable) { setBit(Offset::privateCallMatch(), enable); } Interval DM32UVCodeplug::GeneralSettingsElement::dmrCallHangTime() const { return Interval::fromMilliseconds( ((unsigned int)getUInt8(Offset::dmrCallHangTime())) * 500); } void DM32UVCodeplug::GeneralSettingsElement::setDMRCallHangTime(const Interval &time) { setUInt8(Offset::dmrCallHangTime(), time.milliseconds()/500); } Interval DM32UVCodeplug::GeneralSettingsElement::activeWaitTime() const { return Interval::fromMilliseconds(((unsigned int)getUInt8(Offset::activeWaitTime()))*30 + 300); } void DM32UVCodeplug::GeneralSettingsElement::setActiveWaitTime(Interval waitTime) { setUInt8(Offset::activeWaitTime(), (waitTime.milliseconds()-300)/30); } unsigned int DM32UVCodeplug::GeneralSettingsElement::activeRetries() const { return getUInt8(Offset::activeReties()); } void DM32UVCodeplug::GeneralSettingsElement::setActiveRetries(unsigned int retries) { setUInt8(Offset::activeReties(), retries); } Interval DM32UVCodeplug::GeneralSettingsElement::dmrPreambleDuration() const { return Interval::fromMilliseconds(((unsigned int)getUInt8(Offset::dmrPreambleDur()))*120 + 120); } void DM32UVCodeplug::GeneralSettingsElement::setDmrPreambleDuration(Interval duration) { setUInt8(Offset::dmrPreambleDur(), (duration.milliseconds()-120)/120); } bool DM32UVCodeplug::GeneralSettingsElement::dmrRemoteMonitorEnabled() const { return getBit(Offset::dmrMonitor()); } void DM32UVCodeplug::GeneralSettingsElement::enableDMRRemoteMonitor(bool enable) { setBit(Offset::dmrMonitor(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::dmrRemoteKillEnabled() const { return getBit(Offset::dmrKill()); } void DM32UVCodeplug::GeneralSettingsElement::enableDMRRemoteKill(bool enable) { setBit(Offset::dmrKill(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::dmrRemoteRadioCheckEnabled() const { return getBit(Offset::dmrRadioCheck()); } void DM32UVCodeplug::GeneralSettingsElement::enableDMRRemoteRadioCheck(bool enable) { setBit(Offset::dmrRadioCheck(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::dmrRemoteReenableEnabled() const { return getBit(Offset::dmrReenable()); } void DM32UVCodeplug::GeneralSettingsElement::enableDMRRemoteReenable(bool enable) { setBit(Offset::dmrReenable(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::dmrRXAlertEnabled() const { return getBit(Offset::dmrRXAlert()); } void DM32UVCodeplug::GeneralSettingsElement::enableDMRRXAlert(bool enable) { setBit(Offset::dmrRXAlert(), enable); } SMSExtension::Format DM32UVCodeplug::GeneralSettingsElement::smsFormat() const { switch ((SMSFormat)getUInt2(Offset::smsFormat())) { case SMSFormat::Motorola: return SMSExtension::Format::Motorola; case SMSFormat::Hytera: return SMSExtension::Format::Hytera; case SMSFormat::DMR: return SMSExtension::Format::DMR; } return SMSExtension::Format::DMR; } void DM32UVCodeplug::GeneralSettingsElement::setSMSFormat(SMSExtension::Format format) { switch (format) { case SMSExtension::Format::Motorola: setUInt2(Offset::smsFormat(), (unsigned int)SMSFormat::Motorola); break; case SMSExtension::Format::Hytera: setUInt2(Offset::smsFormat(), (unsigned int)SMSFormat::Hytera); break; case SMSExtension::Format::DMR: setUInt2(Offset::smsFormat(), (unsigned int)SMSFormat::DMR); break; } } bool DM32UVCodeplug::GeneralSettingsElement::missedCallNotificationEnabled() const { return getBit(Offset::missedCallNotification()); } void DM32UVCodeplug::GeneralSettingsElement::enableMissedCallNotification(bool enable) { setBit(Offset::missedCallNotification(), enable); } Interval DM32UVCodeplug::GeneralSettingsElement::dmrRemoteMonitorDuration() const { return Interval::fromSeconds(((unsigned int)getUInt8(Offset::dmrRemoteMonitorDuration()))*10 + 10); } void DM32UVCodeplug::GeneralSettingsElement::setDMRRemoteMonitorDuration(Interval duration) { setUInt8(Offset::dmrRemoteMonitorDuration(), (duration.seconds()-10)/10); } DMRSettings::TalkerAliasEncoding DM32UVCodeplug::GeneralSettingsElement::talkerAliasEncoding() const { return getBit(Offset::dmrTalkerAliasFormat()) ? DMRSettings::TalkerAliasEncoding::Unicode : DMRSettings::TalkerAliasEncoding::Iso8; } void DM32UVCodeplug::GeneralSettingsElement::setTalkerAliasEncoding(DMRSettings::TalkerAliasEncoding format) { setBit(Offset::dmrTalkerAliasFormat(), DMRSettings::TalkerAliasEncoding::Unicode == format); } bool DM32UVCodeplug::GeneralSettingsElement::txTalkerAliasEnabled() const { return getBit(Offset::txTalkerAlias()); } void DM32UVCodeplug::GeneralSettingsElement::enableTXTalkerAlias(bool enable) { setBit(Offset::txTalkerAlias(), enable); } DM32UVCodeplug::GeneralSettingsElement::TalkerAliasSource DM32UVCodeplug::GeneralSettingsElement::talkerAliasSource() const { return getBit(Offset::talkerSource()) ? TalkerAliasSource::OverTheAir : TalkerAliasSource::CallsignDB; } void DM32UVCodeplug::GeneralSettingsElement::setTalkerAliasSource(TalkerAliasSource source) { setBit(Offset::talkerSource(), TalkerAliasSource::OverTheAir == source); } DM32UVCodeplug::GeneralSettingsElement::DualStandbyMode DM32UVCodeplug::GeneralSettingsElement::dualStandbyMode() const { return (DualStandbyMode) getUInt2(Offset::dualStandbyMode()); } void DM32UVCodeplug::GeneralSettingsElement::setDualStandbyMode(DualStandbyMode mode) { setUInt2(Offset::dualStandbyMode(), (unsigned int)mode); } DM32UVCodeplug::GeneralSettingsElement::VFO DM32UVCodeplug::GeneralSettingsElement::mainVFO() const { return getBit(Offset::mainVFO()) ? VFO::B : VFO::A; } void DM32UVCodeplug::GeneralSettingsElement::setMainVFO(VFO mainVFO) { setBit(Offset::mainVFO(), VFO::B == mainVFO); } DM32UVCodeplug::GeneralSettingsElement::VFODisplayMode DM32UVCodeplug::GeneralSettingsElement::vfoDisplayModeA() const { return getBit(Offset::displayModeA()) ? VFODisplayMode::ChannelName : VFODisplayMode::Frequency; } void DM32UVCodeplug::GeneralSettingsElement::setVFODisplayModeA(VFODisplayMode mode) { setBit(Offset::displayModeA(), VFODisplayMode::ChannelName == mode); } DM32UVCodeplug::GeneralSettingsElement::VFODisplayMode DM32UVCodeplug::GeneralSettingsElement::vfoDisplayModeB() const { return getBit(Offset::displayModeB()) ? VFODisplayMode::ChannelName : VFODisplayMode::Frequency; } void DM32UVCodeplug::GeneralSettingsElement::setVFODisplayModeB(VFODisplayMode mode) { setBit(Offset::displayModeB(), VFODisplayMode::ChannelName == mode); } DM32UVCodeplug::GeneralSettingsElement::VFOMode DM32UVCodeplug::GeneralSettingsElement::vfoModeA() const { return getBit(Offset::vfoModeA()) ? VFOMode::VFO : VFOMode::Channel; } void DM32UVCodeplug::GeneralSettingsElement::setVFOModeA(VFOMode mode) { setBit(Offset::vfoModeA(), VFOMode::VFO == mode); } DM32UVCodeplug::GeneralSettingsElement::VFOMode DM32UVCodeplug::GeneralSettingsElement::vfoModeB() const { return getBit(Offset::vfoModeB()) ? VFOMode::VFO : VFOMode::Channel; } void DM32UVCodeplug::GeneralSettingsElement::setVFOModeB(VFOMode mode) { setBit(Offset::vfoModeB(), VFOMode::VFO == mode); } bool DM32UVCodeplug::GeneralSettingsElement::vfoModeDisabled() const { return getBit(Offset::disableVFOMode()); } void DM32UVCodeplug::GeneralSettingsElement::disableVFOMode(bool enable) { setBit(Offset::disableVFOMode(), enable); } Interval DM32UVCodeplug::GeneralSettingsElement::dualStandbyHangTime() const { return Interval::fromMilliseconds(((unsigned int)getUInt8(Offset::dualStandbyHangTime()))*500); } void DM32UVCodeplug::GeneralSettingsElement::setDualStandbyHangTime(Interval hangTime) { setUInt8(Offset::dualStandbyHangTime(), hangTime.milliseconds()/500); } bool DM32UVCodeplug::GeneralSettingsElement::sideKeyLockEnabled() const { return getBit(Offset::sideKeyLock()); } void DM32UVCodeplug::GeneralSettingsElement::enableSideKeyLock(bool enable) { setBit(Offset::sideKeyLock(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::knobLockEnabled() const { return getBit(Offset::knobLock()); } void DM32UVCodeplug::GeneralSettingsElement::enableKnobLock(bool enable) { setBit(Offset::knobLock(), enable); } Interval DM32UVCodeplug::GeneralSettingsElement::autoKeyLockDelay() const { if (! getBit(Offset::enableAutoKeyLock())) return Interval::infinity(); return Interval::fromMinutes(5 + getUInt8(Offset::autoKeyLockDelay())); } void DM32UVCodeplug::GeneralSettingsElement::setAutoKeyLockDelay(Interval delay) { if (delay.isInfinite()||delay.isNull()) { setBit(Offset::enableAutoKeyLock(), false); } else { setBit(Offset::enableAutoKeyLock(), true); setUInt8(Offset::autoKeyLockDelay(), delay.minutes()-5); } } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::sk1Short() const { return KeyFunction::decode(getUInt8(Offset::sk1Short())); } void DM32UVCodeplug::GeneralSettingsElement::setSK1Short(KeyFunction::Function function) { setUInt8(Offset::sk1Short(), KeyFunction::encode(function)); } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::sk1Long() const { return KeyFunction::decode(getUInt8(Offset::sk1Long())); } void DM32UVCodeplug::GeneralSettingsElement::setSK1Long(KeyFunction::Function function) { setUInt8(Offset::sk1Long(), KeyFunction::encode(function)); } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::sk2Short() const { return KeyFunction::decode(getUInt8(Offset::sk2Short())); } void DM32UVCodeplug::GeneralSettingsElement::setSK2Short(KeyFunction::Function function) { setUInt8(Offset::sk2Short(), KeyFunction::encode(function)); } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::sk2Long() const { return KeyFunction::decode(getUInt8(Offset::sk2Long())); } void DM32UVCodeplug::GeneralSettingsElement::setSK2Long(KeyFunction::Function function) { setUInt8(Offset::sk2Long(), KeyFunction::encode(function)); } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::p1Short() const { return KeyFunction::decode(getUInt8(Offset::p1Short())); } void DM32UVCodeplug::GeneralSettingsElement::setP1Short(KeyFunction::Function function) { setUInt8(Offset::p1Short(), KeyFunction::encode(function)); } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::p1Long() const { return KeyFunction::decode(getUInt8(Offset::p1Long())); } void DM32UVCodeplug::GeneralSettingsElement::setP1Long(KeyFunction::Function function) { setUInt8(Offset::p1Long(), KeyFunction::encode(function)); } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::p2Short() const { return KeyFunction::decode(getUInt8(Offset::p2Short())); } void DM32UVCodeplug::GeneralSettingsElement::setP2Short(KeyFunction::Function function) { setUInt8(Offset::p2Short(), KeyFunction::encode(function)); } DM32UVCodeplug::GeneralSettingsElement::KeyFunction::Function DM32UVCodeplug::GeneralSettingsElement::p2Long() const { return KeyFunction::decode(getUInt8(Offset::p2Long())); } void DM32UVCodeplug::GeneralSettingsElement::setP2Long(KeyFunction::Function function) { setUInt8(Offset::p2Long(), KeyFunction::encode(function)); } Interval DM32UVCodeplug::GeneralSettingsElement::longPressDuration() const { return Interval::fromSeconds(getUInt8(Offset::longPressDuration()) + 1); } void DM32UVCodeplug::GeneralSettingsElement::setLongPressDuration(Interval duration) { duration = Limit::longPressDuration().limit(duration); setUInt8(Offset::longPressDuration(), duration.seconds()-1); } Interval DM32UVCodeplug::GeneralSettingsElement::transmitTimeout() const { if (0 == getUInt8(Offset::transmitTimeout())) return Interval::infinity(); return Interval::fromSeconds(((unsigned int)getUInt8(Offset::transmitTimeout()))*5 + 15); } void DM32UVCodeplug::GeneralSettingsElement::setTransmitTimeout(Interval timeout) { if (timeout.isInfinite() || timeout.isNull()) { setUInt8(Offset::transmitTimeout(), 0); } else { timeout = Limit::transmitTimeout().limit(timeout); setUInt8(Offset::transmitTimeout(), (timeout.seconds()-15)/5); } } Interval DM32UVCodeplug::GeneralSettingsElement::transmitTimeoutReminder() const { return Interval::fromSeconds(getUInt8(Offset::totReminder())); } void DM32UVCodeplug::GeneralSettingsElement::setTransmitTimeoutReminder(Interval timeout) { setUInt8(Offset::totReminder(), timeout.seconds()); } Level DM32UVCodeplug::GeneralSettingsElement::voxLevel() const { if (0 == getUInt8(Offset::voxLevel())) return Level::null(); return Level::fromValue(getUInt8(Offset::voxLevel()), Limit::vox()); } void DM32UVCodeplug::GeneralSettingsElement::setVOXLevel(Level voxLevel) { if (voxLevel.isNull()) setUInt8(Offset::voxLevel(), 0); else setUInt8(Offset::voxLevel(), voxLevel.mapTo(Limit::vox())); } Interval DM32UVCodeplug::GeneralSettingsElement::voxDelay() const { return Interval::fromMilliseconds(((unsigned int)getUInt8(Offset::voxDelay()))*100); } void DM32UVCodeplug::GeneralSettingsElement::setVoxDelay(Interval delay) { delay = Limit::voxDelay().limit(delay); setUInt8(Offset::voxDelay(), delay.milliseconds()/100); } DM32UVCodeplug::GeneralSettingsElement::PowerSaveMode DM32UVCodeplug::GeneralSettingsElement::powerSaveMode() const { return (PowerSaveMode) getUInt2(Offset::powerSaveMode()); } void DM32UVCodeplug::GeneralSettingsElement::setPowerSaveMode(PowerSaveMode mode) { setUInt2(Offset::powerSaveMode(), (unsigned int)mode); } bool DM32UVCodeplug::GeneralSettingsElement::weatherAlarmEnabled() const { return getBit(Offset::weatherAlarm()); } void DM32UVCodeplug::GeneralSettingsElement::enableWeatherAlarm(bool enable) { setBit(Offset::weatherAlarm(), enable); } bool DM32UVCodeplug::GeneralSettingsElement::allLEDsDisabled() const { return getBit(Offset::disableLEDs()); } void DM32UVCodeplug::GeneralSettingsElement::disableAllLEDs(bool disable) { setBit(Offset::disableLEDs(), disable); } Frequency DM32UVCodeplug::GeneralSettingsElement::tbstFrequency() const { switch ((TBSTFrequency)getUInt2(Offset::tbstFrequency())) { case TBSTFrequency::Hz1000: return Frequency::fromHz(1000); case TBSTFrequency::Hz1450: return Frequency::fromHz(1450); case TBSTFrequency::Hz1750: return Frequency::fromHz(1750); case TBSTFrequency::Hz2100: return Frequency::fromHz(2100); } return Frequency::fromHz(1750); } void DM32UVCodeplug::GeneralSettingsElement::setTBSTFrequency(const Frequency &frequency) { if (frequency.inHz() <= 1000) setUInt2(Offset::tbstFrequency(), (unsigned int)TBSTFrequency::Hz1000); else if (frequency.inHz() <= 1450) setUInt2(Offset::tbstFrequency(), (unsigned int)TBSTFrequency::Hz1450); else if (frequency.inHz() <= 1750) setUInt2(Offset::tbstFrequency(), (unsigned int)TBSTFrequency::Hz1750); else setUInt2(Offset::tbstFrequency(), (unsigned int)TBSTFrequency::Hz2100); } DM32UVCodeplug::GeneralSettingsElement::STEMode DM32UVCodeplug::GeneralSettingsElement::steMode() const { return (STEMode)getUInt2(Offset::steMode()); } void DM32UVCodeplug::GeneralSettingsElement::setSTEMode(STEMode mode) { setUInt2(Offset::steMode(), (unsigned int)mode); } Level DM32UVCodeplug::GeneralSettingsElement::fmMicLevel() const { return Level::fromValue(getUInt8(Offset::fmMicLevel()), Limit::micGain()); } void DM32UVCodeplug::GeneralSettingsElement::setFMMicLevel(Level level) { return setUInt8(Offset::fmMicLevel(), level.mapTo(Limit::micGain())); } Level DM32UVCodeplug::GeneralSettingsElement::dmrMicLevel() const { return Level::fromValue(getUInt8(Offset::dmrMicLevel()), Limit::micGain()); } void DM32UVCodeplug::GeneralSettingsElement::setDMRMicLevel(Level level) { return setUInt8(Offset::dmrMicLevel(), level.mapTo(Limit::micGain())); } bool DM32UVCodeplug::GeneralSettingsElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // Boot settings ctx.config()->settings()->boot()->setBootDisplay(bootDisplay()); ctx.config()->settings()->boot()->setMessage1(bootMessage1()); ctx.config()->settings()->boot()->setMessage2(bootMessage2()); ctx.config()->settings()->boot()->enableReset(mcuResetEnabled()); // Audio settings ctx.config()->settings()->audio()->setMicGain(dmrMicLevel()); if (dmrMicLevel() != fmMicLevel()) ctx.config()->settings()->audio()->setFMMicGain(fmMicLevel()); ctx.config()->settings()->audio()->setVOXDelay(voxDelay()); ctx.config()->settings()->audio()->enableSpeechSynthesis(voicePromptEnabled()); ctx.config()->settings()->audio()->setVox(voxLevel()); // Tone settings ctx.config()->settings()->tone()->enableSilent(radioSilentEnabled()); ctx.config()->settings()->tone()->setKeyToneVolume( keyToneEnabled() ? Level::fromValue(5) : Level::null()); ctx.config()->settings()->tone()->enableSMSTone(smsToneEnabled()); ctx.config()->settings()->tone()->enableBootTone(bootToneEnabled()); ctx.config()->settings()->tone()->enableRingtone( privateCallToneEnabled()); ctx.config()->settings()->tone()->setTalkPermit( talkPermitToneEnabled() ? (Channel::Type::FM|Channel::Type::DMR) : Channel::Type::None ); ctx.config()->settings()->tone()->setCallEnd( eotToneEnabled() ? (Channel::Type::FM|Channel::Type::DMR) : Channel::Type::None ); if (transmitTimeout().isInfinite()) ctx.config()->settings()->disableTOT(); else ctx.config()->settings()->setTOT(transmitTimeout()); ctx.config()->smsExtension()->setFormat(smsFormat()); ctx.config()->settings()->gnss()->setSystems(gnss()); ctx.config()->settings()->dmr()->enablePrivateCallMatch(privateCallMatchEnabled()); ctx.config()->settings()->dmr()->enableGroupCallMatch(groupCallMatchEnabled()); ctx.config()->settings()->dmr()->setGroupCallHangTime(dmrCallHangTime()); ctx.config()->settings()->dmr()->enableSendTalkerAlias(txTalkerAliasEnabled()); ctx.config()->settings()->dmr()->setTalkerAliasEncoding(talkerAliasEncoding()); ctx.config()->settings()->dmr()->setPreamble(dmrPreambleDuration()); return true; } bool DM32UVCodeplug::GeneralSettingsElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // boot settings setBootDisplay(ctx.config()->settings()->boot()->bootDisplay()); setBootMessage1(ctx.config()->settings()->boot()->message1()); setBootMessage2(ctx.config()->settings()->boot()->message2()); enableMCUReset(ctx.config()->settings()->boot()->resetEnabled()); // audio settings setDMRMicLevel(ctx.config()->settings()->audio()->micGain()); if (ctx.config()->settings()->audio()->fmMicGainEnabled()) setFMMicLevel(ctx.config()->settings()->audio()->fmMicGain()); else setFMMicLevel(ctx.config()->settings()->audio()->micGain()); enableVoicePrompt(ctx.config()->settings()->audio()->speechSynthesisEnabled()); if (ctx.config()->settings()->audio()->voxEnabled()) setVOXLevel(ctx.config()->settings()->audio()->vox()); else setVOXLevel(Level::null()); setVoxDelay(ctx.config()->settings()->audio()->voxDelay()); // tone settings enableRadioSilent(ctx.config()->settings()->tone()->silent()); enableKeyTone(ctx.config()->settings()->tone()->keyToneEnabled()); enableSMSTone(ctx.config()->settings()->tone()->smsToneEnabled()); enableBootTone(ctx.config()->settings()->tone()->bootToneEnabled()); enablePrivateCallTone(ctx.config()->settings()->tone()->ringtoneEnabled()); enableTalkPermitTone( ctx.config()->settings()->tone()->talkPermit().testAnyFlags( Channel::Type::FM | Channel::Type::DMR)); enableEOTTone(ctx.config()->settings()->tone()->callEnd().testAnyFlags( Channel::Type::FM | Channel::Type::DMR)); if (ctx.config()->settings()->totDisabled()) setTransmitTimeout(Interval::infinity()); else setTransmitTimeout(ctx.config()->settings()->tot()); if (ctx.config()->smsExtension()) setSMSFormat(ctx.config()->smsExtension()->format()); setGNSS(ctx.config()->settings()->gnss()->systems()); enablePrivateCallMatch(ctx.config()->settings()->dmr()->privateCallMatchEnabled()); enableGroupCallMatch(ctx.config()->settings()->dmr()->groupCallMatchEnabled()); setDMRCallHangTime(ctx.config()->settings()->dmr()->groupCallHangTime()); enableTXTalkerAlias(ctx.config()->settings()->dmr()->sendTalkerAliasEnabled()); setTalkerAliasEncoding(ctx.config()->settings()->dmr()->talkerAliasEncoding()); setDmrPreambleDuration(ctx.config()->settings()->dmr()->preamble()); return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::APRSSettingsElement * ******************************************************************************************** */ DM32UVCodeplug::APRSSettingsElement::APRSSettingsElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } Interval DM32UVCodeplug::APRSSettingsElement::updatePeriod() { if (0 == getUInt8(Offset::updatePeriod())) return Interval::infinity(); return Interval::fromSeconds(((unsigned int)getUInt8(Offset::updatePeriod()))*30); } void DM32UVCodeplug::APRSSettingsElement::setUpdatePeriod(Interval interval) { if (interval.isInfinite() || interval.isNull()) { setUInt8(Offset::updatePeriod(), 0); } else { interval = Limit::updatePeriod().limit(interval); setUInt8(Offset::updatePeriod(), (unsigned int)interval.seconds()/30); } } bool DM32UVCodeplug::APRSSettingsElement::fixedLocationEnabled() const { return 0 != getUInt8(Offset::enableFixedLocation()); } QGeoCoordinate DM32UVCodeplug::APRSSettingsElement::fixedLocation() const { static QRegularExpression re(R"((\d+\.\d+)([NSEW]))"); QString lat = readASCII(Offset::fixedLocationLatitude(), 10, 0x00); QString lon = readASCII(Offset::fixedLocationLongitude(), 10, 0x00); auto latMatch = re.match(lat), lonMatch = re.match(lon); if (latMatch.hasMatch() && lonMatch.hasMatch()) { return QGeoCoordinate{ latMatch.captured(1).toDouble() * (latMatch.captured(2)=="S" ? -1 : 1), lonMatch.captured(1).toDouble() * (lonMatch.captured(2)=="W" ? -1 : 1), }; } return {}; } void DM32UVCodeplug::APRSSettingsElement::setFixedLocation(const QGeoCoordinate &coordinate) { if (! coordinate.isValid()) { setUInt8(Offset::enableFixedLocation(), 0); return; } QString latString, lonString; latString.asprintf("%02.6f%c",std::abs(coordinate.latitude()), coordinate.latitude()<0 ? 'S' : 'N'); lonString.asprintf("%03.5f%c",std::abs(coordinate.longitude()), coordinate.longitude()<0 ? 'W' : 'E'); setUInt8(Offset::enableFixedLocation(), 1); writeASCII(Offset::fixedLocationLatitude(), latString, 10); writeASCII(Offset::fixedLocationLongitude(), lonString, 10); } void DM32UVCodeplug::APRSSettingsElement::enableFixedLocation(bool enable) { setUInt8(Offset::enableFixedLocation(), enable ? 0x01 : 0x00); } bool DM32UVCodeplug::APRSSettingsElement::revertChannelIsCurrent(unsigned int n) { return 0 == getUInt16_le(Offset::revertChannelIndices() + n * Offset::betweenRevertChannelIndices()); } unsigned int DM32UVCodeplug::APRSSettingsElement::revertChannelIndex(unsigned int n) const { return getUInt16_le(Offset::revertChannelIndices() + n * Offset::betweenRevertChannelIndices()) -1 ; } void DM32UVCodeplug::APRSSettingsElement::setRevertChannelIndex(unsigned int n, unsigned int idx) { setUInt16_le(Offset::revertChannelIndices() + n * Offset::betweenRevertChannelIndices(), idx+1); } void DM32UVCodeplug::APRSSettingsElement::setRevertChannelToCurrent(unsigned int n) { setUInt16_le(Offset::revertChannelIndices() + n * Offset::betweenRevertChannelIndices(), 0); } Interval DM32UVCodeplug::APRSSettingsElement::preWaveDelay() const { return Interval::fromMilliseconds((unsigned int)getUInt8(Offset::prewaveDelay()) * 100); } void DM32UVCodeplug::APRSSettingsElement::setPreWaveDelay(const Interval &delay) { setUInt8(Offset::prewaveDelay(), delay.milliseconds()/100); } DMRContact::Type DM32UVCodeplug::APRSSettingsElement::callType() const { switch ((CallType)getUInt8(Offset::callType())) { case CallType::Private: return DMRContact::Type::PrivateCall; case CallType::Group: return DMRContact::Type::GroupCall; } return DMRContact::Type::PrivateCall; } void DM32UVCodeplug::APRSSettingsElement::setCallType(DMRContact::Type type) { switch (type) { case DMRContact::Type::PrivateCall: setUInt8(Offset::callType(), (unsigned int) CallType::Private); break; case DMRContact::Type::AllCall: case DMRContact::Type::GroupCall: setUInt8(Offset::callType(), (unsigned int) CallType::Group); break; } } unsigned int DM32UVCodeplug::APRSSettingsElement::destinationId() const { return getUInt24_le(Offset::destinationId()); } void DM32UVCodeplug::APRSSettingsElement::setDestinationId(unsigned int id) { setUInt24_le(Offset::destinationId(), id); } bool DM32UVCodeplug::APRSSettingsElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // GNSS settings ctx.config()->settings()->gnss()->setFixedPosition(fixedLocation()); ctx.config()->settings()->gnss()->enableFixedPosition(fixedLocationEnabled()); if (0 == destinationId()) return true; auto aprs = new DMRAPRSSystem("DMR APRS System"); if (updatePeriod().isFinite()) aprs->setPeriod(updatePeriod()); else aprs->disablePeriod(); ctx.add(aprs, 0); ctx.config()->posSystems()->add(aprs); return true; } bool DM32UVCodeplug::APRSSettingsElement::link(Context &ctx, const ErrorStack &err) { if (0 == destinationId()) return true; auto aprs = ctx.get(0); if (nullptr == aprs) { errMsg(err) << "Cannot resolve DMR APRS System at index 0!"; return false; } DMRContact *cont = nullptr; for (unsigned int i=0; i(); i++) { if (destinationId() == ctx.get(i)->number()) { cont = ctx.get(i); break; } } if (nullptr == cont) { cont = new DMRContact(callType(), "DMR APRS Contact", destinationId()); ctx.config()->contacts()->add(cont); } aprs->setContact(cont); if (revertChannelIsCurrent(0)) { aprs->resetRevertChannel(); } else { if (! ctx.has(revertChannelIndex(0))) { errMsg(err) << "Cannot resolve revert channel index " << revertChannelIndex(0) << "."; return false; } if (! ctx.get(revertChannelIndex(0))->is()) { errMsg(err) << "Revert channel with index " << revertChannelIndex(0) << " must be DMR channel."; return false; } aprs->setRevertChannel(ctx.get(revertChannelIndex(0))->as()); } return true; } bool DM32UVCodeplug::APRSSettingsElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // GNSS settings if (ctx.config()->settings()->gnss()->fixedPosition().isValid()) { setFixedLocation(ctx.config()->settings()->gnss()->fixedPosition()); enableFixedLocation(ctx.config()->settings()->gnss()->fixedPositionEnabled()); } if (0 == ctx.count()) { setDestinationId(0); return true; } // We can only encode a single system -> use the first auto sys = ctx.get(0); if (sys->periodDisabled()) setUpdatePeriod(Interval::infinity()); else setUpdatePeriod(sys->period()); setDestinationId(sys->contact()->number()); setCallType(sys->contact()->type()); for (unsigned int i=0; ihasRevertChannel()) setRevertChannelIndex(0, ctx.index((Channel*)sys->revertChannel())); return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::PasswordSettingsElement * ******************************************************************************************** */ DM32UVCodeplug::PasswordSettingsElement::PasswordSettingsElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } bool DM32UVCodeplug::PasswordSettingsElement::bootPasswordEnabled() const { return 165 == getUInt8(Offset::enableBootPassword()); } QString DM32UVCodeplug::PasswordSettingsElement::bootPassword() const { return readASCII(Offset::bootPassword(), Limit::passwordLength(), 0x00); } void DM32UVCodeplug::PasswordSettingsElement::setBootPassword(const QString &value) { writeASCII(Offset::bootPassword(), value, Limit::passwordLength(), 0x00); setUInt8(Offset::enableBootPassword(), 165); } void DM32UVCodeplug::PasswordSettingsElement::clearBootPassword() { setUInt8(Offset::enableBootPassword(), 0); } bool DM32UVCodeplug::PasswordSettingsElement::writePasswordEnabled() const { return 165 == getUInt8(Offset::enableWritePassword()); } QString DM32UVCodeplug::PasswordSettingsElement::writePassword() const { return readASCII(Offset::writePassword(), Limit::passwordLength(), 0x00); } void DM32UVCodeplug::PasswordSettingsElement::setWritePassword(const QString &value) { writeASCII(Offset::writePassword(), value, Limit::passwordLength(), 0x00); setUInt8(Offset::enableWritePassword(), 165); } void DM32UVCodeplug::PasswordSettingsElement::clearWritePassword() { setUInt8(Offset::enableWritePassword(), 0); } bool DM32UVCodeplug::PasswordSettingsElement::readPasswordEnabled() const { return 165 == getUInt8(Offset::enableReadPassword()); } QString DM32UVCodeplug::PasswordSettingsElement::readPassword() const { return readASCII(Offset::readPassword(), Limit::passwordLength(), 0x00); } void DM32UVCodeplug::PasswordSettingsElement::setReadPassword(const QString &value) { writeASCII(Offset::readPassword(), value, Limit::passwordLength(), 0x00); setUInt8(Offset::enableReadPassword(), 165); } void DM32UVCodeplug::PasswordSettingsElement::clearReadPassword() { setUInt8(Offset::enableReadPassword(), 0); } bool DM32UVCodeplug::PasswordSettingsElement::encode(Context &ctx, ErrorStack err) { if (ctx.config()->settings()->boot()->bootPasswordEnabled()) { if (ctx.config()->settings()->boot()->bootPassword().length() > Limit::passwordLength()) { errMsg(err) << "Cannot encode boot password: password is too long."; clearBootPassword(); return false; } setBootPassword(ctx.config()->settings()->boot()->bootPassword()); } else { clearBootPassword(); } return true; } bool DM32UVCodeplug::PasswordSettingsElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); ctx.config()->settings()->boot()->enableBootPassword(bootPasswordEnabled()); if (bootPasswordEnabled()) ctx.config()->settings()->boot()->setBootPassword(bootPassword()); return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::EncrpytionKeyElement * ******************************************************************************************** */ DM32UVCodeplug::EncryptionKeyElement::EncryptionKeyElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } void DM32UVCodeplug::EncryptionKeyElement::clear() { setType(Type::Off); setName(""); setKey(QByteArray(Limit::keyLength(), '\x00')); } unsigned int DM32UVCodeplug::EncryptionKeyElement::keyId() const { return getUInt8(Offset::keyId()); } void DM32UVCodeplug::EncryptionKeyElement::setKeyId(unsigned int id) { setUInt8(Offset::keyId(), id); } QString DM32UVCodeplug::EncryptionKeyElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DM32UVCodeplug::EncryptionKeyElement::setName(const QString &value) { writeASCII(Offset::name(), value, Limit::nameLength(), 0x00); } DM32UVCodeplug::EncryptionKeyElement::Type DM32UVCodeplug::EncryptionKeyElement::type() const { return (Type)getUInt8(Offset::type()); } void DM32UVCodeplug::EncryptionKeyElement::setType(Type type) { setUInt8(Offset::type(), (unsigned int)type); } QByteArray DM32UVCodeplug::EncryptionKeyElement::key() const { return QByteArray((const char *)_data + Offset::key(), (qsizetype) Limit::keyLength()); } void DM32UVCodeplug::EncryptionKeyElement::setKey(const QByteArray &key) { std::memset(_data + Offset::key(), 0, Limit::keyLength()); std::memcpy(_data + Offset::key(), key.constData(), std::min(Limit::keyLength(), (unsigned int)key.length())); } EncryptionKey * DM32UVCodeplug::EncryptionKeyElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); switch (type()) { case Type::Off: case Type::Custom: errMsg(err) << "Cannot decode disabled or custom key."; return nullptr; case Type::ARC4: { auto key = new ARC4EncryptionKey(); key->setName(name()); if (! key->setKey(this->key().left(5), err)) return nullptr; return key; } case Type::AES128: { auto key = new AESEncryptionKey(); key->setName(name()); if (! key->setKey(this->key().left(16), err)) return nullptr; return key; } case Type::AES256: { auto key = new AESEncryptionKey(); key->setName(name()); if (! key->setKey(this->key(), err)) return nullptr; return key; } } errMsg(err) << "Unhandled key type " << (unsigned int)type() << "."; return nullptr; } bool DM32UVCodeplug::EncryptionKeyElement::encode(const EncryptionKey *key, Context &ctx, const ErrorStack &err) { setKeyId(ctx.index((EncryptionKey*)key)+1); setName(key->name()); setKey(key->key()); if (key->is()) { setType(Type::ARC4); } else if (key->is() && (16 == key->key().size())) { setType(Type::AES128); } else if (key->is() && (32 == key->key().size())) { setType(Type::AES256); } else { errMsg(err) << "Cannot encode encryption key: Format not supported."; return false; } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug::EncryptionKeyBankElement * ******************************************************************************************** */ DM32UVCodeplug::EncryptionKeyBankElement::EncryptionKeyBankElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } bool DM32UVCodeplug::EncryptionKeyBankElement::keyValid(unsigned int idx) const { return (EncryptionKeyElement::Type::ARC4 == key(idx).type()) || (EncryptionKeyElement::Type::AES128 == key(idx).type()) || (EncryptionKeyElement::Type::AES256 == key(idx).type()); } DM32UVCodeplug::EncryptionKeyElement DM32UVCodeplug::EncryptionKeyBankElement::key(unsigned int idx) const { return EncryptionKeyElement(_data + Offset::keys() + idx*Offset::betweenKeys()); } bool DM32UVCodeplug::EncryptionKeyBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; ikey(i).decode(ctx, err); if (nullptr == key) { errMsg(err) << "Cannot decode encryption key at index " << i << "."; return false; } ctx.add(key, i); ctx.config()->commercialExtension()->encryptionKeys()->add(key); } return true; } bool DM32UVCodeplug::EncryptionKeyBankElement::encode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i= ctx.count()) continue; if (! key(i).encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode encryption key."; return false; } } return true; } /* ******************************************************************************************** * * Implementation of DM32UVCodeplug * ******************************************************************************************** */ DM32UVCodeplug::DM32UVCodeplug(QObject *parent) : Codeplug{parent} { addImage("Baofeng DM32UV Codeplug"); } Config * DM32UVCodeplug::preprocess(Config *config, const ErrorStack &err) const { Config *copy = Codeplug::preprocess(config, err); if (nullptr == copy) { errMsg(err) << "Cannot pre-process DM32UV codeplug."; return nullptr; } // Remove all M17 channels ObjectFilterVisitor m17Filter{M17Channel::staticMetaObject}; if (! m17Filter.process(copy, err)) { errMsg(err) << "Remove M17 channels."; delete copy; return nullptr; } // Keep only ARC4, AES (128 and 256) EncryptionKeyFilterVisitor encFilter{ EncryptionKeyFilterVisitor::Filter(ARC4EncryptionKey::staticMetaObject, 40), EncryptionKeyFilterVisitor::Filter(AESEncryptionKey::staticMetaObject, 128), EncryptionKeyFilterVisitor::Filter(AESEncryptionKey::staticMetaObject, 256) }; if (! encFilter.process(copy, err)) { errMsg(err) << "Clear encryption keys."; errMsg(err) << "Cannot pre-process DM32UV codeplug."; delete copy; return nullptr; } // Split dual-zones into two. ZoneSplitVisitor splitter; if (! splitter.process(copy, err)) { errMsg(err) << "Cannot split dual VFO zones."; errMsg(err) << "Cannot pre-process DM32UV codeplug."; delete copy; return nullptr; } return copy; } bool DM32UVCodeplug::postprocess(Config *config, const ErrorStack &err) const { if (! Codeplug::postprocess(config, err)) { errMsg(err) << "Cannot post-process DM32UV codeplug."; return false; } // Merge split zones into one. ZoneMergeVisitor merger; if (! merger.process(config, err)) { errMsg(err) << "Cannot post-process DM32UV codeplug."; return false; } return true; } bool DM32UVCodeplug::index(Config *config, Context &ctx, const ErrorStack &err) const { Q_UNUSED(err) // All indices as 0-based. That is, the first channel gets index 0 etc. // There must be a default DMR radio ID. if (nullptr == ctx.config()->settings()->defaultId()) { errMsg(err) << "No default DMR radio ID specified."; errMsg(err) << "Cannot index codeplug for encoding for the BTECH DM32UV."; return false; } // Map radio IDs for (int i=0; iradioIDs()->count(); i++) { if (ctx.config()->radioIDs()->get(i)->is()) ctx.add(ctx.config()->radioIDs()->get(i)->as(), i); } // Map DMR contacts for (int i=0, d=0; icontacts()->count(); i++) { if (config->contacts()->contact(i)->is()) { ctx.add(config->contacts()->contact(i)->as(), d); d++; } } // Map rx group lists for (int i=0; irxGroupLists()->count(); i++) ctx.add(config->rxGroupLists()->list(i), i); // Map channels for (int i=0; ichannelList()->count(); i++) ctx.add(config->channelList()->channel(i), i); // Map zones for (int i=0; izones()->count(); i++) ctx.add(config->zones()->zone(i), i); // Map scan lists for (int i=0; iscanlists()->count(); i++) ctx.add(config->scanlists()->scanlist(i), i); // Map roaming channels and zones for (int i=0; iroamingChannels()->count(); i++) ctx.add(config->roamingChannels()->channel(i), i); for (int i=0; iroamingZones()->count(); i++) ctx.add(config->roamingZones()->zone(i), i); // Map encryption keys if (config->commercialExtension()) { for (int i=0; icommercialExtension()->encryptionKeys()->count(); i++) ctx.add(config->commercialExtension()->encryptionKeys()->key(i), i); } return true; } bool DM32UVCodeplug::encode(Config *config, const Flags &flags, const ErrorStack &err) { Q_UNUSED(flags); Context ctx(config); ctx.remTable(&BasicEncryptionKey::staticMetaObject, true); ctx.remTable(&ARC4EncryptionKey::staticMetaObject, true); ctx.remTable(&AESEncryptionKey::staticMetaObject, true); ctx.addTable(&EncryptionKey::staticMetaObject); if (! index(config, ctx, err)) { errMsg(err) << "Index elements."; return false; } if (! encodeElements(ctx, err)) { errMsg(err) << "Cannot encode codeplug."; return false; } return true; } bool DM32UVCodeplug::decode(Config *config, const ErrorStack &err) { Context ctx(config); if (! decodeElements(ctx, err)) { errMsg(err) << "Cannot decode elements."; return false; } if (! linkElements(ctx, err)) { errMsg(err) << "Cannot decode elements."; return false; } return true; } bool DM32UVCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { if (! decodeChannels(ctx, err)) { errMsg(err) << "Cannot decode channels."; return false; } if (! decodeContacts(ctx, err)) { errMsg(err) << "Cannot decode contacts."; return false; } if (! GroupListBankElement(data(Offset::groupListBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode group lists."; return false; } if (! RadioIdBankElement(data(Offset::radioIdBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode radio IDs."; return false; } if (! decodeZones(ctx, err)) { errMsg(err) << "Cannot decode zones."; return false; } if (! ScanListBankElement(data(Offset::scanListBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode scan lists."; return false; } if (! RoamingChannelBankElement(data(Offset::roamingChannelBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode roaming channels."; return false; } if (! RoamingZoneBankElement(data(Offset::roamingZoneBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode roaming zones."; return false; } if (! GeneralSettingsElement(data(Offset::generalSettings())).decode(ctx, err)) { errMsg(err) << "Cannot decode general settings."; return false; } if (! PasswordSettingsElement(data(Offset::passwordSettings())).decode(ctx, err)) { errMsg(err) << "Cannot decode password settings."; return false; } if (! APRSSettingsElement(data(Offset::aprsSettings())).decode(ctx, err)) { errMsg(err) << "Cannot decode APRS settings."; return false; } if (! EncryptionKeyBankElement(data(Offset::encryptionKeys())).decode(ctx, err)) { errMsg(err) << "Cannot decode encryption keys."; return false; } return true; } bool DM32UVCodeplug::linkElements(Context &ctx, const ErrorStack &err) { if (! linkChannels(ctx, err)) { errMsg(err) << "Cannot link channels."; return false; } if (! GroupListBankElement(data(Offset::groupListBank())).link(ctx, err)) { errMsg(err) << "Cannot link group lists."; return false; } if (! linkZones(ctx, err)) { errMsg(err) << "Cannot link zones."; return false; } if (! ScanListBankElement(data(Offset::scanListBank())).link(ctx, err)) { errMsg(err) << "Cannot link scan lists."; return false; } if (! RoamingZoneBankElement(data(Offset::roamingZoneBank())).link(ctx, err)) { errMsg(err) << "Cannot link roaming zones."; return false; } if (! APRSSettingsElement(data(Offset::aprsSettings())).link(ctx, err)) { errMsg(err) << "Cannot link APRS settings."; return false; } return true; } bool DM32UVCodeplug::encodeElements(Context &ctx, const ErrorStack &err) { if (! encodeChannels(ctx, err)) { errMsg(err) << "Cannot encode channels."; return false; } if (! encodeContacts(ctx, err)) { errMsg(err) << "Cannot encode contacts."; return false; } if (! image(0).isAllocated(Offset::groupListBank())) image(0).addElement(Offset::groupListBank(), GroupListBankElement::size()); if (! GroupListBankElement(data(Offset::groupListBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode group lists."; return false; } if (! image(0).isAllocated(Offset::radioIdBank())) image(0).addElement(Offset::radioIdBank(), RadioIdBankElement::size()); if (! RadioIdBankElement(data(Offset::radioIdBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode radio IDs."; return false; } if (! encodeZones(ctx, err)) { errMsg(err) << "Cannot encode zones."; return false; } if (! image(0).isAllocated(Offset::scanListBank())) image(0).addElement(Offset::scanListBank(), ScanListBankElement::size()); if (! ScanListBankElement(data(Offset::scanListBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode scan lists."; return false; } if (! image(0).isAllocated(Offset::roamingChannelBank())) image(0).addElement(Offset::roamingChannelBank(), RoamingChannelBankElement::size()); if (! RoamingChannelBankElement(data(Offset::roamingChannelBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode roaming channels."; return false; } if (! image(0).isAllocated(Offset::roamingZoneBank())) image(0).addElement(Offset::roamingZoneBank(), RoamingZoneBankElement::size()); if (! RoamingZoneBankElement(data(Offset::roamingZoneBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode roaming zones."; return false; } if (! image(0).isAllocated(Offset::generalSettings())) image(0).addElement(Offset::generalSettings(), Limit::blockSize()); if (! GeneralSettingsElement(data(Offset::generalSettings())).encode(ctx, err)) { errMsg(err) << "Cannot encode settings."; return false; } if (! APRSSettingsElement(data(Offset::aprsSettings())).encode(ctx, err)) { errMsg(err) << "Cannot encode APRS settings."; return false; } if (! PasswordSettingsElement(data(Offset::passwordSettings())).encode(ctx, err)) { errMsg(err) << "Cannot encode password settings."; return false; } if (! image(0).isAllocated(Offset::extendedSettings())) image(0).addElement(Offset::extendedSettings(), Limit::blockSize()); if (! EncryptionKeyBankElement(data(Offset::encryptionKeys())).encode(ctx, err)) { errMsg(err) << "Cannot encode encryption keys."; return false; } return true; } bool DM32UVCodeplug::decodeChannels(Context &ctx, const ErrorStack &err) { ChannelBankElement bank(data(Offset::channelBanks())); for (unsigned int i=0; ichannelList()->add(ch); ctx.add(ch, i); } // Link channel extensions for (unsigned int i=0; i(); i++) { unsigned int blockNumber = i / ChannelExtensionBankElement::Limit::count(); unsigned int indexInBlock = i % ChannelExtensionBankElement::Limit::count(); uint32_t addr = Offset::channelExtensionBanks() + blockNumber * ChannelExtensionBankElement::Offset::betweenBanks() + indexInBlock * ChannelExtensionElement::size(); if (! ChannelExtensionElement(data(addr)).decode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot decode channel extension at index " << i << "."; return false; } } return true; } bool DM32UVCodeplug::linkChannels(Context &ctx, const ErrorStack &err) { ChannelBankElement bank(data(Offset::channelBanks())); for (unsigned int i=0; i(i); // link channel if (! ChannelElement(data(addr)).link(ch, ctx, err)) { errMsg(err) << "Cannot link channel at index " << i << "."; return false; } } // Link channel extensions for (unsigned int i=0; i(); i++) { unsigned int blockNumber = i / ChannelExtensionBankElement::Limit::count(); unsigned int indexInBlock = i % ChannelExtensionBankElement::Limit::count(); uint32_t addr = Offset::channelExtensionBanks() + blockNumber * ChannelExtensionBankElement::Offset::betweenBanks() + indexInBlock * ChannelExtensionElement::size(); if (! ChannelExtensionElement(data(addr)).link(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot link channel extension at index " << i << "."; return false; } } return true; } bool DM32UVCodeplug::encodeChannels(Context &ctx, const ErrorStack &err) { // Allocate blocks auto numBlocks = Limit::channelBanks().limit( ChannelBankElement::bankCount(ctx.count())); for (unsigned int b=0; b(), ChannelBankElement::Limit::channels())); for (unsigned int i=0; i(i), ctx, err)) { errMsg(err) << "Cannot encode channel at index " << i << "."; return false; } } // Allocate blocks for extensions auto numExtBlocks = (ctx.count()>ChannelExtensionBankElement::Limit::count()) ? 2u : 1u; for (unsigned int i=0; i(); i++) { unsigned int blockNumber = i / ChannelExtensionBankElement::Limit::count(); unsigned int indexInBlock = i % ChannelExtensionBankElement::Limit::count(); uint32_t addr = Offset::channelExtensionBanks() + blockNumber * ChannelExtensionBankElement::Offset::betweenBanks() + indexInBlock * ChannelExtensionElement::size(); if (! ChannelExtensionElement(data(addr)).encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode channel extension at index " << i << "."; return false; } } return true; } bool DM32UVCodeplug::decodeContacts(Context &ctx, const ErrorStack &err) { ContactIndexElement index(data(Offset::contactIndex())); for (unsigned int i=0,c=0; (iname() << "' (" << contact->number() << ") at index " << i << "."; ctx.add(contact, c++); ctx.config()->contacts()->add(contact); } return true; } bool DM32UVCodeplug::encodeContacts(Context &ctx, const ErrorStack &err) { // Allocate index if (! isAllocated(Offset::contactIndex())) image(0).addElement(Offset::contactIndex(), ContactIndexElement::size()); // Allocate blocks auto numBlocks = Limit::contactBanks().limit( ctx.count()/ContactBankElement::Limit::contactsPerBlock() + ((0 != ctx.count() % ContactBankElement::Limit::contactsPerBlock()) ? 1 : 0)); for (unsigned int b=0; b(); i++) { unsigned int blockIndex = i / ContactBankElement::Limit::contactsPerBlock(), indexInBlock = i % ContactBankElement::Limit::contactsPerBlock(); uint32_t addr = Offset::contactBanks() + blockIndex * ContactBankElement::Offset::betweenBlocks() + indexInBlock * ContactElement::size(); if (! ContactElement(data(addr)).encode(ctx.get(i), err)) { errMsg(err) << "Cannot encode contact '" << ctx.get(i)->name() << "' at index " << i << "."; return false; } } return true; } bool DM32UVCodeplug::decodeZones(Context &ctx, const ErrorStack &err) { ZoneBankElement bank(data(Offset::zoneBanks())); for (unsigned int i=0; izones()->add(zone); ctx.add(zone, i); } return true; } bool DM32UVCodeplug::linkZones(Context &ctx, const ErrorStack &err) { ZoneBankElement bank(data(Offset::zoneBanks())); for (unsigned int i=0; i(i); // link zone if (! ZoneElement(data(addr)).link(zone, ctx, err)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } } return true; } bool DM32UVCodeplug::encodeZones(Context &ctx, const ErrorStack &err) { // Allocate blocks auto numBlocks = Limit::zoneBanks().limit( ctx.count()/ZoneBankElement::Limit::zonesPerBlock() + ((0 != ctx.count() % ZoneBankElement::Limit::zonesPerBlock()) ? 1 : 0)); for (unsigned int b=0; b())); for (unsigned int i=0; i(i), ctx, err)) { errMsg(err) << "Cannot encode zone '" << ctx.get(i)->name() << "' at index " << i << "."; return false; } } return true; } ================================================ FILE: lib/dm32uv_codeplug.hh ================================================ #ifndef DM32UV_CODEPLUG_HH #define DM32UV_CODEPLUG_HH #include #include #include "channel.hh" #include "codeplug.hh" #include "contact.hh" #include "frequency.hh" #include "ranges.hh" #include "roamingchannel.hh" #include "bootsettings.hh" #include "smsextension.hh" #include "gnsssettings.hh" #include "dmrsettings.hh" // forward declaration class Zone; class SMSTemplate; /** Implementation of the binary codeplug for the Baofeng DM32UV. * @ingroup dm32uv */ class DM32UVCodeplug : public Codeplug { Q_OBJECT public: /** Implements encoding channel for the binary codeplug. */ class ChannelElement: public Element { public: /** Possible channel types. */ enum class ChannelType { FM = 0, DMR = 1, FMFixed = 2, DMRFixed = 3 }; /** Possible power settings. */ enum class Power { Low = 0, Medium = 1, High = 2 }; /** Possible admit criteria. */ enum class Admit { Always = 0, ChannelFree = 1, ToneOrCCMatch = 2, ToneMismatch = 3 }; protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ChannelElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0030; } void clear() override; /** Returns the channel name. */ virtual QString name() const; /** Sets the channel name. */ virtual void setName(const QString &name); /** Returns the RX frequency. */ virtual Frequency rxFrequency() const; /** Set RX frequency. */ virtual void setRXFrequency(const Frequency &freq); /** Returns @c true, if a valid TX frequency is stored. */ virtual bool validTXFrequency() const; /** Returns the TX frequency. */ virtual Frequency txFrequency() const; /** Set TX frequency. */ virtual void setTXFrequency(const Frequency &freq); /** Invalidates the TX frequency. */ virtual void clearTXFrequency(); /** Returns the channel type. */ virtual ChannelType channelType() const; /** Sets the channel type. */ virtual void setChannelType(ChannelType type); /** Returns the channel power setting. */ virtual Channel::Power power() const; /** Sets the channel power. */ virtual void setPower(Channel::Power power); /** Returns @c true, if the lone-worker feature is enabled. */ virtual bool loneWorkerEnabled() const; /** Enables/disables lone worker feature. */ virtual void enableLoneWorker(bool enable); /** Returns the bandwidth for FM channels. */ virtual FMChannel::Bandwidth bandwidth() const; /** Sets the FM channel bandwidth. */ virtual void setBandwidth(FMChannel::Bandwidth bw); /** Returns @c true, if the scan list index is set. */ virtual bool validScanListIndex() const; /** Returns the 0-based scan list index. */ virtual unsigned int scanListIndex() const; /** Sets the scan list index. */ virtual void setScanListIndex(unsigned int idx); /** Invalidates the scan list index. */ virtual void clearScanListIndex(); /** Returns @c true if talkaround is enabled on this channel. */ virtual bool talkaroundEnabled() const; /** Enables/disables talkaround feature on this channel. */ virtual void enableTalkaround(bool enable); /** Returns the admit criterion. */ virtual Admit admitCriterion() const; /** Sets the admit criterion. */ virtual void setAdmitCriterion(Admit admit); /** Returns @c true if DMR APRS reception is enabled. */ virtual bool rxDMRAPRSEnabled() const; /** Enables/disables DMR APRS reception. */ virtual void enableRXDMRAPRS(bool enable); /** Retruns @c true, if the emergency notification is enabled. */ virtual bool emergencyNotificationEnabled() const; /** Enables/disables emergency notification. */ virtual void enableEmergencyNotification(bool enable); /** Retruns @c true, if the emergency ACK is enabled. */ virtual bool emergencyACKEnabled() const; /** Enables/disables emergency notification. */ virtual void enableEmergencyACK(bool enable); /** Returns @c true if the emergency system index is set. */ virtual bool validEmergencySystemIndex() const; /** Returns the emergency system index. */ virtual unsigned int emergencySystemIndex() const; /** Sets the emergency system index. */ virtual void setEmergencySystemIndex(unsigned int idx); /** Invalidates the emergency system index. */ virtual void clearEmergencySystemIndex(); /** Returns FM and DMR squelch level. */ virtual Level squelchLevel() const; /** Sets the FM and DMR squelch level. */ virtual void setSquelchLevel(Level level); /** Returns @c true if the channel is RX only. */ virtual bool rxOnlyEnabled() const; /** Enables RX only for the channel. */ virtual void enableRXOnly(bool enable); /** Returns @c true if DMR APRS is enabled. */ virtual bool dmrAPRSEnabled() const; /** Enables DMR APRS for the channel. */ virtual void enableDMRAPRS(bool enable); /** Return @c true if private calls are confirmed. */ virtual bool privateCallACKEnabled() const; /** Enables confirmation of private calls. */ virtual void enablePrivateCallACK(bool enable); /** Return @c true if data is confirmed. */ virtual bool dataACKEnabled() const; /** Enables confirmation of data. */ virtual void enableDataACK(bool enable); /** Return @c true if DCDM (dual capacity direct mode) is enabled. */ virtual bool dcdmEnabled() const; /** Enables DCDM (dual capacity direct mode).. */ virtual void enableDCDM(bool enable); /** Returns the timeslot of the channel. */ virtual DMRChannel::TimeSlot timeslot() const; /** Sets the timeslot of the channel. */ virtual void setTimeslot(DMRChannel::TimeSlot ts); /** Returns the color code of the channel. */ virtual unsigned int colorCode() const; /** Sets the color code of the channel. */ virtual void setColorCode(unsigned int cc); /** Returns @c true if encryption is enabled. */ virtual bool encryptionEnabled() const; /* Enables encryption. */ virtual void enableEncryption(bool enable); /** Returns @c true if group list index is valid. */ virtual bool validGroupListIndex() const; /** Get group list index. */ virtual unsigned int groupListIndex() const; /** Set group list index. */ virtual void setGroupListIndex(unsigned int idx); /** Invalidates group list index. */ virtual void clearGroupListIndex(); /** Returns DMR APRS report channel index. */ virtual unsigned int dmrAPRSChannelIndex() const; /** Sets the DMR APRS report channel index. */ virtual void setDMRAPRSChannelIndex(unsigned int idx); /** Returns the RX tone. */ virtual SelectiveCall rxTone() const; /** Sets the RX tone. */ virtual void setRXTone(const SelectiveCall &tone); /** Returns the TX tone. */ virtual SelectiveCall txTone() const; /** Sets the TX tone. */ virtual void setTXTone(const SelectiveCall &tone); /** Returns @c true, if the VOX is enabled. */ virtual bool voxEnabled() const; /** Enables the VOX. */ virtual void enableVOX(bool enable); /** Returns the DMR radio id index. */ virtual unsigned int dmrIdIndex() const; /** Sets the dmr ID index. */ virtual void setDMRIdIndex(unsigned int id); /** Constructs a channel object. */ virtual Channel *decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links the channel object. */ virtual bool link(Channel *channel, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes a channel. */ virtual bool encode(const Channel *channel, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } /** Maximum squelch level. */ static constexpr Range squelchLevel() { return {0, 15}; } }; protected: /** Helper function to decode selective call. */ static SelectiveCall decodeSelectiveCall(uint16_t code); /** Helper function to encode selective call. */ static uint16_t encodeSelectiveCall(const SelectiveCall &tone); protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int rxFrequency() { return 0x0010; } static constexpr unsigned int txFrequency() { return 0x0014; } static constexpr Bit channelType() { return {0x0018, 4}; } static constexpr Bit rxOnly() { return {0x0018, 3}; } static constexpr Bit power() { return {0x0018, 1}; } static constexpr Bit loneWorker() { return {0x0018, 0}; } static constexpr Bit bandwidth() { return {0x0019, 7}; } static constexpr Bit scanListIndex() { return {0x0019, 2}; } static constexpr Bit preventTalkaround() { return {0x001a, 7}; } static constexpr Bit admitCriterion() { return {0x001a, 4}; } static constexpr Bit rxDMRAPRS() { return {0x001a, 2}; } static constexpr Bit emergencyNotification() { return {0x001b, 7}; } static constexpr Bit emergencyACK() { return {0x001b, 6}; } static constexpr Bit emergencySystemIndex() { return {0x001b, 0}; } static constexpr Bit squelchLevel() { return {0x001c, 4}; } static constexpr Bit dmrAPRS() { return {0x001c, 2}; } static constexpr Bit privateCallACK() { return {0x001d, 7}; } static constexpr Bit dataACK() { return {0x001d, 6}; } static constexpr Bit dcdm() { return {0x001d, 5}; } static constexpr Bit timeslot() { return {0x001d, 4}; } static constexpr Bit colorcode() { return {0x001d, 0}; } static constexpr Bit encryptionEnable() { return {0x001f, 6}; } static constexpr Bit groupListIndex() { return {0x001f, 0}; } static constexpr Bit dmrAPRSChannelIndex() { return {0x0020, 0}; } static constexpr unsigned int rxTone() { return 0x0021; } static constexpr unsigned int txTone() { return 0x0023; } static constexpr Bit vox() { return {0x0025, 4}; } static constexpr Bit showPTTId() { return {0x0026, 7}; } static constexpr Bit optSigEnable() { return {0x0026, 4}; } static constexpr Bit optSigType() { return {0x0026, 0}; } static constexpr Bit pttIdEnable() { return {0x0029, 2}; } static constexpr unsigned int dmrIdIndex() { return 0x002b; } /// @endcond }; }; /** Channel bank element. */ class ChannelBankElement: public Element { public: /** Constructor. */ explicit ChannelBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0010; } /** Returns the channel count. */ virtual unsigned int channelCount() const; /** Sets the channel count. */ virtual void setChannelCount(unsigned int n); public: /** Returns the block index for the given channel index. */ static unsigned int channelBank(unsigned int index); /** Returns the channel index within the block for the given channel index. */ static unsigned int indexInBank(unsigned int index); /** Computes the number of channel banks required to encode the given number of channels. */ static unsigned int bankCount(unsigned int channelCount); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum number of channels. */ static constexpr unsigned int channels() { return 4000; } /** Maximum number of channels in block 0. */ static constexpr unsigned int channelsInBlock0() { return 84; } /** Maximum number of channels per block. */ static constexpr unsigned int channelsPerBlock() { return 85; } }; /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int channelCount() { return 0x0000; } static constexpr unsigned int channelBlock0() { return 0x0010; } static constexpr unsigned int betweenChannelBlocks() { return DM32UVCodeplug::Limit::blockSize(); } /// @endcond }; }; /** Implements an extended settings field for each channel. * This is obviously a bug fix by a very unexperienced FW engineer. They hot-fixed the missing * transmit contact via these extended settings although there are enough unused bits available * within the channel element. Moveover, the encoding is a wild mix of little and big endian. */ class ChannelExtensionElement: public Element { public: /** Constructor. */ explicit ChannelExtensionElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0002; } /** Resets the element. */ void clear() override; /** Returns @c true if a TX contact index is set. */ virtual bool hasContactIndex() const; /** Returns the TX contact index. */ virtual unsigned int contactIndex() const; /** Sets the contact index. */ virtual void setContactIndex(unsigned int index); /** Resets the contact index. */ virtual void clearContactIndex(); /** Encodes the extended settings from the given channel. */ virtual bool encode(const Channel *ch, Context &ctx, const ErrorStack &err); /** Updates the given channel. */ virtual bool decode(Channel *ch, Context &ctx, const ErrorStack &err) const; /** Links the given channel. */ virtual bool link(Channel *ch, Context &ctx, const ErrorStack &err) const; protected: /** Some internal offsets within the element. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr Bit indexMSN() { return { 0x0000, 4}; } static constexpr unsigned int indexLSB() { return 0x0001; } /// @endcond }; }; /** Encodes a bank of channel extension settings. */ class ChannelExtensionBankElement: public Element { public: /** Some limits. */ struct Limit : Element::Limit { /** The number of elements within each bank. */ static constexpr unsigned int count() { return 2047; } }; /** Some offsets. */ struct Offset: Element::Offset { /** Offset betwenn banks. */ static constexpr unsigned int betweenBanks(){ return DM32UVCodeplug::Limit::blockSize(); } }; }; /** Implements a single DMR contact. */ class ContactElement: public Element { public: /** Possible contact types. */ enum class Type { Private = 3, Group = 4, All = 5 }; protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactElement(uint8_t *ptr); /** Size of the element. */ static constexpr unsigned int size() { return 0x0018; } /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString &name); /** Returns the call type. */ virtual DMRContact::Type callType() const; /** Sets the call type. */ virtual void setCallType(DMRContact::Type type); /** Returns the DMR Id. */ virtual unsigned int dmrId() const; /** Sets the DMR Id. */ virtual void setDMRId(unsigned int id); /** Constructs a contact object. */ virtual DMRContact *decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes the contact. */ virtual bool encode(const DMRContact *contact, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0002; } static constexpr unsigned int dmrId() { return 0x0013; } static constexpr unsigned int callType() { return 0x0016; } // @endcond }; }; /** Implements the contact bank. */ class ContactBankElement: public Element { public: /** Some limts for the contact bank. */ struct Limit: Element::Limit { /** Maximum number of contacts. */ static constexpr unsigned int contacts() { return 800; } static constexpr unsigned int contactsPerBlock() { return 170; } }; /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int betweenBlocks() { return 0x1000; }; /// @endcond }; }; /** Contact index block. */ class ContactIndexElement: public Element { protected: /** Hidden constructor. */ ContactIndexElement(uint8_t *ptr, size_t size); public: /** Contact bitmap element. */ class ContactBitmapElement: public InvertedBitmapElement { public: /** Constructor. */ ContactBitmapElement(uint8_t *ptr); /** Returns the size of the bitmap. */ static constexpr unsigned int size() { return 0x0064; } }; /** Contact index entry. */ class EntryElement: public Element { public: typedef ContactElement::Type Type; protected: /** Hidden constructor. */ EntryElement(uint8_t *ptr, size_t size); public: /** Constructor. */ EntryElement(uint8_t *ptr); void clear() override; /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0002; } /** Returns @c true if the entry is valid. */ bool isValid() const override; /** Returns the call type. */ virtual DMRContact::Type callType() const; /** Sets the call type. */ virtual void setCallType(DMRContact::Type type); /** Returns the contact index. */ virtual unsigned int index() const; /** Sets the index. */ virtual void setIndex(unsigned int idx); }; public: /** Constructor. */ ContactIndexElement(uint8_t *ptr); /** Size of this element. */ static constexpr unsigned int size() { return 0x1000; } /** Returns the number of contacts encoded. */ virtual unsigned int contactCount() const; /** Sets the number of contacts. */ virtual void setContactCount(unsigned int n); /** Returns the number of group call contacts encoded. */ virtual unsigned int groupCallCount() const; /** Sets the number of group call contacts. */ virtual void setGroupCallCount(unsigned int n); /** Returns the number of private call contacts encoded. */ virtual unsigned int privateCallCount() const; /** Sets the number of private call contacts. */ virtual void setPrivateCallCount(unsigned int n); /** Returns the contact bitmap. */ virtual ContactBitmapElement bitmap() const; /** Returns the n-th index entry. */ virtual EntryElement indexEntry(unsigned n); /** Returns the n-th sorted index entry. */ virtual EntryElement sortedIndexEntry(unsigned n); /** Encodes the contact index. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static unsigned int contactCount() { return 0x0000; } static unsigned int groupCount() { return 0x0002; } static unsigned int privateCount() { return 0x0004; } static unsigned int bitmap() { return 0x0010; } static unsigned int index() { return 0x0100; } static unsigned int sorted() { return 0x0740; } /// @endcond }; }; /** Implements the group list bank element. */ class GroupListElement: public Element { protected: /** Hidden constructor. */ GroupListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x006d; } /** Returns the name of the list. */ virtual QString name() const; /** Sets the name of the list. */ virtual void setName(const QString &name); /** Retruns @c true if the n-th ID is set. */ virtual bool validId(unsigned int n); /** Returns the n-th ID. */ virtual unsigned int id(unsigned int n); /** Sets the n-th ID. */ virtual void setId(unsigned int n, unsigned int id); /** Clears the n-th ID. */ virtual void clearId(unsigned int n); /** Decodes this group list. */ virtual RXGroupList *decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links this group list. */ virtual bool link(RXGroupList *gl, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes this group list. */ virtual bool encode(const RXGroupList *gl, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 11; } /** Maximum number of indices. */ static constexpr unsigned int contacts() { return 32; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int ids() { return 0x000b; } static constexpr unsigned int betweenIds() { return 0x0003; } /// @endcond }; }; /** Implements the group list bank element. */ class GroupListBankElement: public Element { public: /** Bitmap for all group lists. */ class GroupListBitmapElement : public BitmapElement { public: /** Constructor. */ GroupListBitmapElement(uint8_t *ptr); }; protected: /** Hidden constructor. */ GroupListBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x1000; } /** Retunrs the bitmap. */ virtual GroupListBitmapElement bitmap() const; /** Returns the n-th group list element. */ virtual GroupListElement groupList(unsigned int n) const; /** Decodes all group lists. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links all group lists. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes all group lists. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum number of group lists. */ static constexpr unsigned int groupLists() { return 32; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bitmap() { return 0x0000; } static constexpr unsigned int groupLists() { return 0x0011; } /// @endcond }; }; /** Implements a DMR radio ID. */ class RadioIdElement: public Element { public: /** Contsturctor. */ RadioIdElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0010; } /** Returns the DMR Id. */ virtual unsigned int id() const; /** Sets the DMR id. */ virtual void setId(unsigned int id); /** Returns the name of the id. */ virtual QString name() const; /** Sets the name of the id. */ virtual void setName(const QString &name); /** Decodes the radio ID. */ virtual DMRRadioID *decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given ID. */ virtual bool encode(const DMRRadioID *id, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 12; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int id() { return 0x0000; } static constexpr unsigned int name() { return 0x0003; } /// @endcond }; }; /** Implements the DMR radio ID bank. */ class RadioIdBankElement: public Element { public: /** Contsturctor. */ RadioIdBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x1000; } /** Returns the number of IDs. */ virtual unsigned int idCount() const; /** Sets the number of encoded IDs. */ virtual void setIdCount(unsigned int n); /** Returns the n-th DMR Id. */ virtual RadioIdElement id(unsigned int n) const; /** Decodes all radio IDs. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes add radio IDs. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum number of IDs. */ static constexpr unsigned int ids() { return 250; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } static constexpr unsigned int ids() { return 0x0010; } static constexpr unsigned int betweenIds() { return RadioIdElement::size(); } /// @endcond }; }; /** Implementation of zone. */ class ZoneElement: public Element { public: /** Constructor. */ ZoneElement(uint8_t *ptr); /** Size of the element. */ static constexpr unsigned int size() { return 0x0091; } /** Returns the name. */ virtual QString name() const; /** Sets the name of the zone. */ virtual void setName(const QString &name); /** Retunrs the number of channels. */ virtual unsigned int channelCount() const; /** Sets the number of channels. */ virtual void setChannelCount(unsigned int n); /** Returns @c true, if the channel index is set. */ virtual bool channelIndexValid(unsigned int n) const; /** Returns the n-th channel index. */ virtual unsigned int channelIndex(unsigned int n) const; /** Sets the n-th channel index. */ virtual void setChannelIndex(unsigned int n, unsigned int idx); /** Clears the n-th channel index. */ virtual void clearChannelIndex(unsigned int n); /** Decode zone. */ virtual Zone *decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link zone. */ virtual bool link(Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes a zone. */ virtual bool encode(const Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } /** Maximum number of channels. */ static constexpr unsigned int channels() { return 64; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int channelCount() { return 0x0010; } static constexpr unsigned int channels() { return 0x0011; } /// @endcond }; }; class ZoneBankElement: public Element { public: /** Constructor. */ ZoneBankElement(uint8_t *ptr); /** Returns the size of a zone bank. */ static constexpr unsigned int size() { return 0x1000; } /** Returns the total number of zones. */ virtual unsigned int count() const; /** Sets the total number of zones. */ virtual void setCount(unsigned int n); /** Returns @c true if the channel index VFO A is set. */ virtual bool channelIndexAValid() const; /** Returns the channel index VFO A. */ virtual unsigned int channelIndexA() const; /** Sets the channel index VFO A. */ virtual void setChannelIndexA(unsigned int idx); /** Clears the channel index VFO A. */ virtual void clearChannelIndexA(); /** Returns @c true if the channel index VFO B is set. */ virtual bool channelIndexBValid() const; /** Returns the channel index VFO B. */ virtual unsigned int channelIndexB() const; /** Sets the channel index VFO B. */ virtual void setChannelIndexB(unsigned int idx); /** Clears the channel index VFO B. */ virtual void clearChannelIndexB(); /** Returns @c true if the zone index VFO A is set. */ virtual bool zoneIndexAValid() const; /** Returns the zone index VFO A. */ virtual unsigned int zoneIndexA() const; /** Sets the zone index VFO A. */ virtual void setZoneIndexA(unsigned int idx); /** Clears the zone index VFO A. */ virtual void clearZoneIndexA(); /** Returns @c true if the zone index VFO B is set. */ virtual bool zoneIndexBValid() const; /** Returns the zone index VFO B. */ virtual unsigned int zoneIndexB() const; /** Sets the zone index VFO B. */ virtual void setZoneIndexB(unsigned int idx); /** Clears the zone index VFO B. */ virtual void clearZoneIndexB(); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum number of zones. */ static constexpr unsigned int zones() { return 250; } /** Maximum number of zones per block. */ static constexpr unsigned int zonesPerBlock() { return 28; } }; /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } static constexpr unsigned int channelIndexA() { return 0x0001; } static constexpr unsigned int channelIndexB() { return 0x0003; } static constexpr unsigned int zoneIndexA() { return 0x0005; } static constexpr unsigned int zoneIndexB() { return 0x0007; } static constexpr unsigned int zones0() { return 0x0010; } static constexpr unsigned int betweenBlocks() { return 0x1000; } /// @endcond }; }; /** Implements a scan list. */ class ScanListElement: public Element { public: /** Possible transmit modes. */ enum class TransmitMode { CurrentChannel=0, ActiveChannel=1, RevertChannel=2 }; /** Possible CTCSS/DCS detection modes. */ enum class ToneDetectionMode { None = 0, NonPriority = 1, Priority = 2, All = 3 }; public: /** Constructor. */ ScanListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0039; } /** Returns the name of the scan list. */ virtual QString name() const; /** Sets the name of the scan list. */ virtual void setName(const QString &name); /** Returns the number of channels. */ virtual unsigned int channelCount() const; /** Sets the channel count. */ virtual void setChannelCount(unsigned int count); /** Returns the transmit mode. */ virtual TransmitMode transmitMode() const; /** Sets the transmit mode. */ virtual void setTransmitMode(TransmitMode mode); /** Returns the tone-detection mode. */ virtual ToneDetectionMode toneDetectionMode() const; /** Sets the tone-detection mode. */ virtual void setToneDetectionMode(ToneDetectionMode mode); /** Returns the scan hang time. */ virtual Interval hangTime() const; /** Sets the scan hang time. */ virtual void setHangTime(const Interval &dur); /** Returns @c true if the secondary priority channel index is set. */ virtual bool secondaryChannelIndexValid() const; /** Returns the secondary priority channel index. */ virtual unsigned int secondaryChannelIndex() const; /** Sets the secondary priority channel index. */ virtual void setSecondaryChannelIndex(unsigned int idx); /** Clears the secondary priority channel index. */ virtual void clearSecondaryChannelIndex(); /** Returns @c true if the primary priority channel index is set. */ virtual bool primaryChannelIndexValid() const; /** Returns the primary priority channel index. */ virtual unsigned int primaryChannelIndex() const; /** Sets the primary priority channel index. */ virtual void setPrimaryChannelIndex(unsigned int idx); /** Clears the primary priority channel index. */ virtual void clearPrimaryChannelIndex(); /** Returns @c true if the revert channel index is set. */ virtual bool revertChannelIndexValid() const; /** Returns the revert channel index. */ virtual unsigned int revertChannelIndex() const; /** Sets the revert channel index. */ virtual void setRevertChannelIndex(unsigned int idx); /** Clears the revert channel index. */ virtual void clearRevertChannelIndex(); /** Returns the priority sweep time. */ virtual Interval prioritySweepTime() const; /** Sets the priority sweep time. */ virtual void setPrioritySweepTime(const Interval &dur); /** Returns @c true, if the n-th channel index refers to the current channel. */ virtual bool isCurrentChannel(unsigned int n) const; /** Returns the n-th channel index. */ virtual unsigned int channelIndex(unsigned int n) const; /** Sets the n-th channel index. */ virtual void setChannelIndex(unsigned int n, unsigned int idx); /** Sets the n-th channel index to the current channel. */ virtual void setCurrentChannel(unsigned int n); /** Decodes the scan list. */ virtual ScanList *decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the given scan list. */ virtual bool link(ScanList *lst, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode the scan list. */ virtual bool encode(const ScanList *lst, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 11; } /** Maximum number of channels. */ static constexpr unsigned int channels() { return 15; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int channelCount() { return 0x000b; } static constexpr Bit transmitMode() { return {0x000c, 4}; } static constexpr Bit toneDetection() { return {0x000c, 0}; } static constexpr Bit hangTime() { return {0x000d, 0}; } static constexpr Bit secondaryChannel() { return {0x000e, 4}; } static constexpr Bit primaryChannel() { return {0x000e, 0}; } static constexpr unsigned int revertChannel() { return 0x000f; } static constexpr Bit prioritySweepTime() { return {0x0015, 2}; } static constexpr unsigned int channels() { return 0x0018; } /// @endcond }; }; /** Implements the scan list bank. */ class ScanListBankElement: public Element { public: /** Possible scan modes. */ enum class ScanMode { Time = 0 , Carrier = 1, Search = 2 }; public: /* Constructor from pointer to the element. */ ScanListBankElement(uint8_t *ptr); /** Size of the element. */ static constexpr unsigned int size() { return 0x1000; } /** Returns the number of scan lists. */ virtual unsigned int count() const; /** Sets the number of scan lists. */ virtual void setCount(unsigned int n); /** Returns the n-th scan list. */ virtual ScanListElement scanList(unsigned int n) const; /** Returns the scan mode. */ virtual ScanMode scanMode() const; /** Sets the scan mode. */ virtual void setScanMode(ScanMode mode); /** Returns the VHF scan range. */ virtual FrequencyRange vhfRange() const; /** Sets the VHF scan range. */ virtual void setVHFRange(const FrequencyRange &range); /** Returns the UHF scan range. */ virtual FrequencyRange uhfRange() const; /** Sets the UHF scan range. */ virtual void setUHFRange(const FrequencyRange &range); /** Decode all scan lists. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link all scan lists. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes all scan lists. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum number of scan lists. */ static constexpr unsigned int scanLists() { return 32; } }; /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } static constexpr unsigned int scanLists() { return 0x0001; } static constexpr unsigned int scanMode() { return 0x0e00; } static constexpr unsigned int vhfLower() { return 0x0e01; } static constexpr unsigned int vhfUpper() { return 0x0e03; } static constexpr unsigned int uhfLower() { return 0x0e05; } static constexpr unsigned int uhfUpper() { return 0x0e07; } /// @endcond }; }; /** Implements a roaming channel. */ class RoamingChannelElement: public Element { public: /** Possible time-slot settings. */ enum class TimeSlot { TS1 = 0, TS2 = 1 }; public: /** Constructor from pointer to element. */ RoamingChannelElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x001a; } /** Returns the name of the channel. */ virtual QString name() const; /** Sets the name of the channel. */ virtual void setName(const QString &name); /** Returns the RX frequency. */ virtual Frequency rxFrequency() const; /** Sets the RX frequency. */ virtual void setRXFrequency(const Frequency &f); /** Returns the TX frequency. */ virtual Frequency txFrequency() const; /** Sets the TX frequency. */ virtual void setTXFrequency(const Frequency &f); /** Return the color code. */ virtual unsigned int colorCode() const; /** Sets the color code. */ virtual void setColorCode(unsigned int cc); /** Returns the time slot. */ virtual DMRChannel::TimeSlot timeSlot() const; /** Sets the time slots. */ virtual void setTimeSlot(DMRChannel::TimeSlot ts); /** Decodes the roaming channel. * @returns nullptr on error. */ virtual RoamingChannel *decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes the roaming channel. */ virtual bool encode(const RoamingChannel *ch, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int rxFrequency() { return 0x0010; } static constexpr unsigned int txFrequency() { return 0x0014; } static constexpr unsigned int colorCode() { return 0x0018; } static constexpr unsigned int timeSlot() { return 0x0019; } /// @endcond }; }; /** The bank of all roaming channels. */ class RoamingChannelBankElement: public Element { public: /** Constructor from pointer to element. */ RoamingChannelBankElement(uint8_t *ptr); /** Returns the size of the elment. */ static constexpr unsigned int size() { return 0x1000; } /** Returns the number of channels. */ virtual unsigned int count() const; /** Sets the number of channels. */ virtual void setCount(unsigned int n); /** Returns the n-th channel. */ virtual RoamingChannelElement channel(unsigned int n); /** Decides all romaming channels. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes all roaming channels. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum number of channels. */ static constexpr unsigned int channels() { return 150; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int channels() { return 0x0000; } static constexpr unsigned int count() { return 0x0ff0; } static constexpr unsigned int betweenChannels() { return RoamingChannelElement::size(); } /// @endcond }; }; /** En/Decodes a roaming zone. */ class RoamingZoneElement: public Element { public: /** Constructor from pointer to element. */ RoamingZoneElement(uint8_t *ptr); /** Returns the size of the elment. */ static constexpr unsigned int size() { return 0x0021; } /** Returns the name of the zone. */ virtual QString name() const; /** Sets the name. */ virtual void setName(const QString &name); /** Returns the channel count. */ virtual unsigned int count() const; /** Sets the number of channels. */ virtual void setCount(unsigned int n); /** Returns @c true if the n-th channel index is set. */ virtual bool channelIndexValid(unsigned int n); /** Returns the n-th channel index. */ virtual unsigned int channelIndex(unsigned int n); /** Sets the n-th channel index. */ virtual void setChannelIndex(unsigned int n, unsigned int idx); /** Clears the n-th channel index. */ virtual void clearChannelIndex(unsigned int n); /** Decodes the roaming zone. */ virtual RoamingZone *decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the given roaming zone. */ virtual bool link(RoamingZone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given roaming zone. */ virtual bool encode(const RoamingZone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } /** Maximum number of channels per zone. */ static constexpr unsigned int channels() { return 16; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int channelCount() { return 0x0010; } static constexpr unsigned int channels() { return 0x0011; } static constexpr unsigned int betweenChannels() { return 0x0001; } /// @endcond }; }; /** En/decodes the roaming zone bank. */ class RoamingZoneBankElement : public Element { public: /** Constructor from pointer to element. */ RoamingZoneBankElement(uint8_t *ptr); /** Returns the size of the elment. */ static constexpr unsigned int size() { return 0x1000; } /** Returns the zone count. */ virtual unsigned int count() const; /** Sets the number of zones. */ virtual void setCount(unsigned int n); /** Returns @c true if auto roaming is enabled. */ virtual bool autoRoamEnabled() const; /** Enables auto roaming. */ virtual void enableAutoRoam(bool enable); /** Returns the roaming delay. */ virtual Interval roamingDelay() const; /** Sets the roaming delay. */ virtual void setRoamingDelay(const Interval &delay); /** Returns @c true, if a default roaming zone is set. */ virtual bool defaultRoamingZoneIndexValid() const; /** Returns the default roaming zone index. */ virtual unsigned int defaultRoamingZoneIndex() const; /** Sets the default roaming zone index. */ virtual void setDefaultRoamingZoneIndex(unsigned int idx); /** Clears the default roaming zone index. */ virtual void clearDefaultRoamingZoneIndex(); /** Returns the n-th roaming zone. */ virtual RoamingZoneElement zone(unsigned int n); /** Decodes all roaming zones. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links all roaming zones. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode all roaming zones. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum number of zones. */ static constexpr unsigned int zones() { return 64; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } static constexpr unsigned int autoRoam() { return 0x0001; } static constexpr unsigned int roamingDelay() { return 0x0002; } static constexpr unsigned int defaultRoamingZone() { return 0x0003; } static constexpr unsigned int zones() { return 0x0010; } static constexpr unsigned int betweenZones() { return RoamingZoneElement::size(); } /// @endcond }; }; /** A sinlge preset message. */ class SMSTemplateElement: public Element { public: /** Constructor from pointer to element. */ SMSTemplateElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0081; } /** Returns the message text. */ virtual QString message() const; /** Sets the message text. */ virtual void setMessage(const QString &msg); /** Decodes a single message. */ SMSTemplate *decode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum message length. */ static constexpr unsigned int messageLength() { return 126; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int length() { return 0x0000; } static constexpr unsigned int message() { return 0x0001; } /// @endcond }; }; /** Preset message (SMS) bank. */ class SMSTemplateBankElement: public Element { public: /** Constructor from pointer. */ SMSTemplateBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x1000; } /** Returns the number of messages. */ virtual unsigned int count() const; /** Sets the number of messages. */ virtual void setCount(unsigned int n); /** Returns the n-th message. */ virtual SMSTemplateElement message(unsigned int n) const; /** Decodes all preset SMS messages. */ virtual bool decode(Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum number of messages. */ static constexpr unsigned int messages() { return 20; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } static constexpr unsigned int messages() { return 0x0010; } static constexpr unsigned int betweenMessages() { return SMSTemplateElement::size(); } /// @endcond }; }; /** Common settings block. */ class GeneralSettingsElement: public Element { public: /** Possible boot display settings. */ enum class BootDisplay { Image=0, Message=1, Voltage=2 }; /** Possible auto-power-off delays. */ enum class AutoPowerOffDelay { Off = 0, T30Min=1, T60Min=2, T2h=3, T4h=4, T8h=5 }; /** Possible FM roger tones. */ enum class FMRogerTone { Off=0, Beep=1, BDC=2 }; /** Possible settings for the backlight duration. */ enum class BacklightDuration { Infinity = 0, T5s=1, T10s=2, T15s=3, T20s=4, T25s=5, T30s=6, T1min=7, T2min=8, T3min=9, T4min=10, T5min=11 }; /** Possible date formats. */ enum class DateFormat { YYYYMMDD = 0, DDMMYYYY = 1 }; /** Implements the translation between color names and code. * For now, this struct is futile but becomes necessary, once there is a common color name * scheme. */ struct Color { enum class Code { White = 0, Black = 1, Orange = 2, Red = 3, Yellow = 4, Green = 5, Cyan = 6, Blue = 7 }; static unsigned int encode(Code name); static Code decode(unsigned int code); }; /** Possible position formats. */ enum class PositionFormat { DD = 0, ///< Decimal degree DMS = 1 ///< Degree, minute, second }; /** GNSS modes. */ enum class GNSSMode { GPS = 0, Beidou = 1, Both = 2 }; /** Possible recording modes. */ enum class RecordMode { RX = 0, TX = 1, Both = 2 }; /** Possible SMS formats. */ enum class SMSFormat { Hytera = 0, Motorola = 1, DMR = 2 }; /** Talker alias formats. */ enum class TalkerAliasFormat { ISO8 = 0, UnicodeU16 = 1 }; /** Talker alias sources. */ enum class TalkerAliasSource { CallsignDB = 0, OverTheAir = 1 }; /** Possible dual-standby modes. */ enum class DualStandbyMode { SingleVFO = 0, DoubleStandby = 1, SingleStandby = 2 }; /** VFO selection. */ enum class VFO { A = 0, B = 1 }; /** VFO display modes. */ enum class VFODisplayMode { Frequency = 0, ChannelName = 1 }; /** VFO Modes. */ enum class VFOMode { Channel = 0, VFO = 1 }; /** Helper encoding/decoding key functions. */ struct KeyFunction { enum class Function { None=0, PowerSelect=1, Volt=2, Talkaround=3, DMREncryption=4, VOX=6, ChannelMode=7, Alarm=8, OneTouch1=9, OneTouch2=10, OneTouch3=11,OneTouch4=12, OneTouch5=13, SMS=14, Contacts=15, ZoneUp=16, ZoneDown=17, Scan=18, ToggleRecord=19, PreviousRecord=20, NextRecord=21, FMBCRadio=22, FMBCScan=23, GPSInformation=24, Monitor=25, ToggleMainChannel=26, LoneWorker=27, KeypadLock=28, Mute=29, TBST=30, APRSTX=31, ChannelType=32, DisplayMode=33, CTCSSDSCScan=34, CTCSSDSCSettings=25, SilentTone=36, Roaming=37, SubPTT=38, OneKeyScanFrequency=40, Flashlight=41 }; static unsigned int encode(Function func); static Function decode(unsigned int code); }; /** Possible power-save mode. */ enum class PowerSaveMode { Off=0, Percent50 = 1, Percent66 = 2, Percent75=3 }; /** Possible TBST frequencies. */ enum class TBSTFrequency { Hz1000=0, Hz1450=1, Hz1750=2, Hz2100=3 }; /** Possible squelch tail-eliminations. */ enum class STEMode { Off = 0, Deg120 = 1, Deg180 = 2, Hz55 = 3 }; public: /** Constructor from pointer to element. */ GeneralSettingsElement(uint8_t *ptr); /** Returns the size og the element. */ static constexpr unsigned int size() { return 0x0100; } /** Returns the boot display setting. */ virtual BootSettings::BootDisplay bootDisplay() const; /** Sets the boot display. */ virtual void setBootDisplay(BootSettings::BootDisplay dis); /** Returns the first boot message line. */ virtual QString bootMessage1() const; /** Sets the first boot message line. */ virtual void setBootMessage1(const QString &msg); /** Returns the second boot message line. */ virtual QString bootMessage2() const; /** Sets the second boot message line. */ virtual void setBootMessage2(const QString &msg); /** Returns @c true if MCU reset is enabled. */ virtual bool mcuResetEnabled() const; /** Enables MCU reset. */ virtual void enableMCUReset(bool enable); /** Returns the auto-power-off delay. */ virtual Interval autoPowerOffDelay() const; /** Sets the auto-power-off delay. */ virtual void setAutoPowerOffDelay(const Interval &delay); /** Returns @c true if all tones are disabled. */ virtual bool radioSilentEnabled() const; /** Disables all tones. */ virtual void enableRadioSilent(bool enable); /** Returns @c true if key tones are enabled. */ virtual bool keyToneEnabled() const; /** Enables key tones. */ virtual void enableKeyTone(bool enable); /** Returns @c true if sms tones are enabled. */ virtual bool smsToneEnabled() const; /** Enables sms tones. */ virtual void enableSMSTone(bool enable); /** Returns @c true if group call tone is enabled. */ virtual bool groupCallToneEnabled() const; /** Enables group call tones. */ virtual void enableGroupCallTone(bool enable); /** Returns @c true if private call tone is enabled. */ virtual bool privateCallToneEnabled() const; /** Enables group call tones. */ virtual void enablePrivateCallTone(bool enable); /** Returns @c true if EOT tone is enabled. */ virtual bool eotToneEnabled() const; /** Enables EOT tones. */ virtual void enableEOTTone(bool enable); /** Returns @c true if talk permit tone is enabled. */ virtual bool talkPermitToneEnabled() const; /** Enables talk permit tones. */ virtual void enableTalkPermitTone(bool enable); /** Returns @c true if boot tone is enabled. */ virtual bool bootToneEnabled() const; /** Enables boot tones. */ virtual void enableBootTone(bool enable); /** Returns @c true if voice prompt is enabled. */ virtual bool voicePromptEnabled() const; /** Enables voice prompt. */ virtual void enableVoicePrompt(bool enable); /** Returns @c true if low-battery tone is enabled. */ virtual bool lowBatteryToneEnabled() const; /** Enables low-battery tones. */ virtual void enableLowBatteryTone(bool enable); /** Returns the FM roger tone setting. */ virtual FMRogerTone fmRogerTone() const; /** Sets the FM roger tone. */ virtual void setFMRogerTone(FMRogerTone tone); /** Returns the display brightness setting [0-10]. */ virtual unsigned int displayBrightness() const; /** Sets the display brightness [0-10]. */ virtual void setDisplayBrightness(unsigned int n); /** Returns the backlight duration. */ virtual Interval backlightDuration() const; /** Sets the backlight duration. */ virtual void setBacklightDuration(Interval duration); /** Returns the menu hold time. */ virtual Interval menuDuration() const; /** Sets the menu hold time. */ virtual void setMenuDuration(Interval duration); /** Returns @c true if the volume change prompt is shown. */ virtual bool showVolmueChange() const; /** Enable volume change prompt. */ virtual void enableShowVolumeChange(bool enable); /** Returns the date format. */ virtual DateFormat dateFormat() const; /** Sets the date format. */ virtual void setDateFormat(DateFormat format); /** Returns @c true if the clock is shown. */ virtual bool showClock() const; /** Enable clock. */ virtual void enableShowClock(bool enable); /** Returns the call color. */ virtual Color::Code callColor() const; /** Sets the call color. */ virtual void setCallColor(Color::Code c); /** Returns the standby text color. */ virtual Color::Code standbyColor() const; /** Sets the standby text color. */ virtual void setStandbyColor(Color::Code c); /** Returns the channel name A color. */ virtual Color::Code channelNameAColor() const; /** Sets the channel name A color. */ virtual void setChannelNameAColor(Color::Code c); /** Returns the channel name B color. */ virtual Color::Code channelNameBColor() const; /** Sets the channel name B color. */ virtual void setChannelNameBColor(Color::Code c); /** Returns the zone name A color. */ virtual Color::Code zoneNameAColor() const; /** Sets the zone name A color. */ virtual void setZoneNameAColor(Color::Code c); /** Returns the zone name B color. */ virtual Color::Code zoneNameBColor() const; /** Sets the zone name B color. */ virtual void setZoneNameBColor(Color::Code c); /** Returns the position display format. */ virtual PositionFormat positionFormat() const; /** Sets the position display format. */ virtual void setPositionFormat(PositionFormat format); /** Returns the GNSS mode. */ virtual GNSSSettings::Systems gnss() const; /** Sets the GNSS mode. */ virtual void setGNSS(GNSSSettings::Systems mode); /** Returns @c true if GNSS is enabled. */ virtual bool gnssEnabled() const; /** Enables GNSS. */ virtual void enableGNSS(bool enable); /** Returns the time zone. */ virtual QTimeZone timeZone() const; /** Sets the timezone. */ virtual void setTimeZone(const QTimeZone &timeZone); /** Returns the position update period. */ virtual Interval posUpdatePeriod() const; /** Sets the position update period. */ virtual void setPosUpdatePeriod(const Interval &period); /** Returns possible recording modes. */ virtual RecordMode recordMode() const; /** Sets the record mode. */ virtual void setRecordMode(RecordMode mode); /** Returns @c true if recording is enabled. */ virtual bool recordingEnabled() const; /** Enables/disables recording. */ virtual void enableRecording(bool enable); /** Returns @c true if group call must match. */ virtual bool groupCallMatchEnabled() const; /** Enables group call match. */ virtual void enableGroupCallMatch(bool enable); /** Returns @c true if private call must match. */ virtual bool privateCallMatchEnabled() const; /** Enables private call match. */ virtual void enablePrivateCallMatch(bool enable); /** Returns the DMR call hang time. */ virtual Interval dmrCallHangTime() const; /** Sets the DMR call hang time. */ virtual void setDMRCallHangTime(const Interval &hangTime); /** Returns the active wait time. */ virtual Interval activeWaitTime() const; /** Sets the active wait time. */ virtual void setActiveWaitTime(Interval waitTime); /** Returns the number of active retries. */ virtual unsigned int activeRetries() const; /** Sets the number of active retires. */ virtual void setActiveRetries(unsigned int retries); /** Retruns the DMR preamble duration. */ virtual Interval dmrPreambleDuration() const; /** Sets the DMR preamble duration. */ virtual void setDmrPreambleDuration(Interval duration); /** Returns @c true if the DMR remote monitor is enabled. */ virtual bool dmrRemoteMonitorEnabled() const; /** Enables the remote monitor. */ virtual void enableDMRRemoteMonitor(bool enable); /** Returns @c true if the remote kill switch is enabled. */ virtual bool dmrRemoteKillEnabled() const; /** Enables the remote kill swtich. */ virtual void enableDMRRemoteKill(bool enable); /** Returns @c true if the remote radio check is enabled. */ virtual bool dmrRemoteRadioCheckEnabled() const; /** Enables remote radio check. */ virtual void enableDMRRemoteRadioCheck(bool enable); /** Returns @c true if the remote reenable is enabled. */ virtual bool dmrRemoteReenableEnabled() const; /** Enables DMR remote reenable. */ virtual void enableDMRRemoteReenable(bool enable); /** Returns @c true reception of DMR alerts is enabled. */ virtual bool dmrRXAlertEnabled() const; /** Enables enables reception of DMR alerts. */ virtual void enableDMRRXAlert(bool enable); /** Returns the DMR SMS format. */ virtual SMSExtension::Format smsFormat() const; /** Sets the DMR SMS format. */ virtual void setSMSFormat(SMSExtension::Format format); /** Retruns @c true missed call notification is enabled. */ virtual bool missedCallNotificationEnabled() const; /** Enables missed call notification. */ virtual void enableMissedCallNotification(bool enable); /** Returns the remote monitor duration. */ virtual Interval dmrRemoteMonitorDuration() const; /** Sets the remote monitor duration. */ virtual void setDMRRemoteMonitorDuration(Interval duration); /** Returns the talker alias format. */ virtual DMRSettings::TalkerAliasEncoding talkerAliasEncoding() const; /** Sets the talker alias format. */ virtual void setTalkerAliasEncoding(DMRSettings::TalkerAliasEncoding format); /** Returns @c true, if transmission of talker alias is enabled. */ virtual bool txTalkerAliasEnabled() const; /** Enables transmission of talker alias. */ virtual void enableTXTalkerAlias(bool enable); /** Returns the talker alias source. */ virtual TalkerAliasSource talkerAliasSource() const; /** Sets the talker alias source. */ virtual void setTalkerAliasSource(TalkerAliasSource source); /** Returns the dual-standby mode. */ virtual DualStandbyMode dualStandbyMode() const; /** Sets the dual-standby mode. */ virtual void setDualStandbyMode(DualStandbyMode mode); /** Returns the main VFO. */ virtual VFO mainVFO() const; /** Sets the main VFO. */ virtual void setMainVFO(VFO mainVFO); /** Returns the VFO A display mode. */ virtual VFODisplayMode vfoDisplayModeA() const; /** Sets the VFO A display mode. */ virtual void setVFODisplayModeA(VFODisplayMode mode); /** Returns the VFO B display mode. */ virtual VFODisplayMode vfoDisplayModeB() const; /** Sets the VFO B display mode. */ virtual void setVFODisplayModeB(VFODisplayMode mode); /** Returns the VFO A mode. */ virtual VFOMode vfoModeA() const; /** Sets the VFO A mode. */ virtual void setVFOModeA(VFOMode mode); /** Returns the VFO B mode. */ virtual VFOMode vfoModeB() const; /** Sets the VFO B mode. */ virtual void setVFOModeB(VFOMode mode); /** Returns @c true if VFO modes are disabled. */ virtual bool vfoModeDisabled() const; /** Disables VFO modes. */ virtual void disableVFOMode(bool enable); /** Returns the dual-standby hang-time. */ virtual Interval dualStandbyHangTime() const; /** Sets the dual-standby hang-time. */ virtual void setDualStandbyHangTime(Interval hangTime); /** Returns @c true if the side keys are locked. */ virtual bool sideKeyLockEnabled() const; /** Enable side key lock. */ virtual void enableSideKeyLock(bool enable); /** Returns @c true, if the knowb is locked. */ virtual bool knobLockEnabled() const; /** Enables the knob lock. */ virtual void enableKnobLock(bool enable); /** Returns the auto key-lock delay. If set to infinity, auto key-lock is disabled. */ virtual Interval autoKeyLockDelay() const; /** Sets the auto key-lock delay. If set to infinity, the auto key-lock is disabled. */ virtual void setAutoKeyLockDelay(Interval delay); /** Returns the side-key 1 short-press function. */ virtual KeyFunction::Function sk1Short() const; /** Sets the side-key 1 short-press function. */ virtual void setSK1Short(KeyFunction::Function function); /** Returns the side-key 1 long-press function. */ virtual KeyFunction::Function sk1Long() const; /** Sets the side-key 1 long-press function. */ virtual void setSK1Long(KeyFunction::Function function); /** Returns the side-key 2 short-press function. */ virtual KeyFunction::Function sk2Short() const; /** Sets the side-key 2 short-press function. */ virtual void setSK2Short(KeyFunction::Function function); /** Returns the side-key 2 long-press function. */ virtual KeyFunction::Function sk2Long() const; /** Sets the side-key 2 long-press function. */ virtual void setSK2Long(KeyFunction::Function function); /** Returns the programmable key 1 short-press function. */ virtual KeyFunction::Function p1Short() const; /** Sets the programmable key 1 short-press function. */ virtual void setP1Short(KeyFunction::Function function); /** Returns the programmable key 1 long-press function. */ virtual KeyFunction::Function p1Long() const; /** Sets the programmable key 1 long-press function. */ virtual void setP1Long(KeyFunction::Function function); /** Returns the programmable key 2 short-press function. */ virtual KeyFunction::Function p2Short() const; /** Sets the programmable key 2 short-press function. */ virtual void setP2Short(KeyFunction::Function function); /** Returns the programmable key 2 long-press function. */ virtual KeyFunction::Function p2Long() const; /** Sets the programmable key 2 long-press function. */ virtual void setP2Long(KeyFunction::Function function); /** Returns the long-press duration. */ virtual Interval longPressDuration() const; /** Sets the long-press duration. */ virtual void setLongPressDuration(Interval duration); /** Returns the transmit timeout. If infinite, the transmit timeout is disabled. */ virtual Interval transmitTimeout() const; /** Sets transmit timeout. If set to infinity, the tot is disabled. */ virtual void setTransmitTimeout(Interval timeout); /** Returns the transmit timeout reminder. If 0 disabled. */ virtual Interval transmitTimeoutReminder() const; /** Sets transmit timeout reminder. If set to 0 disabled. */ virtual void setTransmitTimeoutReminder(Interval timeout); /** Returns the VOX level. */ virtual Level voxLevel() const; /** Sets the VOX level. */ virtual void setVOXLevel(Level voxLevel); /** Returns the VOX delay. */ virtual Interval voxDelay() const; /** Sets the VOX delay. */ virtual void setVoxDelay(Interval delay); /** Returns the power-save mode. */ virtual PowerSaveMode powerSaveMode() const; /** Sets the power-save mode. */ virtual void setPowerSaveMode(PowerSaveMode mode); /** Returns @c true, if the NOAA weather alarm is enabled. */ virtual bool weatherAlarmEnabled() const; /** Enables the NOAA weather alarm. */ virtual void enableWeatherAlarm(bool enable); /** Returns @c true if all LEDs are disabled. */ virtual bool allLEDsDisabled() const; /** Disables all LEDs. */ virtual void disableAllLEDs(bool disable); /** Returns the TBST frequency. */ virtual Frequency tbstFrequency() const; /** Sets the TBST frequency. */ virtual void setTBSTFrequency(const Frequency &frequency); /** Returns the squelch tail elimination mode. */ virtual STEMode steMode() const; /** Sets the STE mode. */ virtual void setSTEMode(STEMode mode); /** Returns the FM mic level. */ virtual Level fmMicLevel() const; /** Sets the FM mic level.*/ virtual void setFMMicLevel(Level level); /** Returns the DMR mic level. */ virtual Level dmrMicLevel() const; /** Sets the DMR mic level.*/ virtual void setDMRMicLevel(Level level); /** Decodes the general settings. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode general settings. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum boot message length */ static constexpr unsigned int bootMessageLength() { return 14; } /** Maximum display brightness. */ static constexpr unsigned int maxBrightness() { return 5; } /** Range of valid active retires. */ static constexpr Range activeRetires() { return {1, 8}; } /** Range of valid long-press durations. */ static constexpr Range longPressDuration() { return {Interval::fromSeconds(1), Interval::fromSeconds(5)}; } /** Range of valid transmit timeouts. */ static constexpr Range transmitTimeout() { return {Interval::fromSeconds(20), Interval::fromSeconds(500)}; } /** Range of valid VOX delays. */ static constexpr Range voxDelay() { return {Interval::fromMilliseconds(300), Interval::fromSeconds(5)}; } /** Valid VOX sensitivity levels. */ static constexpr Range vox() { return {1,5}; } /** Valid mic gain settings. */ static constexpr Range micGain() { return {0,4}; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bootDisplay() { return 0x0000; } static constexpr unsigned int bootMessage1() { return 0x0001; } static constexpr unsigned int bootMessage2() { return 0x000f; } static constexpr unsigned int mcuReset() { return 0x001d; } static constexpr unsigned int autoPowerOffDelay() { return 0x001e; } static constexpr Bit radioSilent() { return {0x0020, 7}; } static constexpr Bit keyTone() { return {0x0020, 6}; } static constexpr Bit smsTone() { return {0x0020, 5}; } static constexpr Bit groupCallTone() { return {0x0020, 4}; } static constexpr Bit privateCallTone() { return {0x0020, 3}; } static constexpr Bit eotTone() { return {0x0020, 2}; } static constexpr Bit talkPermitTone() { return {0x0020, 1}; } static constexpr Bit bootTone() { return {0x0020, 0}; } static constexpr Bit voicePrompt() { return {0x0021, 7}; } static constexpr Bit lowBatteryTone() { return {0x0021, 6}; } static constexpr Bit fmRogerTone() { return {0x0021, 1}; } static constexpr unsigned int displayBrightness() { return 0x0030; } static constexpr unsigned int backlightDuration() { return 0x0031; } static constexpr unsigned int menuDuration() { return 0x0032; } static constexpr Bit showVolumeChange() { return {0x0033, 4}; } static constexpr Bit dateFormat() { return {0x0033, 3}; } static constexpr Bit showClock() { return {0x0033, 0}; } static constexpr unsigned int callColor() { return 0x0034; } static constexpr unsigned int standbyColor() { return 0x0035; } static constexpr unsigned int channelNameAColor() { return 0x0038; } static constexpr unsigned int channelNameBColor() { return 0x0039; } static constexpr unsigned int zoneNameAColor() { return 0x003a; } static constexpr unsigned int zoneNameBColor() { return 0x003b; } static constexpr Bit positionFormat() { return {0x0040, 6}; } static constexpr Bit gnssMode() { return {0x0040, 3}; } static constexpr Bit enableGNSS() { return {0x0040, 0}; } static constexpr unsigned int timeZone() { return 0x0041; } static constexpr unsigned int posUpdatePeriod() { return 0x0042; } static constexpr Bit recordMode() { return {0x0043, 2}; } static constexpr Bit enableRecording() { return {0x0045, 0}; } static constexpr Bit groupCallMatch() { return {0x0060, 1}; } static constexpr Bit privateCallMatch() { return {0x0060, 0}; } static constexpr unsigned int dmrCallHangTime() { return 0x0061; } static constexpr unsigned int activeWaitTime() { return 0x0062; } static constexpr unsigned int activeReties() { return 0x0063; } static constexpr unsigned int dmrPreambleDur() { return 0x0064; } static constexpr Bit dmrMonitor() { return {0x0065, 7}; } static constexpr Bit dmrKill() { return {0x0065, 6}; } static constexpr Bit dmrRadioCheck() { return {0x0065, 5}; } static constexpr Bit dmrReenable() { return {0x0065, 4}; } static constexpr Bit dmrRXAlert() { return {0x0065, 3}; } static constexpr Bit smsFormat() { return {0x0065, 1}; } static constexpr Bit missedCallNotification() { return {0x0065, 0}; } static constexpr unsigned int dmrRemoteMonitorDuration() { return 0x0066; } static constexpr Bit dmrTalkerAliasFormat() { return {0x0067, 4}; } static constexpr Bit txTalkerAlias() { return {0x0067, 3}; } static constexpr Bit talkerSource() { return {0x0067, 2}; } static constexpr Bit dualStandbyMode() { return {0x0080, 6}; } static constexpr Bit mainVFO() { return {0x0080, 5}; } static constexpr Bit displayModeB() { return {0x0080, 4}; } static constexpr Bit displayModeA() { return {0x0080, 3}; } static constexpr Bit vfoModeB() { return {0x0080, 2}; } static constexpr Bit vfoModeA() { return {0x0080, 1}; } static constexpr Bit disableVFOMode() { return {0x0080, 0}; } static constexpr unsigned int dualStandbyHangTime() { return 0x0081;} static constexpr Bit sideKeyLock() { return {0x0085, 2}; } static constexpr Bit knobLock() { return {0x0085, 1}; } static constexpr Bit enableAutoKeyLock() { return {0x0085, 0}; } static constexpr unsigned int autoKeyLockDelay() { return 0x0086; } static constexpr unsigned int sk1Short() { return 0x0087; } static constexpr unsigned int sk1Long() { return 0x0088; } static constexpr unsigned int sk2Short() { return 0x0089; } static constexpr unsigned int sk2Long() { return 0x008a; } static constexpr unsigned int p1Short() { return 0x008d; } static constexpr unsigned int p1Long() { return 0x008e; } static constexpr unsigned int p2Short() { return 0x008f; } static constexpr unsigned int p2Long() { return 0x0090; } static constexpr unsigned int longPressDuration() { return 0x0094; } static constexpr unsigned int transmitTimeout() { return 0x00a0; } static constexpr unsigned int totReminder() { return 0x00a1; } static constexpr unsigned int voxLevel() { return 0x00a2; } static constexpr unsigned int voxDelay() { return 0x00a3; } static constexpr Bit powerSaveMode() { return {0x00a4, 4}; } static constexpr Bit weatherAlarm() { return {0x00a4, 2}; } static constexpr Bit disableLEDs() { return {0x00a4, 0}; } static constexpr Bit tbstFrequency() { return {0x00a5, 4}; } static constexpr Bit steMode() { return {0x00a5, 0}; } static constexpr unsigned int fmMicLevel() { return 0x00a6; } static constexpr unsigned int dmrMicLevel() { return 0x00a7; } /// @endcond }; }; /** Implements the APRS settings. */ class APRSSettingsElement: public Element { protected: /** Encoding of call type. */ enum class CallType { Private = 0, Group = 1 }; public: /** Constructor from pointer. */ APRSSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0100; } /** Returns the update interval. If set to infinity, update is disabled. */ virtual Interval updatePeriod(); /** Sets the update interval. */ virtual void setUpdatePeriod(Interval interval); /** Returns @c true if the fixed location is used. */ virtual bool fixedLocationEnabled() const; /** Returns the fixed location. */ virtual QGeoCoordinate fixedLocation() const; /** Sets the fixed location. */ virtual void setFixedLocation(const QGeoCoordinate &coordinate); /** Enabled fixed location. */ virtual void enableFixedLocation(bool enable); /** Returns @c true if the n-th revert channel is set. */ virtual bool revertChannelIsCurrent(unsigned int n); /** Returns the n-th revert channel index. */ virtual unsigned int revertChannelIndex(unsigned int n) const; /** Sets the n-th revert channel index. */ virtual void setRevertChannelIndex(unsigned int n, unsigned int idx); /** Sets the n-th revert channel to be the current channel. */ virtual void setRevertChannelToCurrent(unsigned int n); /** Returns the pre-wave delay. */ virtual Interval preWaveDelay() const; /** Sets the pre-wave delay. */ virtual void setPreWaveDelay(const Interval &delay); /** Returns the call type for all revert channels. */ virtual DMRContact::Type callType() const; /** Sets the call type for all revert channels. */ virtual void setCallType(DMRContact::Type type); /** Returns the destination ID */ virtual unsigned int destinationId() const; /** Sets the destination ID for all revert channels. */ virtual void setDestinationId(unsigned int id); /** Decodes the APRS settings. */ bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the APRS settings. */ bool link(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the APRS settings. */ bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the settings. */ struct Limit: Element::Limit { /** Valid range for update period. */ static constexpr Range updatePeriod() { return {Interval::fromSeconds(30), Interval::fromMinutes(120) }; } /** Number of revert channels. */ static constexpr unsigned int revertChannels() { return 8; } }; protected: /** Some offsets within the element. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int updatePeriod() { return 0x0001; } static constexpr unsigned int enableFixedLocation() { return 0x0002; } static constexpr unsigned int fixedLocationLatitude() { return 0x0006; } static constexpr unsigned int fixedLocationLongitude() { return 0x0010; } static constexpr unsigned int revertChannelIndices() { return 0x0020; } static constexpr unsigned int betweenRevertChannelIndices() { return 0x0002; } static constexpr unsigned int prewaveDelay() { return 0x0030; } static constexpr unsigned int callType() { return 0x0031; } static constexpr unsigned int destinationId() { return 0x0032; } /// @endcond }; }; /** Implements the password settings. */ class PasswordSettingsElement: public Element { public: /** Constructor. */ PasswordSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0100; } /** Returns @c true if the boot password is set. */ virtual bool bootPasswordEnabled() const; /** Returns the boot password. */ virtual QString bootPassword() const; /** Sets the boot password. */ virtual void setBootPassword(const QString &value); /** Clears the boot password. */ virtual void clearBootPassword(); /** Returns @c true if the write password is set. */ virtual bool writePasswordEnabled() const; /** Returns the write password. */ virtual QString writePassword() const; /** Sets the write password. */ virtual void setWritePassword(const QString &value); /** Clears the write password. */ virtual void clearWritePassword(); /** Returns @c true if the read password is set. */ virtual bool readPasswordEnabled() const; /** Returns the read password. */ virtual QString readPassword() const; /** Sets the read password. */ virtual void setReadPassword(const QString &value); /** Clears the read password. */ virtual void clearReadPassword(); /** Encode password settings from given config. */ virtual bool encode(Context &ctx, ErrorStack err=ErrorStack()); /** Decode password settings and update config. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the element. */ struct Limit: Element::Limit { /** Maximum password length. */ static constexpr unsigned int passwordLength() { return 8;} }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int enableBootPassword() { return 0x0030; } static constexpr unsigned int bootPassword() { return 0x0031; } static constexpr unsigned int enableWritePassword() { return 0x0039; } static constexpr unsigned int enableReadPassword() { return 0x003a; } static constexpr unsigned int writePassword() { return 0x003b; } static constexpr unsigned int readPassword() { return 0x0043; } /// @endcond }; }; /** Implements the menu settings. */ class MenuSettingElement: public Element { public: /** Constructor. */ MenuSettingElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0100; } protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr Bit newZone() { return {0x0000,1}; } static constexpr Bit listZones() { return {0x0000,0}; } static constexpr Bit measurePeriod() { return {0x0001,5}; } static constexpr Bit remoteKill() { return {0x0001,4}; } static constexpr Bit reenableRadio() { return {0x0001,3}; } static constexpr Bit remoteMonitor() { return {0x0001,2}; } static constexpr Bit radioCheck() { return {0x0001,1}; } static constexpr Bit callAlert() { return {0x0001,0}; } static constexpr Bit matchGroupCall() { return {0x0002,7}; } static constexpr Bit displayMode() { return {0x0002,6}; } static constexpr Bit matchPrivateCall() { return {0x0002,5}; } static constexpr Bit languageSelect() { return {0x0002,4}; } static constexpr Bit bootDisplay() { return {0x0002,3}; } static constexpr Bit transmitPower() { return {0x0002,2}; } static constexpr Bit alertTone() { return {0x0002,1}; } static constexpr Bit talkaround() { return {0x0002,0}; } static constexpr Bit record() { return {0x0003,6}; } static constexpr Bit aprs() { return {0x0003,5}; } static constexpr Bit gnss() { return {0x0003,4}; } static constexpr Bit powerSave() { return {0x0003,3}; } static constexpr Bit subChannelMode() { return {0x0003,2}; } static constexpr Bit fmBCRadio() { return {0x0003,1}; } static constexpr Bit smsFormat() { return {0x0003,0}; } static constexpr Bit callsignDB() { return {0x0004,6}; } static constexpr Bit manualDial() { return {0x0004,5}; } static constexpr Bit sendMessage() { return {0x0004,4}; } static constexpr Bit contactFunc() { return {0x0004,3}; } static constexpr Bit editContact() { return {0x0004,2}; } static constexpr Bit deleteContact() { return {0x0004,1}; } static constexpr Bit addContact() { return {0x0004,0}; } static constexpr Bit clearCallLog() { return {0x0005,3}; } static constexpr Bit outgoingCalls() { return {0x0005,2}; } static constexpr Bit incomingCalls() { return {0x0005,1}; } static constexpr Bit missedCalls() { return {0x0005,0}; } static constexpr Bit radioName() { return {0x0006,7}; } static constexpr Bit radioId() { return {0x0006,6}; } static constexpr Bit timeslot() { return {0x0006,5}; } static constexpr Bit colorCode() { return {0x0006,4}; } static constexpr Bit txContact() { return {0x0006,3}; } static constexpr Bit ctcssDcs() { return {0x0006,2}; } static constexpr Bit txFrequency() { return {0x0006,1}; } static constexpr Bit rxFrequency() { return {0x0006,0}; } static constexpr Bit channelName() { return {0x0007,4}; } static constexpr Bit addChannel() { return {0x0007,3}; } static constexpr Bit groupList() { return {0x0007,2}; } static constexpr Bit dcdm() { return {0x0007,1}; } static constexpr Bit channelType() { return {0x0007,0}; } /// @endcond }; }; /** Implementation of a single encryption key. */ class EncryptionKeyElement: Element { public: /** Possible key types. */ enum class Type { Off = 0, Custom = 1, ARC4 = 2, AES128 = 3, AES256 = 4 }; public: /** Constructor. */ EncryptionKeyElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x2c; } virtual void clear() override; /** Returns the key id. */ virtual unsigned int keyId() const; /** Sets the key id. */ virtual void setKeyId(unsigned int id); /** Returns the key name. */ virtual QString name() const; /** Sets the name. */ virtual void setName(const QString &name); /** Returns the key type. */ virtual Type type() const; /** Sets the key type. */ virtual void setType(Type type); /** Returns the key data. */ virtual QByteArray key() const; /** Sets the key data. */ virtual void setKey(const QByteArray &key); /** Decodes the key. */ EncryptionKey *decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes the key. */ bool encode(const EncryptionKey *key, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits of the element. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 10; } /** Maximum key length (bytes). */ static constexpr unsigned int keyLength() { return 32; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int keyId() { return 0x0000; } static constexpr unsigned int name() { return 0x0001; } static constexpr unsigned int type() { return 0x000b; } static constexpr unsigned int key() { return 0x000c; } /// @endcond }; }; /** Implements encryption key bank. */ class EncryptionKeyBankElement: public Element { public: /** Constructor from pointer to element. */ EncryptionKeyBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x600; } /** Returns @c true, if the key is valid. */ virtual bool keyValid(unsigned int idx) const; /** Returns the n-th key. */ virtual EncryptionKeyElement key(unsigned int idx) const; /** Decodes all keys. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes all keys. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the element. */ struct Limit: Element::Limit { /** Maximum number of keys. */ static constexpr unsigned int keys() { return 32; } }; protected: /** Some internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int keys() { return 0x0000; } static constexpr unsigned int betweenKeys() { return EncryptionKeyElement::size(); } /// @endcond }; }; public: /** Default/empty constructor. * Before encoding, @c encode will allocate all elements necessary to encode the codeplug. * Before decoding, all elements that are allocated on the device must be allocated within the * codeplug. */ explicit DM32UVCodeplug(QObject *parent = nullptr); Config * preprocess(Config *config, const ErrorStack &err) const override; bool postprocess(Config *config, const ErrorStack &err) const override; bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const override; bool encode(Config *config, const Flags &flags, const ErrorStack &err=ErrorStack()) override; bool decode(Config *config, const ErrorStack &err=ErrorStack()) override; protected: /** Decode codeplug elements. */ virtual bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link decoded elements. */ virtual bool linkElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode all elements. */ virtual bool encodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes all channels defined. */ virtual bool decodeChannels(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links all decoded channels. */ virtual bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes all channels. */ virtual bool encodeChannels(Context &ctx, const ErrorStack &err=ErrorStack()); /** Decode contacts. */ virtual bool decodeContacts(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocate and encode contacts. */ virtual bool encodeContacts(Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes all zones defined. */ virtual bool decodeZones(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links all decoded zones. */ virtual bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes all zones. */ virtual bool encodeZones(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some internal limits. */ struct Limit: Element::Limit { static constexpr unsigned int blockSize() { return 0x1000; } static constexpr Range channelBanks() { return {1, 49}; } static constexpr Range contactBanks() { return {1, 5}; } static constexpr Range zoneBanks() { return {1, 8}; } }; protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int generalSettings() { return 0x00004000; } static constexpr unsigned int aprsSettings() { return 0x00004300; } static constexpr unsigned int passwordSettings() { return 0x00004400; } static constexpr unsigned int contactIndex() { return 0x0000b000; } static constexpr unsigned int groupListBank() { return 0x0000f000; } static constexpr unsigned int extendedSettings() { return 0x00010000; } static constexpr unsigned int encryptionKeys() { return 0x00010300; } static constexpr unsigned int scanListBank() { return 0x00011000; } static constexpr unsigned int channelBanks() { return 0x00012000; } static constexpr unsigned int channelExtensionBanks() { return 0x00042000; } static constexpr unsigned int contactBanks() { return 0x00044000; } static constexpr unsigned int zoneBanks() { return 0x0005c000; } static constexpr unsigned int roamingZoneBank() { return 0x00065000; } static constexpr unsigned int roamingChannelBank() { return 0x00066000; } static constexpr unsigned int radioIdBank() { return 0x00067000; } /// @endcond }; }; #endif // DM32UV_CODEPLUG_HH ================================================ FILE: lib/dm32uv_interface.cc ================================================ #include "dm32uv_interface.hh" #include "logger.hh" #include #define USB_VID 0x067b #define USB_PID 0x23a3 #define TIMEOUT 1000 /* ********************************************************************************************* * * Device detection request * ********************************************************************************************* */ bool DM32UVInterface::DeviceDetectionRequest::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(DeviceDetectionRequest), TIMEOUT, err); } /* ********************************************************************************************* * * Device detection response * ********************************************************************************************* */ bool DM32UVInterface::DeviceDetectionResponse::receive(DM32UVInterface *dev, const ErrorStack &err) { quint64 n = sizeof(DeviceDetectionResponse); char *data = (char *)this; if (! dev->receive(data, 1, TIMEOUT, err)) { errMsg(err) << "Cannot read response."; return false; } n -= 1; data += 1; if (6 != this->result) { errMsg(err) << "Invalid response. Expected 6, got " << Qt::hex << this->result << "."; return false; } return dev->receive(data, n, TIMEOUT, err); } QString DM32UVInterface::DeviceDetectionResponse::identifier() const { return QString::fromLatin1(QByteArray(payload, 7)); } /* ********************************************************************************************* * * Password request * ********************************************************************************************* */ bool DM32UVInterface::PasswordRequest::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(PasswordRequest), TIMEOUT, err); } /* ********************************************************************************************* * * Password response * ********************************************************************************************* */ bool DM32UVInterface::PasswordResponse::receive(DM32UVInterface *dev, const ErrorStack &err) { qint64 n = sizeof(PasswordResponse); char *data = (char *)this; if (! dev->receive(data, 1, TIMEOUT, err)) return false; n -= 1; data += 1; if (0x50 != this->result) { errMsg(err) << "Invalid response. Expected 50h, got " << Qt::hex << this->code << "h."; return false; } return dev->receive(data, n, TIMEOUT, err); } /* ********************************************************************************************* * * System Info Request * ********************************************************************************************* */ bool DM32UVInterface::SysinfoRequest::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(SysinfoRequest), TIMEOUT, err); } /* ********************************************************************************************* * * Generic ACK Response * ********************************************************************************************* */ bool DM32UVInterface::ACKResponse::receive(DM32UVInterface *dev, const ErrorStack &err) { if (! dev->receive((char *)this, 1, TIMEOUT, err)) return false; if (0x06 != result) { errMsg(err) << "Unexpected response " << Qt::hex << result << "h, expected 6h."; return false; } return true; } /* ********************************************************************************************* * * Value Request * ********************************************************************************************* */ DM32UVInterface::ValueRequest::ValueRequest(ValueId vId) : valueId((uint8_t)vId) { // pass... } DM32UVInterface::ValueRequest::ValueRequest(uint8_t vId) : valueId(vId) { // pass... } bool DM32UVInterface::ValueRequest::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(ValueRequest), TIMEOUT, err); } /* ********************************************************************************************* * * Value Response * ********************************************************************************************* */ bool DM32UVInterface::ValueResponse::receive(DM32UVInterface *dev, const ErrorStack &err) { char *data = (char *)this; if (! dev->receive(data, 1, TIMEOUT, err)) return false; if ('V' != this->response_type) { errMsg(err) << "Unexpected response " << Qt::hex << this->response_type << "h, expected " << Qt::hex << 'V'; return false; } data += 1; // Receive value ID and length if (! dev->receive(data, 2, TIMEOUT, err)) return false; data += 2; return dev->receive(data, this->length, TIMEOUT, err); } QByteArray DM32UVInterface::ValueResponse::string() const { return {reinterpret_cast(payload), length}; } uint32_t DM32UVInterface::ValueResponse::lowerMemoryBound() const { return qFromLittleEndian(memory.lower); } uint32_t DM32UVInterface::ValueResponse::upperMemoryBound() const { return qFromLittleEndian(memory.upper); } /* ********************************************************************************************* * * Enter Program Mode Request * ********************************************************************************************* */ bool DM32UVInterface::EnterProgramModeRequest::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(EnterProgramModeRequest), TIMEOUT, err); } /* ********************************************************************************************* * * Unknown 02h Request * ********************************************************************************************* */ bool DM32UVInterface::Unknown02Request::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(Unknown02Request), TIMEOUT, err); } /* ********************************************************************************************* * * Unknown 02h Response * ********************************************************************************************* */ bool DM32UVInterface::Unknown02Response::receive(DM32UVInterface *dev, const ErrorStack &err) { return dev->receive((char *)this, sizeof(Unknown02Response), TIMEOUT, err); } /* ********************************************************************************************* * * Ping Request * ********************************************************************************************* */ bool DM32UVInterface::PingRequest::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(PingRequest), TIMEOUT, err); } /* ********************************************************************************************* * * Read Request * ********************************************************************************************* */ DM32UVInterface::ReadRequest::ReadRequest(uint32_t addr, uint16_t len) { address[0] = (addr >> 0) & 0xff; address[1] = (addr >> 8) & 0xff; address[2] = (addr >> 16) & 0xff; length = qToLittleEndian(len); } bool DM32UVInterface::ReadRequest::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(ReadRequest), TIMEOUT, err); } /* ********************************************************************************************* * * Read Response * ********************************************************************************************* */ bool DM32UVInterface::ReadResponse::receive(DM32UVInterface *dev, const ErrorStack &err) { char *data = (char *)this; if (! dev->receive(data, 1, TIMEOUT, err)) return false; if ('W' != this->response_type) { errMsg(err) << "Unexpected response " << Qt::hex << (uint8_t)this->response_type << "h, expected " << Qt::hex << (uint8_t)'W' << "h."; return false; } data += 1; // Receive address (24bit) and length (16bit) if (! dev->receive(data, 5, TIMEOUT, err)) return false; data += 5; // Receive payload return dev->receive(data, this->length(), TIMEOUT, err); } uint32_t DM32UVInterface::ReadResponse::address() const { return (((uint32_t)_address[0]) << 0) | (((uint32_t)_address[1]) << 8) | (((uint32_t)_address[2]) << 16); } uint16_t DM32UVInterface::ReadResponse::length() const { return qFromLittleEndian(_length); } QByteArray DM32UVInterface::ReadResponse::payload() const { return QByteArray((const char*)_payload, length()); } /* ********************************************************************************************* * * Write request * ********************************************************************************************* */ DM32UVInterface::WriteRequest::WriteRequest(uint32_t address, const QByteArray &payload) { _address[0] = ((address >> 0) & 0xff); _address[1] = ((address >> 8) & 0xff); _address[2] = ((address >> 16) & 0xff); uint16_t len = std::min((qsizetype)4096, payload.length()); _length = qToLittleEndian(len); memcpy(_payload, payload.constData(), len); } bool DM32UVInterface::WriteRequest::send(DM32UVInterface *dev, const ErrorStack &err) const { return dev->send((const char *)this, sizeof(WriteRequest), TIMEOUT, err); } /* ********************************************************************************************* * * Implementation of the DM-32UV interface. * ********************************************************************************************* */ DM32UVInterface::DM32UVInterface(const USBDeviceDescriptor &descr, const ErrorStack &err, QObject *parent) : USBSerial{descr, QSerialPort::Baud115200, err, parent}, _state(State::Closed), _info() { if (isOpen()) _state = State::Open; else if (QSerialPort::NoError != QSerialPort::error()) _state = State::Error; if (! request_identifier(err)) _state = State::Error; _state = State::SystemInfo; } RadioInfo DM32UVInterface::identifier(const ErrorStack &err) { // If not yet requested -> request info if ((State::Open == _state) && (! _info.isValid())) { if (! request_identifier(err)) _state = State::Error; _state = State::SystemInfo; } // return it. return _info; } bool DM32UVInterface::getAddressMap(DM32UV::AddressMap &map, const ErrorStack &err, void (*progress)(unsigned int)) { // If not yet in program mode -> enter if ((State::Program != _state) && (! enter_program_mode(err))) { errMsg(err) << "Cannot enter program mode."; _state = State::Error; return false; } _state = State::Program; logDebug() << "Enter PROG mode."; // Map entire codeplug memory region for (uint32_t addr=_codeplugMemory.first; addr<_codeplugMemory.second; addr += 0x1000) { QThread::msleep(100); ReadRequest mapReq(addr+0xfff, 1); ReadResponse mapRes; if (! sendReceive(mapReq, mapRes, err)) { errMsg(err) << "Cannot request codeplug memory map at address " << Qt::hex << (addr+0xfff) << "h."; return false; } // Unpack prefix uint8_t prefix = (uint8_t)mapRes.payload()[0]; // If prefix is invalid -> do not map if ((0x00 == prefix) || (0xff == prefix)) continue; // add to address map uint32_t vaddr = ((uint32_t)prefix) << 12; //logDebug() << "Map " << Qt::hex << addr << "h -> " << vaddr << "h."; map.map(addr, vaddr); // Signal progress if (progress) progress((100*addr)/_codeplugMemory.second); } return true; } bool DM32UVInterface::read_start(uint32_t bank, uint32_t address, const ErrorStack &err) { Q_UNUSED(bank); Q_UNUSED(address); // If not yet in program mode -> enter if ((State::Program != _state) && (! enter_program_mode(err))) { errMsg(err) << "Cannot enter program mode."; _state = State::Error; return false; } _state = State::Program; return true; } bool DM32UVInterface::read(uint32_t bank, uint32_t address, uint8_t *data, int nbytes, const ErrorStack &err) { Q_UNUSED(bank); // align read with 1000h blocks if (address & 0xfff) { int n = std::min(nbytes, (int)(0x1000 - (address & 0xfff))); ReadRequest req(address, n); ReadResponse res; if (! sendReceive(req, res, err)) { errMsg(err) << "Cannot read from " << Qt::hex << address << "h " << n << " bytes."; return false; } std::memcpy(data, res.payload().constData(), n); data += n; address += n; nbytes -= n; } while (nbytes > 0) { int n = std::min(nbytes, 0x1000); ReadRequest req(address, n); ReadResponse res; if (! sendReceive(req, res, err)) { errMsg(err) << "Cannot read from " << Qt::hex << address << "h " << n << " bytes."; return false; } std::memcpy(data, res.payload().constData(), n); data += n; address += n; nbytes -= n; } return true; } bool DM32UVInterface::read_finish(const ErrorStack &err) { Q_UNUSED(err) // Nothing to do. return true; } bool DM32UVInterface::write_start(uint32_t bank, uint32_t address, const ErrorStack &err) { Q_UNUSED(bank); Q_UNUSED(address); // If not yet in program mode -> enter if ((State::Program != _state) && (! enter_program_mode(err))) { errMsg(err) << "Cannot enter program mode."; _state = State::Error; return false; } _state = State::Program; return true; } bool DM32UVInterface::write(uint32_t bank, uint32_t address, uint8_t *data, int nbytes, const ErrorStack &err) { Q_UNUSED(bank); // align with 1000h blocks if (address & 0xfff) { int n = std::min(nbytes, (int)(0x1000 - (address & 0xfff))); WriteRequest req(address, QByteArray((const char*)data, n)); ACKResponse res; if (! sendReceive(req, res, err)) { errMsg(err) << "Cannot write to " << Qt::hex << address << "h " << n << " bytes."; return false; } data += n; address += n; nbytes -= n; } while (nbytes > 0) { int n = std::min(nbytes, 0x1000); WriteRequest req(address, QByteArray((const char*)data, n)); ACKResponse res; if (! sendReceive(req, res, err)) { errMsg(err) << "Cannot write to " << Qt::hex << address << "h " << n << " bytes."; return false; } data += n; address += n; nbytes -= n; } return true; } bool DM32UVInterface::write_finish(const ErrorStack &err) { Q_UNUSED(err) // Nothing to do. return true; } USBDeviceInfo DM32UVInterface::interfaceInfo() { return USBDeviceInfo(USBDeviceInfo::Class::Serial, 0, 0, false); } QList DM32UVInterface::detect(bool saveOnly) { if (saveOnly) return QList(); return USBSerial::detect(); } bool DM32UVInterface::DM32UVInterface::request_identifier(const ErrorStack &err) { if (State::Open != _state) { errMsg(err) << "Cannot identify radio, interface not it OPEN state."; return false; } QThread::msleep(100); DeviceDetectionRequest devDetReq; DeviceDetectionResponse devDetRes; if (! sendReceive(devDetReq, devDetRes, err)) { errMsg(err) << "Cannot identify device."; return false; } logDebug() << "Got " << devDetRes.identifier() << "."; if ("DP570UV" != devDetRes.identifier()) { errMsg(err) << "Unknown radio " << devDetRes.identifier(); return false; } QThread::msleep(100); // Formally, request password PasswordRequest passReq; PasswordResponse passRes; if (! sendReceive(passReq, passRes, err)) { errMsg(err) << "Cannot request programming password."; return false; } logDebug() << "Finished password request."; QThread::msleep(100); // Enter System information mode SysinfoRequest sysInfoReq; ACKResponse sysInfoRes; if (! sendReceive(sysInfoReq, sysInfoRes, err)) { errMsg(err) << "Cannot enter system info mode."; return false; } logDebug() << "Finished SysInfo request."; QThread::msleep(100); // Request FW version ValueRequest fwVersionReq(ValueRequest::ValueId::FirmwareVersion); ValueResponse fwVersionRes; if (! sendReceive(fwVersionReq, fwVersionRes, err)) { errMsg(err) << "Cannot request firmware version."; return false; } _firmwareVersion = fwVersionRes.string(); logDebug() << "Got firmware version " << _firmwareVersion << "."; QThread::msleep(100); // Request codeplug memory region ValueRequest cpMemReq(ValueRequest::ValueId::MainConfigMemory); ValueResponse cpMemRes; if (! sendReceive(cpMemReq, cpMemRes, err)) { errMsg(err) << "Cannot request codeplug memory region."; return false; } _codeplugMemory = {cpMemRes.lowerMemoryBound(), cpMemRes.upperMemoryBound()}; logDebug() << "Codeplug memory region: " << Qt::hex << _codeplugMemory.first << "h - " << Qt::hex << _codeplugMemory.second << "h."; QThread::msleep(100); // Request callsign memory region ValueRequest callMemReq(ValueRequest::ValueId::CallSignDBMemory); ValueResponse callMemRes; if (! sendReceive(callMemReq, callMemRes, err)) { errMsg(err) << "Cannot request callsign memory region."; return false; } _callsignMemory = {callMemRes.lowerMemoryBound(), callMemRes.upperMemoryBound()}; logDebug() << "Call-sign DB memory region: " << Qt::hex << _callsignMemory.first << "h - " << Qt::hex << _callsignMemory.second << "h."; // Done _info = RadioInfo::byID(RadioInfo::Radio::DM32UV); return true; } bool DM32UVInterface::enter_program_mode(const ErrorStack &err) { if (State::SystemInfo != _state) { errMsg(err) << "Cannot enter PROGRAM mode. Interface not in SYSINFO mode."; return false; } if (_firmwareVersion.isEmpty() && !DM32UV::supportedFirmwareVersions().contains(_firmwareVersion)) { logWarn() << "Firmware version " << _firmwareVersion << " is currently not supported by qdmr. " << "Supported versions: " << DM32UV::supportedFirmwareVersions().values().join(", ") << "."; } EnterProgramModeRequest progReq; ACKResponse progRes; if (! sendReceive(progReq, progRes, err)) { errMsg(err) << "Cannot send enter-program-mode request."; return false; } Unknown02Request uknReq; Unknown02Response uknRes; if (! sendReceive(uknReq, uknRes, err)) { errMsg(err) << "Cannot send unknown 02h request."; return false; } PingRequest pingReq; ACKResponse pingRes; if (! sendReceive(pingReq, pingRes)) { errMsg(err) << "Cannot send ping request."; return false; } return true; } bool DM32UVInterface::send(const char *data, qint64 n, int timeout, const ErrorStack &err) { Q_UNUSED(timeout) //logDebug() << "Send " << QByteArray(data, n).toHex(' ') << "."; while (n) { auto k = QSerialPort::write(data, n); if (0 > k) { errMsg(err) << "QSerialPort: " << errorString(); errMsg(err) << "Cannot send device detection request."; return false; } n -= k; data += k; } return true; } bool DM32UVInterface::receive(char *data, qint64 n, int timeout, const ErrorStack &err) { if (bytesAvailable()) { auto k = QSerialPort::read(data, n); if (0 > k) { errMsg(err) << "Cannot read from serial port: " << errorString() << "."; return false; } n -= k; data += k; } while (n) { if (! waitForReadyRead(timeout)) { errMsg(err) << "QSerialPort: " << errorString() << "."; errMsg(err) << "Cannot read from serial port, timeout."; return false; } auto k = QSerialPort::read(data, n); if (0 > k) { errMsg(err) << "Cannot read from serial port: " << errorString() << "."; return false; } n -= k; data += k; } return true; } ================================================ FILE: lib/dm32uv_interface.hh ================================================ #ifndef DM32UVINTERFACE_HH #define DM32UVINTERFACE_HH #include "usbserial.hh" #include #include "dm32uv.hh" /** Interface to Baofeng Dm-32UV devices. * * The protocol is documented in detail at * https://github.com/infamy/DM32-Protocol-Spec/blob/main/02-CONNECTION-SEQUENCE.md * * @ingroup dm32uv */ class DM32UVInterface : public USBSerial { Q_OBJECT protected: /** Device detection request. */ struct Q_PACKED DeviceDetectionRequest { /** Request content. */ const char payload[7] = {'P','S','E','A','R','C','H'}; /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; /** Device detection response. */ struct Q_PACKED DeviceDetectionResponse { /** Result flag. */ uint8_t result; /** Response payload. */ char payload[7]; /** Receive response though the given interface. */ bool receive(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()); /** Extracts the identifier from the response payload. */ QString identifier() const; }; /** Password request. */ struct Q_PACKED PasswordRequest { /** Request content. */ const char payload[7] = {'P', 'A', 'S', 'S', 'S', 'T', 'A'}; /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; /** Passwort response. */ struct Q_PACKED PasswordResponse { /** Response result code. */ uint8_t result; /** Some unknown data. */ uint16_t code; /** Receive response though the given interface. */ bool receive(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()); }; /** System info request. */ struct Q_PACKED SysinfoRequest { /** Request content. */ const char payload[7] = {'S','Y','S','I','N','F','O'}; /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; /** System info response. */ struct Q_PACKED ACKResponse { /** Response result code. */ uint8_t result; /** Receive response though the given interface. */ bool receive(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()); }; /** Value request. These requests are used to read some global information like address ranges * and versions. */ struct Q_PACKED ValueRequest { /** Some possible value IDs. */ enum class ValueId{ FirmwareVersion = 0x1, BuildDate = 0x3, MainConfigMemory = 0xa, CallSignDBMemory = 0xf }; /** Static request type. */ const char request_type = 'V'; /** Some unknown fields. Likely some flags and length field. */ const uint8_t unused[3] = {0x00, 0x00, 0x00}; /** The value ID to read. */ uint8_t valueId; /** Constructor from value ID. */ ValueRequest(ValueId valueId); /** Constructor from raw value ID. */ explicit ValueRequest(uint8_t valueId); /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; /** Value response. */ struct Q_PACKED ValueResponse { /** The received response code, should be 'V'. */ char response_type; /** The value ID read. */ uint8_t valueId; /** THe length of the payload. The format depends on the value being read. */ uint8_t length; union { /** A memory range. */ struct { /** Lower bound in little endian. */ uint32_t lower; /** Upper bound (inclusive) in little endian. */ uint32_t upper; } memory; // Raw payload. uint8_t payload[255]; }; /** Receive response though the given interface. */ bool receive(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()); /** Extracts a string from the response payload. */ QByteArray string() const; /** Extracts the lower memory bound. */ uint32_t lowerMemoryBound() const; /** Extracts the upper memory bound. */ uint32_t upperMemoryBound() const; }; /** Enter program mode request. */ struct Q_PACKED EnterProgramModeRequest { /** Request content. */ const char payload[12] = { '\xff', '\xff', '\xff', '\xff', '\x0c', 'P','R','O','G','R','A','M' }; /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; /** Unkown 02h request */ struct Q_PACKED Unknown02Request { /** Request content. */ const char payload[1] {'\x02'}; /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; /** Unknown 02h response. */ struct Q_PACKED Unknown02Response { /** Some unknown response payload. */ char payload[8]; /** Receive response though the given interface. */ bool receive(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()); }; /** Ping request. */ struct Q_PACKED PingRequest { /** Request content. */ const char payload[1] = {'\x06'}; /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; /** Read request. */ struct Q_PACKED ReadRequest { /** Static request type. */ const char request_type = 'R'; /** 24bit little endian address. */ uint8_t address[3]; /** 16bit little endian length. */ uint16_t length; /** Constructs a read request for the given address and length. */ ReadRequest(uint32_t address, uint16_t length); /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; /** Read response. */ struct Q_PACKED ReadResponse { /** Static respone type field. */ const char response_type = 'W'; /** 24bit little endian address. */ uint8_t _address[3]; /** 16bit payload length. */ uint16_t _length; /** Payload of upto 4096 bytes. */ uint8_t _payload[4096]; /** Unpacks the address. */ uint32_t address() const; /** Unpacks the length. */ uint16_t length() const; /** Returns the payload as byte array. */ QByteArray payload() const; /** Receive response though the given interface. */ bool receive(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()); }; /** Write request. */ struct Q_PACKED WriteRequest { /** Static request type field. */ const char request_type = 'W'; /** Encodes the 24bit address in little endian. */ uint8_t _address[3]; /** Encodes the 16bit length in little endian. */ uint16_t _length; /** Holds the payload. */ uint8_t _payload[4096]; /** Constructs a new write request to the given address with the given payload. */ WriteRequest(uint32_t address, const QByteArray &payload); /** Send request though the given interface. */ bool send(DM32UVInterface *dev, const ErrorStack &err=ErrorStack()) const; }; protected: /** Possible states, the interface might be in. */ enum class State { Closed, Open, Connected, SystemInfo, Program, Error }; public: /** Constructs a new DM32UV interface for the given USB descriptor. The constructor also enters * the SYSINFO state and identifies the radio. */ explicit DM32UVInterface(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Returns the radio info, after identifying the radio. */ RadioInfo identifier(const ErrorStack &err=ErrorStack()) override; /** Reads the obfuscation address map from the device. */ bool getAddressMap(DM32UV::AddressMap &map, const ErrorStack &err=ErrorStack(), void (*progress)(unsigned int percent)=nullptr); bool read_start(uint32_t bank, uint32_t address, const ErrorStack &err=ErrorStack()) override; bool read(uint32_t bank, uint32_t address, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()) override; bool read_finish(const ErrorStack &err=ErrorStack()) override; bool write_start(uint32_t bank, uint32_t address, const ErrorStack &err=ErrorStack()) override; bool write(uint32_t bank, uint32_t address, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()) override; bool write_finish(const ErrorStack &err=ErrorStack()) override; public: /** Returns some information about this interface. */ static USBDeviceInfo interfaceInfo(); /** Tries to find all interfaces connected AnyTone radios. */ static QList detect(bool saveOnly=true); protected: /*** Identifies the radio. */ bool request_identifier(const ErrorStack &err = ErrorStack()); /** Enters program mode. */ bool enter_program_mode(const ErrorStack &err = ErrorStack()); /** Helper function to send a request and receives the associated response. */ template bool sendReceive(const Request &req, Response &res, const ErrorStack &err=ErrorStack()) { if (! req.send(this, err)) return false; return res.receive(this, err); } /** Send some data. */ bool send(const char *data, qint64 n, int timeout, const ErrorStack &err=ErrorStack()); /** Receives some data. */ bool receive(char *data, qint64 n, int timeout, const ErrorStack &err=ErrorStack()); protected: /** Current state, the interface is in. */ State _state; /** Radio info, identifying the radio. */ RadioInfo _info; /** Firmware version string. */ QByteArray _firmwareVersion; /** Codeplug memory range. */ QPair _codeplugMemory; /** Callsign memory range. */ QPair _callsignMemory; }; #endif // DM32UVINTERFACE_HH ================================================ FILE: lib/dm32uv_limits.cc ================================================ #include "dm32uv_limits.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "scanlist.hh" #include "zone.hh" #include "gpssystem.hh" #include "roamingzone.hh" #include "dm32uv_codeplug.hh" DM32UVLimits::DM32UVLimits(QObject *parent) : RadioLimits(true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 0; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; add("settings", new RadioLimitItem { { "introLine1", new RadioLimitString( -1, DM32UVCodeplug::GeneralSettingsElement::Limit::bootMessageLength(), RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString( -1, DM32UVCodeplug::GeneralSettingsElement::Limit::bootMessageLength(), RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 5}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::High) } }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(DM32UVCodeplug::PasswordSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } /// @todo check default radio ID. } ); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList{ { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString( 1, DM32UVCodeplug::RadioIdElement::Limit::nameLength(), RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } } ); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, DM32UVCodeplug::ContactBankElement::Limit::contacts(), new RadioLimitObject { { "name", new RadioLimitString( 1, DM32UVCodeplug::ContactElement::Limit::nameLength(), RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum { (unsigned) DMRContact::PrivateCall, (unsigned) DMRContact::GroupCall, (unsigned) DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, -1, -1, new RadioLimitIgnored() } } ); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, DM32UVCodeplug::GroupListBankElement::Limit::groupLists(), new RadioLimitObject { { "name", new RadioLimitString(1, -1, RadioLimitString::ASCII) }, // Name is not encoded. { "contacts", new RadioLimitGroupCallRefList(1, 32) } }) ); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, DM32UVCodeplug::ChannelBankElement::Limit::channels(), new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString( 1, DM32UVCodeplug::ChannelElement::Limit::nameLength(), RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored(nullptr, RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString( 1, DM32UVCodeplug::ChannelElement::Limit::nameLength(), RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::High), }}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, true)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored()}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } }, { AMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString( 1, DM32UVCodeplug::ChannelElement::Limit::nameLength(), RadioLimitString::ASCII) }, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(118.), Frequency::fromMHz(137.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(118.), Frequency::fromMHz(137.)}})}, {"power", new RadioLimitEnum {unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()} } } } ) ); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, DM32UVCodeplug::ZoneBankElement::Limit::zones(), new RadioLimitObject { { "name", new RadioLimitString( 1, DM32UVCodeplug::ZoneElement::Limit::nameLength(), RadioLimitString::Unicode) }, { "A", new RadioLimitRefList( 0, DM32UVCodeplug::ZoneElement::Limit::channels(), Channel::staticMetaObject) }, { "B", new RadioLimitRefList(0, 0, Channel::staticMetaObject) }, { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions } ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, DM32UVCodeplug::ScanListBankElement::Limit::scanLists(), new RadioLimitObject{ { "name", new RadioLimitString( 1, DM32UVCodeplug::ScanListElement::Limit::nameLength(), RadioLimitString::Unicode) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList( 0, DM32UVCodeplug::ScanListBankElement::Limit::scanLists(), Channel::staticMetaObject) } }) ); /* Define limits for positioning systems. */ add("positioning", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); } ================================================ FILE: lib/dm32uv_limits.hh ================================================ #ifndef DM32UVLIMITS_HH #define DM32UVLIMITS_HH #include "radiolimits.hh" /** Implements the configuration limits for the BTECH DR-1801UV. * @ingroup dm32uv */ class DM32UVLimits : public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit DM32UVLimits(QObject *parent=nullptr); }; #endif // DM32UV ================================================ FILE: lib/dmr6x2uv.cc ================================================ #include "gpssystem.hh" #include "userdatabase.hh" #include "dmr6x2uv.hh" #include "dmr6x2uv_codeplug.hh" #include "dmr6x2uv_limits.hh" #include "d868uv_callsigndb.hh" #include "logger.hh" DMR6X2UV::DMR6X2UV(AnytoneInterface *device, QObject *parent) : AnytoneRadio("BTECH DMR-6X2UV", device, parent), _limits(nullptr) { _codeplug = new DMR6X2UVCodeplug(this); _codeplug->clear(); _callsigns = new D868UVCallsignDB(this); // Get device info and determine supported TX frequency bands AnytoneInterface::RadioVariant info; if (_dev) _dev->getInfo(info); switch (info.bands) { case 0x00: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x01: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x02: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x03: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x04: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x05: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x06: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x07: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x08: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x09: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, info.version, this); break; case 0x0a: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x0b: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x0c: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, info.version, this); break; default: logInfo() << "Unknown band-code" << QString::number(int(info.bands), 16) << ": Do not check frequency range."; _limits = new DMR6X2UVLimits({}, {}, info.version, this); break; } } const RadioLimits & DMR6X2UV::limits() const { return *_limits; } RadioInfo DMR6X2UV::defaultRadioInfo() { return RadioInfo( RadioInfo::DMR6X2UV, "dmr6x2uv", "DMR-6X2UV", "BTECH", {AnytoneGD32Interface::interfaceInfo()}); } ================================================ FILE: lib/dmr6x2uv.hh ================================================ /** @defgroup dmr6x2uv BTECH DMR-6X2UV * Device specific classes for BTECH DMR-6X2UV. * * Although labeled BTECH (Baofeng USA), this device is basically a relabeled AnyTone AT-D868UV. * However, there are some minor differences in the codeplug format, hence it needs a separate * implementation. * * \image html dmr6x2uv.jpg "DMR-6X2UV" width=200px * \image latex dmr6x2uv.jpg "DMR-6X2UV" width=200px * * @ingroup anytone */ #ifndef DMR6X2UV_HH #define DMR6X2UV_HH #include "anytone_radio.hh" #include "anytone_interface.hh" /** Represents a BTECH DMR-6X2UV. * @ingroup dmr6x2uv */ class DMR6X2UV: public AnytoneRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit DMR6X2UV(AnytoneInterface *device=nullptr, QObject *parent=nullptr); const RadioLimits &limits() const; /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: RadioLimits *_limits; }; #endif // DMR6X2UV_HH ================================================ FILE: lib/dmr6x2uv2.cc ================================================ #include "gpssystem.hh" #include "userdatabase.hh" #include "dmr6x2uv2.hh" #include "dmr6x2uv_codeplug.hh" #include "dmr6x2uv_limits.hh" #include "d878uv2_callsigndb.hh" #include "logger.hh" DMR6X2UV2::DMR6X2UV2(AnytoneInterface *device, QObject *parent) : AnytoneRadio("BTECH DMR-6X2UV PRO", device, parent), _limits(nullptr) { _codeplug = new DMR6X2UVCodeplug(this); _codeplug->clear(); _callsigns = new D878UV2CallsignDB(this); // Get device info and determine supported TX frequency bands AnytoneInterface::RadioVariant info; if (_dev) _dev->getInfo(info); switch (info.bands) { case 0x00: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x01: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(420.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x02: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x03: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x04: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x05: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(440.), Frequency::fromMHz(480.)} }, info.version, this); break; case 0x06: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x07: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(446.), Frequency::fromMHz(447.)} }, info.version, this); break; case 0x08: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)} }, info.version, this); break; case 0x09: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(432.)} }, info.version, this); break; case 0x0a: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(148.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(450.)} }, info.version, this); break; case 0x0b: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)} }, { {Frequency::fromMHz(144.), Frequency::fromMHz(146.)}, {Frequency::fromMHz(430.), Frequency::fromMHz(440.)} }, info.version, this); break; case 0x0c: _limits = new DMR6X2UVLimits({ {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, { {Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(403.), Frequency::fromMHz(470.)} }, info.version, this); break; default: logInfo() << "Unknown band-code" << QString::number(int(info.bands), 16) << ": Do not check frequency range."; _limits = new DMR6X2UVLimits({}, {}, info.version, this); break; } } const RadioLimits & DMR6X2UV2::limits() const { return *_limits; } RadioInfo DMR6X2UV2::defaultRadioInfo() { return RadioInfo( RadioInfo::DMR6X2UV2, "dmr6x2uv2", "DMR-6X2UV PRO", "BTECH", {AnytoneGD32Interface::interfaceInfo()}); } ================================================ FILE: lib/dmr6x2uv2.hh ================================================ /** @defgroup dmr6x2uv2 BTECH DMR-6X2UV PRO * Device specific classes for BTECH DMR-6X2UV PRO. * * This is basically the same device as the DMR6X2 but with larger memory (larger calls-sign db), * FM APRS RX, Bluetooth and GPS roaming. * * \image html dmr6x2uv.jpg "DMR-6X2UV" width=200px * \image latex dmr6x2uv.jpg "DMR-6X2UV" width=200px * * @ingroup anytone */ #ifndef DMR6X2UV2_HH #define DMR6X2UV2_HH #include "anytone_radio.hh" #include "anytone_interface.hh" /** Represents a BTECH DMR-6X2UV PRO. * @ingroup dmr6x2uv2 */ class DMR6X2UV2: public AnytoneRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit DMR6X2UV2(AnytoneInterface *device=nullptr, QObject *parent=nullptr); const RadioLimits &limits() const; /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: RadioLimits *_limits; }; #endif // DMR6X2UV_HH ================================================ FILE: lib/dmr6x2uv2_codeplug.cc ================================================ #include "gpssystem.hh" #include "roamingchannel.hh" #include "config.hh" #include "intermediaterepresentation.hh" #include "dmr6x2uv2_codeplug.hh" #include "utils.hh" #include "logger.hh" /* ********************************************************************************************* * * Implementation of DMR6X2UV2Codeplug::GeneralSettingsElement::KeyFunction * ********************************************************************************************* */ uint8_t DMR6X2UV2Codeplug::GeneralSettingsElement::KeyFunction::encode(AnytoneKeySettingsExtension::KeyFunction func) { switch (func) { case AnytoneKeySettingsExtension::KeyFunction::Off: return (uint8_t)KeyFunction::Off; case AnytoneKeySettingsExtension::KeyFunction::Voltage: return (uint8_t)KeyFunction::Voltage; case AnytoneKeySettingsExtension::KeyFunction::Power: return (uint8_t)KeyFunction::Power; case AnytoneKeySettingsExtension::KeyFunction::Repeater: return (uint8_t)KeyFunction::Repeater; case AnytoneKeySettingsExtension::KeyFunction::Reverse: return (uint8_t)KeyFunction::Reverse; case AnytoneKeySettingsExtension::KeyFunction::Encryption: return (uint8_t)KeyFunction::Encryption; case AnytoneKeySettingsExtension::KeyFunction::Call: return (uint8_t)KeyFunction::Call; case AnytoneKeySettingsExtension::KeyFunction::VOX: return (uint8_t)KeyFunction::VOX; case AnytoneKeySettingsExtension::KeyFunction::ToggleVFO: return (uint8_t)KeyFunction::ToggleVFO; case AnytoneKeySettingsExtension::KeyFunction::SubPTT: return (uint8_t)KeyFunction::SubPTT; case AnytoneKeySettingsExtension::KeyFunction::Scan: return (uint8_t)KeyFunction::Scan; case AnytoneKeySettingsExtension::KeyFunction::WFM: return (uint8_t)KeyFunction::WFM; case AnytoneKeySettingsExtension::KeyFunction::Alarm: return (uint8_t)KeyFunction::Alarm; case AnytoneKeySettingsExtension::KeyFunction::RecordSwitch: return (uint8_t)KeyFunction::RecordSwitch; case AnytoneKeySettingsExtension::KeyFunction::Record: return (uint8_t)KeyFunction::Record; case AnytoneKeySettingsExtension::KeyFunction::SMS: return (uint8_t)KeyFunction::SMS; case AnytoneKeySettingsExtension::KeyFunction::Dial: return (uint8_t)KeyFunction::Dial; case AnytoneKeySettingsExtension::KeyFunction::GPSInformation: return (uint8_t)KeyFunction::GPSInformation; case AnytoneKeySettingsExtension::KeyFunction::Monitor: return (uint8_t)KeyFunction::Monitor; case AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel: return (uint8_t)KeyFunction::ToggleMainChannel; case AnytoneKeySettingsExtension::KeyFunction::HotKey1: return (uint8_t)KeyFunction::HotKey1; case AnytoneKeySettingsExtension::KeyFunction::HotKey2: return (uint8_t)KeyFunction::HotKey2; case AnytoneKeySettingsExtension::KeyFunction::HotKey3: return (uint8_t)KeyFunction::HotKey3; case AnytoneKeySettingsExtension::KeyFunction::HotKey4: return (uint8_t)KeyFunction::HotKey4; case AnytoneKeySettingsExtension::KeyFunction::HotKey5: return (uint8_t)KeyFunction::HotKey5; case AnytoneKeySettingsExtension::KeyFunction::HotKey6: return (uint8_t)KeyFunction::HotKey6; case AnytoneKeySettingsExtension::KeyFunction::WorkAlone: return (uint8_t)KeyFunction::WorkAlone; case AnytoneKeySettingsExtension::KeyFunction::SkipChannel: return (uint8_t)KeyFunction::SkipChannel; case AnytoneKeySettingsExtension::KeyFunction::DMRMonitor: return (uint8_t)KeyFunction::DMRMonitor; case AnytoneKeySettingsExtension::KeyFunction::SubChannel: return (uint8_t)KeyFunction::SubChannel; case AnytoneKeySettingsExtension::KeyFunction::PriorityZone: return (uint8_t)KeyFunction::PriorityZone; case AnytoneKeySettingsExtension::KeyFunction::VFOScan: return (uint8_t)KeyFunction::VFOScan; case AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality: return (uint8_t)KeyFunction::MICSoundQuality; case AnytoneKeySettingsExtension::KeyFunction::LastCallReply: return (uint8_t)KeyFunction::LastCallReply; case AnytoneKeySettingsExtension::KeyFunction::ChannelType: return (uint8_t)KeyFunction::ChannelType; case AnytoneKeySettingsExtension::KeyFunction::SimplexRepeater: return (uint8_t)KeyFunction::SimplexRepeater; case AnytoneKeySettingsExtension::KeyFunction::Ranging: return (uint8_t)KeyFunction::Ranging; case AnytoneKeySettingsExtension::KeyFunction::ChannelRanging: return (uint8_t)KeyFunction::ChannelRanging; case AnytoneKeySettingsExtension::KeyFunction::MaxVolume: return (uint8_t)KeyFunction::MaxVolume; case AnytoneKeySettingsExtension::KeyFunction::Slot: return (uint8_t)KeyFunction::Slot; case AnytoneKeySettingsExtension::KeyFunction::Squelch: return (uint8_t)KeyFunction::Squelch; case AnytoneKeySettingsExtension::KeyFunction::Roaming: return (uint8_t)KeyFunction::Roaming; case AnytoneKeySettingsExtension::KeyFunction::Zone: return (uint8_t)KeyFunction::Zone; case AnytoneKeySettingsExtension::KeyFunction::RoamingSet: return (uint8_t)KeyFunction::RoamingSet; case AnytoneKeySettingsExtension::KeyFunction::Mute: return (uint8_t)KeyFunction::Mute; case AnytoneKeySettingsExtension::KeyFunction::CtcssDcsSet: return (uint8_t)KeyFunction::CtcssDcsSet; case AnytoneKeySettingsExtension::KeyFunction::APRSTypeSwitch: return (uint8_t)KeyFunction::APRSType; case AnytoneKeySettingsExtension::KeyFunction::APRSSet: return (uint8_t)KeyFunction::APRSSet; case AnytoneKeySettingsExtension::KeyFunction::TBSTSend: return (uint8_t)KeyFunction::TBSTSend; case AnytoneKeySettingsExtension::KeyFunction::Bluetooth: return (uint8_t)KeyFunction::BluetoothToggle; case AnytoneKeySettingsExtension::KeyFunction::GPS: return (uint8_t)KeyFunction::GPSToggle; case AnytoneKeySettingsExtension::KeyFunction::ChannelName: return (uint8_t)KeyFunction::ChannelName; case AnytoneKeySettingsExtension::KeyFunction::APRSSend: return (uint8_t)KeyFunction::APRSSend; case AnytoneKeySettingsExtension::KeyFunction::APRSInfo: return (uint8_t)KeyFunction::APRSInfo; case AnytoneKeySettingsExtension::KeyFunction::GPSRoaming: return (uint8_t)KeyFunction::GPSRoaming; case AnytoneKeySettingsExtension::KeyFunction::CDTScan: return (uint8_t)KeyFunction::CTCSSScan; case AnytoneKeySettingsExtension::KeyFunction::DIMShut: return (uint8_t)KeyFunction::DIMShut; case AnytoneKeySettingsExtension::KeyFunction::SatPredict: return (uint8_t)KeyFunction::SatellitePredict; default: return (uint8_t)KeyFunction::Off; } } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::KeyFunction::decode(uint8_t code) { switch ((KeyFunctionCode)code) { case KeyFunction::Off: return AnytoneKeySettingsExtension::KeyFunction::Off; case KeyFunction::Voltage: return AnytoneKeySettingsExtension::KeyFunction::Voltage; case KeyFunction::Power: return AnytoneKeySettingsExtension::KeyFunction::Power; case KeyFunction::Repeater: return AnytoneKeySettingsExtension::KeyFunction::Repeater; case KeyFunction::Reverse: return AnytoneKeySettingsExtension::KeyFunction::Reverse; case KeyFunction::Encryption: return AnytoneKeySettingsExtension::KeyFunction::Encryption; case KeyFunction::Call: return AnytoneKeySettingsExtension::KeyFunction::Call; case KeyFunction::VOX: return AnytoneKeySettingsExtension::KeyFunction::VOX; case KeyFunction::ToggleVFO: return AnytoneKeySettingsExtension::KeyFunction::ToggleVFO; case KeyFunction::SubPTT: return AnytoneKeySettingsExtension::KeyFunction::SubPTT; case KeyFunction::Scan: return AnytoneKeySettingsExtension::KeyFunction::Scan; case KeyFunction::WFM: return AnytoneKeySettingsExtension::KeyFunction::WFM; case KeyFunction::Alarm: return AnytoneKeySettingsExtension::KeyFunction::Alarm; case KeyFunction::RecordSwitch: return AnytoneKeySettingsExtension::KeyFunction::RecordSwitch; case KeyFunction::Record: return AnytoneKeySettingsExtension::KeyFunction::Record; case KeyFunction::SMS: return AnytoneKeySettingsExtension::KeyFunction::SMS; case KeyFunction::Dial: return AnytoneKeySettingsExtension::KeyFunction::Dial; case KeyFunction::GPSInformation: return AnytoneKeySettingsExtension::KeyFunction::GPSInformation; case KeyFunction::Monitor: return AnytoneKeySettingsExtension::KeyFunction::Monitor; case KeyFunction::ToggleMainChannel: return AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel; case KeyFunction::HotKey1: return AnytoneKeySettingsExtension::KeyFunction::HotKey1; case KeyFunction::HotKey2: return AnytoneKeySettingsExtension::KeyFunction::HotKey2; case KeyFunction::HotKey3: return AnytoneKeySettingsExtension::KeyFunction::HotKey3; case KeyFunction::HotKey4: return AnytoneKeySettingsExtension::KeyFunction::HotKey4; case KeyFunction::HotKey5: return AnytoneKeySettingsExtension::KeyFunction::HotKey5; case KeyFunction::HotKey6: return AnytoneKeySettingsExtension::KeyFunction::HotKey6; case KeyFunction::WorkAlone: return AnytoneKeySettingsExtension::KeyFunction::WorkAlone; case KeyFunction::SkipChannel: return AnytoneKeySettingsExtension::KeyFunction::SkipChannel; case KeyFunction::DMRMonitor: return AnytoneKeySettingsExtension::KeyFunction::DMRMonitor; case KeyFunction::SubChannel: return AnytoneKeySettingsExtension::KeyFunction::SubChannel; case KeyFunction::PriorityZone: return AnytoneKeySettingsExtension::KeyFunction::PriorityZone; case KeyFunction::VFOScan: return AnytoneKeySettingsExtension::KeyFunction::VFOScan; case KeyFunction::MICSoundQuality: return AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality; case KeyFunction::LastCallReply: return AnytoneKeySettingsExtension::KeyFunction::LastCallReply; case KeyFunction::ChannelType: return AnytoneKeySettingsExtension::KeyFunction::ChannelType; case KeyFunction::SimplexRepeater: return AnytoneKeySettingsExtension::KeyFunction::SimplexRepeater; case KeyFunction::Ranging: return AnytoneKeySettingsExtension::KeyFunction::Ranging; case KeyFunction::ChannelRanging: return AnytoneKeySettingsExtension::KeyFunction::ChannelRanging; case KeyFunction::MaxVolume: return AnytoneKeySettingsExtension::KeyFunction::MaxVolume; case KeyFunction::Slot: return AnytoneKeySettingsExtension::KeyFunction::Slot; case KeyFunction::Squelch: return AnytoneKeySettingsExtension::KeyFunction::Squelch; case KeyFunction::Roaming: return AnytoneKeySettingsExtension::KeyFunction::Roaming; case KeyFunction::Zone: return AnytoneKeySettingsExtension::KeyFunction::Zone; case KeyFunction::RoamingSet: return AnytoneKeySettingsExtension::KeyFunction::RoamingSet; case KeyFunction::Mute: return AnytoneKeySettingsExtension::KeyFunction::Mute; case KeyFunction::CtcssDcsSet: return AnytoneKeySettingsExtension::KeyFunction::CtcssDcsSet; case KeyFunction::APRSType: return AnytoneKeySettingsExtension::KeyFunction::APRSTypeSwitch; case KeyFunction::APRSSet: return AnytoneKeySettingsExtension::KeyFunction::APRSSet; case KeyFunction::TBSTSend: return AnytoneKeySettingsExtension::KeyFunction::TBSTSend; case KeyFunction::BluetoothToggle: return AnytoneKeySettingsExtension::KeyFunction::Bluetooth; case KeyFunction::GPSToggle: return AnytoneKeySettingsExtension::KeyFunction::GPS; case KeyFunction::ChannelName: return AnytoneKeySettingsExtension::KeyFunction::ChannelName; case KeyFunction::APRSSend: return AnytoneKeySettingsExtension::KeyFunction::APRSSend; case KeyFunction::APRSInfo: return AnytoneKeySettingsExtension::KeyFunction::APRSInfo; case KeyFunction::GPSRoaming: return AnytoneKeySettingsExtension::KeyFunction::GPSRoaming; case KeyFunction::CTCSSScan: return AnytoneKeySettingsExtension::KeyFunction::CDTScan; case KeyFunction::DIMShut: return AnytoneKeySettingsExtension::KeyFunction::DIMShut; case KeyFunction::SatellitePredict: return AnytoneKeySettingsExtension::KeyFunction::SatPredict; default: return AnytoneKeySettingsExtension::KeyFunction::Off; } } /* ********************************************************************************************* * * Implementation of DMR6X2UV2Codeplug::GeneralSettingsElement * ********************************************************************************************* */ DMR6X2UV2Codeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, unsigned size) : DMR6X2UVCodeplug::GeneralSettingsElement(ptr, size) { // pass... } DMR6X2UV2Codeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : DMR6X2UVCodeplug::GeneralSettingsElement(ptr, GeneralSettingsElement::size()) { // pass... } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKeyAShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyAShort())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyAShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKeyBShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyBShort())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyBShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKeyCShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyCShort())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyCShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKey1Short() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1Short())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey1Short(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKey2Short() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2Short())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey2Short(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKeyALong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyALong())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyALong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKeyBLong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyBLong())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyBLong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKeyCLong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyCLong())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyCLong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKey1Long() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1Long())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey1Long(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UV2Codeplug::GeneralSettingsElement::funcKey2Long() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2Long())); } void DMR6X2UV2Codeplug::GeneralSettingsElement::setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey2Long(), KeyFunction::encode(func)); } bool DMR6X2UV2Codeplug::GeneralSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! DMR6X2UVCodeplug::GeneralSettingsElement::fromConfig(flags, ctx, err)) return false; // apply DMR-6X2UV Pro specific settings. AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return true; return true; } bool DMR6X2UV2Codeplug::GeneralSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { if (! DMR6X2UVCodeplug::GeneralSettingsElement::updateConfig(ctx, err)) return false; // Get or add settings extension AnytoneSettingsExtension *ext = nullptr; if (ctx.config()->settings()->anytoneExtension()) { ext = ctx.config()->settings()->anytoneExtension(); } else { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } return true; } /* ********************************************************************************************* * * Implementation of DMR6X2UV2Codeplug::ExtendedSettingsElement * ********************************************************************************************* */ DMR6X2UV2Codeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr, unsigned size) : DMR6X2UVCodeplug::ExtendedSettingsElement(ptr, size) { // pass... } DMR6X2UV2Codeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr) : DMR6X2UVCodeplug::ExtendedSettingsElement(ptr, ExtendedSettingsElement::size()) { // pass... } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::bluetoothEnabled() const { return 0 != getUInt8(Offset::bluetoothEnable()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::enableBluetooth(bool enable) { setUInt8(Offset::bluetoothEnable(), enable ? 0x01 : 0x00); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::internalMicEnabled() const { return 0 != getUInt8(Offset::internalMicEnable()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::enableInternalMic(bool enable) { setUInt8(Offset::internalMicEnable(), enable ? 0x01 : 0x00); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::internalSpeakerEnabled() const { return 0 != getUInt8(Offset::internalSpeakerEnable()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::enableInternalSpeaker(bool enable) { setUInt8(Offset::internalSpeakerEnable(), enable ? 0x01 : 0x00); } unsigned int DMR6X2UV2Codeplug::ExtendedSettingsElement::bluetoothMicGain() const { return getUInt8(Offset::bluetoothMicGain()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setBluetoothMicGain(unsigned int gain) { gain = std::min(4U, gain); setUInt8(Offset::bluetoothMicGain(), gain); } unsigned int DMR6X2UV2Codeplug::ExtendedSettingsElement::bluetoothSpeakerGain() const { return getUInt8(Offset::bluetoothSpeakerGain()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setBluetoothSpeakerGain(unsigned int gain) { gain = std::min(4U, gain); setUInt8(Offset::bluetoothSpeakerGain(), gain); } Interval DMR6X2UV2Codeplug::ExtendedSettingsElement::bluetoothHoldDuration() const { auto num = getUInt8(Offset::bluetoothHoldDuration()); if (num <= 30) return Interval::fromSeconds(num); else if (31 == num) return Interval::fromMinutes(1); else if (32 == num) return Interval::fromMinutes(2); /// @todo Implement interval=infinite. return Interval(); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setBluetoothHoldDuration(const Interval &dur) { if (dur.seconds() <=30) setUInt8(Offset::bluetoothHoldDuration(), dur.seconds()); else if (dur.seconds() <=60) setUInt8(Offset::bluetoothHoldDuration(), 31); else if (dur.seconds() <=120) setUInt8(Offset::bluetoothHoldDuration(), 32); else setUInt8(Offset::bluetoothHoldDuration(), 33); } Interval DMR6X2UV2Codeplug::ExtendedSettingsElement::bluetoothHoldDelay() const { auto num = getUInt8(Offset::bluetoothHoldDelay()); if (0 == num) return Interval::fromMilliseconds(30); return Interval::fromMilliseconds(500*num); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setBluetoothHoldDelay(const Interval &dur) { if (dur.milliseconds() <=30) setUInt8(Offset::bluetoothHoldDelay(), 0); else setUInt8(Offset::bluetoothHoldDelay(), dur.milliseconds()/500); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::bluetoothPTTLatchEnabled() const { return 0 != getUInt8(Offset::bluetoothPTTLatch()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::enableBluetoothPTTLatch(bool enable) { setUInt8(Offset::bluetoothPTTLatch(), enable ? 0x01 : 0x00); } Interval DMR6X2UV2Codeplug::ExtendedSettingsElement::bluetoothPTTSleepTimeout() const { auto num = getUInt8(Offset::bluetoothPTTSleepTimeout()); return Interval::fromMinutes(num); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setBluetoothPTTSleepTimeout(const Interval &dur) { if (dur.isNull() || (4 < dur.minutes())) setUInt8(Offset::bluetoothPTTSleepTimeout(), 0); else setUInt8(Offset::bluetoothPTTSleepTimeout(), dur.minutes()); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::fmIdleToneEnabled() const { return 0x00 != getUInt8(Offset::fmIdleTone()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::enableFMIdleTone(bool enable) { setUInt8(Offset::fmIdleTone(), enable ? 0x01 : 0x00); } Level DMR6X2UV2Codeplug::ExtendedSettingsElement::fmMicGain() const { return Level::fromValue(getUInt8(Offset::fmMicGain()), Limit::micGain()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setFMMicGain(Level gain) { setUInt8(Offset::fmMicGain(), gain.mapTo(Limit::micGain())); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::totWarningToneEnabled() const { return 0x00 != getUInt8(Offset::totWarningTone()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::enableTOTWarningTone(bool enable) { setUInt8(Offset::totWarningTone(), enable ? 0x01 : 0x00); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::atpcEnabled() const { return 0x00 != getUInt8(Offset::atpc()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::enableATPC(bool enable) { setUInt8(Offset::atpc(), enable ? 0x01 : 0x00); } GNSSSettings::Systems DMR6X2UV2Codeplug::ExtendedSettingsElement::gnss() const { switch ((GNSS) getUInt8(Offset::gnss())) { case GNSS::GPS: return GNSSSettings::System::GPS; case GNSS::BeiDou: return GNSSSettings::System::Beidou; case GNSS::Both: return GNSSSettings::System::GPS|GNSSSettings::System::Beidou; } return GNSSSettings::System::GPS; } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setGNSS(GNSSSettings::Systems gnss) { if (gnss.testFlag(GNSSSettings::System::GPS)) setUInt8(Offset::gnss(), (unsigned int)GNSS::GPS); if (gnss.testFlag(GNSSSettings::System::Beidou)) setUInt8(Offset::gnss(), (unsigned int)GNSS::BeiDou); if (gnss.testAnyFlags(GNSSSettings::System::GPS|GNSSSettings::System::Beidou)) setUInt8(Offset::gnss(), (unsigned int)GNSS::Both); } DMR6X2UV2Codeplug::ExtendedSettingsElement::ChannelIndexDisplay DMR6X2UV2Codeplug::ExtendedSettingsElement::channelIndexDisplay() const { return (ChannelIndexDisplay)getUInt8(Offset::displayChannelIndex()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setChannelIndexDisplay(ChannelIndexDisplay mode) { setUInt8(Offset::displayChannelIndex(), (unsigned int)mode); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::wxAlarmEnabled() const { return 0x00 != getUInt8(Offset::wxAlarm()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::enableWXAlarm(bool enable) { setUInt8(Offset::wxAlarm(), enable ? 0x01 : 0x00); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::locationSourceGNSS() const { return 0 == getUInt8(Offset::fixedLocationIndex()); } unsigned int DMR6X2UV2Codeplug::ExtendedSettingsElement::fixedLocationIndex() const { return getUInt8(Offset::fixedLocationIndex())-1; } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setFixedLocationIndex(unsigned int idx) { setUInt8(Offset::fixedLocationIndex(), idx+1); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setLocationSourceGNSS() { setUInt8(Offset::fixedLocationIndex(), 0); } Channel::Power DMR6X2UV2Codeplug::ExtendedSettingsElement::satPower() const { switch ((Power)getUInt8(Offset::satPower())) { case Power::Low: return Channel::Power::Low; case Power::Medium: return Channel::Power::Mid; case Power::High: return Channel::Power::High; case Power::Turbo: return Channel::Power::Max; } return Channel::Power::Low; } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setSatPower(Channel::Power power) { switch (power) { case Channel::Power::Min: case Channel::Power::Low: setUInt8(Offset::satPower(), (unsigned int)Power::Low); break; case Channel::Power::Mid: setUInt8(Offset::satPower(), (unsigned int)Power::Medium); break; case Channel::Power::High: setUInt8(Offset::satPower(), (unsigned int)Power::High); break; case Channel::Power::Max: setUInt8(Offset::satPower(), (unsigned int)Power::Turbo); break; } } unsigned int DMR6X2UV2Codeplug::ExtendedSettingsElement::satSquelchLevel() const { return 2*getUInt8(Offset::satSquelch()); } void DMR6X2UV2Codeplug::ExtendedSettingsElement::setSatSquelchLevel(unsigned int level) { level = std::min(10U, level); if (1 == level) level = 2; // otherwise level=1 => open setUInt8(Offset::satSquelch(), level/2); } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! DMR6X2UVCodeplug::ExtendedSettingsElement::fromConfig(flags, ctx, err)) { errMsg(err) << "Cannot encode extended settings for DMR-6X2UV codeplug."; return false; } // GPS settings setGNSS(ctx.config()->settings()->gnss()->systems()); // audio settings if (ctx.config()->settings()->audio()->fmMicGainEnabled()) setFMMicGain(ctx.config()->settings()->audio()->fmMicGain()); else setFMMicGain(ctx.config()->settings()->audio()->micGain()); // tone settings enableFMIdleTone(ctx.config()->settings()->tone()->channelIdle().setFlag(Channel::Type::FM)); // Encode device specific settings AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return true; // Bluetooth settings. enableBluetooth(ext->bluetoothSettings()->bluetoothEnabled()); enableInternalMic(ext->bluetoothSettings()->internalMicEnabled()); enableInternalSpeaker(ext->bluetoothSettings()->internalSpeakerEnabled()); setBluetoothMicGain(ext->bluetoothSettings()->micGain()); setBluetoothSpeakerGain(ext->bluetoothSettings()->speakerGain()); setBluetoothHoldDuration(ext->bluetoothSettings()->holdDuration()); setBluetoothHoldDelay(ext->bluetoothSettings()->holdDelay()); enableBluetoothPTTLatch(ext->bluetoothSettings()->pttLatch()); setBluetoothPTTSleepTimeout(ext->bluetoothSettings()->pttSleepTimer()); // Encode audio settings enableTOTWarningTone(ext->toneSettings()->totNotification()); enableWXAlarm(ext->toneSettings()->wxAlarm()); // Power settings enableATPC(ext->powerSaveSettings()->atpc()); // Display settings setChannelIndexDisplay(ext->displaySettings()->showGlobalChannelNumber() ? ChannelIndexDisplay::GlobalIndex : ChannelIndexDisplay::IndexWithinZone); // Sat settings setSatPower(ext->satelliteSettings()->power()); setSatSquelchLevel(ext->satelliteSettings()->squelch()); return true; } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { if (! DMR6X2UVCodeplug::ExtendedSettingsElement::updateConfig(ctx, err)) { errMsg(err) << "Cannot decode extended settings for DMR-6X2UV codeplug"; return false; } // Store GPS settings ctx.config()->settings()->gnss()->setSystems(gnss()); // Store audio settings if (ctx.config()->settings()->audio()->micGain() == fmMicGain()) ctx.config()->settings()->audio()->disableFMMicGain(); else ctx.config()->settings()->audio()->setFMMicGain(fmMicGain()); // Store tone settings ctx.config()->settings()->tone()->setChannelIdle( ctx.config()->settings()->tone()->channelIdle() | (fmIdleToneEnabled() ? Channel::Type::FM : Channel::Type::None) ); AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Bluetooth settings ext->bluetoothSettings()->enableBluetooth(bluetoothEnabled()); ext->bluetoothSettings()->enableInternalMic(internalMicEnabled()); ext->bluetoothSettings()->enableInternalSpeaker(internalSpeakerEnabled()); ext->bluetoothSettings()->setMicGain(bluetoothMicGain()); ext->bluetoothSettings()->setSpeakerGain(bluetoothSpeakerGain()); ext->bluetoothSettings()->setHoldDuration(bluetoothHoldDuration()); ext->bluetoothSettings()->setHoldDelay(bluetoothHoldDelay()); ext->bluetoothSettings()->enablePTTLatch(bluetoothPTTLatchEnabled()); ext->bluetoothSettings()->setPTTSleepTimer(bluetoothPTTSleepTimeout()); ext->toneSettings()->enableTOTNotification(totWarningToneEnabled()); ext->toneSettings()->enableWXAlarm(wxAlarmEnabled()); // Power settings ext->powerSaveSettings()->enableATPC(atpcEnabled()); // Display settings ext->displaySettings()->enableShowGlobalChannelNumber( ChannelIndexDisplay::GlobalIndex == channelIndexDisplay()); // Satellite settings ext->satelliteSettings()->setPower(satPower()); ext->satelliteSettings()->setSquelch(satSquelchLevel()); return true; } bool DMR6X2UV2Codeplug::ExtendedSettingsElement::linkConfig(Context &ctx, const ErrorStack &err) { if (! DMR6X2UVCodeplug::ExtendedSettingsElement::linkConfig(ctx, err)) { errMsg(err) << "Cannot link extended settings for DMR-6X2UV codeplug"; return false; } AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return false; return true; } /* ********************************************************************************************* * * Implementation of DMR6X2UV2Codeplug::APRSFilterElement * ********************************************************************************************* */ DMR6X2UV2Codeplug::APRSFilterElement::APRSFilterElement(uint8_t *ptr, size_t size) : Element{ptr, size} { // pass } DMR6X2UV2Codeplug::APRSFilterElement::APRSFilterElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } void DMR6X2UV2Codeplug::APRSFilterElement::clear() { memset(_data, 0, _size); } bool DMR6X2UV2Codeplug::APRSFilterElement::isValid() const { if (! Element::isValid()) return false; return 0 != getUInt8(Offset::valid()); } QString DMR6X2UV2Codeplug::APRSFilterElement::call() const { return readASCII(Offset::call(), Limit::call(), 0x00); } void DMR6X2UV2Codeplug::APRSFilterElement::setCall(const QString &call) { writeASCII(Offset::call(), call, Limit::call(), 0x00); } unsigned int DMR6X2UV2Codeplug::APRSFilterElement::ssid() const { return getUInt8(Offset::ssid()); } void DMR6X2UV2Codeplug::APRSFilterElement::setSSID(unsigned int ssid) { setUInt8(Offset::ssid(), ssid); } /* ********************************************************************************************* * * Implementation of DMR6X2UV2Codeplug::GPSRoamingZoneElement * ********************************************************************************************* */ DMR6X2UV2Codeplug::GPSRoamingZoneElement::GPSRoamingZoneElement(uint8_t *ptr, size_t size) : Element{ptr, size} { // pass... } DMR6X2UV2Codeplug::GPSRoamingZoneElement::GPSRoamingZoneElement(uint8_t *ptr) : Element{ptr, size()} { // pass... } void DMR6X2UV2Codeplug::GPSRoamingZoneElement::clear() { memset(_data, 0, size()); } bool DMR6X2UV2Codeplug::GPSRoamingZoneElement::isValid() const { return Element::isValid() && (0 != getUInt8(Offset::valid())); } bool DMR6X2UV2Codeplug::GPSRoamingZoneElement::hasRoamingZoneIndex() const { return 0xff != getUInt8(Offset::zoneIndex()); } unsigned int DMR6X2UV2Codeplug::GPSRoamingZoneElement::roamingZoneIndex() const { return getUInt8(Offset::zoneIndex()); } void DMR6X2UV2Codeplug::GPSRoamingZoneElement::setRoamingZoneIndex(unsigned int idx) { setUInt8(Offset::zoneIndex(), idx); } void DMR6X2UV2Codeplug::GPSRoamingZoneElement::clearRoamingZoneIndex() { setUInt8(Offset::zoneIndex(), 0xff); } QGeoCoordinate DMR6X2UV2Codeplug::GPSRoamingZoneElement::coordinate() const { double latDeg = getUInt8(Offset::latDegrees()), latMin = getUInt8(Offset::latMinutes()), latSec = getUInt8(Offset::latSeconds()), lonDeg = getUInt8(Offset::lonDegrees()), lonMin = getUInt8(Offset::lonMinutes()), lonSec = getUInt8(Offset::lonSeconds()); double lat = latDeg + (latMin + (latSec/100))/60, lon = lonDeg + (lonMin + (lonSec/100))/60; if (getUInt8(Offset::latHemisphere())) lat *= -1; if (getUInt8(Offset::lonHemisphere())) lon *= -1; return QGeoCoordinate(lat, lon); } void DMR6X2UV2Codeplug::GPSRoamingZoneElement::setCoordinate(const QGeoCoordinate &coor) { double lat = coor.latitude(), lon = coor.longitude(); setUInt8(Offset::latHemisphere(), (lat<0) ? 1 : 0); lat = std::abs(lat); setUInt8(Offset::lonHemisphere(), (lon<0) ? 1 : 0); lon = std::abs(lon); setUInt8(Offset::latDegrees(), lat); lat -= int(lat); lat *=60; setUInt8(Offset::lonDegrees(), lon); lon -= int(lon); lon *=60; setUInt8(Offset::latMinutes(), lat); lat -= int(lat); lat *=100; setUInt8(Offset::lonMinutes(), lon); lon -= int(lon); lon *=100; setUInt8(Offset::latSeconds(), lat); setUInt8(Offset::lonSeconds(), lon); } unsigned int DMR6X2UV2Codeplug::GPSRoamingZoneElement::radius() const { return getUInt16_le(Offset::radius()); } void DMR6X2UV2Codeplug::GPSRoamingZoneElement::setRadius(unsigned int radius) { setUInt16_le(Offset::radius(), radius); } /* ********************************************************************************************* * * Implementation of DMR6X2UV2Codeplug * ********************************************************************************************* */ DMR6X2UV2Codeplug::DMR6X2UV2Codeplug(const QString &label, QObject *parent) : DMR6X2UVCodeplug(label, parent) { // pass... } DMR6X2UV2Codeplug::DMR6X2UV2Codeplug(QObject *parent) : DMR6X2UVCodeplug("BTECH DMR-6X2UV PRO", parent) { // pass... } void DMR6X2UV2Codeplug::allocateUpdated() { image(0).addElement(Offset::aprsFilterBank(), Limit::aprsFilter()*APRSFilterElement::size()); image(0).addElement(Offset::gpsRoamingZones(), Limit::gpsRoamingZones()*GPSRoamingZoneElement::size()); } void DMR6X2UV2Codeplug::allocateForEncoding() { DMR6X2UVCodeplug::allocateForEncoding(); } void DMR6X2UV2Codeplug::allocateForDecoding() { DMR6X2UVCodeplug::allocateForDecoding(); } bool DMR6X2UV2Codeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! DMR6X2UVCodeplug::encodeElements(flags, ctx, err)) return false; return true; } bool DMR6X2UV2Codeplug::createElements(Context &ctx, const ErrorStack &err) { if (! DMR6X2UVCodeplug::createElements(ctx, err)) return false; return true; } void DMR6X2UV2Codeplug::allocateGeneralSettings() { image(0).addElement(Offset::settings(), GeneralSettingsElement::size()); image(0).addElement(Offset::settingsExtension(), ExtendedSettingsElement::size()); } bool DMR6X2UV2Codeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! GeneralSettingsElement(data(Offset::settings())).fromConfig(flags, ctx, err)) { errMsg(err) << "Cannot encode general settings element."; return false; } if (! ExtendedSettingsElement(data(Offset::settingsExtension())).fromConfig(flags, ctx)) { errMsg(err) << "Cannot encode extended settings element."; return false; } return true; } bool DMR6X2UV2Codeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { if (! GeneralSettingsElement(data(Offset::settings())).updateConfig(ctx, err)) { errMsg(err) << "Cannot decode general settings element."; return false; } if (! ExtendedSettingsElement(data(Offset::settingsExtension())).updateConfig(ctx)) { errMsg(err) << "Cannot decode extended settings element."; return false; } return true; } bool DMR6X2UV2Codeplug::linkGeneralSettings(Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings())).linkSettings(ctx.config()->settings(), ctx, err); } ================================================ FILE: lib/dmr6x2uv2_codeplug.hh ================================================ #ifndef DMR6X2UV2CODEPLUG_HH #define DMR6X2UV2CODEPLUG_HH #include "dmr6x2uv_codeplug.hh" #include "d878uv_codeplug.hh" /** Represents the device specific binary codeplug for BTECH DMR-6X2UV PRO radios. * * This codeplug implementation is compatible with firmware revision 1.21. * * For details, see https://dmr-tools.github.io/codeplugs * * @ingroup dmr6x2uv */ class DMR6X2UV2Codeplug : public DMR6X2UVCodeplug { Q_OBJECT protected: public: /** General settings element for the DMR-6X2UV PRO. * * Extends the @c DMR6X2UVCodeplug::GeneralSettingsElement by the device specific settings for * the BTECH DMR-6X2UV PRO. */ class GeneralSettingsElement: public DMR6X2UVCodeplug::GeneralSettingsElement { protected: /** Device specific encoding of the key functions. */ struct KeyFunction { public: /** Encodes key function. */ static uint8_t encode(AnytoneKeySettingsExtension::KeyFunction tone); /** Decodes key function. */ static AnytoneKeySettingsExtension::KeyFunction decode(uint8_t code); protected: /** Device specific key functions. */ typedef enum { Off = 0x00, Voltage = 0x01, Power = 0x02, Repeater = 0x03, Reverse = 0x04, Encryption = 0x05, Call = 0x06, VOX = 0x07, ToggleVFO = 0x08, SubPTT = 0x09, Scan = 0x0a, WFM = 0x0b, Alarm = 0x0c, RecordSwitch = 0x0d, Record = 0x0e, SMS = 0x0f, Dial = 0x10, GPSInformation = 0x11, Monitor = 0x12, ToggleMainChannel = 0x13, HotKey1 = 0x14, HotKey2 = 0x15, HotKey3 = 0x16, HotKey4 = 0x17, HotKey5 = 0x18, HotKey6 = 0x19, WorkAlone = 0x1a, SkipChannel = 0x1b, DMRMonitor = 0x1c, SubChannel = 0x1d, PriorityZone = 0x1e, VFOScan = 0x1f, MICSoundQuality = 0x20, LastCallReply = 0x21, ChannelType = 0x22, SimplexRepeater = 0x23, Ranging = 0x24, ChannelRanging = 0x25, MaxVolume = 0x26, Slot = 0x27, Squelch = 0x28, Roaming = 0x29, Zone = 0x2a, RoamingSet = 0x2b, Mute = 0x02c, CtcssDcsSet=0x2d, APRSType = 0x2e, APRSSet = 0x2f, TBSTSend = 0x30, BluetoothToggle = 0x31, GPSToggle = 0x32, ChannelName = 0x33, APRSSend = 0x34, APRSInfo = 0x35, GPSRoaming = 0x36, CTCSSScan = 0x37, DIMShut = 0x38, SatellitePredict = 0x39 } KeyFunctionCode; }; protected: /** Hidden Constructor. */ GeneralSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit GeneralSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00e0; } AnytoneKeySettingsExtension::KeyFunction funcKeyAShort() const; void setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyBShort() const; void setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyCShort() const; void setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey1Short() const; void setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey2Short() const; void setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyALong() const; void setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyBLong() const; void setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKeyCLong() const; void setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey1Long() const; void setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func); AnytoneKeySettingsExtension::KeyFunction funcKey2Long() const; void setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func); bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err); bool updateConfig(Context &ctx, const ErrorStack &err); protected: /** Some internal used offsets within the element. */ struct Offset: public D868UVCodeplug::GeneralSettingsElement::Offset { /// @cond DO_NOT_DOCUMENT /// @endcond }; }; /** Implements some settings extension for the BTECH DMR-6X2UV PRO. */ class ExtendedSettingsElement: public DMR6X2UVCodeplug::ExtendedSettingsElement { public: enum class GNSS { GPS=0, BeiDou=1, Both=2 }; enum class ChannelIndexDisplay { GlobalIndex = 0, IndexWithinZone = 1 }; enum class Power { Low = 0, Medium = 1, High = 2, Turbo = 3 }; protected: /** Hidden Constructor. */ ExtendedSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ExtendedSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0030; } /** Returns @c true, if bluetooth is enabled. */ virtual bool bluetoothEnabled() const; /** Enables/disables bluetooth. */ virtual void enableBluetooth(bool enable); /** Returns @c true, if the internal mic is enabled additionally to the bluetooth input. */ virtual bool internalMicEnabled() const; /** Enables/disables the internal mic additionally to the bluetooth input. */ virtual void enableInternalMic(bool enable); /** Returns @c true, if the internal speaker is enabled additionally to the bluetooth output. */ virtual bool internalSpeakerEnabled() const; /** Enables/disables the internal speaker additionally to the bluetooth output. */ virtual void enableInternalSpeaker(bool enable); /** Returns the bluetooth mic gain. Valid values are 0,...,4. */ virtual unsigned int bluetoothMicGain() const; /** Sets the bluetooth mic gain. Valid values are 0,...,4. */ virtual void setBluetoothMicGain(unsigned int gain); /** Returns the bluetooth speaker gain. Valid values are 0,...,4. */ virtual unsigned int bluetoothSpeakerGain() const; /** Sets the bluetooth speaker gain. Valid values are 0,...,4. */ virtual void setBluetoothSpeakerGain(unsigned int gain); /** Returns the hold duration. */ virtual Interval bluetoothHoldDuration() const; /** Sets the hold duration. */ virtual void setBluetoothHoldDuration(const Interval &dur); /** Returns the hold delay. */ virtual Interval bluetoothHoldDelay() const; /** Sets the hold duration. */ virtual void setBluetoothHoldDelay(const Interval &dur); /** Returns @c true, if PTT latches. */ virtual bool bluetoothPTTLatchEnabled() const; /** Enable/disable bluetooth PTT latch. */ virtual void enableBluetoothPTTLatch(bool enable); /** Returns the bluetooth PTT sleep timeout. */ virtual Interval bluetoothPTTSleepTimeout() const; /** Sets the bluetooth PTT sleep timeout. */ virtual void setBluetoothPTTSleepTimeout(const Interval &dur); /** Returns @c true if the FM channel idle tone is enabled. */ virtual bool fmIdleToneEnabled() const; /** Enables/disables FM channel idle tone. */ virtual void enableFMIdleTone(bool enable); /** Returns the FM mic gain [1-10]. */ virtual Level fmMicGain() const; /** Sets the FM mic gain [1-10]. */ virtual void setFMMicGain(Level gain); /** Returns @c true, if transmit timeout warning tone is enabled. */ virtual bool totWarningToneEnabled() const; /** Enables/disables transmit timeout warning tone. */ virtual void enableTOTWarningTone(bool enable); /** Returns @c true, if ATPC is enabled. */ virtual bool atpcEnabled() const; /** Enables/disables ATPC. */ virtual void enableATPC(bool enable); /** Returns enabled GNSSs */ virtual GNSSSettings::Systems gnss() const; /** Sets enabled GNSSs */ virtual void setGNSS(GNSSSettings::Systems gnss); /** Returns the channel index display mode. */ virtual ChannelIndexDisplay channelIndexDisplay() const; /** Sets the channel index display mode. */ virtual void setChannelIndexDisplay(ChannelIndexDisplay mode); /** Returns @c true if the weather alarm is enabled. */ virtual bool wxAlarmEnabled() const; /** Enables/disables the weather alarm. */ virtual void enableWXAlarm(bool enable); /** Returns @c true if the location is taken from GNSS, otherwise a fixed location is used. */ virtual bool locationSourceGNSS() const; /** Returns the fixed location index. */ virtual unsigned int fixedLocationIndex() const; /** Sets the fixed location index. */ virtual void setFixedLocationIndex(unsigned int idx); /** Sets the location source to GNSS. */ virtual void setLocationSourceGNSS(); /** Returns the power setting for satellite mode. */ virtual Channel::Power satPower() const; /** Sets the power level for satellite mode. */ virtual void setSatPower(Channel::Power power); /** Returns the squelch level for satellite mode [0,1-10], 0=open.*/ virtual unsigned int satSquelchLevel() const; /** Sets the squelch level for satellite mode [0,1-10], 0=open. */ virtual void setSatSquelchLevel(unsigned int level); bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkConfig(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: DMR6X2UVCodeplug::ExtendedSettingsElement::Limit { static constexpr Range micGain() { return {0,4}; } ///< Valid mic gain settings. }; protected: /** Some internal offset within the codeplug element. */ struct Offset { /// @cond DO_NOT_DOCUEMNT static constexpr unsigned int bluetoothEnable() { return 0x0016; } static constexpr unsigned int internalMicEnable() { return 0x0017; } static constexpr unsigned int internalSpeakerEnable() { return 0x0018; } static constexpr unsigned int bluetoothMicGain() { return 0x0019; } static constexpr unsigned int bluetoothSpeakerGain() { return 0x001a; } static constexpr unsigned int bluetoothHoldDuration() { return 0x001b; } static constexpr unsigned int bluetoothHoldDelay() { return 0x001c; } static constexpr unsigned int bluetoothPTTLatch() { return 0x001d; } static constexpr unsigned int bluetoothPTTSleepTimeout() { return 0x001e; } static constexpr unsigned int fmIdleTone() { return 0x001f; } static constexpr unsigned int fmMicGain() { return 0x0020; } static constexpr unsigned int totWarningTone() { return 0x0021; } static constexpr unsigned int atpc() { return 0x0022; } static constexpr unsigned int gnss() { return 0x0023; } static constexpr unsigned int displayChannelIndex() { return 0x0024; } static constexpr unsigned int wxAlarm() { return 0x0026; } static constexpr unsigned int fixedLocationIndex() { return 0x0027; } static constexpr unsigned int satPower() { return 0x0028; } static constexpr unsigned int satSquelch() { return 0x0029; } /// @endcond }; }; /** Implements a single APRS RX filter. */ class APRSFilterElement: public Element { protected: /** Hidden constructor. */ APRSFilterElement(uint8_t *ptr, size_t size); public: /** Constructor. */ APRSFilterElement(uint8_t *ptr); void clear() override; bool isValid() const override; /** The size of the element. */ static constexpr unsigned int size() { return 0x08; } /** Returns the call. */ virtual QString call() const; /** Sets the callsign. */ virtual void setCall(const QString &call); /** Returns the SSID. */ virtual unsigned int ssid() const; /** Sets the SSID. */ virtual void setSSID(unsigned int ssid); public: /** Some limits for the element. */ struct Limit: public Element::Limit { /** Maximum call sign length. */ static constexpr unsigned int call() { return 6; } }; protected: /** Internal Offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int valid() { return 0x0000; } static constexpr unsigned int call() { return 0x0001; } static constexpr unsigned int ssid() { return 0x0007; } /// @endcond }; }; /** Implements a GPS roaming zone. */ class GPSRoamingZoneElement: public Element { protected: /** Hidden constructor. */ GPSRoamingZoneElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GPSRoamingZoneElement(uint8_t *ptr); /** Size of the element. */ static constexpr unsigned int size() { return 0x0020; } void clear() override; bool isValid() const override; /** Returns @c true, if a roaming zone is set. */ virtual bool hasRoamingZoneIndex() const; /** Returns the roaming zone index. */ virtual unsigned int roamingZoneIndex() const; /** Sets the roaming zone index. */ virtual void setRoamingZoneIndex(unsigned int idx); /** Clears the roaming zone index. */ virtual void clearRoamingZoneIndex(); /** Returns the center of the roaming zone. */ virtual QGeoCoordinate coordinate() const; /** Sets the center of the roaming zone. */ virtual void setCoordinate(const QGeoCoordinate &coor); /** Returns the radius in unknown units. */ virtual unsigned int radius() const; /** Sets the radius in unknown units. */ virtual void setRadius(unsigned int radius); protected: /** Internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int valid() { return 0x0000; } static constexpr unsigned int zoneIndex() { return 0x0001; } static constexpr unsigned int latDegrees() { return 0x0002; } static constexpr unsigned int latMinutes() { return 0x0003; } static constexpr unsigned int latSeconds() { return 0x0004; } static constexpr unsigned int latHemisphere() { return 0x0005; } static constexpr unsigned int lonDegrees() { return 0x0006; } static constexpr unsigned int lonMinutes() { return 0x0007; } static constexpr unsigned int lonSeconds() { return 0x0008; } static constexpr unsigned int lonHemisphere() { return 0x0009; } static constexpr unsigned int radius() { return 0x000c; } /// @endcond }; }; public: /** Hidden constructor. */ explicit DMR6X2UV2Codeplug(const QString &label, QObject *parent=nullptr); public: /** Empty constructor. */ explicit DMR6X2UV2Codeplug(QObject *parent=nullptr); public: /** Some limits for the codeplug. */ struct Limit: public DMR6X2UVCodeplug::Limit { /** Maximum number of APRS receive filters. */ static constexpr unsigned int aprsFilter() { return 32; } /** Maximum number of GPS roaming zones. */ static constexpr unsigned int gpsRoamingZones() { return 32; } }; protected: void allocateUpdated() override; void allocateForEncoding() override; void allocateForDecoding() override; bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) override; bool createElements(Context &ctx, const ErrorStack &err=ErrorStack()) override; void allocateGeneralSettings() override; bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) override; bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) override; bool linkGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) override; protected: /** Some offsets within the codeplug. */ struct Offset: public DMR6X2UVCodeplug::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int aprsFilterBank() { return 0x02501800; } static constexpr unsigned int gpsRoamingZones() { return 0x02504000; } /// @endcond }; }; #endif // DMR6X2UV2CODEPLUG_HH ================================================ FILE: lib/dmr6x2uv2_limits.cc ================================================ #include "dmr6x2uv2_limits.hh" #include "dmr6x2uv2_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingzone.hh" #include "anytone_satelliteconfig.hh" DMR6X2UV2Limits::DMR6X2UV2Limits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent) : AnytoneLimits(hardwareRevision, "V100", true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 500000; // Define limits for satellite config _hasSatelliteConfig = true; _satelliteConfigImplemented = true; _numSatellites = AnytoneSatelliteConfig::Limit::satellites(); /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(DMR6X2UV2Codeplug::BootSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 250, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, -1, -1, new RadioLimitIgnored() } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1, 64) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 4000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRef(FMAPRSSystem::staticMetaObject)}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false)}, {"aprs", new RadioLimitObjRef(PositionReportingSystem::staticMetaObject, true)}, {"roaming", new RadioLimitObjRef(RoamingZone::staticMetaObject, true) }, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 250, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } })); /* Handle positioning systems. */ add("positioning", new RadioLimitList{ { DMRAPRSSystem::staticMetaObject, 0, 8, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, 1, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, FMChannel::staticMetaObject}, false) }, { "icon", new RadioLimitEnum{} }, { "message", new RadioLimitString(0, 60, RadioLimitString::ASCII) } ///@todo extend APRSSystem to expose other settings as properties. }} } ); /* Handle roaming zones. */ add("roaming", new RadioLimitList(RoamingZone::staticMetaObject, 0, 64, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "channels", new RadioLimitRefList(0, 64, DMRChannel::staticMetaObject) } } ) ); } ================================================ FILE: lib/dmr6x2uv2_limits.hh ================================================ #ifndef DMR6X2UV2LIMITS_HH #define DMR6X2UV2LIMITS_HH #include "anytone_limits.hh" /** Implements the limits for the BTECH DMR-6X2UV PRO. * @ingroup dmr6x2uv2 */ class DMR6X2UV2Limits: public AnytoneLimits { Q_OBJECT public: /** Constructor. */ DMR6X2UV2Limits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent=nullptr); }; #endif // D878UVLIMITS_HH ================================================ FILE: lib/dmr6x2uv_codeplug.cc ================================================ #include "gpssystem.hh" #include "roamingchannel.hh" #include "config.hh" #include "intermediaterepresentation.hh" #include "dmr6x2uv_codeplug.hh" #include "utils.hh" #include "logger.hh" /* ******************************************************************************************** * * Implementation of DMR6X2UVCodeplug::Color * ******************************************************************************************** */ AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::Color::decode(uint8_t code) { switch((CodedColor) code) { case Orange: return AnytoneDisplaySettingsExtension::Color::Orange; case Red: return AnytoneDisplaySettingsExtension::Color::Red; case Yellow: return AnytoneDisplaySettingsExtension::Color::Yellow; case Green: return AnytoneDisplaySettingsExtension::Color::Green; case Turquoise: return AnytoneDisplaySettingsExtension::Color::Turquoise; case Blue: return AnytoneDisplaySettingsExtension::Color::Blue; case White: return AnytoneDisplaySettingsExtension::Color::White; case Black: return AnytoneDisplaySettingsExtension::Color::Black; default: break; } return AnytoneDisplaySettingsExtension::Color::White; } uint8_t DMR6X2UVCodeplug::Color::encode(AnytoneDisplaySettingsExtension::Color color) { switch(color) { case AnytoneDisplaySettingsExtension::Color::Orange: return (uint8_t) Orange; case AnytoneDisplaySettingsExtension::Color::Red: return (uint8_t) Red; case AnytoneDisplaySettingsExtension::Color::Yellow: return (uint8_t) Yellow; case AnytoneDisplaySettingsExtension::Color::Green: return (uint8_t) Green; case AnytoneDisplaySettingsExtension::Color::Turquoise: return (uint8_t) Turquoise; case AnytoneDisplaySettingsExtension::Color::Blue: return (uint8_t) Blue; case AnytoneDisplaySettingsExtension::Color::White: return (uint8_t) White; case AnytoneDisplaySettingsExtension::Color::Black: return (uint8_t) Black; default: break; } return (uint8_t) White; } /* ******************************************************************************************** * * Implementation of DMR6X2UVCodeplug::BackgroundColor * ******************************************************************************************** */ AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::BackgroundColor::decode(uint8_t code) { switch((CodedColor) code) { case Black: return AnytoneDisplaySettingsExtension::Color::Black; case Blue: return AnytoneDisplaySettingsExtension::Color::Blue; default: break; } return AnytoneDisplaySettingsExtension::Color::Black; } uint8_t DMR6X2UVCodeplug::BackgroundColor::encode(AnytoneDisplaySettingsExtension::Color color) { switch(color) { case AnytoneDisplaySettingsExtension::Color::Black: return (uint8_t) Black; case AnytoneDisplaySettingsExtension::Color::Blue: return (uint8_t) Blue; default: break; } return (uint8_t) Black; } /* ******************************************************************************************** * * Implementation of DMR6X2UVCodeplug::FontColor * ******************************************************************************************** */ AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::FontColor::decode(uint8_t code) { switch((CodedColor) code) { case Orange: return AnytoneDisplaySettingsExtension::Color::Orange; case Red: return AnytoneDisplaySettingsExtension::Color::Red; case Yellow: return AnytoneDisplaySettingsExtension::Color::Yellow; case Green: return AnytoneDisplaySettingsExtension::Color::Green; case Turquoise: return AnytoneDisplaySettingsExtension::Color::Turquoise; case Blue: return AnytoneDisplaySettingsExtension::Color::Blue; case White: return AnytoneDisplaySettingsExtension::Color::White; case Black: return AnytoneDisplaySettingsExtension::Color::Black; default: break; } return AnytoneDisplaySettingsExtension::Color::White; } uint8_t DMR6X2UVCodeplug::FontColor::encode(AnytoneDisplaySettingsExtension::Color color) { switch(color) { case AnytoneDisplaySettingsExtension::Color::Orange: return (uint8_t) Orange; case AnytoneDisplaySettingsExtension::Color::Red: return (uint8_t) Red; case AnytoneDisplaySettingsExtension::Color::Yellow: return (uint8_t) Yellow; case AnytoneDisplaySettingsExtension::Color::Green: return (uint8_t) Green; case AnytoneDisplaySettingsExtension::Color::Turquoise: return (uint8_t) Turquoise; case AnytoneDisplaySettingsExtension::Color::Blue: return (uint8_t) Blue; case AnytoneDisplaySettingsExtension::Color::White: return (uint8_t) White; case AnytoneDisplaySettingsExtension::Color::Black: return (uint8_t) Black; default: break; } return (uint8_t) White; } /* ********************************************************************************************* * * Implementation of DMR6X2UVCodeplug::GeneralSettingsElement::KeyFunction * ********************************************************************************************* */ uint8_t DMR6X2UVCodeplug::GeneralSettingsElement::KeyFunction::encode(AnytoneKeySettingsExtension::KeyFunction func) { switch (func) { case AnytoneKeySettingsExtension::KeyFunction::Off: return (uint8_t)KeyFunction::Off; case AnytoneKeySettingsExtension::KeyFunction::Voltage: return (uint8_t)KeyFunction::Voltage; case AnytoneKeySettingsExtension::KeyFunction::Power: return (uint8_t)KeyFunction::Power; case AnytoneKeySettingsExtension::KeyFunction::Repeater: return (uint8_t)KeyFunction::Repeater; case AnytoneKeySettingsExtension::KeyFunction::Reverse: return (uint8_t)KeyFunction::Reverse; case AnytoneKeySettingsExtension::KeyFunction::Encryption: return (uint8_t)KeyFunction::Encryption; case AnytoneKeySettingsExtension::KeyFunction::Call: return (uint8_t)KeyFunction::Call; case AnytoneKeySettingsExtension::KeyFunction::VOX: return (uint8_t)KeyFunction::VOX; case AnytoneKeySettingsExtension::KeyFunction::ToggleVFO: return (uint8_t)KeyFunction::ToggleVFO; case AnytoneKeySettingsExtension::KeyFunction::SubPTT: return (uint8_t)KeyFunction::SubPTT; case AnytoneKeySettingsExtension::KeyFunction::Scan: return (uint8_t)KeyFunction::Scan; case AnytoneKeySettingsExtension::KeyFunction::WFM: return (uint8_t)KeyFunction::WFM; case AnytoneKeySettingsExtension::KeyFunction::Alarm: return (uint8_t)KeyFunction::Alarm; case AnytoneKeySettingsExtension::KeyFunction::RecordSwitch: return (uint8_t)KeyFunction::RecordSwitch; case AnytoneKeySettingsExtension::KeyFunction::Record: return (uint8_t)KeyFunction::Record; case AnytoneKeySettingsExtension::KeyFunction::SMS: return (uint8_t)KeyFunction::SMS; case AnytoneKeySettingsExtension::KeyFunction::Dial: return (uint8_t)KeyFunction::Dial; case AnytoneKeySettingsExtension::KeyFunction::GPSInformation: return (uint8_t)KeyFunction::GPSInformation; case AnytoneKeySettingsExtension::KeyFunction::Monitor: return (uint8_t)KeyFunction::Monitor; case AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel: return (uint8_t)KeyFunction::ToggleMainChannel; case AnytoneKeySettingsExtension::KeyFunction::HotKey1: return (uint8_t)KeyFunction::HotKey1; case AnytoneKeySettingsExtension::KeyFunction::HotKey2: return (uint8_t)KeyFunction::HotKey2; case AnytoneKeySettingsExtension::KeyFunction::HotKey3: return (uint8_t)KeyFunction::HotKey3; case AnytoneKeySettingsExtension::KeyFunction::HotKey4: return (uint8_t)KeyFunction::HotKey4; case AnytoneKeySettingsExtension::KeyFunction::HotKey5: return (uint8_t)KeyFunction::HotKey5; case AnytoneKeySettingsExtension::KeyFunction::HotKey6: return (uint8_t)KeyFunction::HotKey6; case AnytoneKeySettingsExtension::KeyFunction::WorkAlone: return (uint8_t)KeyFunction::WorkAlone; case AnytoneKeySettingsExtension::KeyFunction::SkipChannel: return (uint8_t)KeyFunction::SkipChannel; case AnytoneKeySettingsExtension::KeyFunction::DMRMonitor: return (uint8_t)KeyFunction::DMRMonitor; case AnytoneKeySettingsExtension::KeyFunction::SubChannel: return (uint8_t)KeyFunction::SubChannel; case AnytoneKeySettingsExtension::KeyFunction::PriorityZone: return (uint8_t)KeyFunction::PriorityZone; case AnytoneKeySettingsExtension::KeyFunction::VFOScan: return (uint8_t)KeyFunction::VFOScan; case AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality: return (uint8_t)KeyFunction::MICSoundQuality; case AnytoneKeySettingsExtension::KeyFunction::LastCallReply: return (uint8_t)KeyFunction::LastCallReply; case AnytoneKeySettingsExtension::KeyFunction::ChannelType: return (uint8_t)KeyFunction::ChannelType; case AnytoneKeySettingsExtension::KeyFunction::SimplexRepeater: return (uint8_t)KeyFunction::SimplexRepeater; case AnytoneKeySettingsExtension::KeyFunction::Ranging: return (uint8_t)KeyFunction::Ranging; case AnytoneKeySettingsExtension::KeyFunction::ChannelRanging: return (uint8_t)KeyFunction::ChannelRanging; case AnytoneKeySettingsExtension::KeyFunction::MaxVolume: return (uint8_t)KeyFunction::MaxVolume; case AnytoneKeySettingsExtension::KeyFunction::Slot: return (uint8_t)KeyFunction::Slot; case AnytoneKeySettingsExtension::KeyFunction::Squelch: return (uint8_t)KeyFunction::Squelch; case AnytoneKeySettingsExtension::KeyFunction::Roaming: return (uint8_t)KeyFunction::Roaming; case AnytoneKeySettingsExtension::KeyFunction::Zone: return (uint8_t)KeyFunction::Zone; case AnytoneKeySettingsExtension::KeyFunction::RoamingSet: return (uint8_t)KeyFunction::RoamingSet; case AnytoneKeySettingsExtension::KeyFunction::Mute: return (uint8_t)KeyFunction::Mute; case AnytoneKeySettingsExtension::KeyFunction::CtcssDcsSet: return (uint8_t)KeyFunction::CtcssDcsSet; case AnytoneKeySettingsExtension::KeyFunction::APRSTypeSwitch: return (uint8_t)KeyFunction::APRSType; case AnytoneKeySettingsExtension::KeyFunction::APRSSet: return (uint8_t)KeyFunction::APRSSet; case AnytoneKeySettingsExtension::KeyFunction::DIMShut: return (uint8_t)KeyFunction::DIMShut; case AnytoneKeySettingsExtension::KeyFunction::SatPredict: return (uint8_t)KeyFunction::SatPredict; default: return (uint8_t)KeyFunction::Off; } } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::KeyFunction::decode(uint8_t code) { switch ((KeyFunctionCode)code) { case KeyFunction::Off: return AnytoneKeySettingsExtension::KeyFunction::Off; case KeyFunction::Voltage: return AnytoneKeySettingsExtension::KeyFunction::Voltage; case KeyFunction::Power: return AnytoneKeySettingsExtension::KeyFunction::Power; case KeyFunction::Repeater: return AnytoneKeySettingsExtension::KeyFunction::Repeater; case KeyFunction::Reverse: return AnytoneKeySettingsExtension::KeyFunction::Reverse; case KeyFunction::Encryption: return AnytoneKeySettingsExtension::KeyFunction::Encryption; case KeyFunction::Call: return AnytoneKeySettingsExtension::KeyFunction::Call; case KeyFunction::VOX: return AnytoneKeySettingsExtension::KeyFunction::VOX; case KeyFunction::ToggleVFO: return AnytoneKeySettingsExtension::KeyFunction::ToggleVFO; case KeyFunction::SubPTT: return AnytoneKeySettingsExtension::KeyFunction::SubPTT; case KeyFunction::Scan: return AnytoneKeySettingsExtension::KeyFunction::Scan; case KeyFunction::WFM: return AnytoneKeySettingsExtension::KeyFunction::WFM; case KeyFunction::Alarm: return AnytoneKeySettingsExtension::KeyFunction::Alarm; case KeyFunction::RecordSwitch: return AnytoneKeySettingsExtension::KeyFunction::RecordSwitch; case KeyFunction::Record: return AnytoneKeySettingsExtension::KeyFunction::Record; case KeyFunction::SMS: return AnytoneKeySettingsExtension::KeyFunction::SMS; case KeyFunction::Dial: return AnytoneKeySettingsExtension::KeyFunction::Dial; case KeyFunction::GPSInformation: return AnytoneKeySettingsExtension::KeyFunction::GPSInformation; case KeyFunction::Monitor: return AnytoneKeySettingsExtension::KeyFunction::Monitor; case KeyFunction::ToggleMainChannel: return AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel; case KeyFunction::HotKey1: return AnytoneKeySettingsExtension::KeyFunction::HotKey1; case KeyFunction::HotKey2: return AnytoneKeySettingsExtension::KeyFunction::HotKey2; case KeyFunction::HotKey3: return AnytoneKeySettingsExtension::KeyFunction::HotKey3; case KeyFunction::HotKey4: return AnytoneKeySettingsExtension::KeyFunction::HotKey4; case KeyFunction::HotKey5: return AnytoneKeySettingsExtension::KeyFunction::HotKey5; case KeyFunction::HotKey6: return AnytoneKeySettingsExtension::KeyFunction::HotKey6; case KeyFunction::WorkAlone: return AnytoneKeySettingsExtension::KeyFunction::WorkAlone; case KeyFunction::SkipChannel: return AnytoneKeySettingsExtension::KeyFunction::SkipChannel; case KeyFunction::DMRMonitor: return AnytoneKeySettingsExtension::KeyFunction::DMRMonitor; case KeyFunction::SubChannel: return AnytoneKeySettingsExtension::KeyFunction::SubChannel; case KeyFunction::PriorityZone: return AnytoneKeySettingsExtension::KeyFunction::PriorityZone; case KeyFunction::VFOScan: return AnytoneKeySettingsExtension::KeyFunction::VFOScan; case KeyFunction::MICSoundQuality: return AnytoneKeySettingsExtension::KeyFunction::MICSoundQuality; case KeyFunction::LastCallReply: return AnytoneKeySettingsExtension::KeyFunction::LastCallReply; case KeyFunction::ChannelType: return AnytoneKeySettingsExtension::KeyFunction::ChannelType; case KeyFunction::SimplexRepeater: return AnytoneKeySettingsExtension::KeyFunction::SimplexRepeater; case KeyFunction::Ranging: return AnytoneKeySettingsExtension::KeyFunction::Ranging; case KeyFunction::ChannelRanging: return AnytoneKeySettingsExtension::KeyFunction::ChannelRanging; case KeyFunction::MaxVolume: return AnytoneKeySettingsExtension::KeyFunction::MaxVolume; case KeyFunction::Slot: return AnytoneKeySettingsExtension::KeyFunction::Slot; case KeyFunction::Squelch: return AnytoneKeySettingsExtension::KeyFunction::Squelch; case KeyFunction::Roaming: return AnytoneKeySettingsExtension::KeyFunction::Roaming; case KeyFunction::Zone: return AnytoneKeySettingsExtension::KeyFunction::Zone; case KeyFunction::RoamingSet: return AnytoneKeySettingsExtension::KeyFunction::RoamingSet; case KeyFunction::Mute: return AnytoneKeySettingsExtension::KeyFunction::Mute; case KeyFunction::CtcssDcsSet: return AnytoneKeySettingsExtension::KeyFunction::CtcssDcsSet; case KeyFunction::APRSType: return AnytoneKeySettingsExtension::KeyFunction::APRSTypeSwitch; case KeyFunction::APRSSet: return AnytoneKeySettingsExtension::KeyFunction::APRSSet; case KeyFunction::DIMShut: return AnytoneKeySettingsExtension::KeyFunction::DIMShut; case KeyFunction::GPSToggle: return AnytoneKeySettingsExtension::KeyFunction::GPS; case KeyFunction::SatPredict: return AnytoneKeySettingsExtension::KeyFunction::SatPredict; default: return AnytoneKeySettingsExtension::KeyFunction::Off; } } /* ********************************************************************************************* * * Implementation of DMR6X2UVCodeplug::GeneralSettingsElement * ********************************************************************************************* */ DMR6X2UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, unsigned size) : D868UVCodeplug::GeneralSettingsElement(ptr, size) { // pass... } DMR6X2UVCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : D868UVCodeplug::GeneralSettingsElement(ptr, GeneralSettingsElement::size()) { // pass... } bool DMR6X2UVCodeplug::GeneralSettingsElement::idleChannelTone() const { return getUInt8(Offset::idleChannelTone()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableIdleChannelTone(bool enable) { return setUInt8(Offset::idleChannelTone(), (enable ? 0x01 : 0x00)); } unsigned DMR6X2UVCodeplug::GeneralSettingsElement::transmitTimeout() const { return ((unsigned)getUInt8(Offset::transmitTimeout()))*30; } void DMR6X2UVCodeplug::GeneralSettingsElement::setTransmitTimeout(unsigned tot) { setUInt8(Offset::transmitTimeout(), tot/30); } AnytoneDisplaySettingsExtension::Language DMR6X2UVCodeplug::GeneralSettingsElement::language() const { return (AnytoneDisplaySettingsExtension::Language)getUInt8(Offset::language()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setLanguage(AnytoneDisplaySettingsExtension::Language lang) { setUInt8(Offset::language(), (unsigned)lang); } Frequency DMR6X2UVCodeplug::GeneralSettingsElement::vfoFrequencyStep() const { switch (getUInt8(Offset::vfoFrequencyStep())) { case FREQ_STEP_2_5kHz: return Frequency::fromkHz(2.5); case FREQ_STEP_5kHz: return Frequency::fromkHz(5); case FREQ_STEP_6_25kHz: return Frequency::fromkHz(6.25); case FREQ_STEP_10kHz: return Frequency::fromkHz(10); case FREQ_STEP_12_5kHz: return Frequency::fromkHz(12.5); case FREQ_STEP_20kHz: return Frequency::fromkHz(20); case FREQ_STEP_25kHz: return Frequency::fromkHz(25); case FREQ_STEP_50kHz: return Frequency::fromkHz(50); } return Frequency::fromkHz(2.5); } void DMR6X2UVCodeplug::GeneralSettingsElement::setVFOFrequencyStep(Frequency freq) { if (freq.inkHz() <= 2.5) setUInt8(Offset::vfoFrequencyStep(), FREQ_STEP_2_5kHz); else if (freq.inkHz() <= 5) setUInt8(Offset::vfoFrequencyStep(), FREQ_STEP_5kHz); else if (freq.inkHz() <= 6.25) setUInt8(Offset::vfoFrequencyStep(), FREQ_STEP_6_25kHz); else if (freq.inkHz() <= 10) setUInt8(Offset::vfoFrequencyStep(), FREQ_STEP_10kHz); else if (freq.inkHz() <= 12.5) setUInt8(Offset::vfoFrequencyStep(), FREQ_STEP_12_5kHz); else if (freq.inkHz() <= 20) setUInt8(Offset::vfoFrequencyStep(), FREQ_STEP_20kHz); else if (freq.inkHz() <= 25) setUInt8(Offset::vfoFrequencyStep(), FREQ_STEP_25kHz); else setUInt8(Offset::vfoFrequencyStep(), FREQ_STEP_50kHz); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKeyAShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyAShort())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyAShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKeyBShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyBShort())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyBShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKeyCShort() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyCShort())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyCShort(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKey1Short() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1Short())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey1Short(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKey2Short() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2Short())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey2Short(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKeyALong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyALong())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyALong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKeyBLong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyBLong())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyBLong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKeyCLong() const { return KeyFunction::decode(getUInt8(Offset::progFuncKeyCLong())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKeyCLong(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKey1Long() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1Long())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey1Long(), KeyFunction::encode(func)); } AnytoneKeySettingsExtension::KeyFunction DMR6X2UVCodeplug::GeneralSettingsElement::funcKey2Long() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2Long())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func) { setUInt8(Offset::progFuncKey2Long(), KeyFunction::encode(func)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::vfoModeA() const { return getUInt8(Offset::vfoModeA()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableVFOModeA(bool enable) { setUInt8(Offset::vfoModeA(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::vfoModeB() const { return getUInt8(Offset::vfoModeB()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableVFOModeB(bool enable) { setUInt8(Offset::vfoModeB(), (enable ? 0x01 : 0x00)); } AnytoneSettingsExtension::STEType DMR6X2UVCodeplug::GeneralSettingsElement::steType() const { return (AnytoneSettingsExtension::STEType)getUInt8(Offset::steType()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setSTEType(AnytoneSettingsExtension::STEType type) { setUInt8(Offset::steType(), (unsigned)type); } double DMR6X2UVCodeplug::GeneralSettingsElement::steFrequency() const { switch ((STEFrequency)getUInt8(Offset::steFrequency())) { case STEFrequency::Off: return 0; case STEFrequency::Hz55_2: return 55.2; case STEFrequency::Hz259_2: return 259.2; } return 0; } void DMR6X2UVCodeplug::GeneralSettingsElement::setSTEFrequency(double freq) { if (0 >= freq) { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Off); } else if (100 > freq) { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Hz55_2); } else { setUInt8(Offset::steFrequency(), (unsigned)STEFrequency::Hz259_2); } } Interval DMR6X2UVCodeplug::GeneralSettingsElement::groupCallHangTime() const { return Interval::fromSeconds(getUInt8(Offset::groupCallHangTime())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setGroupCallHangTime(Interval intv) { setUInt8(Offset::groupCallHangTime(), intv.seconds()); } Interval DMR6X2UVCodeplug::GeneralSettingsElement::privateCallHangTime() const { return Interval::fromSeconds(getUInt8(Offset::privateCallHangTime())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setPrivateCallHangTime(Interval intv) { setUInt8(Offset::privateCallHangTime(), intv.seconds()); } Interval DMR6X2UVCodeplug::GeneralSettingsElement::preWaveDelay() const { return Interval::fromMilliseconds((unsigned)getUInt8(Offset::preWaveDelay())*20); } void DMR6X2UVCodeplug::GeneralSettingsElement::setPreWaveDelay(Interval intv) { setUInt8(Offset::preWaveDelay(), intv.milliseconds()/20); } Interval DMR6X2UVCodeplug::GeneralSettingsElement::wakeHeadPeriod() const { return Interval::fromMilliseconds(((unsigned)getUInt8(Offset::wakeHeadPeriod()))*20); } void DMR6X2UVCodeplug::GeneralSettingsElement::setWakeHeadPeriod(Interval intv) { setUInt8(Offset::wakeHeadPeriod(), intv.milliseconds()/20); } unsigned DMR6X2UVCodeplug::GeneralSettingsElement::wfmChannelIndex() const { return getUInt8(Offset::wfmChannelIndex()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setWFMChannelIndex(unsigned idx) { setUInt8(Offset::wfmChannelIndex(), idx); } bool DMR6X2UVCodeplug::GeneralSettingsElement::wfmVFOEnabled() const { return getUInt8(Offset::wfmVFOEnabled()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableWFMVFO(bool enable) { setUInt8(Offset::wfmVFOEnabled(), (enable ? 0x01 : 0x00)); } unsigned DMR6X2UVCodeplug::GeneralSettingsElement::dtmfToneDuration() const { switch (getUInt8(Offset::dtmfToneDuration())) { case DTMF_DUR_50ms: return 50; case DTMF_DUR_100ms: return 100; case DTMF_DUR_200ms: return 200; case DTMF_DUR_300ms: return 300; case DTMF_DUR_500ms: return 500; } return 50; } void DMR6X2UVCodeplug::GeneralSettingsElement::setDTMFToneDuration(unsigned ms) { if (ms<=50) { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_50ms); } else if (ms<=100) { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_100ms); } else if (ms<=200) { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_200ms); } else if (ms<=300) { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_300ms); } else { setUInt8(Offset::dtmfToneDuration(), DTMF_DUR_500ms); } } bool DMR6X2UVCodeplug::GeneralSettingsElement::manDown() const { return getUInt8(Offset::manDown()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableManDown(bool enable) { setUInt8(Offset::manDown(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::wfmMonitor() const { return getUInt8(Offset::wfmMonitor()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableWFMMonitor(bool enable) { setUInt8(Offset::wfmMonitor(), (enable ? 0x01 : 0x00)); } Frequency DMR6X2UVCodeplug::GeneralSettingsElement::tbstFrequency() const { switch ((TBSTFrequency)getUInt8(Offset::tbstFrequency())) { case TBSTFrequency::Hz1000: return Frequency::fromHz(1000); case TBSTFrequency::Hz1450: return Frequency::fromHz(1450); case TBSTFrequency::Hz1750: return Frequency::fromHz(1750); case TBSTFrequency::Hz2100: return Frequency::fromHz(2100); } return Frequency::fromHz(1750); } void DMR6X2UVCodeplug::GeneralSettingsElement::setTBSTFrequency(Frequency freq) { if (1000 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1000); } else if (1450 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1450); } else if (1750 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1750); } else if (2100 == freq.inHz()) { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz2100); } else { setUInt8(Offset::tbstFrequency(), (unsigned)TBSTFrequency::Hz1750); } } bool DMR6X2UVCodeplug::GeneralSettingsElement::proMode() const { return getUInt8(Offset::proMode()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableProMode(bool enable) { setUInt8(Offset::proMode(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::filterOwnID() const { return getUInt8(Offset::filterOwnID()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableFilterOwnID(bool enable) { setUInt8(Offset::filterOwnID(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::keyToneEnabled() const { return 0x00 != getUInt8(Offset::enableKeyTone()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableKeyTone(bool enable) { setUInt8(Offset::enableKeyTone(), enable ? 0x01 : 0x00); } bool DMR6X2UVCodeplug::GeneralSettingsElement::remoteStunKill() const { return getUInt8(Offset::remoteStunKill()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableRemoteStunKill(bool enable) { setUInt8(Offset::remoteStunKill(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::remoteMonitor() const { return getUInt8(Offset::remoteMonitor()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableRemoteMonitor(bool enable) { setUInt8(Offset::remoteMonitor(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::selectTXContactEnabled() const { return 0x01 == getUInt8(Offset::selectTXContact()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableSelectTXContact(bool enable) { setUInt8(Offset::selectTXContact(), enable ? 0x01 : 0x00); } AnytoneDMRSettingsExtension::SlotMatch DMR6X2UVCodeplug::GeneralSettingsElement::monitorSlotMatch() const { return (AnytoneDMRSettingsExtension::SlotMatch)getUInt8(Offset::monSlotMatch()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setMonitorSlotMatch(AnytoneDMRSettingsExtension::SlotMatch match) { setUInt8(Offset::monSlotMatch(), (unsigned)match); } bool DMR6X2UVCodeplug::GeneralSettingsElement::monitorColorCodeMatch() const { return getUInt8(Offset::monColorCodeMatch()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableMonitorColorCodeMatch(bool enable) { setUInt8(Offset::monColorCodeMatch(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::monitorIDMatch() const { return getUInt8(Offset::monIDMatch()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableMonitorIDMatch(bool enable) { setUInt8(Offset::monIDMatch(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::monitorTimeSlotHold() const { return getUInt8(Offset::monTimeSlotHold()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableMonitorTimeSlotHold(bool enable) { setUInt8(Offset::monTimeSlotHold(), (enable ? 0x01 : 0x00)); } Interval DMR6X2UVCodeplug::GeneralSettingsElement::manDownDelay() const { return Interval::fromSeconds(getUInt8(Offset::manDownDelay())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setManDownDelay(Interval sec) { setUInt8(Offset::manDownDelay(), sec.seconds()); } unsigned DMR6X2UVCodeplug::GeneralSettingsElement::fmCallHold() const { return getUInt8(Offset::fmCallHold()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setFMCallHold(unsigned sec) { setUInt8(Offset::fmCallHold(), sec); } bool DMR6X2UVCodeplug::GeneralSettingsElement::gpsMessageEnabled() const { return getUInt8(Offset::enableGPSMessage()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableGPSMessage(bool enable) { setUInt8(Offset::enableGPSMessage(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::maintainCallChannel() const { return getUInt8(Offset::maintainCallChannel()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableMaintainCallChannel(bool enable) { setUInt8(Offset::maintainCallChannel(), (enable ? 0x01 : 0x00)); } unsigned DMR6X2UVCodeplug::GeneralSettingsElement::priorityZoneAIndex() const { return getUInt8(Offset::priorityZoneA()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setPriorityZoneAIndex(unsigned idx) { setUInt8(Offset::priorityZoneA(), idx); } unsigned DMR6X2UVCodeplug::GeneralSettingsElement::priorityZoneBIndex() const { return getUInt8(Offset::priorityZoneB()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setPriorityZoneBIndex(unsigned idx) { setUInt8(Offset::priorityZoneB(), idx); } bool DMR6X2UVCodeplug::GeneralSettingsElement::smsConfirmEnabled() const { return 0x01 == getUInt8(Offset::smsConfirm()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableSMSConfirm(bool enable) { setUInt8(Offset::smsConfirm(), enable ? 0x01 : 0x00); } bool DMR6X2UVCodeplug::GeneralSettingsElement::simplexRepeaterEnabled() const { return 0x01 == getUInt8(Offset::simplexRepEnable()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableSimplexRepeater(bool enable) { setUInt8(Offset::simplexRepEnable(), enable ? 0x01 : 0x00); } Interval DMR6X2UVCodeplug::GeneralSettingsElement::gpsUpdatePeriod() const { return Interval::fromSeconds(getUInt8(Offset::gpsUpdatePeriod())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setGPSUpdatePeriod(Interval intv) { setUInt8(Offset::gpsUpdatePeriod(), intv.seconds()); } bool DMR6X2UVCodeplug::GeneralSettingsElement::monitorSimplexRepeaterEnabled() const { return 0x01 == getUInt8(Offset::simplxRepSpeaker()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableMonitorSimplexRepeater(bool enable) { setUInt8(Offset::simplxRepSpeaker(), enable ? 0x01 : 0x00); } bool DMR6X2UVCodeplug::GeneralSettingsElement::showCurrentContact() const { return getUInt8(Offset::showContact()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableShowCurrentContact(bool enable) { setUInt8(Offset::showContact(), (enable ? 0x01 : 0x00)); } bool DMR6X2UVCodeplug::GeneralSettingsElement::keyToneLevelAdjustable() const { return keyToneLevel().isNull(); } Level DMR6X2UVCodeplug::GeneralSettingsElement::keyToneLevel() const { return Level::fromValue(getUInt8(Offset::keyToneLevel()), Limit::keyTone()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setKeyToneLevel(Level level) { setUInt8(Offset::keyToneLevel(), level.mapTo(Limit::keyTone())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setKeyToneLevelAdjustable() { setUInt8(Offset::keyToneLevel(), 0); } bool DMR6X2UVCodeplug::GeneralSettingsElement::knobLock() const { return getBit(Offset::knobLock()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableKnobLock(bool enable) { setBit(Offset::knobLock(), enable); } bool DMR6X2UVCodeplug::GeneralSettingsElement::keypadLock() const { return getBit(Offset::keypadLock()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableKeypadLock(bool enable) { setBit(Offset::keypadLock(), enable); } bool DMR6X2UVCodeplug::GeneralSettingsElement::sidekeysLock() const { return getBit(Offset::sideKeyLock()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableSidekeysLock(bool enable) { setBit(Offset::sideKeyLock(), enable); } bool DMR6X2UVCodeplug::GeneralSettingsElement::keyLockForced() const { return getBit(Offset::forceKeyLock()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableKeyLockForced(bool enable) { setBit(Offset::forceKeyLock(), enable); } AnytoneRepeaterSettingsExtension::TimeSlot DMR6X2UVCodeplug::GeneralSettingsElement::simplexRepeaterTimeslot() const { switch ((RepeaterTimeSlot)getUInt8(Offset::simplxRepSlot())) { case RepeaterTimeSlot::TS1: return AnytoneRepeaterSettingsExtension::TimeSlot::TS1; case RepeaterTimeSlot::TS2: return AnytoneRepeaterSettingsExtension::TimeSlot::TS2; case RepeaterTimeSlot::Channel: return AnytoneRepeaterSettingsExtension::TimeSlot::Channel; } return AnytoneRepeaterSettingsExtension::TimeSlot::Channel; } void DMR6X2UVCodeplug::GeneralSettingsElement::setSimplexRepeaterTimeslot(AnytoneRepeaterSettingsExtension::TimeSlot slot) { switch (slot) { case AnytoneRepeaterSettingsExtension::TimeSlot::TS1: setUInt8(Offset::simplxRepSlot(), (unsigned int) RepeaterTimeSlot::TS1); break; case AnytoneRepeaterSettingsExtension::TimeSlot::TS2: setUInt8(Offset::simplxRepSlot(), (unsigned int) RepeaterTimeSlot::TS2); break; case AnytoneRepeaterSettingsExtension::TimeSlot::Channel: setUInt8(Offset::simplxRepSlot(), (unsigned int) RepeaterTimeSlot::Channel); break; default: setUInt8(Offset::simplxRepSlot(), (unsigned int) RepeaterTimeSlot::Channel); break; } } bool DMR6X2UVCodeplug::GeneralSettingsElement::showLastHeard() const { return getUInt8(Offset::showLastHeard()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableShowLastHeard(bool enable) { setUInt8(Offset::showLastHeard(), (enable ? 0x01 : 0x00)); } SMSExtension::Format DMR6X2UVCodeplug::GeneralSettingsElement::smsFormat() const { switch ((SMSFormat) getUInt8(Offset::smsFormat())) { case SMSFormat::Motorola: return SMSExtension::Format::Motorola; case SMSFormat::Hytera: return SMSExtension::Format::Hytera; case SMSFormat::DMR: return SMSExtension::Format::DMR; } return SMSExtension::Format::Motorola; } void DMR6X2UVCodeplug::GeneralSettingsElement::setSMSFormat(SMSExtension::Format format) { switch (format) { case SMSExtension::Format::Motorola: setUInt8(Offset::smsFormat(), (unsigned int)SMSFormat::Motorola); break; case SMSExtension::Format::Hytera: setUInt8(Offset::smsFormat(), (unsigned int)SMSFormat::Hytera); break; case SMSExtension::Format::DMR: setUInt8(Offset::smsFormat(), (unsigned int)SMSFormat::DMR); break; } } bool DMR6X2UVCodeplug::GeneralSettingsElement::gpsUnitsImperial() const { return getUInt8(Offset::gpsUnits()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableGPSUnitsImperial(bool enable) { setUInt8(Offset::gpsUnits(), (enable ? 0x01 : 0x00)); } Frequency DMR6X2UVCodeplug::GeneralSettingsElement::autoRepeaterMinFrequencyVHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMinVHF())*10); } void DMR6X2UVCodeplug::GeneralSettingsElement::setAutoRepeaterMinFrequencyVHF(Frequency freq) { setUInt32_le(Offset::autoRepMinVHF(), freq.inHz()/10); } Frequency DMR6X2UVCodeplug::GeneralSettingsElement::autoRepeaterMaxFrequencyVHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMaxVHF())*10); } void DMR6X2UVCodeplug::GeneralSettingsElement::setAutoRepeaterMaxFrequencyVHF(Frequency freq) { setUInt32_le(Offset::autoRepMaxVHF(), freq.inHz()/10); } Frequency DMR6X2UVCodeplug::GeneralSettingsElement::autoRepeaterMinFrequencyUHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMinUHF())*10); } void DMR6X2UVCodeplug::GeneralSettingsElement::setAutoRepeaterMinFrequencyUHF(Frequency freq) { setUInt32_le(Offset::autoRepMinUHF(), freq.inHz()/10); } Frequency DMR6X2UVCodeplug::GeneralSettingsElement::autoRepeaterMaxFrequencyUHF() const { return Frequency::fromHz(getUInt32_le(Offset::autoRepMaxUHF())*10); } void DMR6X2UVCodeplug::GeneralSettingsElement::setAutoRepeaterMaxFrequencyUHF(Frequency freq) { setUInt32_le(Offset::autoRepMaxUHF(), freq.inHz()/10); } AnytoneAutoRepeaterSettingsExtension::Direction DMR6X2UVCodeplug::GeneralSettingsElement::autoRepeaterDirectionB() const { return (AnytoneAutoRepeaterSettingsExtension::Direction)getUInt8(Offset::autoRepeaterDirB()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setAutoRepeaterDirectionB(AnytoneAutoRepeaterSettingsExtension::Direction dir) { setUInt8(Offset::autoRepeaterDirB(), (uint8_t)dir); } bool DMR6X2UVCodeplug::GeneralSettingsElement::fmSendIDAndContact() const { return 0 != getUInt8(Offset::fmSendIDAndContact()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableFMSendIDAndContact(bool enable) { setUInt8(Offset::fmSendIDAndContact(), enable ? 0x01 : 0x00); } bool DMR6X2UVCodeplug::GeneralSettingsElement::defaultChannel() const { return 0x01 == getUInt8(Offset::defaultChannels()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableDefaultChannel(bool enable) { setUInt8(Offset::defaultChannels(), enable ? 0x01 : 0x00); } unsigned int DMR6X2UVCodeplug::GeneralSettingsElement::defaultZoneIndexA() const { return getUInt8(Offset::defaultZoneA()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setDefaultZoneIndexA(unsigned int index) { setUInt8(Offset::defaultZoneA(), index); } bool DMR6X2UVCodeplug::GeneralSettingsElement::defaultChannelAIsVFO() const { return 0xff == defaultChannelAIndex(); } unsigned int DMR6X2UVCodeplug::GeneralSettingsElement::defaultChannelAIndex() const { return getUInt8(Offset::defaultChannelA()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setDefaultChannelAIndex(unsigned int index) { return setUInt8(Offset::defaultChannelA(), index); } void DMR6X2UVCodeplug::GeneralSettingsElement::setDefaultChannelAToVFO() { setDefaultChannelAIndex(0xff); } unsigned int DMR6X2UVCodeplug::GeneralSettingsElement::defaultZoneIndexB() const { return getUInt8(Offset::defaultZoneB()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setDefaultZoneIndexB(unsigned int index) { setUInt8(Offset::defaultZoneB(), index); } bool DMR6X2UVCodeplug::GeneralSettingsElement::defaultChannelBIsVFO() const { return 0xff == defaultChannelBIndex(); } unsigned int DMR6X2UVCodeplug::GeneralSettingsElement::defaultChannelBIndex() const { return getUInt8(Offset::defaultChannelB()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setDefaultChannelBIndex(unsigned int index) { return setUInt8(Offset::defaultChannelB(), index); } void DMR6X2UVCodeplug::GeneralSettingsElement::setDefaultChannelBToVFO() { setDefaultChannelBIndex(0xff); } bool DMR6X2UVCodeplug::GeneralSettingsElement::keepLastCaller() const { return getUInt8(Offset::keepLastCaller()); } void DMR6X2UVCodeplug::GeneralSettingsElement::enableKeepLastCaller(bool enable) { setUInt8(Offset::keepLastCaller(), (enable ? 0x01 : 0x00)); } Interval DMR6X2UVCodeplug::GeneralSettingsElement::rxBacklightDuration() const { auto seconds = getUInt8(Offset::rxBacklightDuration()); if (0 == seconds) return Interval::infinity(); return Interval::fromSeconds(seconds); } void DMR6X2UVCodeplug::GeneralSettingsElement::setRXBacklightDuration(Interval dur) { if (dur.isFinite() || dur > Interval::fromSeconds(30)) setUInt8(Offset::rxBacklightDuration(), 0); setUInt8(Offset::rxBacklightDuration(), dur.seconds()); } AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::GeneralSettingsElement::standbyBackgroundColor() const { return BackgroundColor::decode(getUInt8(Offset::standbyBackground())); } void DMR6X2UVCodeplug::GeneralSettingsElement::setStandbyBackgroundColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::standbyBackground(), BackgroundColor::encode(color)); } unsigned int DMR6X2UVCodeplug::GeneralSettingsElement::manualDialedGroupCallHangTime() const { return getUInt8(Offset::manGrpCallHangTime()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setManualDialedGroupCallHangTime(unsigned int dur) { setUInt8(Offset::manGrpCallHangTime(), dur); } unsigned int DMR6X2UVCodeplug::GeneralSettingsElement::manualDialedPrivateCallHangTime() const { return getUInt8(Offset::manPrvCallHangTime()); } void DMR6X2UVCodeplug::GeneralSettingsElement::setManualDialedPrivateCallHangTime(unsigned int dur) { setUInt8(Offset::manPrvCallHangTime(), dur); } bool DMR6X2UVCodeplug::GeneralSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! D868UVCodeplug::GeneralSettingsElement::fromConfig(flags, ctx, err)) return false; enableGPSUnitsImperial(GNSSSettings::Units::Archaic == ctx.config()->settings()->gnss()->units()); setGroupCallHangTime(ctx.config()->settings()->dmr()->groupCallHangTime()); setPrivateCallHangTime(ctx.config()->settings()->dmr()->privateCallHangTime()); setPreWaveDelay(ctx.config()->settings()->dmr()->preamble()); setSMSFormat(ctx.config()->smsExtension()->format()); // Encode tone settings setKeyToneLevel(ctx.config()->settings()->tone()->keyToneVolume()); // apply DMR-6X2UV specific settings. AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return true; // Encode boot settings if (ext->bootSettings()->priorityZoneA()->isNull()) setPriorityZoneAIndex(0xff); else setPriorityZoneAIndex(ctx.index(ext->bootSettings()->priorityZoneA()->as())); if (ext->bootSettings()->priorityZoneB()->isNull()) setPriorityZoneBIndex(0xff); else setPriorityZoneBIndex(ctx.index(ext->bootSettings()->priorityZoneB()->as())); // Encode key settings enableKnobLock(ext->keySettings()->knobLockEnabled()); enableKeypadLock(ext->keySettings()->keypadLockEnabled()); enableSidekeysLock(ext->keySettings()->sideKeysLockEnabled()); enableKeyLockForced(ext->keySettings()->forcedKeyLockEnabled()); // Encode display settings setCallDisplayColor(ext->displaySettings()->callColor()); setStandbyBackgroundColor(ext->displaySettings()->standbyBackgroundColor()); setLanguage(ext->displaySettings()->language()); enableShowCurrentContact(ext->displaySettings()->showContact()); enableShowLastHeard(ext->displaySettings()->showLastHeardEnabled()); setRXBacklightDuration(ext->displaySettings()->backlightDurationRX()); // Encode auto-repeater settings setAutoRepeaterDirectionB(ext->autoRepeaterSettings()->directionB()); setAutoRepeaterMinFrequencyVHF(ext->autoRepeaterSettings()->vhfMin()); setAutoRepeaterMaxFrequencyVHF(ext->autoRepeaterSettings()->vhfMax()); setAutoRepeaterMinFrequencyUHF(ext->autoRepeaterSettings()->uhfMin()); setAutoRepeaterMaxFrequencyUHF(ext->autoRepeaterSettings()->uhfMax()); // Encode DMR settings setWakeHeadPeriod(ext->dmrSettings()->wakeHeadPeriod()); enableFilterOwnID(ext->dmrSettings()->filterOwnIDEnabled()); setMonitorSlotMatch(ext->dmrSettings()->monitorSlotMatch()); enableMonitorColorCodeMatch(ext->dmrSettings()->monitorColorCodeMatchEnabled()); enableMonitorIDMatch(ext->dmrSettings()->monitorIDMatchEnabled()); enableMonitorTimeSlotHold(ext->dmrSettings()->monitorTimeSlotHoldEnabled()); // Encode GPS settings. setGPSTimeZone(ext->gpsSettings()->timeZone()); enableGPSMessage(ext->gpsSettings()->positionReportingEnabled()); setGPSUpdatePeriod(ext->gpsSettings()->updatePeriod()); // Encode other settings enableKeepLastCaller(ext->keepLastCallerEnabled()); setVFOFrequencyStep(ext->vfoStep()); setSTEType(ext->steType()); setSTEFrequency(ext->steFrequency()); setTBSTFrequency(ext->tbstFrequency()); enableProMode(ext->proModeEnabled()); enableMaintainCallChannel(ext->maintainCallChannelEnabled()); // Apply simplex repeater settings enableSimplexRepeater(ext->repeaterSettings()->enabled()); enableMonitorSimplexRepeater(ext->repeaterSettings()->monitorEnabled()); setSimplexRepeaterTimeslot(ext->repeaterSettings()->timeSlot()); return true; } bool DMR6X2UVCodeplug::GeneralSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { if (! D868UVCodeplug::GeneralSettingsElement::updateConfig(ctx, err)) return false; ctx.config()->settings()->gnss()->setUnits( this->gpsUnitsImperial() ? GNSSSettings::Units::Archaic : GNSSSettings::Units::Metric); ctx.config()->settings()->dmr()->setGroupCallHangTime(this->groupCallHangTime()); ctx.config()->settings()->dmr()->setPrivateCallHangTime(this->privateCallHangTime()); ctx.config()->settings()->dmr()->setPreamble(this->preWaveDelay()); ctx.config()->smsExtension()->setFormat(this->smsFormat()); // Decode tone settings ctx.config()->settings()->tone()->setKeyToneVolume(keyToneLevel()); // Get or add settings extension AnytoneSettingsExtension *ext = nullptr; if (ctx.config()->settings()->anytoneExtension()) { ext = ctx.config()->settings()->anytoneExtension(); } else { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Decode key settings ext->keySettings()->enableKnobLock(this->knobLock()); ext->keySettings()->enableKeypadLock(this->keypadLock()); ext->keySettings()->enableSideKeysLock(this->sidekeysLock()); ext->keySettings()->enableForcedKeyLock(this->keyLockForced()); // Decode display settings ext->displaySettings()->setCallColor(this->callDisplayColor()); ext->displaySettings()->setLanguage(this->language()); ext->displaySettings()->enableShowContact(this->showCurrentContact()); ext->displaySettings()->enableShowLastHeard(this->showLastHeard()); ext->displaySettings()->setBacklightDurationRX(this->rxBacklightDuration()); ext->displaySettings()->setStandbyBackgroundColor(this->standbyBackgroundColor()); // Decode auto-repeater settings ext->autoRepeaterSettings()->setDirectionB(autoRepeaterDirectionB()); ext->autoRepeaterSettings()->setVHFMin(this->autoRepeaterMinFrequencyVHF()); ext->autoRepeaterSettings()->setVHFMax(this->autoRepeaterMaxFrequencyVHF()); ext->autoRepeaterSettings()->setUHFMin(this->autoRepeaterMinFrequencyUHF()); ext->autoRepeaterSettings()->setUHFMax(this->autoRepeaterMaxFrequencyUHF()); // Encode dmr settings ext->dmrSettings()->setWakeHeadPeriod(this->wakeHeadPeriod()); ext->dmrSettings()->enableFilterOwnID(this->filterOwnID()); ext->dmrSettings()->setMonitorSlotMatch(this->monitorSlotMatch()); ext->dmrSettings()->enableMonitorColorCodeMatch(this->monitorColorCodeMatch()); ext->dmrSettings()->enableMonitorIDMatch(this->monitorIDMatch()); ext->dmrSettings()->enableMonitorTimeSlotHold(this->monitorTimeSlotHold()); // Encode GPS settings ext->gpsSettings()->setTimeZone(this->gpsTimeZone()); ext->gpsSettings()->enablePositionReporting(this->gpsMessageEnabled()); ext->gpsSettings()->setUpdatePeriod(this->gpsUpdatePeriod()); // Decode other settings ext->enableKeepLastCaller(this->keepLastCaller()); ext->setVFOStep(this->vfoFrequencyStep()); ext->setSTEType(this->steType()); ext->setSTEFrequency(this->steFrequency()); ext->setTBSTFrequency(this->tbstFrequency()); ext->enableProMode(this->proMode()); ext->enableMaintainCallChannel(this->maintainCallChannel()); // Decode simplex-repeater feature. ext->repeaterSettings()->enable(this->simplexRepeaterEnabled()); ext->repeaterSettings()->enableMonitor(this->monitorSimplexRepeaterEnabled()); ext->repeaterSettings()->setTimeSlot(this->simplexRepeaterTimeslot()); return true; } /* ********************************************************************************************* * * Implementation of DMR6X2UVCodeplug::ExtendedSettingsElement * ********************************************************************************************* */ DMR6X2UVCodeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr, unsigned size) : AnytoneCodeplug::ExtendedSettingsElement(ptr, size) { // pass... } DMR6X2UVCodeplug::ExtendedSettingsElement::ExtendedSettingsElement(uint8_t *ptr) : AnytoneCodeplug::ExtendedSettingsElement(ptr, ExtendedSettingsElement::size()) { // pass... } void DMR6X2UVCodeplug::ExtendedSettingsElement::clear() { Codeplug::Element::clear(); } bool DMR6X2UVCodeplug::ExtendedSettingsElement::sendTalkerAlias() const { return 0x01 == getUInt8(Offset::sendTalkerAlias()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::enableSendTalkerAlias(bool enable) { setUInt8(Offset::sendTalkerAlias(), enable ? 0x01 : 0x00); } AnytoneDMRSettingsExtension::TalkerAliasSource DMR6X2UVCodeplug::ExtendedSettingsElement::talkerAliasSource() const { return (AnytoneDMRSettingsExtension::TalkerAliasSource) getUInt8(Offset::talkerAliasDisplay()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setTalkerAliasSource(AnytoneDMRSettingsExtension::TalkerAliasSource source) { setUInt8(Offset::talkerAliasDisplay(), (uint8_t)source); } DMRSettings::TalkerAliasEncoding DMR6X2UVCodeplug::ExtendedSettingsElement::talkerAliasEncoding() const { switch ((TalkerAliasEncoding) getUInt8(Offset::talkerAliasEncoding())) { case TalkerAliasEncoding::ISO7: return DMRSettings::TalkerAliasEncoding::Iso7; case TalkerAliasEncoding::ISO8: return DMRSettings::TalkerAliasEncoding::Iso8; case TalkerAliasEncoding::Unicode: return DMRSettings::TalkerAliasEncoding::Unicode; } return DMRSettings::TalkerAliasEncoding::Iso8; } void DMR6X2UVCodeplug::ExtendedSettingsElement::setTalkerAliasEncoding(DMRSettings::TalkerAliasEncoding encoding) { switch (encoding) { case DMRSettings::TalkerAliasEncoding::Iso7: setUInt8(Offset::talkerAliasEncoding(), (uint8_t)TalkerAliasEncoding::ISO7); break; case DMRSettings::TalkerAliasEncoding::Iso8: setUInt8(Offset::talkerAliasEncoding(), (uint8_t)TalkerAliasEncoding::ISO8); break; case DMRSettings::TalkerAliasEncoding::Unicode: setUInt8(Offset::talkerAliasEncoding(), (uint8_t)TalkerAliasEncoding::Unicode); break; } } AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::ExtendedSettingsElement::fontColor() const { return FontColor::decode(getUInt8(Offset::fontColor())); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setFontColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::fontColor(), FontColor::encode(color)); } bool DMR6X2UVCodeplug::ExtendedSettingsElement::customChannelBackgroundEnabled() const { return 0x01 == getUInt8(Offset::customChannelBackground()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::enableCustomChannelBackground(bool enable) { setUInt8(Offset::customChannelBackground(), enable ? 0x01 : 0x00); } unsigned int DMR6X2UVCodeplug::ExtendedSettingsElement::defaultRoamingZoneIndex() const { return getUInt8(Offset::defaultRoamingZone()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setDefaultRoamingZoneIndex(unsigned int index) { setUInt8(Offset::defaultRoamingZone(), index); } bool DMR6X2UVCodeplug::ExtendedSettingsElement::autoRoamingEnabled() const { return 0x01 == getUInt8(Offset::roaming()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::enableAutoRoaming(bool enable) { setUInt8(Offset::roaming(), enable ? 0x01 : 0x00); } bool DMR6X2UVCodeplug::ExtendedSettingsElement::repeaterRangeCheckEnabled() const { return 0x01 == getUInt8(Offset::repRangeCheck()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::enableRepeaterRangeCheck(bool enable) { setUInt8(Offset::repRangeCheck(), enable ? 0x01 : 0x00); } AnytoneRoamingSettingsExtension::OutOfRangeAlert DMR6X2UVCodeplug::ExtendedSettingsElement::repeaterOutOfRangeAlert() const { return (AnytoneRoamingSettingsExtension::OutOfRangeAlert) getUInt8(Offset::repRangeAlert()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setRepeaterOutOfRangeAlert(AnytoneRoamingSettingsExtension::OutOfRangeAlert alert) { setUInt8(Offset::repRangeAlert(), (uint8_t)alert); } unsigned int DMR6X2UVCodeplug::ExtendedSettingsElement::repeaterCheckNumNotifications() const { return getUInt8(Offset::repRangeReminder())+1; } void DMR6X2UVCodeplug::ExtendedSettingsElement::setRepeaterCheckNumNotifications(unsigned int n) { n = Limit::repRangeReminder().map(n); setUInt8(Offset::repRangeReminder(), n-1); } Interval DMR6X2UVCodeplug::ExtendedSettingsElement::repeaterRangeCheckInterval() const { return Interval::fromSeconds((1+getUInt8(Offset::rangeCheckInterval()))*5); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setRepeaterRangeCheckInterval(Interval intv) { intv = Limit::rangeCheckInterval().map(intv); setUInt8(Offset::rangeCheckInterval(), intv.seconds()/5-1); } unsigned int DMR6X2UVCodeplug::ExtendedSettingsElement::repeaterRangeCheckCount() const { return getUInt8(Offset::rangeCheckCount())+3; } void DMR6X2UVCodeplug::ExtendedSettingsElement::setRepeaterRangeCheckCount(unsigned int n) { n = Limit::repeaterReconnections().map(n); setUInt8(Offset::rangeCheckCount(), n-3); } AnytoneRoamingSettingsExtension::RoamStart DMR6X2UVCodeplug::ExtendedSettingsElement::roamingStartCondition() const { return (AnytoneRoamingSettingsExtension::RoamStart) getUInt8(Offset::roamStartCondition()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setRoamingStartCondition(AnytoneRoamingSettingsExtension::RoamStart cond) { setUInt8(Offset::roamStartCondition(), (uint8_t)cond); } Interval DMR6X2UVCodeplug::ExtendedSettingsElement::autoRoamPeriod() const { return Interval::fromMinutes(getUInt8(Offset::autoRoamPeriod()) + 1); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setAutoRoamPeriod(Interval minutes) { minutes = Limit::autoRoamingInterval().map(minutes); setUInt8(Offset::autoRoamPeriod(), minutes.minutes()-1); } Interval DMR6X2UVCodeplug::ExtendedSettingsElement::autoRoamDelay() const { return Interval::fromSeconds(getUInt8(Offset::autoRoamDelay())); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setAutoRoamDelay(Interval sec) { sec = Limit::autoRoamDelay().map(sec); setUInt8(Offset::autoRoamDelay(), sec.seconds()); } AnytoneRoamingSettingsExtension::RoamStart DMR6X2UVCodeplug::ExtendedSettingsElement::roamingReturnCondition() const { return (AnytoneRoamingSettingsExtension::RoamStart) getUInt8(Offset::roamReturnCondition()); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setRoamingReturnCondition(AnytoneRoamingSettingsExtension::RoamStart cond) { setUInt8(Offset::roamReturnCondition(), (uint8_t)cond); } Interval DMR6X2UVCodeplug::ExtendedSettingsElement::muteTimer() const { return Interval::fromMinutes(getUInt8(Offset::muteDelay()) + 1); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setMuteTimer(Interval minutes) { minutes = Limit::muteTimer().map(minutes); setUInt8(Offset::muteDelay(), minutes.minutes()-1); } AnytoneDMRSettingsExtension::EncryptionType DMR6X2UVCodeplug::ExtendedSettingsElement::encryptionType() const { return (0 == getUInt8(0x0011)) ? AnytoneDMRSettingsExtension::EncryptionType::DMR : AnytoneDMRSettingsExtension::EncryptionType::AES; } void DMR6X2UVCodeplug::ExtendedSettingsElement::setEncryptionType(AnytoneDMRSettingsExtension::EncryptionType type) { switch (type) { case AnytoneDMRSettingsExtension::EncryptionType::DMR: setUInt8(Offset::encryptionType(), 0x00); break; case AnytoneDMRSettingsExtension::EncryptionType::AES: setUInt8(Offset::encryptionType(), 0x01); break; } } AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::ExtendedSettingsElement::zoneANameColor() const { return Color::decode(getUInt8(Offset::zoneANameColor())); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setZoneANameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::zoneANameColor(), Color::encode(color)); } AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::ExtendedSettingsElement::zoneBNameColor() const { return Color::decode(getUInt8(Offset::zoneBNameColor())); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setZoneBNameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::zoneBNameColor(), Color::encode(color)); } AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::ExtendedSettingsElement::channelANameColor() const { return Color::decode(getUInt8(Offset::channelANameColor())); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setChannelANameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::channelANameColor(), Color::encode(color)); } AnytoneDisplaySettingsExtension::Color DMR6X2UVCodeplug::ExtendedSettingsElement::channelBNameColor() const { return Color::decode(getUInt8(Offset::channelBNameColor())); } void DMR6X2UVCodeplug::ExtendedSettingsElement::setChannelBNameColor(AnytoneDisplaySettingsExtension::Color color) { setUInt8(Offset::channelBNameColor(), Color::encode(color)); } bool DMR6X2UVCodeplug::ExtendedSettingsElement::fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::ExtendedSettingsElement::fromConfig(flags, ctx, err)) { errMsg(err) << "Cannot encode extended settings for DMR-6X2UV codeplug."; return false; } // By default, use strong encryption setEncryptionType(AnytoneDMRSettingsExtension::EncryptionType::AES); enableSendTalkerAlias(ctx.config()->settings()->dmr()->sendTalkerAliasEnabled()); setTalkerAliasEncoding(ctx.config()->settings()->dmr()->talkerAliasEncoding()); // Encode device specific settings AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return true; // Encode DMR settings setTalkerAliasSource(ext->dmrSettings()->talkerAliasSource()); // Encode audio settings setMuteTimer(ext->audioSettings()->muteDelay()); // Encode display settings setChannelANameColor(ext->displaySettings()->channelNameColor()); setFontColor(ext->displaySettings()->standbyTextColor()); enableCustomChannelBackground(ext->displaySettings()->customChannelBackground()); // Encode DMR settings setEncryptionType(ext->dmrSettings()->encryption()); // Encode ranging/roaming settings. enableAutoRoaming(ext->roamingSettings()->autoRoam()); setAutoRoamPeriod(ext->roamingSettings()->autoRoamPeriod()); setAutoRoamDelay(ext->roamingSettings()->autoRoamDelay()); enableRepeaterRangeCheck(ext->roamingSettings()->repeaterRangeCheckEnabled()); setRepeaterRangeCheckInterval(ext->roamingSettings()->repeaterCheckInterval()); setRepeaterRangeCheckCount(ext->roamingSettings()->repeaterRangeCheckCount()); setRepeaterOutOfRangeAlert(ext->roamingSettings()->outOfRangeAlert()); setRoamingStartCondition(ext->roamingSettings()->roamingStartCondition()); setRoamingReturnCondition(ext->roamingSettings()->roamingReturnCondition()); setRepeaterCheckNumNotifications(ext->roamingSettings()->notificationCount()); if (! ext->roamingSettings()->defaultZone()->isNull()) setDefaultRoamingZoneIndex( ctx.index(ext->roamingSettings()->defaultZone()->as())); return true; } bool DMR6X2UVCodeplug::ExtendedSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::ExtendedSettingsElement::updateConfig(ctx, err)) { errMsg(err) << "Cannot decode extended settings for DMR-6X2UV codeplug"; return false; } ctx.config()->settings()->dmr()->enableSendTalkerAlias(sendTalkerAlias()); ctx.config()->settings()->dmr()->setTalkerAliasEncoding(talkerAliasEncoding()); AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) { ext = new AnytoneSettingsExtension(); ctx.config()->settings()->setAnytoneExtension(ext); } // Store DMR settings ext->dmrSettings()->setTalkerAliasSource(talkerAliasSource()); // Decode audio settings ext->audioSettings()->setMuteDelay(this->muteTimer()); // Decode display settings ext->displaySettings()->setChannelNameColor(this->channelANameColor()); ext->displaySettings()->setStandbyTextColor(this->fontColor()); ext->displaySettings()->enableCustomChannelBackground(this->customChannelBackgroundEnabled()); // Decode DMR settings ext->dmrSettings()->setEncryption(this->encryptionType()); // Decode ranging/roaming settings ext->roamingSettings()->enableAutoRoam(this->autoRoamingEnabled()); ext->roamingSettings()->setAutoRoamPeriod(this->autoRoamPeriod()); ext->roamingSettings()->setAutoRoamDelay(this->autoRoamDelay()); ext->roamingSettings()->enableRepeaterRangeCheck(this->repeaterRangeCheckEnabled()); ext->roamingSettings()->setRepeaterCheckInterval(this->repeaterRangeCheckInterval()); ext->roamingSettings()->setRepeaterRangeCheckCount(this->repeaterRangeCheckCount()); ext->roamingSettings()->setOutOfRangeAlert(this->repeaterOutOfRangeAlert()); ext->roamingSettings()->setRoamingStartCondition(this->roamingStartCondition()); ext->roamingSettings()->setRoamingReturnCondition(this->roamingReturnCondition()); ext->roamingSettings()->setNotificationCount(this->repeaterCheckNumNotifications()); return true; } bool DMR6X2UVCodeplug::ExtendedSettingsElement::linkConfig(Context &ctx, const ErrorStack &err) { if (! AnytoneCodeplug::ExtendedSettingsElement::linkConfig(ctx, err)) { errMsg(err) << "Cannot link extended settings for DMR-6X2UV codeplug"; return false; } AnytoneSettingsExtension *ext = ctx.config()->settings()->anytoneExtension(); if (nullptr == ext) return false; if (ctx.has(defaultRoamingZoneIndex())) { if (! ctx.has(this->defaultRoamingZoneIndex())) { errMsg(err) << "Cannot link roaming zone, index " << this->defaultRoamingZoneIndex() << " not defined."; return false; } ext->roamingSettings()->defaultZone()->set(ctx.get(this->defaultRoamingZoneIndex())); } return true; } /* ********************************************************************************************* * * Implementation of DMR6X2UVCodeplug::ChannelElement * ********************************************************************************************* */ DMR6X2UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, unsigned size) : AnytoneCodeplug::ChannelElement(ptr, size) { // pass... } DMR6X2UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : AnytoneCodeplug::ChannelElement(ptr) { // pass... } bool DMR6X2UVCodeplug::ChannelElement::hasAESEncryptionKeyIndex() const { return 0 != getUInt8(Offset::aesEncryptionKeyIndex()); } unsigned int DMR6X2UVCodeplug::ChannelElement::aesEncryptionKeyIndex() const { return getUInt8(Offset::aesEncryptionKeyIndex())-1; } void DMR6X2UVCodeplug::ChannelElement::setAESEncryptionKeyIndex(unsigned int index) { setUInt8(Offset::aesEncryptionKeyIndex(), index+1); } void DMR6X2UVCodeplug::ChannelElement::clearAESEncryptionKeyIndex() { setUInt8(Offset::aesEncryptionKeyIndex(), 0); } DMR6X2UVCodeplug::ChannelElement::DMREncryptionType DMR6X2UVCodeplug::ChannelElement::dmrEncryptionType() const { return getBit(Offset::dmrEncryptionType()) ? DMREncryptionType::Enhanced : DMREncryptionType::Basic; } void DMR6X2UVCodeplug::ChannelElement::setDMREncryptionType(DMREncryptionType type) { setBit(Offset::dmrEncryptionType(), DMREncryptionType::Enhanced == type); } bool DMR6X2UVCodeplug::ChannelElement::hasDMREncryptionKeyIndex() const { return 0 != getUInt8(Offset::dmrEncryptionKeyIndex()); } unsigned int DMR6X2UVCodeplug::ChannelElement::dmrEncryptionKeyIndex() const { return getUInt8(Offset::dmrAPRSChannelIndex())-1; } void DMR6X2UVCodeplug::ChannelElement::setDMREncryptionKeyIndex(unsigned int index) { setUInt8(Offset::dmrEncryptionKeyIndex(), index+1); } void DMR6X2UVCodeplug::ChannelElement::clearDMREncryptionKeyIndex() { setUInt8(Offset::dmrEncryptionKeyIndex(), 0); } bool DMR6X2UVCodeplug::ChannelElement::hasScanListIndex() const { return hasScanListIndex(0); } unsigned DMR6X2UVCodeplug::ChannelElement::scanListIndex() const { return scanListIndex(0); } void DMR6X2UVCodeplug::ChannelElement::setScanListIndex(unsigned idx) { setScanListIndex(0, idx); } void DMR6X2UVCodeplug::ChannelElement::clearScanListIndex() { clearScanListIndex(0); } bool DMR6X2UVCodeplug::ChannelElement::hasScanListIndex(unsigned int n) const { return 0xff != scanListIndex(n); } unsigned int DMR6X2UVCodeplug::ChannelElement::scanListIndex(unsigned int n) const { if (n >= Limit::scanListIndices()) return 0xff; return getUInt8(Offset::scanListIndices() + n*Offset::betweenScanListIndices()); } void DMR6X2UVCodeplug::ChannelElement::setScanListIndex(unsigned int n, unsigned idx) { if (n >= Limit::scanListIndices()) return; setUInt8(Offset::scanListIndices() + n*Offset::betweenScanListIndices(), idx); } void DMR6X2UVCodeplug::ChannelElement::clearScanListIndex(unsigned int n) { setScanListIndex(n, 0xff); } bool DMR6X2UVCodeplug::ChannelElement::roamingEnabled() const { // inverted return ! getBit(Offset::roaming()); } void DMR6X2UVCodeplug::ChannelElement::enableRoaming(bool enable) { // inverted setBit(Offset::roaming(), !enable); } bool DMR6X2UVCodeplug::ChannelElement::ranging() const { return getBit(Offset::ranging()); } void DMR6X2UVCodeplug::ChannelElement::enableRanging(bool enable) { return setBit(Offset::ranging(), enable); } unsigned int DMR6X2UVCodeplug::ChannelElement::dmrAPRSChannelIndex() const { return getUInt8(Offset::dmrAPRSChannelIndex()); } void DMR6X2UVCodeplug::ChannelElement::setDMRAPRSChannelIndex(unsigned int idx) { setUInt8(Offset::dmrAPRSChannelIndex(), std::min(APRSSettingsElement::Limit::dmrSystems(), idx)); } bool DMR6X2UVCodeplug::ChannelElement::dmrAPRSRXEnabled() const { return getBit(Offset::dmrAPRSRXEnable()); } void DMR6X2UVCodeplug::ChannelElement::enableDMRARPSRX(bool enable) { setBit(Offset::dmrAPRSRXEnable(), enable); } bool DMR6X2UVCodeplug::ChannelElement::dmrAPRSPTTEnabled() const { return getBit(Offset::dmrAPRSPTTEnable()); } void DMR6X2UVCodeplug::ChannelElement::enableDMRAPRSPTT(bool enable) { setBit(Offset::dmrAPRSPTTEnable(), enable); } DMR6X2UVCodeplug::ChannelElement::FMAPRSPTTMode DMR6X2UVCodeplug::ChannelElement::fmAPRSPTTMode() const { return (FMAPRSPTTMode)getUInt2(Offset::fmAPRSPTTMode()); } void DMR6X2UVCodeplug::ChannelElement::setFMAPRSPTTMode(FMAPRSPTTMode mode) { setUInt2(Offset::fmAPRSPTTMode(), (unsigned int)mode); } DMR6X2UVCodeplug::ChannelElement::APRSType DMR6X2UVCodeplug::ChannelElement::aprsType() const { return (APRSType) getUInt2(Offset::aprsType()); } void DMR6X2UVCodeplug::ChannelElement::setAPRSType(APRSType aprstype) { setUInt2(Offset::aprsType(), (unsigned int)aprstype); } bool DMR6X2UVCodeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx) { if (! AnytoneCodeplug::ChannelElement::fromChannelObj(c, ctx)) return false; if (const FMChannel *fm = c->as()) { if (! fm->aprsRef()->isNull()) { setAPRSType(APRSType::FM); if (auto ext = fm->anytoneChannelExtension()) { switch (ext->aprsPTT()) { case AnytoneChannelExtension::APRSPTT::Off: setFMAPRSPTTMode(FMAPRSPTTMode::Off); break; case AnytoneChannelExtension::APRSPTT::Start: setFMAPRSPTTMode(FMAPRSPTTMode::Start); break; case AnytoneChannelExtension::APRSPTT::End: setFMAPRSPTTMode(FMAPRSPTTMode::End); break; } } } } else if (const DMRChannel *dmr = c->as()) { if (! dmr->aprsRef()->isNull()) { if (dmr->aprsRef()->is()) { setAPRSType(APRSType::FM); if (auto ext = dmr->anytoneChannelExtension()) { switch (ext->aprsPTT()) { case AnytoneChannelExtension::APRSPTT::Off: setFMAPRSPTTMode(FMAPRSPTTMode::Off); break; case AnytoneChannelExtension::APRSPTT::Start: setFMAPRSPTTMode(FMAPRSPTTMode::Start); break; case AnytoneChannelExtension::APRSPTT::End: setFMAPRSPTTMode(FMAPRSPTTMode::End); break; } } } else if (DMRAPRSSystem *sys = dmr->aprsRef()->as()){ if (0 <= ctx.index(sys)) { setAPRSType(APRSType::DMR); setDMRAPRSChannelIndex(ctx.index(sys)); if (auto ext = dmr->anytoneChannelExtension()) enableDMRAPRSPTT(AnytoneChannelExtension::APRSPTT::Off != ext->aprsPTT()); } } } // Handle encryption clearAESEncryptionKeyIndex(); clearDMREncryptionKeyIndex(); if (CommercialChannelExtension *cex = dmr->commercialExtension()) { // By default, we assume we have strong encryption unless otherwise set by AnyTone DMR extension. bool hasStrongEncryption = (! ctx.config()->settings()->anytoneExtension()) || ( ctx.config()->settings()->anytoneExtension() && (AnytoneDMRSettingsExtension::EncryptionType::AES == ctx.config()->settings()->anytoneExtension()->dmrSettings()->encryption()) ); if (hasStrongEncryption && cex->encryptionKey() && cex->encryptionKey()->is()) { setAESEncryptionKeyIndex(ctx.index(cex->encryptionKey())); } else if ((! hasStrongEncryption) && cex->encryptionKey() && cex->encryptionKey()->is()) { setDMREncryptionType(DMREncryptionType::Basic); setDMREncryptionKeyIndex(ctx.index(cex->encryptionKey())); } } } return true; } bool DMR6X2UVCodeplug::ChannelElement::linkChannelObj(Channel *c, Context &ctx) const { if (! AnytoneCodeplug::ChannelElement::linkChannelObj(c, ctx)) return false; if (FMChannel *fm = c->as()) { auto ext = fm->anytoneChannelExtension(); if (nullptr == ext) fm->setAnytoneChannelExtension(ext = new AnytoneFMChannelExtension()); if ((APRSType::FM == aprsType()) && ctx.count()) { switch (fmAPRSPTTMode()) { case FMAPRSPTTMode::Off: ext->setAPRSPTT(AnytoneChannelExtension::APRSPTT::Off); break; case FMAPRSPTTMode::Start: ext->setAPRSPTT(AnytoneChannelExtension::APRSPTT::Start); break; case FMAPRSPTTMode::End: ext->setAPRSPTT(AnytoneChannelExtension::APRSPTT::End); break; } fm->setAPRS(ctx.get(0)); } } else if (DMRChannel *dmr = c->as()) { auto ext = dmr->anytoneChannelExtension(); if (nullptr == ext) dmr->setAnytoneChannelExtension(ext = new AnytoneDMRChannelExtension()); if ((APRSType::FM == aprsType()) && ctx.count()) { switch (fmAPRSPTTMode()) { case FMAPRSPTTMode::Off: ext->setAPRSPTT(AnytoneChannelExtension::APRSPTT::Off); break; case FMAPRSPTTMode::Start: ext->setAPRSPTT(AnytoneChannelExtension::APRSPTT::Start); break; case FMAPRSPTTMode::End: ext->setAPRSPTT(AnytoneChannelExtension::APRSPTT::End); break; } dmr->setAPRS(ctx.get(0)); } else if ((APRSType::DMR == aprsType()) && ctx.has(dmrAPRSChannelIndex())) { ext->setAPRSPTT(dmrAPRSPTTEnabled() ? AnytoneChannelExtension::APRSPTT::Start : AnytoneChannelExtension::APRSPTT::Off); dmr->setAPRS(ctx.get(dmrAPRSChannelIndex())); } // By default, we assume we have strong encryption unless otherwise set by AnyTone DMR extension. bool hasStrongEncryption = (! ctx.config()->settings()->anytoneExtension()) || ( ctx.config()->settings()->anytoneExtension() && (AnytoneDMRSettingsExtension::EncryptionType::AES == ctx.config()->settings()->anytoneExtension()->dmrSettings()->encryption()) ); if (hasStrongEncryption && hasAESEncryptionKeyIndex() && ctx.has(aesEncryptionKeyIndex())) { auto cex = dmr->commercialExtension(); if (nullptr == cex) dmr->setCommercialExtension(cex = new CommercialChannelExtension()); cex->setEncryptionKey(ctx.get(aesEncryptionKeyIndex())); } else if ((!hasStrongEncryption) && hasDMREncryptionKeyIndex() && (DMREncryptionType::Basic == dmrEncryptionType()) && ctx.has(dmrEncryptionKeyIndex())) { auto cex = dmr->commercialExtension(); if (nullptr == cex) dmr->setCommercialExtension(cex = new CommercialChannelExtension()); cex->setEncryptionKey(ctx.get(dmrEncryptionKeyIndex())); } } return true; } /* ******************************************************************************************** * * Implementation of DMR6X2UVCodeplug::ChannelExtensionElement * ******************************************************************************************** */ DMR6X2UVCodeplug::ChannelExtensionElement::ChannelExtensionElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DMR6X2UVCodeplug::ChannelExtensionElement::ChannelExtensionElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void DMR6X2UVCodeplug::ChannelExtensionElement::clear() { Element::clear(); memset(_data, 0, _size); } bool DMR6X2UVCodeplug::ChannelExtensionElement::hasARC4KeyIndex() const { return 0 != getUInt8(Offset::arc4KeyIndex()); } unsigned int DMR6X2UVCodeplug::ChannelExtensionElement::arc4KeyIndex() const { return getUInt8(Offset::arc4KeyIndex())-1; } void DMR6X2UVCodeplug::ChannelExtensionElement::setARC4KeyIndex(unsigned int idx) { setUInt8(Offset::arc4KeyIndex(), idx+1); } void DMR6X2UVCodeplug::ChannelExtensionElement::clearARC4KeyIndex() { setUInt8(Offset::arc4KeyIndex(), 0); } bool DMR6X2UVCodeplug::ChannelExtensionElement::linkChannelObj(Channel *c, Context &ctx, const ErrorStack &err) const { if (auto dc = c->as()) { if (! hasARC4KeyIndex()) return true; if (! ctx.has(arc4KeyIndex())) { errMsg(err) << "Cannot link channel '" << c->name() << "': Cannot resolve ARC4 key index " << arc4KeyIndex() << "."; return false; } auto cc = dc->commercialExtension(); if (nullptr == cc) dc->setCommercialExtension(cc = new CommercialChannelExtension()); if (cc->encryptionKeyRef()->isNull()) cc->setEncryptionKey(ctx.get(arc4KeyIndex())); } return true; } bool DMR6X2UVCodeplug::ChannelExtensionElement::fromChannelObj(const Channel *c, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); if (auto dc = c->as()) { clearARC4KeyIndex(); if (nullptr == dc->commercialExtension()) return true; auto cc = dc->commercialExtension(); if (cc->encryptionKeyRef()->isNull()) return true; if (auto key = cc->encryptionKey()->as()) setARC4KeyIndex(ctx.index(key)); } return true; } /* ******************************************************************************************** * * Implementation of DMR6X2UVCodeplug::AnalogAPRSSettingsElement * ******************************************************************************************** */ DMR6X2UVCodeplug::APRSSettingsElement::APRSSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } DMR6X2UVCodeplug::APRSSettingsElement::APRSSettingsElement(uint8_t *ptr) : Element(ptr, APRSSettingsElement::size()) { // pass... } void DMR6X2UVCodeplug::APRSSettingsElement::clear() { memset(_data, 0x00, _size); setUInt8(0x0000, 0xff); setFMTXDelay(Interval::fromMilliseconds(60)); setUInt8(0x003d, 0x01); setUInt8(0x003e, 0x03); setUInt8(0x003f, 0xff); } bool DMR6X2UVCodeplug::APRSSettingsElement::isValid() const { if (! Codeplug::Element::isValid()) return false; return (! destination().simplified().isEmpty()) && (! source().simplified().isEmpty()); } Frequency DMR6X2UVCodeplug::APRSSettingsElement::fmFrequency() const { return Frequency::fromHz(getBCD8_be(Offset::fmFrequency())*10); } void DMR6X2UVCodeplug::APRSSettingsElement::setFMFrequency(Frequency f) { setBCD8_be(Offset::fmFrequency(), f.inHz()/10); } Interval DMR6X2UVCodeplug::APRSSettingsElement::fmTXDelay() const { return Interval::fromMilliseconds(((unsigned)getUInt8(Offset::fmTXDelay())*20)); } void DMR6X2UVCodeplug::APRSSettingsElement::setFMTXDelay(const Interval intv) { setUInt8(Offset::fmTXDelay(), intv.milliseconds()/20); } SelectiveCall DMR6X2UVCodeplug::APRSSettingsElement::txTone() const { if ((uint8_t)SignalingType::Off ==getUInt8(Offset::fmSigType())) { // none return SelectiveCall(); } else if ((uint8_t)SignalingType::CTCSS == getUInt8(Offset::fmSigType())) { // CTCSS return CTCSS::decode(getUInt8(Offset::fmCTCSS())); } else if ((uint8_t)SignalingType::DCS == getUInt8(Offset::fmSigType())) { // DCS uint16_t code = getUInt16_le(Offset::fmDCS()); if (512 < code) return SelectiveCall::fromBinaryDCS(code, false); return SelectiveCall::fromBinaryDCS(code-512, true); } return SelectiveCall(); } void DMR6X2UVCodeplug::APRSSettingsElement::setTXTone(const SelectiveCall &code) { if (code.isInvalid()) { setUInt8(Offset::fmSigType(), (uint8_t)SignalingType::Off); } else if (code.isCTCSS()) { setUInt8(Offset::fmSigType(), (uint8_t)SignalingType::CTCSS); setUInt8(Offset::fmCTCSS(), CTCSS::encode(code)); } else if (code.isDCS()) { setUInt8(Offset::fmSigType(), (uint8_t)SignalingType::DCS); setUInt16_le(Offset::fmDCS(), code.binCode() + (code.isInverted() ? 512 : 0)); } } Interval DMR6X2UVCodeplug::APRSSettingsElement::manualTXInterval() const { return Interval::fromSeconds(getUInt8(Offset::manualTXInterval())); } void DMR6X2UVCodeplug::APRSSettingsElement::setManualTXInterval(Interval sec) { setUInt8(Offset::manualTXInterval(), sec.seconds()); } bool DMR6X2UVCodeplug::APRSSettingsElement::autoTX() const { return ! autoTXInterval().isNull(); } Interval DMR6X2UVCodeplug::APRSSettingsElement::autoTXInterval() const { return Interval::fromSeconds( ((unsigned)getUInt8(Offset::autoTXInterval()))*30 ); } void DMR6X2UVCodeplug::APRSSettingsElement::setAutoTXInterval(Interval sec) { setUInt8(Offset::autoTXInterval(), sec.seconds()/30); } void DMR6X2UVCodeplug::APRSSettingsElement::disableAutoTX() { setAutoTXInterval(Interval::fromMilliseconds(0)); } bool DMR6X2UVCodeplug::APRSSettingsElement::fixedLocationEnabled() const { return getUInt8(Offset::fixedLocation()); } QGeoCoordinate DMR6X2UVCodeplug::APRSSettingsElement::fixedLocation() const { double latitude = getUInt8(Offset::fixedLatDeg()) + double(getUInt8(Offset::fixedLatMin()))/60 + double(getUInt8(Offset::fixedLatSec()))/3600; if (getUInt8(Offset::fixedLatSouth())) latitude *= -1; double longitude = getUInt8(Offset::fixedLonDeg()) + double(getUInt8(Offset::fixedLonMin()))/60 + double(getUInt8(Offset::fixedLonSec()))/3600; if (getUInt8(Offset::fixedLonWest())) longitude *= -1; return QGeoCoordinate(latitude, longitude); } void DMR6X2UVCodeplug::APRSSettingsElement::setFixedLocation(QGeoCoordinate &loc) { double latitude = loc.latitude(); bool south = (0 > latitude); latitude = std::abs(latitude); unsigned lat_deg = int(latitude); latitude -= lat_deg; latitude *= 60; unsigned lat_min = int(latitude); latitude -= lat_min; latitude *= 60; unsigned lat_sec = int(latitude); double longitude = loc.longitude(); bool west = (0 > longitude); longitude = std::abs(longitude); unsigned lon_deg = int(longitude); longitude -= lon_deg; longitude *= 60; unsigned lon_min = int(longitude); longitude -= lon_min; longitude *= 60; unsigned lon_sec = int(longitude); setUInt8(Offset::fixedLatDeg(), lat_deg); setUInt8(Offset::fixedLatMin(), lat_min); setUInt8(Offset::fixedLatSec(), lat_sec); setUInt8(Offset::fixedLatSouth(), (south ? 0x01 : 0x00)); setUInt8(Offset::fixedLonDeg(), lon_deg); setUInt8(Offset::fixedLonMin(), lon_min); setUInt8(Offset::fixedLonSec(), lon_sec); setUInt8(Offset::fixedLonWest(), (west ? 0x01 : 0x00)); // enable fixed location. setUInt8(Offset::fixedLocation(), 0x01); } void DMR6X2UVCodeplug::APRSSettingsElement::disableFixedLocation() { setUInt8(Offset::fixedLocation(), 0x00); } QString DMR6X2UVCodeplug::APRSSettingsElement::destination() const { // Terminated/padded with space return readASCII(Offset::destinationCall(), 6, ' '); } unsigned DMR6X2UVCodeplug::APRSSettingsElement::destinationSSID() const { return getUInt8(Offset::destinationSSID()); } void DMR6X2UVCodeplug::APRSSettingsElement::setDestination(const QString &call, unsigned ssid) { // Terminated/padded with space writeASCII(Offset::destinationCall(), call, 6, ' '); setUInt8(Offset::destinationSSID(), ssid); } QString DMR6X2UVCodeplug::APRSSettingsElement::source() const { // Terminated/padded with space return readASCII(Offset::sourceCall(), 6, ' '); } unsigned DMR6X2UVCodeplug::APRSSettingsElement::sourceSSID() const { return getUInt8(Offset::sourceSSID()); } void DMR6X2UVCodeplug::APRSSettingsElement::setSource(const QString &call, unsigned ssid) { // Terminated/padded with space writeASCII(Offset::sourceCall(), call, 6, ' '); setUInt8(Offset::sourceSSID(), ssid); } QString DMR6X2UVCodeplug::APRSSettingsElement::path() const { return readASCII(Offset::path(), 20, 0x00); } void DMR6X2UVCodeplug::APRSSettingsElement::setPath(const QString &path) { writeASCII(Offset::path(), path, 20, 0x00); } FMAPRSSystem::Icon DMR6X2UVCodeplug::APRSSettingsElement::icon() const { return code2aprsicon(getUInt8(Offset::symbolTable()), getUInt8(Offset::symbol())); } void DMR6X2UVCodeplug::APRSSettingsElement::setIcon(FMAPRSSystem::Icon icon) { setUInt8(Offset::symbolTable(), aprsicon2tablecode(icon)); setUInt8(Offset::symbol(), aprsicon2iconcode(icon)); } Channel::Power DMR6X2UVCodeplug::APRSSettingsElement::power() const { switch (getUInt8(Offset::fmPower())) { case 0: return Channel::Power::Low; case 1: return Channel::Power::Mid; case 2: return Channel::Power::High; case 3: return Channel::Power::Max; } return Channel::Power::Low; } void DMR6X2UVCodeplug::APRSSettingsElement::setPower(Channel::Power power) { switch (power) { case Channel::Power::Min: case Channel::Power::Low: setUInt8(Offset::fmPower(), 0x00); break; case Channel::Power::Mid: setUInt8(Offset::fmPower(), 0x01); break; case Channel::Power::High: setUInt8(Offset::fmPower(), 0x02); break; case Channel::Power::Max: setUInt8(Offset::fmPower(), 0x03); break; } } Interval DMR6X2UVCodeplug::APRSSettingsElement::fmPreWaveDelay() const { return Interval::fromMilliseconds(((unsigned)getUInt8(Offset::fmPrewaveDelay()))*10); } void DMR6X2UVCodeplug::APRSSettingsElement::setFMPreWaveDelay(Interval ms) { setUInt8(Offset::fmPrewaveDelay(), ms.milliseconds()/10); } bool DMR6X2UVCodeplug::APRSSettingsElement::dmrChannelIsSelected(unsigned n) const { return 0xfa2 == dmrChannelIndex(n); } unsigned DMR6X2UVCodeplug::APRSSettingsElement::dmrChannelIndex(unsigned n) const { return getUInt16_le(Offset::dmrChannelIndices() + n*Offset::betweenDMRChannelIndices()); } void DMR6X2UVCodeplug::APRSSettingsElement::setDMRChannelIndex(unsigned n, unsigned idx) { setUInt16_le(Offset::dmrChannelIndices() + n*Offset::betweenDMRChannelIndices(), idx); } void DMR6X2UVCodeplug::APRSSettingsElement::setDMRChannelSelected(unsigned n) { setDMRChannelIndex(n, 0xfa2); } unsigned DMR6X2UVCodeplug::APRSSettingsElement::dmrDestination(unsigned n) const { return getBCD8_be(Offset::dmrDestinations() + n*Offset::betweenDMRDestinations()); } void DMR6X2UVCodeplug::APRSSettingsElement::setDMRDestination(unsigned n, unsigned idx) { setBCD8_be(Offset::dmrDestinations() + n*Offset::betweenDMRDestinations(), idx); } DMRContact::Type DMR6X2UVCodeplug::APRSSettingsElement::dmrCallType(unsigned n) const { switch(getUInt8(Offset::dmrCallTypes() + n*Offset::betweenDMRCallTypes())) { case 0: return DMRContact::PrivateCall; case 1: return DMRContact::GroupCall; case 2: return DMRContact::AllCall; } return DMRContact::PrivateCall; } void DMR6X2UVCodeplug::APRSSettingsElement::setDMRCallType(unsigned n, DMRContact::Type type) { switch(type) { case DMRContact::PrivateCall: setUInt8(Offset::dmrCallTypes() + n*Offset::betweenDMRCallTypes(), 0x00); break; case DMRContact::GroupCall: setUInt8(Offset::dmrCallTypes() + n*Offset::betweenDMRCallTypes(), 0x01); break; case DMRContact::AllCall: setUInt8(Offset::dmrCallTypes() + n*Offset::betweenDMRCallTypes(), 0x02); break; } } bool DMR6X2UVCodeplug::APRSSettingsElement::dmrTimeSlotOverride(unsigned n) { return 0x00 != getUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots()); } DMRChannel::TimeSlot DMR6X2UVCodeplug::APRSSettingsElement::dmrTimeSlot(unsigned n) const { switch (getUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots())) { case 1: return DMRChannel::TimeSlot::TS1; case 2: return DMRChannel::TimeSlot::TS2; } return DMRChannel::TimeSlot::TS1; } void DMR6X2UVCodeplug::APRSSettingsElement::setDMRTimeSlot(unsigned n, DMRChannel::TimeSlot ts) { switch (ts) { case DMRChannel::TimeSlot::TS1: setUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots(), 0x01); break; case DMRChannel::TimeSlot::TS2: setUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots(), 0x02); break; } } void DMR6X2UVCodeplug::APRSSettingsElement::clearDMRTimeSlotOverride(unsigned n) { setUInt8(Offset::dmrTimeSlots() + n*Offset::betweenDMRTimeSlots(), 0); } bool DMR6X2UVCodeplug::APRSSettingsElement::dmrRoaming() const { return getUInt8(Offset::roamingSupport()); } void DMR6X2UVCodeplug::APRSSettingsElement::enableDMRRoaming(bool enable) { setUInt8(Offset::roamingSupport(), (enable ? 0x01 : 0x00)); } Interval DMR6X2UVCodeplug::APRSSettingsElement::dmrPreWaveDelay() const { return Interval::fromMilliseconds(((unsigned)getUInt8(Offset::dmrPrewaveDelay()))*100); } void DMR6X2UVCodeplug::APRSSettingsElement::setDMRPreWaveDelay(Interval ms) { setUInt8(Offset::dmrPrewaveDelay(), ms.milliseconds()/100); } bool DMR6X2UVCodeplug::APRSSettingsElement::fromFMAPRSSystem(const FMAPRSSystem *sys, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) clear(); if (! sys->hasRevertChannel()) { errMsg(err) << "Cannot encode APRS settings: " << "No revert channel defined for APRS system '" << sys->name() <<"'."; return false; } setFMFrequency(sys->revertChannel()->txFrequency()); setTXTone(sys->revertChannel()->txTone()); setPower(sys->revertChannel()->power()); setManualTXInterval(sys->period()); setAutoTXInterval(sys->period()); setDestination(sys->destination(), sys->destSSID()); setSource(sys->source(), sys->srcSSID()); setPath(sys->path()); setIcon(sys->icon()); AnytoneFMAPRSSettingsExtension *ext = sys->anytoneExtension(); if (nullptr == ext) return true; setFMPreWaveDelay(ext->preWaveDelay()); setFMTXDelay(ext->txDelay()); return true; } FMAPRSSystem * DMR6X2UVCodeplug::APRSSettingsElement::toFMAPRSSystem() { FMAPRSSystem *sys = new FMAPRSSystem( tr("APRS %1").arg(destination()), nullptr, destination(), destinationSSID(), source(), sourceSSID(), path(), icon(), "", autoTXInterval()); AnytoneFMAPRSSettingsExtension *ext = new AnytoneFMAPRSSettingsExtension(); ext->setPreWaveDelay(fmPreWaveDelay()); ext->setTXDelay(fmTXDelay()); sys->setAnytoneExtension(ext); return sys; } bool DMR6X2UVCodeplug::APRSSettingsElement::linkFMAPRSSystem(FMAPRSSystem *sys, Context &ctx) { // First, try to find a matching analog channel in list Frequency f = fmFrequency(); FMChannel *ch = ctx.config()->channelList()->findFMChannelByTxFreq(f); if (! ch) { // If no channel is found, create one with the settings from APRS channel: ch = new FMChannel(); ch->setName("APRS Channel"); ch->setRXFrequency(fmFrequency()); ch->setTXFrequency(fmFrequency()); ch->setPower(power()); ch->setTXTone(txTone()); logInfo() << "No matching APRS channel found for TX frequency " << fmFrequency().format() << ", create one as 'APRS Channel'"; ctx.config()->channelList()->add(ch); } sys->setRevertChannel(ch); return true; } bool DMR6X2UVCodeplug::APRSSettingsElement::fromDMRAPRSSystems(Context &ctx) { unsigned int n = std::min(ctx.count(), Limit::dmrSystems()); for (unsigned int idx=0; idx(idx), ctx); return true; } bool DMR6X2UVCodeplug::APRSSettingsElement::fromDMRAPRSSystemObj(unsigned int idx, DMRAPRSSystem *sys, Context &ctx) { if (sys->hasContact()) { setDMRDestination(idx, sys->contact()->number()); setDMRCallType(idx, sys->contact()->type()); } if (sys->hasRevertChannel()) { setDMRChannelIndex(idx, ctx.index(sys->revertChannel())); clearDMRTimeSlotOverride(idx); } else { // no revert channel specified or "selected channel": setDMRChannelSelected(idx); } return true; } DMRAPRSSystem * DMR6X2UVCodeplug::APRSSettingsElement::toDMRAPRSSystemObj(int idx) const { if (0 == dmrDestination(idx)) return nullptr; return new DMRAPRSSystem(tr("GPS Sys #%1").arg(idx+1)); } bool DMR6X2UVCodeplug::APRSSettingsElement::linkDMRAPRSSystem(int idx, DMRAPRSSystem *sys, Context &ctx) const { // if a revert channel is defined -> link to it if (dmrChannelIsSelected(idx)) sys->resetRevertChannel(); else if (ctx.has(dmrChannelIndex(idx)) && ctx.get(dmrChannelIndex(idx))->is()) sys->setRevertChannel(ctx.get(dmrChannelIndex(idx))->as()); // Search for a matching contact in contacts DMRContact *cont = nullptr; for (unsigned int i=0; i(); i++) { if (ctx.get(i)->number() == dmrDestination(idx)) { cont = ctx.get(i); break; } } // If no matching contact is found, create one if (nullptr == cont) { cont = new DMRContact(dmrCallType(idx), tr("GPS #%1 Contact").arg(idx+1), dmrDestination(idx), false); ctx.config()->contacts()->add(cont); } // link contact to GPS system. sys->setContact(cont); return true; } /* ********************************************************************************************* * * Implementation of DMR6X2UVCodeplug * ********************************************************************************************* */ DMR6X2UVCodeplug::DMR6X2UVCodeplug(const QString &label, QObject *parent) : D868UVCodeplug(label, parent) { // pass... } DMR6X2UVCodeplug::DMR6X2UVCodeplug(QObject *parent) : D868UVCodeplug("BTECH DMR-6X2UV", parent) { // pass... } Config * DMR6X2UVCodeplug::preprocess(Config *config, const ErrorStack &err) const { // Apply base preprocessing auto intermediate = AnytoneCodeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot apply preprocessing for DMR-6X2UV."; return nullptr; } // Keep 16bit DMR, ARC4 40bit, 128 bit AES and 256 bit AES keys. EncryptionKeyFilterVisitor filter( { EncryptionKeyFilterVisitor::Filter(BasicEncryptionKey::staticMetaObject, 16, 16), EncryptionKeyFilterVisitor::Filter(AESEncryptionKey::staticMetaObject, 128, 128), EncryptionKeyFilterVisitor::Filter(AESEncryptionKey::staticMetaObject, 256, 256), EncryptionKeyFilterVisitor::Filter(ARC4EncryptionKey::staticMetaObject, 40, 40)}); if (! filter.process(intermediate, err)) { errMsg(err) << "Cannot remove unsupported exncryption."; delete intermediate; return nullptr; } return intermediate; } bool DMR6X2UVCodeplug::allocateBitmaps() { if (! D868UVCodeplug::allocateBitmaps()) return false; // Roaming channel bitmaps image(0).addElement(Offset::roamingChannelBitmap(), RoamingChannelBitmapElement::size()); // Roaming zone bitmaps image(0).addElement(Offset::roamingZoneBitmap(), RoamingZoneBitmapElement::size()); return true; } void DMR6X2UVCodeplug::setBitmaps(Context& ctx) { // First set everything common between D868UV and dmr6x2 codeplugs. D868UVCodeplug::setBitmaps(ctx); // Mark roaming zones RoamingZoneBitmapElement roaming_zone_bitmap(data(Offset::roamingZoneBitmap())); unsigned int num_roaming_zones = std::min(Limit::roamingZones(), ctx.count()); roaming_zone_bitmap.clear(); roaming_zone_bitmap.enableFirst(num_roaming_zones); // Mark roaming channels RoamingChannelBitmapElement roaming_ch_bitmap(data(Offset::roamingChannelBitmap())); unsigned int num_roaming_channel = std::min(Limit::roamingChannels(), ctx.count()); roaming_ch_bitmap.clear(); roaming_ch_bitmap.enableFirst(num_roaming_channel); } void DMR6X2UVCodeplug::allocateForEncoding() { // First allocate everything common between D868UV and dmr6x2 codeplugs. D868UVCodeplug::allocateForEncoding(); this->allocateRoaming(); this->allocateAESKeys(); this->allocateARC4Keys(); } void DMR6X2UVCodeplug::allocateForDecoding() { // First allocate everything common between D868UV and dmr6x2 codeplugs. D868UVCodeplug::allocateForDecoding(); this->allocateRoaming(); this->allocateAESKeys(); this->allocateARC4Keys(); } bool DMR6X2UVCodeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { // Encode everything common between d868uv and dmr6x2 radios. if (! D868UVCodeplug::encodeElements(flags, ctx, err)) return false; if (! this->encodeRoaming(flags, ctx, err)) return false; if (! this->encodeAESKeys(flags, ctx, err)) return false; if (! this->encodeARC4Keys(flags, ctx, err)) return false; return true; } bool DMR6X2UVCodeplug::createElements(Context &ctx, const ErrorStack &err) { // Create everything commong between d868uv and dmr6x2 codeplugs. if (! D868UVCodeplug::createElements(ctx, err)) return false; if (! this->createRoaming(ctx, err)) return false; if (! this->createAESKeys(ctx, err)) return false; if (! this->createARC4Keys(ctx, err)) return false; return true; } bool DMR6X2UVCodeplug::linkElements(Context &ctx, const ErrorStack &err) { // Link everything commong between d868uv and dmr6x2 codeplugs. if (! D868UVCodeplug::linkElements(ctx, err)) return false; if (! this->linkRoaming(ctx, err)) return false; return true; } void DMR6X2UVCodeplug::allocateGeneralSettings() { image(0).addElement(Offset::settings(), GeneralSettingsElement::size()); image(0).addElement(Offset::settingsExtension(), ExtendedSettingsElement::size()); } bool DMR6X2UVCodeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! GeneralSettingsElement(data(Offset::settings())).fromConfig(flags, ctx, err)) { errMsg(err) << "Cannot encode general settings element."; return false; } if (! ExtendedSettingsElement(data(Offset::settingsExtension())).fromConfig(flags, ctx)) { errMsg(err) << "Cannot encode extended settings element."; return false; } return true; } bool DMR6X2UVCodeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { if (! GeneralSettingsElement(data(Offset::settings())).updateConfig(ctx, err)) { errMsg(err) << "Cannot decode general settings element."; return false; } if (! ExtendedSettingsElement(data(Offset::settingsExtension())).updateConfig(ctx)) { errMsg(err) << "Cannot decode extended settings element."; return false; } return true; } bool DMR6X2UVCodeplug::linkGeneralSettings(Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings())).linkSettings(ctx.config()->settings(), ctx, err); } void DMR6X2UVCodeplug::allocateChannels() { /* Allocate channels */ ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); for (uint16_t i=0; i skip if (! channel_bitmap.isEncoded(i)) continue; // compute address for channel uint16_t bank = i/Limit::channelsPerBank(), idx=i%Limit::channelsPerBank(); uint32_t addr = Offset::channelBanks() + bank*Offset::betweenChannelBanks() + idx * ChannelElement::size(); if (!isAllocated(addr, 0)) { image(0).addElement(addr, ChannelElement::size()); image(0).addElement(addr+Offset::toChannelExtension(), ChannelElement::size()); memset(data(addr+Offset::toChannelExtension()), 0, ChannelElement::size()); } } } bool DMR6X2UVCodeplug::encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); // Encode channels for (unsigned int i=0; i(); i++) { // enable channel uint16_t bank = i/Limit::channelsPerBank(), idx = i%Limit::channelsPerBank(); uint32_t addr = Offset::channelBanks() + bank*Offset::betweenChannelBanks()+ idx*ChannelElement::size(); ChannelElement ch(data(addr)); if (! ch.fromChannelObj(ctx.get(i), ctx)) return false; ChannelExtensionElement ext(data(addr + Offset::toChannelExtension())); if (! ext.fromChannelObj(ctx.get(i), ctx, err)) return false; } return true; } bool DMR6X2UVCodeplug::createChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create channels ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); for (uint16_t i=0; ichannelList()->add(obj); ctx.add(obj, i); } } return true; } bool DMR6X2UVCodeplug::linkChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ChannelBitmapElement channel_bitmap(data(Offset::channelBitmap())); // Link channel objects for (uint16_t i=0; i(i)) { if (! ch.linkChannelObj(ctx.get(i), ctx)) return false; if (! ext.linkChannelObj(ctx.get(i), ctx, err)) return false; } } return true; } void DMR6X2UVCodeplug::allocateGPSSystems() { // replaces D868UVCodeplug::allocateGPSSystems // APRS settings image(0).addElement(Offset::aprsSettings(), APRSSettingsElement::size()); image(0).addElement(Offset::fmAPRSMessage(), Size::fmAPRSMessage()); } bool DMR6X2UVCodeplug::encodeGPSSystems(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // replaces D868UVCodeplug::encodeGPSSystems APRSSettingsElement aprs(data(Offset::aprsSettings())); aprs.clear(); // Encode APRS system (there can only be one) if (0 < ctx.count()) { aprs.fromFMAPRSSystem(ctx.get(0), ctx, err); uint8_t *aprsmsg = (uint8_t *)data(Offset::fmAPRSMessage()); encode_ascii(aprsmsg, ctx.get(0)->message(), Limit::fmAPRSMessage(), 0x00); } // Encode GPS systems if (! aprs.fromDMRAPRSSystems(ctx)) return false; if (0 < ctx.count()) { // If there is at least one GPS system defined -> set auto TX interval. // This setting might be overridden by any analog APRS system below aprs.setAutoTXInterval(ctx.get(0)->period()); aprs.setManualTXInterval(ctx.get(0)->period()); } return true; } bool DMR6X2UVCodeplug::createGPSSystems(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // replaces D868UVCodeplug::createGPSSystems // Before creating any GPS/APRS systems, get global auto TX interval APRSSettingsElement aprs(data(Offset::aprsSettings())); // Create APRS system (if enabled) uint8_t *aprsmsg = (uint8_t *)data(Offset::fmAPRSMessage()); if (aprs.isValid()) { FMAPRSSystem *sys = aprs.toFMAPRSSystem(); if (nullptr == sys) { errMsg(err) << "Cannot decode positioning systems."; return false; } sys->setPeriod(aprs.autoTXInterval()); sys->setMessage(decode_ascii(aprsmsg, Limit::fmAPRSMessage(), 0x00)); ctx.config()->posSystems()->add(sys); ctx.add(sys,0); } // Create GPS systems for (unsigned int i=0; isetPeriod(aprs.autoTXInterval()); ctx.config()->posSystems()->add(sys); ctx.add(sys, i); } else { return false; } } return true; } bool DMR6X2UVCodeplug::linkGPSSystems(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // replaces D868UVCodeplug::linkGPSSystems // Link APRS system APRSSettingsElement aprs(data(Offset::aprsSettings())); if (aprs.isValid()) { aprs.linkFMAPRSSystem(ctx.get(0), ctx); } // Link GPS systems for (unsigned int i=0; i(i), ctx); } return true; } void DMR6X2UVCodeplug::allocateRoaming() { /* Allocate roaming channels */ RoamingChannelBitmapElement roaming_channel_bitmap(data(Offset::roamingChannelBitmap())); for (uint8_t i=0; i skip if (! roaming_channel_bitmap.isEncoded(i)) continue; // Allocate roaming channel uint32_t addr = Offset::roamingChannels() + i*D878UVCodeplug::RoamingChannelElement::size(); if (!isAllocated(addr, 0)) image(0).addElement(addr, D878UVCodeplug::RoamingChannelElement::size()); } /* Allocate roaming zones. */ RoamingZoneBitmapElement roaming_zone_bitmap(data(Offset::roamingZoneBitmap())); for (uint8_t i=0; i skip if (! roaming_zone_bitmap.isEncoded(i)) continue; // Allocate roaming zone uint32_t addr = Offset::roamingZones() + i*D878UVCodeplug::RoamingZoneElement::size(); if (!isAllocated(addr, 0)) { logDebug() << "Allocate roaming zone at " << QString::number(addr, 16); image(0).addElement(addr, D878UVCodeplug::RoamingZoneElement::size()); } } } bool DMR6X2UVCodeplug::encodeRoaming(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // Encode roaming channels unsigned int num_roaming_channel = std::min( Limit::roamingChannels(), ctx.count()); for (uint8_t i=0; iroamingChannels()->get(i)->as(); rch_elm.clear(); rch_elm.fromChannel(rch); if (! ctx.add(rch, i)) { errMsg(err) << "Cannot add index " << i << " for roaming channel '" << rch->name() << "' to codeplug context."; return false; } } // Encode roaming zones for (unsigned int i=0; i(); i++){ uint32_t addr = Offset::roamingZones() + i*RoamingZoneElement::size(); RoamingZoneElement zone(data(addr)); logDebug() << "Encode roaming zone " << ctx.config()->roamingZones()->zone(i)->name() << " (" << (i+1) << ") at " << QString::number(addr, 16) << " with " << ctx.config()->roamingZones()->zone(i)->count() << " elements."; zone.fromRoamingZone(ctx.config()->roamingZones()->zone(i), ctx); } return true; } bool DMR6X2UVCodeplug::createRoaming(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create or find roaming channels RoamingChannelBitmapElement roaming_channel_bitmap(data(Offset::roamingChannelBitmap())); for (unsigned int i=0; iroamingZones()->add(zone); ctx.add(zone, i); z.linkRoamingZone(zone, ctx, err); } return true; } bool DMR6X2UVCodeplug::linkRoaming(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) // Pass, no need to link roaming channels. return true; } void DMR6X2UVCodeplug::allocateAESKeys() { // Since there is no bitmap indicating, which keys are valid, we need to allocate all 255 for // decoding. if (! isAllocated(Offset::aesEncryptionKeys(), 0)) image(0).addElement(Offset::aesEncryptionKeys(), Limit::aesEncryptionKeys()*AESEncryptionKeyElement::size()); } bool DMR6X2UVCodeplug::encodeAESKeys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); Q_UNUSED(flags); for (uint8_t i=0; i skip if (! ctx.has(i)) continue; AESEncryptionKeyElement key(data(Offset::aesEncryptionKeys() + i*AESEncryptionKeyElement::size())); key.setIndex(i); key.setKey(ctx.get(i)->key()); } return true; } bool DMR6X2UVCodeplug::createAESKeys(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); for (uint8_t i=0; i skip if (! keyEl.isValid()) continue; // Decode auto key = new AESEncryptionKey(); key->setName(QString("AES Key %1").arg(i+1)); if (! key->setKey(keyEl.key(), err)) { delete key; return false; } // Store ctx.add(key, i); ctx.config()->commercialExtension()->encryptionKeys()->add(key); } return true; } void DMR6X2UVCodeplug::allocateARC4Keys() { // Since there is no bitmap indicating, which keys are valid, we need to allocate all 255 for // decoding. if (! isAllocated(Offset::arc4EncryptionKeys(), 0)) image(0).addElement(Offset::arc4EncryptionKeys(), Limit::arc4EncryptionKeys()*ARC4EncryptionKeyElement::size()); } bool DMR6X2UVCodeplug::encodeARC4Keys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); Q_UNUSED(flags); for (uint8_t i=0; i skip if (! ctx.has(i)) continue; ARC4EncryptionKeyElement key(data(Offset::arc4EncryptionKeys() + i*ARC4EncryptionKeyElement::size())); key.setIndex(i); key.setKey(ctx.get(i)->key()); } return true; } bool DMR6X2UVCodeplug::createARC4Keys(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); for (uint8_t i=0; i skip if (! keyEl.isValid()) continue; // Decode auto key = new ARC4EncryptionKey(); key->setName(QString("ARC4 Key %1").arg(i+1)); if (! key->setKey(keyEl.key(), err)) { delete key; return false; } // Store ctx.add(key, i); ctx.config()->commercialExtension()->encryptionKeys()->add(key); } return true; } ================================================ FILE: lib/dmr6x2uv_codeplug.hh ================================================ #ifndef DMR6X2UVCODEPLUG_HH #define DMR6X2UVCODEPLUG_HH #include "d878uv_codeplug.hh" #include "ranges.hh" /** Represents the device specific binary codeplug for BTECH DMR-6X2UV radios. * * This codeplug implementation is compatible with firmware revision 2.21. See * https://dmr-tools.github.io/codeplugs for details. * * @ingroup dmr6x2uv */ class DMR6X2UVCodeplug : public D868UVCodeplug { Q_OBJECT protected: /** Colors supported by the DMR-6X2UV. */ struct Color { public: /** Maps code -> color. */ static AnytoneDisplaySettingsExtension::Color decode(uint8_t code); /** Maps color -> code. */ static uint8_t encode(AnytoneDisplaySettingsExtension::Color color); protected: /** Encoding of the supported colors. */ typedef enum { Orange=0, Red=1, Yellow=2, Green=3, Turquoise=4, Blue=5, White = 6, Black = 7 } CodedColor; }; /** Background colors supported by the DMR-6X2UV. */ struct BackgroundColor { public: /** Maps code -> color. */ static AnytoneDisplaySettingsExtension::Color decode(uint8_t code); /** Maps color -> code. */ static uint8_t encode(AnytoneDisplaySettingsExtension::Color color); protected: /** Encoding of the supported colors. */ typedef enum { Black = 0, Blue = 1 } CodedColor; }; /** Font colors supported by the DMR-6X2UV. */ struct FontColor { public: /** Maps code -> color. */ static AnytoneDisplaySettingsExtension::Color decode(uint8_t code); /** Maps color -> code. */ static uint8_t encode(AnytoneDisplaySettingsExtension::Color color); protected: /** Encoding of the supported colors. */ typedef enum { White = 0, Black = 1, Orange=2, Red=3, Yellow=4, Green=5, Turquoise=6, Blue=7 } CodedColor; }; public: /** General settings element for the DMR-6X2UV. * * Extends the @c AnytoneCodeplug::GeneralSettingsElement by the device specific settings for * the BTECH DMR-6X2UV. * * Memory representation of the encoded settings element (size 0x0e0 bytes): * @verbinclude dmr6x2uv_generalsettings.txt */ class GeneralSettingsElement: public D868UVCodeplug::GeneralSettingsElement { protected: /** Device specific encoding of the key functions. */ struct KeyFunction { public: /** Encodes key function. */ static uint8_t encode(AnytoneKeySettingsExtension::KeyFunction tone); /** Decodes key function. */ static AnytoneKeySettingsExtension::KeyFunction decode(uint8_t code); protected: /** Device specific key functions. */ typedef enum { Off = 0x00, Voltage = 0x01, Power = 0x02, Repeater = 0x03, Reverse = 0x04, Encryption = 0x05, Call = 0x06, VOX = 0x07, ToggleVFO = 0x08, SubPTT = 0x09, Scan = 0x0a, WFM = 0x0b, Alarm = 0x0c, RecordSwitch = 0x0d, Record = 0x0e, SMS = 0x0f, Dial = 0x10, GPSInformation = 0x11, Monitor = 0x12, ToggleMainChannel = 0x13, HotKey1 = 0x14, HotKey2 = 0x15, HotKey3 = 0x16, HotKey4 = 0x17, HotKey5 = 0x18, HotKey6 = 0x19, WorkAlone = 0x1a, SkipChannel = 0x1b, DMRMonitor = 0x1c, SubChannel = 0x1d, PriorityZone = 0x1e, VFOScan = 0x1f, MICSoundQuality = 0x20, LastCallReply = 0x21, ChannelType = 0x22, SimplexRepeater = 0x23, Ranging = 0x24, ChannelRanging = 0x25, MaxVolume = 0x26, Slot = 0x27, Squelch = 0x28, Roaming = 0x29, Zone = 0x2a, RoamingSet = 0x2b, Mute = 0x02c, CtcssDcsSet=0x2d, APRSType = 0x2e, APRSSet = 0x2f, DIMShut = 0x30, GPSToggle = 0x31, SatPredict = 0x32 } KeyFunctionCode; }; /** Possible VFO frequency steps. */ enum FreqStep { FREQ_STEP_2_5kHz = 0, ///< 2.5kHz FREQ_STEP_5kHz = 1, ///< 5kHz FREQ_STEP_6_25kHz = 2, ///< 6.25kHz FREQ_STEP_10kHz = 3, ///< 10kHz FREQ_STEP_12_5kHz = 4, ///< 12.5kHz FREQ_STEP_20kHz = 5, ///< 20kHz FREQ_STEP_25kHz = 6, ///< 25kHz FREQ_STEP_50kHz = 7 ///< 50kHz }; /** DTMF signalling durations. */ enum DTMFDuration { DTMF_DUR_50ms = 0, DTMF_DUR_100ms = 1, DTMF_DUR_200ms = 2, DTMF_DUR_300ms = 3, DTMF_DUR_500ms = 4 }; /** TBST (open repeater) frequencies. */ enum class TBSTFrequency { Hz1000 = 0, Hz1450 = 1, Hz1750 = 2, Hz2100 = 3 }; /** All possible STE (squelch tail eliminate) frequencies. */ enum class STEFrequency { Off = 0, Hz55_2 = 1, Hz259_2 = 2 }; /** Encoding of repeater timeslot. */ enum class RepeaterTimeSlot { TS1 = 0, TS2 = 1, Channel = 2 }; /** Possible SMS formats. */ enum class SMSFormat { Motorola = 0, Hytera = 1, DMR = 2 }; protected: /** Hidden Constructor. */ GeneralSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit GeneralSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00e0; } bool idleChannelTone() const override; void enableIdleChannelTone(bool enable) override; /** Returns the transmit timeout in seconds. */ virtual unsigned transmitTimeout() const; /** Sets the transmit timeout in seconds. */ virtual void setTransmitTimeout(unsigned tot); /** Returns the UI language. */ virtual AnytoneDisplaySettingsExtension::Language language() const; /** Sets the UI language. */ virtual void setLanguage(AnytoneDisplaySettingsExtension::Language lang); /** Returns the VFO frequency step in kHz. */ virtual Frequency vfoFrequencyStep() const; /** Sets the VFO frequency step in kHz. */ virtual void setVFOFrequencyStep(Frequency kHz); AnytoneKeySettingsExtension::KeyFunction funcKeyAShort() const override; void setFuncKeyAShort(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyBShort() const override; void setFuncKeyBShort(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyCShort() const override; void setFuncKeyCShort(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey1Short() const override; void setFuncKey1Short(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey2Short() const override; void setFuncKey2Short(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyALong() const override; void setFuncKeyALong(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyBLong() const override; void setFuncKeyBLong(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKeyCLong() const override; void setFuncKeyCLong(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey1Long() const override; void setFuncKey1Long(AnytoneKeySettingsExtension::KeyFunction func) override; AnytoneKeySettingsExtension::KeyFunction funcKey2Long() const override; void setFuncKey2Long(AnytoneKeySettingsExtension::KeyFunction func) override; bool vfoModeA() const override; void enableVFOModeA(bool enable) override; bool vfoModeB() const override; void enableVFOModeB(bool enable) override; /** Returns the STE (squelch tail eliminate) type. */ virtual AnytoneSettingsExtension::STEType steType() const; /** Sets the STE (squelch tail eliminate) type. */ virtual void setSTEType(AnytoneSettingsExtension::STEType type); /** Returns the STE (squelch tail eliminate) frequency setting in Hz. * A value of 0 disables the STE. Possible values are 55.2 and 259.2 Hz. */ virtual double steFrequency() const; /** Sets the STE (squelch tail eliminate) frequency setting. * A value of 0 disables the STE. Possible values are 55.2 and 259.2 Hz. */ virtual void setSTEFrequency(double freq); /** Returns the group call hang time in seconds. */ virtual Interval groupCallHangTime() const; /** Sets the group call hang time in seconds. */ virtual void setGroupCallHangTime(Interval sec); /** Returns the private call hang time in seconds. */ virtual Interval privateCallHangTime() const; /** Sets the private call hang time in seconds. */ virtual void setPrivateCallHangTime(Interval sec); /** Returns the pre-wave time in ms. */ virtual Interval preWaveDelay() const; /** Sets the pre-wave time in ms. */ virtual void setPreWaveDelay(Interval ms); /** Returns the wake head-period in ms. */ virtual Interval wakeHeadPeriod() const; /** Sets the wake head-period in ms. */ virtual void setWakeHeadPeriod(Interval ms); /** Returns the wide-FM (broadcast) channel index. */ virtual unsigned wfmChannelIndex() const; /** Sets the wide-FM (broadcast) channel index. */ virtual void setWFMChannelIndex(unsigned idx); /** Returns @c true if the WFM RX is in VFO mode. */ virtual bool wfmVFOEnabled() const; /** Enables/disables VFO mode for WFM RX. */ virtual void enableWFMVFO(bool enable); /** Returns the DTMF tone duration in ms. */ virtual unsigned dtmfToneDuration() const; /** Sets the DTMF tone duration in ms. */ virtual void setDTMFToneDuration(unsigned ms); /** Returns @c true if "man down" is enabled. */ virtual bool manDown() const; /** Enables/disables "man down". */ virtual void enableManDown(bool enable); /** Returns @c true if WFM monitor is enabled. */ virtual bool wfmMonitor() const; /** Enables/disables WFM monitor. */ virtual void enableWFMMonitor(bool enable); /** Returns the TBST frequency. */ virtual Frequency tbstFrequency() const; /** Sets the TBST frequency. */ virtual void setTBSTFrequency(Frequency freq); /** Returns @c true if the "pro mode" is enabled. */ virtual bool proMode() const; /** Enables/disables the "pro mode". */ virtual void enableProMode(bool enable); bool keyToneEnabled() const override; void enableKeyTone(bool enable) override; /** Returns @c true if the own ID is filtered in call lists. */ virtual bool filterOwnID() const; /** Enables/disables filter of own ID in call lists. */ virtual void enableFilterOwnID(bool enable); /** Returns @c true remote stun/kill is enabled. */ virtual bool remoteStunKill() const; /** Enables/disables remote stun/kill. */ virtual void enableRemoteStunKill(bool enable); /** Returns @c true remote monitor is enabled. */ virtual bool remoteMonitor() const; /** Enables/disables remote monitor. */ virtual void enableRemoteMonitor(bool enable); /** Returns @c true, if the selection of a TX contact is enabled. */ virtual bool selectTXContactEnabled() const; /** Enables/disables selection of the TX contact. */ virtual void enableSelectTXContact(bool enable); /** Returns the monitor slot match. */ virtual AnytoneDMRSettingsExtension::SlotMatch monitorSlotMatch() const; /** Sets the monitor slot match. */ virtual void setMonitorSlotMatch(AnytoneDMRSettingsExtension::SlotMatch match); /** Returns @c true if the monitor matches color code. */ virtual bool monitorColorCodeMatch() const; /** Enables/disables monitor color code match. */ virtual void enableMonitorColorCodeMatch(bool enable); /** Returns @c true if the monitor matches ID. */ virtual bool monitorIDMatch() const; /** Enables/disables monitor ID match. */ virtual void enableMonitorIDMatch(bool enable); /** Returns @c true if the monitor holds the time slot. */ virtual bool monitorTimeSlotHold() const; /** Enables/disables monitor time slot hold. */ virtual void enableMonitorTimeSlotHold(bool enable); /** Returns the "man down" delay in seconds. */ virtual Interval manDownDelay() const; /** Sets the "man down" delay in seconds. */ virtual void setManDownDelay(Interval sec); /** Returns the analog call hold in seconds. */ virtual unsigned fmCallHold() const; /** Sets the analog call hold in seconds. */ virtual void setFMCallHold(unsigned sec); /** Returns @c true if the GPS range reporting is enabled. */ virtual bool gpsMessageEnabled() const; /** Enables/disables GPS range reporting. */ virtual void enableGPSMessage(bool enable); /** Returns @c true if the call channel is maintained. */ virtual bool maintainCallChannel() const; /** Enables/disables maintaining the call channel. */ virtual void enableMaintainCallChannel(bool enable); /** Returns the priority Zone A index. */ virtual unsigned priorityZoneAIndex() const; /** Sets the priority zone A index. */ virtual void setPriorityZoneAIndex(unsigned idx); /** Returns the priority Zone B index. */ virtual unsigned priorityZoneBIndex() const; /** Sets the priority zone B index. */ virtual void setPriorityZoneBIndex(unsigned idx); /** Returns @c true, if a SMS confirmation is sent. */ virtual bool smsConfirmEnabled() const; /** Enables/disables SMS confirmation. */ virtual void enableSMSConfirm(bool enable); /** Returns @c true if the simplex repeater feature is enabled. */ virtual bool simplexRepeaterEnabled() const; /** Enables disables the simplex repeater feature. */ virtual void enableSimplexRepeater(bool enable); Interval gpsUpdatePeriod() const override; void setGPSUpdatePeriod(Interval sec) override; /** Returns @c true if the speaker is switched on during RX in simplex repeater mode, * see @c simplexRepeaterEnabled. */ virtual bool monitorSimplexRepeaterEnabled() const; /** Enables/disables the speaker during RX in simplex repeater mode. */ virtual void enableMonitorSimplexRepeater(bool enable); bool showCurrentContact() const override; void enableShowCurrentContact(bool enable) override; bool keyToneLevelAdjustable() const override; Level keyToneLevel() const override; void setKeyToneLevel(Level level) override; void setKeyToneLevelAdjustable() override; bool knobLock() const override; void enableKnobLock(bool enable) override; bool keypadLock() const override; void enableKeypadLock(bool enable) override; bool sidekeysLock() const override; void enableSidekeysLock(bool enable) override; bool keyLockForced() const override; void enableKeyLockForced(bool enable) override; /** Returns the time-slot in simplex repeater mode. */ virtual AnytoneRepeaterSettingsExtension::TimeSlot simplexRepeaterTimeslot() const; /** Sets the time-slot in simplex repeater mode. */ virtual void setSimplexRepeaterTimeslot(AnytoneRepeaterSettingsExtension::TimeSlot slot); bool showLastHeard() const override; void enableShowLastHeard(bool enable) override; /** Returns the SMS format. */ virtual SMSExtension::Format smsFormat() const; /** Sets the SMS format. */ virtual void setSMSFormat(SMSExtension::Format fmt); bool gpsUnitsImperial() const override; void enableGPSUnitsImperial(bool enable) override; Frequency autoRepeaterMinFrequencyVHF() const override; void setAutoRepeaterMinFrequencyVHF(Frequency Hz) override; Frequency autoRepeaterMaxFrequencyVHF() const override; void setAutoRepeaterMaxFrequencyVHF(Frequency Hz) override; Frequency autoRepeaterMinFrequencyUHF() const override; void setAutoRepeaterMinFrequencyUHF(Frequency Hz) override; Frequency autoRepeaterMaxFrequencyUHF() const override; void setAutoRepeaterMaxFrequencyUHF(Frequency Hz) override; AnytoneAutoRepeaterSettingsExtension::Direction autoRepeaterDirectionB() const override; void setAutoRepeaterDirectionB(AnytoneAutoRepeaterSettingsExtension::Direction dir) override; /** If enabled, the FM ID is sent together with selected contact. */ virtual bool fmSendIDAndContact() const; /** Enables/disables sending contact with FM ID. */ virtual void enableFMSendIDAndContact(bool enable); bool defaultChannel() const override; void enableDefaultChannel(bool enable) override; unsigned defaultZoneIndexA() const override; void setDefaultZoneIndexA(unsigned idx) override; unsigned defaultZoneIndexB() const override; void setDefaultZoneIndexB(unsigned idx) override; bool defaultChannelAIsVFO() const override; unsigned defaultChannelAIndex() const override; void setDefaultChannelAIndex(unsigned idx) override; void setDefaultChannelAToVFO() override; bool defaultChannelBIsVFO() const override; unsigned defaultChannelBIndex() const override; void setDefaultChannelBIndex(unsigned idx) override; void setDefaultChannelBToVFO() override; bool keepLastCaller() const override; void enableKeepLastCaller(bool enable) override; /** Returns backlight duration during RX. */ virtual Interval rxBacklightDuration() const; /** Sets the backlight duration during RX. */ virtual void setRXBacklightDuration(Interval sec); /** Returns the stand-by background color. */ virtual AnytoneDisplaySettingsExtension::Color standbyBackgroundColor() const; /** Sets the stand-by background color. */ virtual void setStandbyBackgroundColor(AnytoneDisplaySettingsExtension::Color color); /** Returns the group-call hang time, if group call was dialed manually. */ virtual unsigned int manualDialedGroupCallHangTime() const; /** Sets the group-call hang time, if the group call was dialed manually. */ virtual void setManualDialedGroupCallHangTime(unsigned int dur); /** Returns the private-call hang time, if private call was dialed manually. */ virtual unsigned int manualDialedPrivateCallHangTime() const; /** Sets the private-call hang time, if the private call was dialed manually. */ virtual void setManualDialedPrivateCallHangTime(unsigned int dur); bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err) override; bool updateConfig(Context &ctx, const ErrorStack &err) override; protected: /** Some internal used offsets within the element. */ struct Offset: D868UVCodeplug::GeneralSettingsElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int idleChannelTone() { return 0x0000; } static constexpr unsigned int transmitTimeout() { return 0x0004; } static constexpr unsigned int language() { return 0x0005; } static constexpr unsigned int vfoFrequencyStep() { return 0x0008; } static constexpr unsigned int vfoModeB() { return 0x0015; } static constexpr unsigned int vfoModeA() { return 0x0016; } static constexpr unsigned int steType() { return 0x0017; } static constexpr unsigned int steFrequency() { return 0x0018; } static constexpr unsigned int groupCallHangTime() { return 0x0019; } static constexpr unsigned int privateCallHangTime() { return 0x001a; } static constexpr unsigned int preWaveDelay() { return 0x001b; } static constexpr unsigned int wakeHeadPeriod() { return 0x001c; } static constexpr unsigned int wfmChannelIndex() { return 0x001d; } static constexpr unsigned int wfmVFOEnabled() { return 0x001e; } static constexpr unsigned int dtmfToneDuration() { return 0x0023; } static constexpr unsigned int manDown() { return 0x0024; } static constexpr unsigned int wfmMonitor() { return 0x002b; } static constexpr unsigned int tbstFrequency() { return 0x002e; } static constexpr unsigned int proMode() { return 0x0034; } static constexpr unsigned int enableKeyTone() { return 0x0036; } static constexpr unsigned int filterOwnID() { return 0x0038; } static constexpr unsigned int remoteStunKill() { return 0x003c; } static constexpr unsigned int remoteMonitor() { return 0x003e; } static constexpr unsigned int selectTXContact() { return 0x0040; } static constexpr unsigned int monSlotMatch() { return 0x0049; } static constexpr unsigned int monColorCodeMatch() { return 0x004a; } static constexpr unsigned int monIDMatch() { return 0x004b; } static constexpr unsigned int monTimeSlotHold() { return 0x004c; } static constexpr unsigned int manDownDelay() { return 0x004f; } static constexpr unsigned int fmCallHold() { return 0x0050; } static constexpr unsigned int enableGPSMessage() { return 0x0053; } static constexpr unsigned int maintainCallChannel() { return 0x006e; } static constexpr unsigned int priorityZoneA() { return 0x006f; } static constexpr unsigned int priorityZoneB() { return 0x0070; } static constexpr unsigned int smsConfirm() { return 0x0071; } static constexpr unsigned int simplexRepEnable() { return 0x00b1; } static constexpr unsigned int gpsUpdatePeriod() { return 0x00b2; } static constexpr unsigned int simplxRepSpeaker() { return 0x00b3; } static constexpr unsigned int showContact() { return 0x00b4; } static constexpr unsigned int keyToneLevel() { return 0x00b5; } static constexpr Bit knobLock() { return {0x00b6, 0}; } static constexpr Bit keypadLock() { return {0x00b6, 1}; } static constexpr Bit sideKeyLock() { return {0x00b6, 3}; } static constexpr Bit forceKeyLock() { return {0x00b6, 4}; } static constexpr unsigned int simplxRepSlot() { return 0x00b7; } static constexpr unsigned int showLastHeard() { return 0x00b8; } static constexpr unsigned int smsFormat() { return 0x00b9; } static constexpr unsigned int gpsUnits() { return 0x00ba; } static constexpr unsigned int autoRepMinVHF() { return 0x00bc; } static constexpr unsigned int autoRepMaxVHF() { return 0x00c0; } static constexpr unsigned int autoRepMinUHF() { return 0x00c4; } static constexpr unsigned int autoRepMaxUHF() { return 0x00c8; } static constexpr unsigned int autoRepeaterDirB() { return 0x00cc; } static constexpr unsigned int fmSendIDAndContact() { return 0x00cd; } static constexpr unsigned int defaultChannels() { return 0x00ce; } static constexpr unsigned int defaultZoneA() { return 0x00cf; } static constexpr unsigned int defaultZoneB() { return 0x00d0; } static constexpr unsigned int defaultChannelA() { return 0x00d1; } static constexpr unsigned int defaultChannelB() { return 0x00d2; } static constexpr unsigned int keepLastCaller() { return 0x00d3; } static constexpr unsigned int rxBacklightDuration() { return 0x00d4; } static constexpr unsigned int standbyBackground() { return 0x00d5; } static constexpr unsigned int manGrpCallHangTime() { return 0x00d6; } static constexpr unsigned int manPrvCallHangTime() { return 0x00d7; } /// @endcond }; }; /** Implements some settings extension for the BTECH DMR-6X2UV. * * Memory representation of the encoded settings element (size 0x0e0 bytes): * @verbinclude dmr6x2uv_settingsextension.txt */ class ExtendedSettingsElement: public AnytoneCodeplug::ExtendedSettingsElement { protected: /** Talker alias encoding. */ enum class TalkerAliasEncoding { ISO8 = 0, ISO7 = 1, Unicode = 2, }; protected: /** Hidden Constructor. */ ExtendedSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ExtendedSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0030; } /** Resets the general settings. */ void clear(); /** Returns @c true if the talker alias is sent. */ virtual bool sendTalkerAlias() const; /** Enables/disables sending the talker alias. */ virtual void enableSendTalkerAlias(bool enable); /** Returns the talker alias source. */ virtual AnytoneDMRSettingsExtension::TalkerAliasSource talkerAliasSource() const; /** Sets the talker alias source. */ virtual void setTalkerAliasSource(AnytoneDMRSettingsExtension::TalkerAliasSource mode); /** Returns the talker alias encoding. */ virtual DMRSettings::TalkerAliasEncoding talkerAliasEncoding() const; /** Sets the talker alias encoding. */ virtual void setTalkerAliasEncoding(DMRSettings::TalkerAliasEncoding encoding); /** Returns the font color. */ virtual AnytoneDisplaySettingsExtension::Color fontColor() const; /** Sets the font color. */ virtual void setFontColor(AnytoneDisplaySettingsExtension::Color color); /** Returns @c true if the custom channel background is enabled. */ virtual bool customChannelBackgroundEnabled() const; /** Enables/disables the custom channel background. */ virtual void enableCustomChannelBackground(bool enable); /** Returns @c true if auto roaming is enabled. */ virtual bool autoRoamingEnabled() const; /** Enables/disables auto roaming. */ virtual void enableAutoRoaming(bool enable); /** Returns @c true if repeater check is enabled. */ virtual bool repeaterRangeCheckEnabled() const; /** Enables/disables repeater check. */ virtual void enableRepeaterRangeCheck(bool enable); /** Returns the number of times, the repeater out-of-range reminder is shown (1-10). */ virtual unsigned int repeaterCheckNumNotifications() const; /** Sets the number of times, the repeater out-of-range reminder is shown (1-10). */ virtual void setRepeaterCheckNumNotifications(unsigned int n); /** Returns the repeater check interval in seconds (5-50s). */ virtual Interval repeaterRangeCheckInterval() const; /** Sets the repeater check interval in seconds (5-50s). */ virtual void setRepeaterRangeCheckInterval(Interval intv); /** Returns the repeater out-of-range alert type. */ virtual AnytoneRoamingSettingsExtension::OutOfRangeAlert repeaterOutOfRangeAlert() const; /** Sets the repeater out-of-range alert type. */ virtual void setRepeaterOutOfRangeAlert(AnytoneRoamingSettingsExtension::OutOfRangeAlert alert); /** Returns the number of times, a repeater reconnection is tried (3-5). */ virtual unsigned int repeaterRangeCheckCount() const; /** Sets the number of times, a repeater reconnection is tried (3-5). */ virtual void setRepeaterRangeCheckCount(unsigned int n); /** Returns the roaming zone index. */ virtual unsigned int defaultRoamingZoneIndex() const; /** Sets the roaming zone index. */ virtual void setDefaultRoamingZoneIndex(unsigned int index); /** Returns the condition to start roaming. */ virtual AnytoneRoamingSettingsExtension::RoamStart roamingStartCondition() const; /** Sets the condition to start roaming. */ virtual void setRoamingStartCondition(AnytoneRoamingSettingsExtension::RoamStart cond); /** Returns the auto-roaming interval in minutes (1-256). */ virtual Interval autoRoamPeriod() const; /** Sets the auto-roaming interval in minutes (1-256). */ virtual void setAutoRoamPeriod(Interval minutes); /** Returns the effective roaming waiting time in seconds (0-30s). */ virtual Interval autoRoamDelay() const; /** Sets the effective roaming waiting time in seconds (0-30s). */ virtual void setAutoRoamDelay(Interval sec); /** Returns the roaming return condition. */ virtual AnytoneRoamingSettingsExtension::RoamStart roamingReturnCondition() const; /** Sets the roaming return condition. */ virtual void setRoamingReturnCondition(AnytoneRoamingSettingsExtension::RoamStart cond); /** Returns the mute timer in minutes. */ virtual Interval muteTimer() const; /** Sets the mute timer in minutes. */ virtual void setMuteTimer(Interval minutes); /** Returns the encryption type. */ virtual AnytoneDMRSettingsExtension::EncryptionType encryptionType() const; /** Sets the encryption type. */ virtual void setEncryptionType(AnytoneDMRSettingsExtension::EncryptionType type); AnytoneDisplaySettingsExtension::Color zoneANameColor() const; void setZoneANameColor(AnytoneDisplaySettingsExtension::Color color); AnytoneDisplaySettingsExtension::Color zoneBNameColor() const; void setZoneBNameColor(AnytoneDisplaySettingsExtension::Color color); /** Returns the name color for channel A. */ virtual AnytoneDisplaySettingsExtension::Color channelANameColor() const; /** Sets the name color for channel A. */ virtual void setChannelANameColor(AnytoneDisplaySettingsExtension::Color color); AnytoneDisplaySettingsExtension::Color channelBNameColor() const; void setChannelBNameColor(AnytoneDisplaySettingsExtension::Color color); bool fromConfig(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkConfig(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for entries. */ struct Limit: AnytoneCodeplug::ExtendedSettingsElement::Limit { /// Out of range reminder count limits. static constexpr IntRange repRangeReminder() { return {1, 10}; } /// Repeater range check interval limits. static constexpr TimeRange rangeCheckInterval() { return TimeRange{Interval::fromSeconds(1), Interval::fromSeconds(50)}; } /// Repeater reconnection count limits. static constexpr IntRange repeaterReconnections() { return {3,5}; } /// Auto-roaming interval limits. static constexpr TimeRange autoRoamingInterval() { return TimeRange{Interval::fromMinutes(1), Interval::fromMinutes(256)}; } /// Auto-roaming delay limits. static constexpr TimeRange autoRoamDelay() { return TimeRange{Interval::fromSeconds(0), Interval::fromSeconds(30)}; } /// Mute-timer limits. static constexpr TimeRange muteTimer() { return TimeRange{Interval::fromMinutes(1), Interval::fromMinutes(256)}; } }; protected: /** Some internal offset within the codeplug element. */ struct Offset { /// @cond DO_NOT_DOCUEMNT static constexpr unsigned int sendTalkerAlias() { return 0x0000; } static constexpr unsigned int talkerAliasDisplay() { return 0x0001; } static constexpr unsigned int talkerAliasEncoding() { return 0x0002; } static constexpr unsigned int fontColor() { return 0x0003; } static constexpr unsigned int customChannelBackground() { return 0x0004; } static constexpr unsigned int defaultRoamingZone() { return 0x0005; } static constexpr unsigned int roaming() { return 0x0006; } static constexpr unsigned int repRangeCheck() { return 0x0007; } static constexpr unsigned int repRangeAlert() { return 0x0008; } static constexpr unsigned int repRangeReminder() { return 0x0009; } static constexpr unsigned int rangeCheckInterval() { return 0x000a; } static constexpr unsigned int rangeCheckCount() { return 0x000b; } static constexpr unsigned int roamStartCondition() { return 0x000c; } static constexpr unsigned int autoRoamPeriod() { return 0x000d; } static constexpr unsigned int autoRoamDelay() { return 0x000e; } static constexpr unsigned int roamReturnCondition() { return 0x000f; } static constexpr unsigned int muteDelay() { return 0x0010; } static constexpr unsigned int encryptionType() { return 0x0011; } static constexpr unsigned int zoneANameColor() { return 0x0012; } static constexpr unsigned int zoneBNameColor() { return 0x0013; } static constexpr unsigned int channelANameColor() { return 0x0014; } static constexpr unsigned int channelBNameColor() { return 0x0015; } /// @endcond }; }; /** Implements the channel element for the BTECH DMR-6X2UV. * Extends the AnytoneCodeplug::ChannelElement by the device specific features, like multiple * scan lists associated with the channel. * * Memory representation of the encoded channel element (size 0x040 bytes): * @verbinclude dmr6x2uv_channel.txt */ class ChannelElement: public AnytoneCodeplug::ChannelElement { public: /** Possible PTT modes for FM APRS. */ enum class FMAPRSPTTMode { Off = 0, Start = 1, End = 2 }; /** Possible APRS report types. */ enum class APRSType{ Off = 0, FM = 1, DMR = 2 }; /** Possible encryption types. */ enum class DMREncryptionType { Basic = 0, Enhanced = 1 }; protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ChannelElement(uint8_t *ptr); /** Returns @c true, if an AES encryption key index is set. */ bool hasAESEncryptionKeyIndex() const; /** Returns the AES encryption key index. * The index is 0-based. */ unsigned int aesEncryptionKeyIndex() const; /** Sets the AES encryption key index. */ void setAESEncryptionKeyIndex(unsigned int index); /** Clears the AES encryption key index. */ void clearAESEncryptionKeyIndex(); /** Returns the DMR encryption type. */ DMREncryptionType dmrEncryptionType() const; /** Sets the DMR encryption type. */ void setDMREncryptionType(DMREncryptionType type); /** Returns @c true, if an DMR encryption key index is set. */ bool hasDMREncryptionKeyIndex() const; /** Returns the DMR encryption key index. * The index is 0-based. */ unsigned int dmrEncryptionKeyIndex() const; /** Sets the DMR encryption key index. */ void setDMREncryptionKeyIndex(unsigned int index); /** Clears the DMR encryption key index. */ void clearDMREncryptionKeyIndex(); /** Returns @c true, if the first scan list index is set. */ bool hasScanListIndex() const; /** Returns the first scan list index (0-based). */ unsigned scanListIndex() const; /** Sets the first scan list index (0-based). */ void setScanListIndex(unsigned idx); /** Clears the first scan list index. */ void clearScanListIndex(); /** Returns @c true, if the n-th scan list index is set (n=0,...,7). */ virtual bool hasScanListIndex(unsigned int n) const; /** Returns the n-th scan list index (0-based, n=0,...,7). */ virtual unsigned scanListIndex(unsigned int n) const; /** Sets the n-th scan list index (0-based, n=0,...,7). */ virtual void setScanListIndex(unsigned int n, unsigned idx); /** Clears the n-th scan list index (n=0,...,7). */ virtual void clearScanListIndex(unsigned int n); /** Returns @c true if roaming is enabled for this channel. */ virtual bool roamingEnabled() const; /** Enables/disables roaming. */ virtual void enableRoaming(bool enable); /** Returns @c true, if ranging is enabled. */ virtual bool ranging() const; /** Enables/disables ranging. */ virtual void enableRanging(bool enable); /** Returns the DMR APRS report channel index. */ virtual unsigned int dmrAPRSChannelIndex() const; /** Sets the DMR APRS report channel index. */ virtual void setDMRAPRSChannelIndex(unsigned int idx); /** Returns @c true, if the reception of DMR APRS messages is enabled. */ virtual bool dmrAPRSRXEnabled() const; /** Enables/disables the reception of DMR APRS messages. */ virtual void enableDMRARPSRX(bool enable); /** Returns true, if the position is reported via DMR APRS on PTT. */ virtual bool dmrAPRSPTTEnabled() const; /** Enables/disables reporting the position via DMR APRS on PTT. */ virtual void enableDMRAPRSPTT(bool enable); /** Returns the FM APRS PTT mode. */ virtual FMAPRSPTTMode fmAPRSPTTMode() const; /** Sets the FM APRS PTT mode. */ virtual void setFMAPRSPTTMode(FMAPRSPTTMode mode); /** Returns the APRS type. */ virtual APRSType aprsType() const; /** Sets the APRS type. */ virtual void setAPRSType(APRSType aprstype); bool linkChannelObj(Channel *c, Context &ctx) const; bool fromChannelObj(const Channel *c, Context &ctx); public: /** Some limits of this element. */ struct Limit { /// Maximum number of scan list indices. static constexpr unsigned int scanListIndices() { return 8; } }; protected: /// @cond DO_NOT_DOCUMENT struct Offset: public AnytoneCodeplug::ChannelElement::Offset { static constexpr unsigned int aesEncryptionKeyIndex() { return 0x0013; } static constexpr Bit roaming() { return {0x001b, 2}; } static constexpr Bit ranging() { return {0x001b, 0}; } static constexpr Bit dmrEncryptionType() { return {0x0021, 6}; } static constexpr unsigned int dmrEncryptionKeyIndex() { return 0x0022; } static constexpr unsigned int scanListIndices() { return 0x0036; } static constexpr unsigned int betweenScanListIndices() { return 0x0001; } static constexpr unsigned int dmrAPRSChannelIndex() { return 0x003e; } static constexpr Bit dmrAPRSRXEnable() { return {0x003f, 5}; } static constexpr Bit dmrAPRSPTTEnable() { return {0x003f, 4}; } static constexpr Bit fmAPRSPTTMode() { return {0x003f, 2}; } static constexpr Bit aprsType() { return {0x003f, 0}; } }; /// @endcond }; /** Starting from FW version 2.21, all devices encode an channel settings extension element * at an offset 0x2000 to the original channel. Also the size of the extension element is * identical to the channel element itself. This is weird. Anyway. This class encodes/decodes * these settings. * * This implementation is compatible with firmware version 2.21 */ class ChannelExtensionElement: public Codeplug::Element { protected: /** Hidden constructor. */ ChannelExtensionElement(uint8_t *ptr, size_t size); public: /** Default constructor. */ ChannelExtensionElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return ChannelElement::size(); } /** Resets the channel extension. */ void clear(); /** Returns @c true, if the channel has an ARC4 key index assigned. */ virtual bool hasARC4KeyIndex() const; /** Returns the 0-based ARC4 key index. */ virtual unsigned int arc4KeyIndex() const; /** Sets the ARC4 key index. */ virtual void setARC4KeyIndex(unsigned int idx); /** Clears the ARC4 key index. */ virtual void clearARC4KeyIndex(); /** Links a previously created channel object. */ virtual bool linkChannelObj(Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes the given channel object. */ virtual bool fromChannelObj(const Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Some internal used offsets. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int arc4KeyIndex() { return 0x0000; } /// @endcond }; }; /** Represents the APRS settings within the binary DMR-6X2UV codeplug. * * Memory layout of APRS settings (size 0x00a0 bytes): * @verbinclude dmr6x2uv_aprssetting.txt */ class APRSSettingsElement: public Element { protected: /** Hidden constructor. */ APRSSettingsElement(uint8_t *ptr, unsigned size); /** Possible settings for the FM APRS subtone type. */ enum class SignalingType { Off=0, CTCSS=1, DCS=2 }; public: /** Constructor. */ explicit APRSSettingsElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x00a0; } /** Resets the settings. */ void clear(); bool isValid() const; /** Returns the FM APRS frequency. */ virtual Frequency fmFrequency() const; /** Sets the FM APRS frequency. */ virtual void setFMFrequency(Frequency f); /** Returns the TX delay in ms. */ virtual Interval fmTXDelay() const; /** Sets the TX delay in ms. */ virtual void setFMTXDelay(const Interval intv); /** Returns the sub tone settings. */ virtual SelectiveCall txTone() const; /** Sets the sub tone settings. */ virtual void setTXTone(const SelectiveCall &code); /** Returns the manual TX interval in seconds. */ virtual Interval manualTXInterval() const; /** Sets the manual TX interval in seconds. */ virtual void setManualTXInterval(Interval sec); /** Returns @c true if the auto transmit is enabled. */ virtual bool autoTX() const; /** Returns the auto TX interval in seconds. */ virtual Interval autoTXInterval() const; /** Sets the auto TX interval in seconds. */ virtual void setAutoTXInterval(Interval sec); /** Disables auto tx. */ virtual void disableAutoTX(); /** Returns @c true if a fixed location is send. */ virtual bool fixedLocationEnabled() const; /** Returns the fixed location send. */ virtual QGeoCoordinate fixedLocation() const; /** Sets the fixed location to send. */ virtual void setFixedLocation(QGeoCoordinate &loc); /** Disables sending a fixed location. */ virtual void disableFixedLocation(); /** Returns the destination call. */ virtual QString destination() const; /** Returns the destination SSID. */ virtual unsigned destinationSSID() const; /** Sets the destination call & SSID. */ virtual void setDestination(const QString &call, unsigned ssid); /** Returns the source call. */ virtual QString source() const; /** Returns the source SSID. */ virtual unsigned sourceSSID() const; /** Sets the source call & SSID. */ virtual void setSource(const QString &call, unsigned ssid); /** Returns the path string. */ virtual QString path() const; /** Sets the path string. */ virtual void setPath(const QString &path); /** Returns the APRS icon. */ virtual FMAPRSSystem::Icon icon() const; /** Sets the APRS icon. */ virtual void setIcon(FMAPRSSystem::Icon icon); /** Returns the transmit power. */ virtual Channel::Power power() const; /** Sets the transmit power. */ virtual void setPower(Channel::Power power); /** Returns the pre-wave delay in ms. */ virtual Interval fmPreWaveDelay() const; /** Sets the pre-wave delay in ms. */ virtual void setFMPreWaveDelay(Interval ms); /** Returns @c true if the channel points to the current/selected channel. */ virtual bool dmrChannelIsSelected(unsigned n) const; /** Returns the digital channel index for the n-th system. */ virtual unsigned dmrChannelIndex(unsigned n) const; /** Sets the digital channel index for the n-th system. */ virtual void setDMRChannelIndex(unsigned n, unsigned idx); /** Sets the channel to the current/selected channel. */ virtual void setDMRChannelSelected(unsigned n); /** Returns the destination contact for the n-th system. */ virtual unsigned dmrDestination(unsigned n) const; /** Sets the destination contact for the n-th system. */ virtual void setDMRDestination(unsigned n, unsigned idx); /** Returns the call type for the n-th system. */ virtual DMRContact::Type dmrCallType(unsigned n) const; /** Sets the call type for the n-th system. */ virtual void setDMRCallType(unsigned n, DMRContact::Type type); /** Returns @c true if the n-th system overrides the channel time-slot. */ virtual bool dmrTimeSlotOverride(unsigned n); /** Returns the time slot if overridden (only valid if @c timeSlot returns true). */ virtual DMRChannel::TimeSlot dmrTimeSlot(unsigned n) const; /** Overrides the time slot of the n-th selected channel. */ virtual void setDMRTimeSlot(unsigned n, DMRChannel::TimeSlot ts); /** Clears the time-slot override. */ virtual void clearDMRTimeSlotOverride(unsigned n); /** Returns @c true if the roaming is enabled. */ virtual bool dmrRoaming() const; /** Enables/disables roaming. */ virtual void enableDMRRoaming(bool enable); /** Returns the repeater activation delay in ms. */ virtual Interval dmrPreWaveDelay() const; /** Sets the repeater activation delay in ms. */ virtual void setDMRPreWaveDelay(Interval ms); /** Configures this APRS system from the given generic config. */ virtual bool fromFMAPRSSystem(const FMAPRSSystem *sys, Context &ctx, const ErrorStack &err=ErrorStack()); /** Constructs a generic APRS system configuration from this APRS system. */ virtual FMAPRSSystem *toFMAPRSSystem(); /** Links the transmit channel within the generic APRS system based on the transmit frequency * defined within this APRS system. */ virtual bool linkFMAPRSSystem(FMAPRSSystem *sys, Context &ctx); /** Constructs all GPS system from the generic configuration. */ virtual bool fromDMRAPRSSystems(Context &ctx); /** Encodes the given GPS system. */ virtual bool fromDMRAPRSSystemObj(unsigned int idx, DMRAPRSSystem *sys, Context &ctx); /** Constructs a generic GPS system from the idx-th encoded GPS system. */ virtual DMRAPRSSystem *toDMRAPRSSystemObj(int idx) const; /** Links the specified generic GPS system. */ virtual bool linkDMRAPRSSystem(int idx, DMRAPRSSystem *sys, Context &ctx) const; public: /** Some static limits for this element. */ struct Limit { /// Maximum length of call signs. static constexpr unsigned int callLength() { return 0x0006; } /// Maximum length of the repeater path string. static constexpr unsigned int pathLength() { return 0x0020; } /// Maximum number of DMR APRS systems. static constexpr unsigned int dmrSystems() { return 0x0008; } }; protected: /** Internal used offsets within the codeplug element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int fmFrequency() { return 0x0001; } static constexpr unsigned int fmTXDelay() { return 0x0005; } static constexpr unsigned int fmSigType() { return 0x0006; } static constexpr unsigned int fmCTCSS() { return 0x0007; } static constexpr unsigned int fmDCS() { return 0x0008; } static constexpr unsigned int manualTXInterval() { return 0x000a; } static constexpr unsigned int autoTXInterval() { return 0x000b; } static constexpr unsigned int fmTXMonitor() { return 0x000c; } static constexpr unsigned int fixedLocation() { return 0x000d; } static constexpr unsigned int fixedLatDeg() { return 0x000e; } static constexpr unsigned int fixedLatMin() { return 0x000f; } static constexpr unsigned int fixedLatSec() { return 0x0010; } static constexpr unsigned int fixedLatSouth() { return 0x0011; } static constexpr unsigned int fixedLonDeg() { return 0x0012; } static constexpr unsigned int fixedLonMin() { return 0x0013; } static constexpr unsigned int fixedLonSec() { return 0x0014; } static constexpr unsigned int fixedLonWest() { return 0x0015; } static constexpr unsigned int destinationCall() { return 0x0016; } static constexpr unsigned int destinationSSID() { return 0x001c; } static constexpr unsigned int sourceCall() { return 0x001d; } static constexpr unsigned int sourceSSID() { return 0x0023; } static constexpr unsigned int path() { return 0x0024; } static constexpr unsigned int symbolTable() { return 0x0039; } static constexpr unsigned int symbol() { return 0x003a; } static constexpr unsigned int fmPower() { return 0x003b; } static constexpr unsigned int fmPrewaveDelay() { return 0x003c; } static constexpr unsigned int dmrChannelIndices() { return 0x0040; } static constexpr unsigned int betweenDMRChannelIndices() { return 0x0002; } static constexpr unsigned int dmrDestinations() { return 0x0050; } static constexpr unsigned int betweenDMRDestinations() { return 0x0004; } static constexpr unsigned int dmrCallTypes() { return 0x0070; } static constexpr unsigned int betweenDMRCallTypes() { return 0x0001; } static constexpr unsigned int roamingSupport() { return 0x0078; } static constexpr unsigned int dmrTimeSlots() { return 0x0079; } static constexpr unsigned int betweenDMRTimeSlots() { return 0x0001; } static constexpr unsigned int dmrPrewaveDelay() { return 0x0081; } /// @endcond }; }; /** Reuse roaming channel bitmap from D878UV. */ typedef D878UVCodeplug::RoamingChannelBitmapElement RoamingChannelBitmapElement ; /** Reuse roaming channel from D878UV. */ typedef D878UVCodeplug::RoamingChannelElement RoamingChannelElement; /** Reuse roaming zone bitmap from D878UV. */ typedef D878UVCodeplug::RoamingZoneBitmapElement RoamingZoneBitmapElement; /** Reuse roaming zone from D878UV. */ typedef D878UVCodeplug::RoamingZoneElement RoamingZoneElement; /** Reuse AES encryption key from D878UV. */ typedef D878UVCodeplug::AESEncryptionKeyElement AESEncryptionKeyElement; /** Reuse ARC4 encryption key from D878UV. */ typedef D878UVCodeplug::ARC4EncryptionKeyElement ARC4EncryptionKeyElement; public: /** Hidden constructor. */ explicit DMR6X2UVCodeplug(const QString &label, QObject *parent=nullptr); public: /** Empty constructor. */ explicit DMR6X2UVCodeplug(QObject *parent=nullptr); Config *preprocess(Config *config, const ErrorStack &err) const; protected: bool allocateBitmaps(); void setBitmaps(Context &ctx); void allocateForDecoding(); void allocateForEncoding(); bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createElements(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkElements(Context &ctx, const ErrorStack &err=ErrorStack()); void allocateGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void allocateChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void allocateGPSSystems(); bool encodeGPSSystems(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGPSSystems(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGPSSystems(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates memory to store all roaming channels and zones. */ virtual void allocateRoaming(); /** Encodes the roaming channels and zones. */ virtual bool encodeRoaming(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Creates roaming channels and zones from codeplug. */ virtual bool createRoaming(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links roaming channels and zones. */ virtual bool linkRoaming(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates memory to encode/decode AES keys. */ virtual void allocateAESKeys(); /** Encode all AES keys. */ virtual bool encodeAESKeys(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decode AES keys from the codeplug. */ virtual bool createAESKeys(Context &ctx, const ErrorStack &err=ErrorStack()); /** Allocates memory to encode/decode ARC4 keys. */ virtual void allocateARC4Keys(); /** Encode all ARC4 keys. */ virtual bool encodeARC4Keys(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decode ARC4 keys from the codeplug. */ virtual bool createARC4Keys(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the codeplug. */ struct Limit : public D868UVCodeplug::Limit { /// Maximum length of the FM APRS message static constexpr unsigned int fmAPRSMessage() { return 60; } /// Maximum number of roaming channels. static constexpr unsigned int roamingChannels() { return 250; } /// Maximum number of roaming zones. static constexpr unsigned int roamingZones() { return 64; } /// Maximum number of AES encryption keys. static constexpr unsigned int aesEncryptionKeys() { return 254; } /// Maximum number of ARC4 encryption keys. static constexpr unsigned int arc4EncryptionKeys() { return 254; } }; protected: /** Some internal used offsets within the codeplug. */ struct Offset: public D868UVCodeplug::Offset { ///@cond DO_NOT_DOCUMENT static constexpr unsigned int toChannelExtension() { return 0x00002000; } static constexpr unsigned int roamingChannelBitmap() { return 0x01042000; } static constexpr unsigned int roamingChannels() { return 0x01040000; } static constexpr unsigned int roamingZoneBitmap() { return 0x01042080; } static constexpr unsigned int roamingZones() { return 0x01043000; } static constexpr unsigned int fmAPRSMessage() { return 0x02501200; } static constexpr unsigned int fmAPRSFrequencyNames() { return 0x02502000; } static constexpr unsigned int settingsExtension() { return 0x02501400; } static constexpr unsigned int aesEncryptionKeys() { return 0x025c1000; } static constexpr unsigned int arc4EncryptionKeys() { return 0x025c5000; } /// @endcond }; /** Some internal used sizes. */ struct Size: public D868UVCodeplug::Size { ///@cond DO_NOT_DOCUMENT static constexpr unsigned int fmAPRSMessage() { return 0x00000040; } /// @endcond }; }; #endif // DMR6X2UVCODEPLUG_HH ================================================ FILE: lib/dmr6x2uv_limits.cc ================================================ #include "dmr6x2uv_limits.hh" #include "dmr6x2uv_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingzone.hh" #include "anytone_satelliteconfig.hh" DMR6X2UVLimits::DMR6X2UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent) : AnytoneLimits(hardwareRevision, "V102", true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 200000; // Define limits for satellite config _hasSatelliteConfig = true; _satelliteConfigImplemented = true; _numSatellites = AnytoneSatelliteConfig::Limit::satellites(); /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 14, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(DMR6X2UVCodeplug::BootSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 250, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, -1, -1, new RadioLimitIgnored() } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1, 64) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 4000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRef(FMAPRSSystem::staticMetaObject)}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies(rxFreqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(txFreqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false)}, {"aprs", new RadioLimitObjRef(PositionReportingSystem::staticMetaObject, true)}, {"roaming", new RadioLimitObjRef(RoamingZone::staticMetaObject, true) }, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 250, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } })); /* Handle positioning systems. */ add("positioning", new RadioLimitList{ { DMRAPRSSystem::staticMetaObject, 0, 8, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, 1, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, FMChannel::staticMetaObject}, false) }, { "icon", new RadioLimitEnum{} }, { "message", new RadioLimitString(0, 60, RadioLimitString::ASCII) } ///@todo extend APRSSystem to expose other settings as properties. }} } ); /* Handle roaming zones. */ add("roaming", new RadioLimitList(RoamingZone::staticMetaObject, 0, 64, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "channels", new RadioLimitRefList(0, 64, DMRChannel::staticMetaObject) } } ) ); } ================================================ FILE: lib/dmr6x2uv_limits.hh ================================================ #ifndef DMR6X2UVLIMITS_HH #define DMR6X2UVLIMITS_HH #include "anytone_limits.hh" /** Implements the limits for the BTECH DMR-6X2UV. * @ingroup dmr6x2uv */ class DMR6X2UVLimits: public AnytoneLimits { Q_OBJECT public: /** Constructor. */ DMR6X2UVLimits(const std::initializer_list > &rxFreqRanges, const std::initializer_list > &txFreqRanges, const QString &hardwareRevision, QObject *parent=nullptr); }; #endif // D878UVLIMITS_HH ================================================ FILE: lib/dmrsettings.cc ================================================ #include "dmrsettings.hh" DMRSettings::DMRSettings(QObject *parent) : ConfigExtension{parent}, _privateCallMatch(true), _groupCallMatch(true), _privateCallHangTime(Interval::fromSeconds(5)), _groupCallHangTime(Interval::fromSeconds(3)), _sendTalkerAlias(false), _talkerAliasEncoding(TalkerAliasEncoding::Iso8), _preamble(Interval::fromMilliseconds(100)) { // pass... } void DMRSettings::clear() { ConfigExtension::clear(); _privateCallMatch = true; _groupCallMatch = true; _privateCallHangTime = Interval::fromSeconds(5); _groupCallHangTime = Interval::fromSeconds(3); _sendTalkerAlias = false; _talkerAliasEncoding = TalkerAliasEncoding::Iso8; } ConfigItem * DMRSettings::clone() const { auto obj = new DMRSettings(); if (! obj->copy(*this)) { delete obj; return nullptr; } return obj; } bool DMRSettings::privateCallMatchEnabled() const { return _privateCallMatch; } void DMRSettings::enablePrivateCallMatch(bool enable) { if (_privateCallMatch == enable) return; _privateCallMatch = enable; emit modified(this); } bool DMRSettings::groupCallMatchEnabled() const { return _groupCallMatch; } void DMRSettings::enableGroupCallMatch(bool enable) { if (_groupCallMatch == enable) return; _groupCallMatch = enable; emit modified(this); } Interval DMRSettings::privateCallHangTime() const { return _privateCallHangTime; } void DMRSettings::setPrivateCallHangTime(const Interval &dur) { if (_privateCallHangTime == dur) return; _privateCallHangTime = dur; emit modified(this); } Interval DMRSettings::groupCallHangTime() const { return _groupCallHangTime; } void DMRSettings::setGroupCallHangTime(const Interval &dur) { if (_groupCallHangTime == dur) return; _groupCallHangTime = dur; emit modified(this); } bool DMRSettings::sendTalkerAliasEnabled() const { return _sendTalkerAlias; } void DMRSettings::enableSendTalkerAlias(bool enable) { if (_sendTalkerAlias == enable) return; _sendTalkerAlias = enable; emit modified(this); } DMRSettings::TalkerAliasEncoding DMRSettings::talkerAliasEncoding() const { return _talkerAliasEncoding; } void DMRSettings::setTalkerAliasEncoding(TalkerAliasEncoding encoding) { if (_talkerAliasEncoding == encoding) return; _talkerAliasEncoding = encoding; emit modified(this); } Interval DMRSettings::preamble() const { return _preamble; } void DMRSettings::setPreamble(const Interval &dur) { if (_preamble == dur) return; _preamble = dur; emit modified(this); } ================================================ FILE: lib/dmrsettings.hh ================================================ #ifndef DMRSETTINGS_HH #define DMRSETTINGS_HH #include "interval.hh" #include "configobject.hh" /** Implements some common DMR settings present in many devices. * @ingroup config */ class DMRSettings : public ConfigExtension { Q_OBJECT Q_CLASSINFO("description", "Common DMR settings.") Q_CLASSINFO("privateCallMatchDescription", "If enabled, private calls are only received, " "if they are for you.") Q_PROPERTY(bool privateCallMatch READ privateCallMatchEnabled WRITE enablePrivateCallMatch FINAL) Q_CLASSINFO("groupCallMatchDescription", "If enabled, group calls are only received, " "if they are on the group list.") Q_PROPERTY(bool groupCallMatch READ groupCallMatchEnabled WRITE enableGroupCallMatch FINAL) Q_CLASSINFO("privateCallHangTimeDescription", "Time span during which a direct answer for a " "private call is possible.") Q_PROPERTY(Interval privateCallHangTime READ privateCallHangTime WRITE setPrivateCallHangTime FINAL) Q_CLASSINFO("groupCallHangTimeDescription", "Time span during which a direct answer for a " "group call is possible.") Q_PROPERTY(Interval groupCallHangTime READ groupCallHangTime WRITE setGroupCallHangTime FINAL) Q_CLASSINFO("sendTalkerAliasDescription", "If enabled, the talker alias is send.") Q_PROPERTY(bool sendTalkerAlias READ sendTalkerAliasEnabled WRITE enableSendTalkerAlias FINAL) Q_CLASSINFO("talkerAliasEncodingDescription", "Specifies the encoding of the talker alias.") Q_PROPERTY(TalkerAliasEncoding talkerAliasEncoding READ talkerAliasEncoding WRITE setTalkerAliasEncoding FINAL) Q_CLASSINFO("preambleDescription", "Specifies preamble duration. Usually 100ms.") Q_PROPERTY(Interval preamble READ preamble WRITE setPreamble FINAL) public: /** Possible talker alias encodings. */ enum class TalkerAliasEncoding { Iso7, Iso8, Unicode }; Q_ENUM(TalkerAliasEncoding) public: /** Default constructor. */ explicit DMRSettings(QObject *parent = nullptr); void clear() override; ConfigItem *clone() const override; /** Returns @c true if the private call must match. */ bool privateCallMatchEnabled() const; /** Enables private call match. */ void enablePrivateCallMatch(bool enable); /** Returns @c true if the group call must match. */ bool groupCallMatchEnabled() const; /** Enables group call match. */ void enableGroupCallMatch(bool enable); /** Returns the private call hang time. */ Interval privateCallHangTime() const; /** Sets the private call hang time. */ void setPrivateCallHangTime(const Interval &dur); /** Returns the group call hang time. */ Interval groupCallHangTime() const; /** Sets the group call hang time. */ void setGroupCallHangTime(const Interval &dur); /** Retunrs @c true if the talker alias is send. */ bool sendTalkerAliasEnabled() const; /** Enables sending talker alias. */ void enableSendTalkerAlias(bool enable); /** Returns the talker alias encoding. */ TalkerAliasEncoding talkerAliasEncoding() const; /** Sets the talker alias encoding. */ void setTalkerAliasEncoding(TalkerAliasEncoding encoding); /** Returns the preamble duration. */ Interval preamble() const; /** Sets the preamble duration. */ void setPreamble(const Interval &dur); protected: /** Enables private call match. */ bool _privateCallMatch; /** Enables group call match. */ bool _groupCallMatch; /** The private call hang time. */ Interval _privateCallHangTime; /** The group call hang time. */ Interval _groupCallHangTime; /** Enables sending the talker alias. */ bool _sendTalkerAlias; /** The talker alias encoding. */ TalkerAliasEncoding _talkerAliasEncoding; /** The preamble duration. */ Interval _preamble; }; #endif // DMRSETTINGS_HH ================================================ FILE: lib/dr1801uv.cc ================================================ #include "dr1801uv.hh" #include "dr1801uv_interface.hh" #include "logger.hh" RadioLimits * DR1801UV::_limits = nullptr; DR1801UV::DR1801UV(DR1801UVInterface *device, QObject *parent) : Radio(parent), _device(device), _name("Baofeng DR-1801UV") { // Check if device is open if ((nullptr==_device) || (! _device->isOpen())) { _task = StatusError; return; } } RadioInfo DR1801UV::defaultRadioInfo() { return RadioInfo(RadioInfo::DR1801UV, "dr1801uv", "DR-1801UV", "Baofeng", {DR1801UVInterface::interfaceInfo()}); } const QString & DR1801UV::name() const { return _name; } const Codeplug & DR1801UV::codeplug() const { return _codeplug; } Codeplug & DR1801UV::codeplug() { return _codeplug; } const RadioLimits & DR1801UV::limits() const { if (nullptr == _limits) _limits = new DR1801UVLimits(); return *_limits; } bool DR1801UV::startDownload(const TransferFlags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; _task = StatusDownload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_device && _device->isOpen()) _device->moveToThread(this); start(); return true; } bool DR1801UV::startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; if (! (_config = config)) return false; _codeplugFlags = flags; _task = StatusUpload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_device && _device->isOpen()) _device->moveToThread(this); start(); return true; } bool DR1801UV::startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(selection); errMsg(err) << "This device does not support a call-sign DB."; return false; } bool DR1801UV::startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(flags); errMsg(err) << "Satellite config upload is not implemented yet."; return false; } void DR1801UV::run() { if (StatusDownload == _task) { if ((nullptr==_device) || (! _device->isOpen())) { emit downloadError(this); return; } emit downloadStarted(); if (! download()) { _device->read_finish(); _device->reboot(); _device->close(); _task = StatusError; emit downloadError(this); return; } _task = StatusIdle; _device->reboot(); _device->close(); emit downloadFinished(this, &codeplug()); //_config = nullptr; } else if (StatusUpload == _task) { if ((nullptr==_device) || (! _device->isOpen())) { emit uploadError(this); return; } emit uploadStarted(); if (! upload()) { _device->write_finish(); _device->reboot(); _device->close(); _task = StatusError; emit uploadError(this); return; } _device->write_finish(); _device->reboot(); _device->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadCallsigns == _task) { // Not implemented. emit uploadError(this); return; } } bool DR1801UV::download() { if (! _device->readCodeplug(_codeplug, [this](unsigned int n, unsigned int total){ emit downloadProgress(float(n*100)/total); }, _errorStack)) { errMsg(_errorStack) << "Cannot read codeplug from device."; return false; } return true; } bool DR1801UV::upload() { // First, read codeplug from the device if (! _device->readCodeplug(_codeplug, [this](unsigned int n, unsigned int total) { emit uploadProgress(float(n*50)/total); }, _errorStack)) { errMsg(_errorStack) << "Cannot read codeplug."; return false; } // Encode config into codeplug _codeplug.encode(_config, _codeplugFlags); _codeplug.data(0x304)[0] = 0; // Write codeplug back to the device if (! _device->writeCodeplug(_codeplug, [this](unsigned int n, unsigned int total) { emit uploadProgress(50+float(n*50)/total); }, _errorStack)) { errMsg(_errorStack) << "Cannot write codeplug to the device."; } return true; } ================================================ FILE: lib/dr1801uv.hh ================================================ /** @defgroup dr1801uv BTECH DR-1801 UV * * This module collects all classes implementing the codeplug and communication protocol * for the BTECH DR-1801UV. This device is also known as BF-1801 A6 and is a completely different * device than the well known DM-1801. The former uses the Auctu A6 radio-on-a-chip. * * @ingroup auctus */ #ifndef DR1801UV_HH #define DR1801UV_HH #include "radio.hh" #include "dr1801uv_interface.hh" #include "dr1801uv_codeplug.hh" #include "dr1801uv_limits.hh" /** Implements the BTECH DR-1801UV (BF-1801 A6). * * @ingroup dr1801uv */ class DR1801UV : public Radio { Q_OBJECT public: /** Constructs a new instance representig a DR-1801UV. */ explicit DR1801UV(DR1801UVInterface *device=nullptr, QObject *parent=nullptr); public: /** Returns the default radio info. */ static RadioInfo defaultRadioInfo(); const QString &name() const; const RadioLimits &limits() const; const Codeplug &codeplug() const; Codeplug &codeplug(); bool startDownload(const TransferFlags &flags, const ErrorStack &err); bool startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err); bool startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err); bool startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err); protected: /** Thread main routine, performs all blocking IO operations for codeplug up- and download. */ void run(); private: virtual bool download(); virtual bool upload(); protected: /** Owns the interface to the device. */ DR1801UVInterface *_device; /** Holds the device name, once it got identified. */ QString _name; /** The binary codeplug. */ DR1801UVCodeplug _codeplug; /** The generic configuration. */ Config *_config; /** Some codeplug flags. */ Codeplug::Flags _codeplugFlags; private: /** Holds the singleton instance of the radio limits. */ static RadioLimits *_limits; }; #endif // DR1801UV_HH ================================================ FILE: lib/dr1801uv_codeplug.cc ================================================ #include "dr1801uv_codeplug.hh" #include "logger.hh" #include "zone.hh" #include "config.hh" #include "intermediaterepresentation.hh" #include /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::ChannelBankElement * ******************************************************************************************** */ DR1801UVCodeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr) : Element(ptr, ChannelBankElement::size()) { // pass... } void DR1801UVCodeplug::ChannelBankElement::clear() { memset(_data, 0, ChannelBankElement::size()); } unsigned int DR1801UVCodeplug::ChannelBankElement::channelCount() const { return getUInt16_le(Offset::channelCount()); } void DR1801UVCodeplug::ChannelBankElement::setChannelCount(unsigned int count) { count = std::min(Limit::channelCount(), count); setUInt16_le(Offset::channelCount(), count); } DR1801UVCodeplug::ChannelElement DR1801UVCodeplug::ChannelBankElement::channel(unsigned int index) const { return ChannelElement(_data + Offset::channel() + index*ChannelElement::size()); } QString DR1801UVCodeplug::ChannelBankElement::channelName(unsigned int index) const { return readASCII(Offset::channelName() + index*Limit::channelNameLength(), Limit::channelNameLength(), 0x00); } void DR1801UVCodeplug::ChannelBankElement::setChannelName(unsigned int index, const QString &name) { writeASCII(Offset::channelName() + index*Limit::channelNameLength(), name, Limit::channelNameLength(), 0x00); } bool DR1801UVCodeplug::ChannelBankElement::decode(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0,j=0; isetName(channelName(i)); // Add channel to context and config ctx.add(obj, ch.index()); ctx.config()->channelList()->add(obj); } return true; } bool DR1801UVCodeplug::ChannelBankElement::link(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0,j=0; i(ch.index())) { errMsg(err) << "Cannot link channel at index " << i << ". Channel not defined."; return false; } Channel *obj = ctx.get(ch.index()); if (! ch.linkChannelObj(obj, ctx, err)) { errMsg(err) << "Cannot link channel at index " << i << "."; return false; } } return true; } bool DR1801UVCodeplug::ChannelBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int n = std::min(Limit::channelCount(), ctx.count()); setChannelCount(n); for (unsigned int i=0; i(i), ctx, err)) { errMsg(err) << "Cannot encode channel '" << ctx.get(i) << "' at index " << i << "."; return false; } ch.setIndex(i); setChannelName(i, ctx.get(i)->name()); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::ChannelElement * ******************************************************************************************** */ DR1801UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : Element(ptr, ChannelElement::size()) { // pass... } bool DR1801UVCodeplug::ChannelElement::isValid() const { return 0xffff != index(); } void DR1801UVCodeplug::ChannelElement::clear() { memset(_data, 0, _size); setIndex(0xffff); } unsigned int DR1801UVCodeplug::ChannelElement::index() const { return getUInt16_le(Offset::index()); } void DR1801UVCodeplug::ChannelElement::setIndex(unsigned int idx) { setUInt16_le(Offset::index(), idx); } DR1801UVCodeplug::ChannelElement::Type DR1801UVCodeplug::ChannelElement::channelType() const { return (Type)getUInt8(Offset::channelType()); } void DR1801UVCodeplug::ChannelElement::setChannelType(Type type) { setUInt8(Offset::channelType(), (uint8_t)type); } Channel::Power DR1801UVCodeplug::ChannelElement::power() const { switch ((Power)getUInt8(Offset::power())) { case Power::Low: return Channel::Power::Low; case Power::High: return Channel::Power::High; } return Channel::Power::Low; } void DR1801UVCodeplug::ChannelElement::setPower(Channel::Power pwr) { switch (pwr) { case Channel::Power::Min: case Channel::Power::Low: setUInt8(Offset::power(), (uint8_t)Power::Low); break; case Channel::Power::Mid: case Channel::Power::High: case Channel::Power::Max: setUInt8(Offset::power(), (uint8_t)Power::High); break; } } Frequency DR1801UVCodeplug::ChannelElement::rxFrequency() const { return Frequency::fromHz(getUInt32_le(Offset::rxFrequency())); } void DR1801UVCodeplug::ChannelElement::setRXFrequency(Frequency MHz) { setUInt32_le(Offset::rxFrequency(), MHz.inHz()); } Frequency DR1801UVCodeplug::ChannelElement::txFrequency() const { return Frequency::fromHz(getUInt32_le(Offset::txFrequency())); } void DR1801UVCodeplug::ChannelElement::setTXFrequency(Frequency MHz) { setUInt32_le(Offset::txFrequency(), MHz.inHz()); } bool DR1801UVCodeplug::ChannelElement::hasTransmitContact() const { return 0 != getUInt16_le(Offset::transmitContactIndex()); } unsigned int DR1801UVCodeplug::ChannelElement::transmitContactIndex() const { return getUInt16_le(Offset::transmitContactIndex())-1; } void DR1801UVCodeplug::ChannelElement::setTransmitContactIndex(unsigned int index) { setUInt16_le(Offset::transmitContactIndex(), index+1); } void DR1801UVCodeplug::ChannelElement::clearTransmitContactIndex() { setUInt16_le(Offset::transmitContactIndex(), 0); } DR1801UVCodeplug::ChannelElement::Admit DR1801UVCodeplug::ChannelElement::admitCriterion() const { return (Admit) getUInt8(Offset::admitCriterion()); } void DR1801UVCodeplug::ChannelElement::setAdmitCriterion(Admit admit) { setUInt8(Offset::admitCriterion(), (uint8_t)admit); } unsigned int DR1801UVCodeplug::ChannelElement::colorCode() const { return getUInt8(Offset::colorCode()); } void DR1801UVCodeplug::ChannelElement::setColorCode(unsigned int cc) { setUInt8(Offset::colorCode(), cc); } DMRChannel::TimeSlot DR1801UVCodeplug::ChannelElement::timeSlot() const { switch ((TimeSlot)getUInt8(Offset::timeSlot())) { case TimeSlot::TS1: return DMRChannel::TimeSlot::TS1; case TimeSlot::TS2: return DMRChannel::TimeSlot::TS2; } return DMRChannel::TimeSlot::TS1; } void DR1801UVCodeplug::ChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { switch (ts) { case DMRChannel::TimeSlot::TS1: setUInt8(Offset::timeSlot(), (uint8_t)TimeSlot::TS1); break; case DMRChannel::TimeSlot::TS2: setUInt8(Offset::timeSlot(), (uint8_t)TimeSlot::TS2); break; } } bool DR1801UVCodeplug::ChannelElement::hasEncryptionKey() const { return 0 != getUInt8(Offset::encryptionKeyIndex()); } unsigned int DR1801UVCodeplug::ChannelElement::encryptionKeyIndex() const { return getUInt8(Offset::encryptionKeyIndex())-1; } void DR1801UVCodeplug::ChannelElement::setEncryptionKeyIndex(unsigned int index) { setUInt8(Offset::encryptionKeyIndex(), index+1); } void DR1801UVCodeplug::ChannelElement::clearEncryptionKeyIndex() { setUInt8(Offset::encryptionKeyIndex(), 0); } bool DR1801UVCodeplug::ChannelElement::dcdm() const { return getBit(Offset::dcdm().byte, Offset::dcdm().bit); } void DR1801UVCodeplug::ChannelElement::enableDCDM(bool enable) { setBit(Offset::dcdm().byte, Offset::dcdm().bit, enable); } bool DR1801UVCodeplug::ChannelElement::confirmPrivateCall() const { return getBit(Offset::confirmPivateCall().byte, Offset::confirmPivateCall().bit); } void DR1801UVCodeplug::ChannelElement::enablePrivateCallConfirmation(bool enable) { setBit(Offset::confirmPivateCall().byte, Offset::confirmPivateCall().bit, enable); } DR1801UVCodeplug::ChannelElement::SignalingMode DR1801UVCodeplug::ChannelElement::signalingMode() const { return (SignalingMode) getUInt8(Offset::signalingMode()); } void DR1801UVCodeplug::ChannelElement::setSignalingMode(SignalingMode mode) { setUInt8(Offset::signalingMode(), (uint8_t)mode); } bool DR1801UVCodeplug::ChannelElement::hasAlarmSystem() const { return 0 != getUInt8(Offset::alarmSystemIndex()); } unsigned int DR1801UVCodeplug::ChannelElement::alarmSystemIndex() const { return getUInt8(Offset::alarmSystemIndex())-1; } void DR1801UVCodeplug::ChannelElement::setAlarmSystemIndex(unsigned int index) { setUInt8(Offset::alarmSystemIndex(), index+1); } void DR1801UVCodeplug::ChannelElement::clearAlarmSystemIndex() { setUInt8(Offset::alarmSystemIndex(), 0); } FMChannel::Bandwidth DR1801UVCodeplug::ChannelElement::bandwidth() const { switch ((Bandwidth)getUInt8(Offset::bandwidth())) { case Bandwidth::Narrow: return FMChannel::Bandwidth::Narrow; case Bandwidth::Wide: return FMChannel::Bandwidth::Wide; } return FMChannel::Bandwidth::Narrow; } void DR1801UVCodeplug::ChannelElement::setBandwidth(FMChannel::Bandwidth bw) { switch (bw){ case FMChannel::Bandwidth::Narrow: setUInt8(Offset::bandwidth(), (uint8_t)Bandwidth::Narrow); break; case FMChannel::Bandwidth::Wide: setUInt8(Offset::bandwidth(), (uint8_t)Bandwidth::Wide); break; } } bool DR1801UVCodeplug::ChannelElement::autoScanEnabled() const { return 0x01 == getUInt8(Offset::autoScan()); } void DR1801UVCodeplug::ChannelElement::enableAutoScan(bool enable) { setUInt8(Offset::autoScan(), enable ? 0x01 : 0x00); } bool DR1801UVCodeplug::ChannelElement::hasScanList() const { return 0x00 != getUInt8(Offset::scanListIndex()); } unsigned int DR1801UVCodeplug::ChannelElement::scanListIndex() const { return getUInt8(Offset::scanListIndex())-1; } void DR1801UVCodeplug::ChannelElement::setScanListIndex(unsigned int index) { setUInt8(Offset::scanListIndex(), index+1); } void DR1801UVCodeplug::ChannelElement::clearScanListIndex() { setUInt8(Offset::scanListIndex(), 0); } SelectiveCall DR1801UVCodeplug::ChannelElement::rxTone() const { uint16_t ctcss_dcs = getUInt16_le(Offset::rxSubtoneCode()); SubToneType type = (SubToneType)getUInt8(Offset::rxSubtoneType()); DCSMode dcsMode = (DCSMode)getUInt8(Offset::rxDCSMode()); switch (type) { case SubToneType::None: return SelectiveCall(); case SubToneType::CTCSS: return SelectiveCall(float(ctcss_dcs)/10); case SubToneType::DCS: return SelectiveCall(ctcss_dcs, DCSMode::Inverted == dcsMode); } return SelectiveCall(); } void DR1801UVCodeplug::ChannelElement::setRXTone(const SelectiveCall &code) { uint16_t ctcss_dcs = 0; SubToneType type = SubToneType::None; DCSMode dcsMode = DCSMode::Normal; if (code.isCTCSS()) { type = SubToneType::CTCSS; ctcss_dcs = code.Hz()*10; } else if (code.isDCS()) { type = SubToneType::DCS; ctcss_dcs = code.octalCode(); dcsMode = code.isInverted() ? DCSMode::Inverted : DCSMode::Normal; } setUInt16_le(Offset::rxSubtoneCode(), ctcss_dcs); setUInt8(Offset::rxSubtoneType(), (uint8_t)type); setUInt8(Offset::rxDCSMode(), (uint8_t)dcsMode); } SelectiveCall DR1801UVCodeplug::ChannelElement::txTone() const { uint16_t ctcss_dcs = getUInt16_le(Offset::txSubtoneCode()); SubToneType type = (SubToneType)getUInt8(Offset::txSubtoneType()); DCSMode dcsMode = (DCSMode)getUInt8(Offset::txDCSMode()); switch (type) { case SubToneType::None: return SelectiveCall(); case SubToneType::CTCSS: return SelectiveCall(float(ctcss_dcs)/10); case SubToneType::DCS: return SelectiveCall(ctcss_dcs, DCSMode::Inverted == dcsMode); } return SelectiveCall(); } void DR1801UVCodeplug::ChannelElement::setTXTone(const SelectiveCall &code) { uint16_t ctcss_dcs = 0; SubToneType type = SubToneType::None; DCSMode dcsMode = DCSMode::Normal; if (code.isCTCSS()) { type = SubToneType::CTCSS; ctcss_dcs = code.Hz()*10; } else if (code.isDCS()) { type = SubToneType::DCS; ctcss_dcs = code.octalCode(); dcsMode = code.isInverted() ? DCSMode::Inverted : DCSMode::Normal; } setUInt16_le(Offset::txSubtoneCode(), ctcss_dcs); setUInt8(Offset::txSubtoneType(), (uint8_t)type); setUInt8(Offset::txDCSMode(), (uint8_t)dcsMode); } bool DR1801UVCodeplug::ChannelElement::talkaround() const { return getBit(Offset::talkaround(), 7); } void DR1801UVCodeplug::ChannelElement::enableTalkaround(bool enable) { setBit(Offset::talkaround(), 7, enable); } bool DR1801UVCodeplug::ChannelElement::hasPTTID() const { return 0 != getUInt8(Offset::pttIDIndex()); } unsigned int DR1801UVCodeplug::ChannelElement::pttIDIndex() const { return getUInt8(Offset::pttIDIndex())-1; } void DR1801UVCodeplug::ChannelElement::setPTTIDIndex(unsigned int idx) { setUInt8(Offset::pttIDIndex(), idx+1); } void DR1801UVCodeplug::ChannelElement::clearPTTID() { setUInt8(Offset::pttIDIndex(), 0); } bool DR1801UVCodeplug::ChannelElement::hasGroupList() const { return 0 != getUInt8(Offset::groupListIndex()); } unsigned int DR1801UVCodeplug::ChannelElement::groupListIndex() const { return getUInt8(Offset::groupListIndex())-1; } void DR1801UVCodeplug::ChannelElement::setGroupListIndex(unsigned int index) { setUInt8(Offset::groupListIndex(), index+1); } void DR1801UVCodeplug::ChannelElement::clearGroupListIndex() { setUInt8(Offset::groupListIndex(), 0); } bool DR1801UVCodeplug::ChannelElement::loneWorker() const { return 0x01 == getUInt8(Offset::loneWorker()); } void DR1801UVCodeplug::ChannelElement::enableLoneWorker(bool enable) { setUInt8(Offset::loneWorker(), enable ? 0x01 : 0x00); } Channel * DR1801UVCodeplug::ChannelElement::toChannelObj(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Channel *ch = nullptr; if (Type::FM == channelType()) { FMChannel *fm = new FMChannel(); ch = fm; switch (admitCriterion()) { case Admit::Always: fm->setAdmit(FMChannel::Admit::Always); break; case Admit::ColorCode_or_Tone: fm->setAdmit(FMChannel::Admit::Tone); break; case Admit::ChannelFree: fm->setAdmit(FMChannel::Admit::Free); break; } fm->setBandwidth(bandwidth()); fm->setRXTone(rxTone()); fm->setTXTone(txTone()); fm->extended()->enableTalkaround(talkaround()); } else if (Type::DMR == channelType()) { DMRChannel *dmr = new DMRChannel(); ch = dmr; switch (admitCriterion()) { case Admit::Always: dmr->setAdmit(DMRChannel::Admit::Always); break; case Admit::ColorCode_or_Tone: dmr->setAdmit(DMRChannel::Admit::ColorCode); break; case Admit::ChannelFree: dmr->setAdmit(DMRChannel::Admit::Free); break; } dmr->setColorCode(colorCode()); dmr->setTimeSlot(timeSlot()); dmr->extended()->enableTalkaround(talkaround()); dmr->extended()->enableDCDM(dcdm()); dmr->extended()->enablePrivateCallConfirm(confirmPrivateCall()); dmr->extended()->enableLoneWorker(loneWorker()); } else { errMsg(err) << "Unknown channel type " << (uint8_t)channelType() << "."; return nullptr; } ch->setPower(power()); ch->setRXFrequency(rxFrequency()); ch->setTXFrequency(txFrequency()); return ch; } bool DR1801UVCodeplug::ChannelElement::linkChannelObj(Channel *channel, Context &ctx, const ErrorStack &err) const { // Common references if (hasScanList()) { if (! ctx.has(scanListIndex())) { errMsg(err) << "Scanlist with index " << scanListIndex() << " not known."; return false; } channel->setScanList(ctx.get(scanListIndex())); } // Handle DMR specific references if (DMRChannel *dmr = channel->as()) { if (hasTransmitContact()) { if (! ctx.has(transmitContactIndex())) { errMsg(err) << "DMR contact with index " << transmitContactIndex() << " not known."; return false; } dmr->setContact(ctx.get(transmitContactIndex())); } if (hasGroupList()) { if (! ctx.has(groupListIndex())) { errMsg(err) << "Group list with index " << groupListIndex() << " not known."; return false; } dmr->setGroupList(ctx.get(groupListIndex())); } } return true; } bool DR1801UVCodeplug::ChannelElement::encode(Channel *channel, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); // Encode common properties setPower(channel->power()); setRXFrequency(channel->rxFrequency()); setTXFrequency(channel->txFrequency()); if (channel->scanList()) setScanListIndex(ctx.index(channel->scanList())); else clearScanListIndex(); // Encode type specific settings if (channel->is()) { FMChannel *fm = channel->as(); setChannelType(Type::FM); setBandwidth(fm->bandwidth()); setRXTone(fm->rxTone()); setTXTone(fm->txTone()); switch (fm->admit()) { case FMChannel::Admit::Always: setAdmitCriterion(Admit::Always); break; case FMChannel::Admit::Free: setAdmitCriterion(Admit::ChannelFree); break; case FMChannel::Admit::Tone: setAdmitCriterion(Admit::ColorCode_or_Tone); break; } enableTalkaround(fm->extended()->talkaround()); } else if (channel->is()) { DMRChannel *dmr = channel->as(); setChannelType(Type::DMR); setBandwidth(FMChannel::Bandwidth::Narrow); if (dmr->contact()) setTransmitContactIndex(ctx.index(dmr->contact())); else clearTransmitContactIndex(); switch (dmr->admit()) { case DMRChannel::Admit::Always: setAdmitCriterion(Admit::Always); break; case DMRChannel::Admit::Free: setAdmitCriterion(Admit::ChannelFree); break; case DMRChannel::Admit::ColorCode: setAdmitCriterion(Admit::ColorCode_or_Tone); break; } setColorCode(dmr->colorCode()); setTimeSlot(dmr->timeSlot()); if (dmr->commercialExtension() && dmr->commercialExtension()->encryptionKey()) setEncryptionKeyIndex(ctx.index(dmr->commercialExtension()->encryptionKey())); else clearEncryptionKeyIndex(); if (dmr->groupList()) setGroupListIndex(ctx.index(dmr->groupList())); else clearGroupListIndex(); enableTalkaround(dmr->extended()->talkaround()); enableDCDM(dmr->extended()->dcdm()); enablePrivateCallConfirmation(dmr->extended()->privateCallConfirm()); enableLoneWorker(dmr->extended()->loneWorker()); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::ContactBankElement * ******************************************************************************************** */ DR1801UVCodeplug::ContactBankElement::ContactBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::ContactBankElement::ContactBankElement(uint8_t *ptr) : Element(ptr, ContactBankElement::size()) { // pass... } void DR1801UVCodeplug::ContactBankElement::clear() { memset(_data, 0, ContactBankElement::size()); } unsigned int DR1801UVCodeplug::ContactBankElement::contactCount() const { return getUInt16_le(Offset::contactCount()); } void DR1801UVCodeplug::ContactBankElement::setContactCount(unsigned int count) { count = std::min(Limit::contactCount(), count); setUInt16_le(Offset::contactCount(), count); } unsigned int DR1801UVCodeplug::ContactBankElement::firstIndex() const { return getUInt16_le(Offset::firstIndex())-1; } void DR1801UVCodeplug::ContactBankElement::setFirstIndex(unsigned int index) { setUInt16_le(Offset::firstIndex(), index+1); } DR1801UVCodeplug::ContactElement DR1801UVCodeplug::ContactBankElement::contact(unsigned int index) const { return ContactElement(_data + Offset::contacts() + index*ContactElement::size()); } bool DR1801UVCodeplug::ContactBankElement::decode(Context &ctx, const ErrorStack &err) const { // Get first element in list unsigned int currentIndex = firstIndex(); ContactElement currentContact = contact(currentIndex); for (unsigned int i=0; icontacts()->add(obj); // continue with successor if (currentContact.hasSuccessor()) { currentIndex = currentContact.successorIndex(); currentContact = contact(currentIndex); } } return true; } bool DR1801UVCodeplug::ContactBankElement::link(Context &ctx, const ErrorStack &err) const { if (0 == contactCount()) return true; unsigned int currentIndex = firstIndex(); for (unsigned int i=0; i(currentIndex)) { errMsg(err) << "Cannot link contact at index " << currentIndex << ", not defined."; } DMRContact *obj = ctx.get(currentIndex); if (! currentContact.linkContactObj(obj, ctx, err)) { errMsg(err) << "Cannot link contact element at index " << currentIndex; return false; } currentIndex = currentContact.successorIndex(); } return true; } bool DR1801UVCodeplug::ContactBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int n = std::min(Limit::contactCount(), ctx.count()); setContactCount(n); setFirstIndex(0); for (unsigned int i=0; icontact(i); if (! contact.encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode contact '" << ctx.get(i)->name() << "' at index " << i << "."; return false; } if ((i+1) < n) contact.setSuccessorIndex(i+1); else contact.clearSuccessorIndex(); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::ContactElement * ******************************************************************************************** */ DR1801UVCodeplug::ContactElement::ContactElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::ContactElement::ContactElement(uint8_t *ptr) : Element(ptr, ContactElement::size()) { // pass... } bool DR1801UVCodeplug::ContactElement::isValid() const { return 0 != getUInt8(Offset::nameLength()) && // has name 0 != getUInt24_le(0x0004) && // has DMR ID 0 != getUInt8(0x0007); // has call type } void DR1801UVCodeplug::ContactElement::clear() { memset(_data, 0, _size); } bool DR1801UVCodeplug::ContactElement::hasSuccessor() const { return 0x0000 != getUInt16_le(Offset::successorIndex()); } uint16_t DR1801UVCodeplug::ContactElement::successorIndex() const { return getUInt16_le(Offset::successorIndex())-1; } void DR1801UVCodeplug::ContactElement::setSuccessorIndex(uint16_t index) { setUInt16_le(Offset::successorIndex(), index+1); } void DR1801UVCodeplug::ContactElement::clearSuccessorIndex() { setUInt16_le(Offset::successorIndex(), 0); } uint32_t DR1801UVCodeplug::ContactElement::dmrID() const { if (DMRContact::AllCall == type()) return 0xffffff; return getUInt24_le(Offset::dmrID()); } void DR1801UVCodeplug::ContactElement::setDMRID(uint32_t id) { setUInt24_le(Offset::dmrID(), id); } DMRContact::Type DR1801UVCodeplug::ContactElement::type() const { switch ((CallType)getUInt8(Offset::callType())) { case CallType::AllCall: return DMRContact::AllCall; case CallType::PrivateCall: return DMRContact::PrivateCall; case CallType::GroupCall: return DMRContact::GroupCall; } return DMRContact::PrivateCall; } void DR1801UVCodeplug::ContactElement::setCallType(DMRContact::Type type) { switch (type) { case DMRContact::AllCall: setUInt8(Offset::callType(), (uint8_t)CallType::AllCall); setDMRID(0xffffff); break; case DMRContact::PrivateCall: setUInt8(Offset::callType(), (uint8_t)CallType::PrivateCall); break; case DMRContact::GroupCall: setUInt8(Offset::callType(), (uint8_t)CallType::GroupCall); break; } } QString DR1801UVCodeplug::ContactElement::name() const { return readASCII(Offset::name(), getUInt8(Offset::nameLength()), 0x00); } void DR1801UVCodeplug::ContactElement::setName(const QString &name) { uint8_t len = std::min(Limit::nameLength(), (unsigned int)name.size()); setUInt8(Offset::nameLength(), len); writeASCII(Offset::name(), name, len, 0x00); } DMRContact * DR1801UVCodeplug::ContactElement::toContactObj(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err) return new DMRContact(type(), name(), dmrID()); } bool DR1801UVCodeplug::ContactElement::linkContactObj(DMRContact *contact, Context &ctx, const ErrorStack &err) { Q_UNUSED(contact); Q_UNUSED(ctx); Q_UNUSED(err); return true; } bool DR1801UVCodeplug::ContactElement::encode(DMRContact *contact, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); setName(contact->name()); setCallType(contact->type()); setDMRID(contact->number()); return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::GroupListBankElement * ******************************************************************************************** */ DR1801UVCodeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr) : Element(ptr, GroupListBankElement::size()) { // pass... } void DR1801UVCodeplug::GroupListBankElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::GroupListBankElement::groupListCount() const { return getUInt8(Offset::groupListCount()); } void DR1801UVCodeplug::GroupListBankElement::setGroupListCount(unsigned int count) { count = std::min(Limit::groupListCount(), count); setUInt8(Offset::groupListCount(), count); } DR1801UVCodeplug::GroupListElement DR1801UVCodeplug::GroupListBankElement::groupList(unsigned int index) const { return GroupListElement(_data + Offset::groupLists() + index*GroupListElement::size()); } bool DR1801UVCodeplug::GroupListBankElement::decode(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0,j=0; irxGroupLists()->add(obj); } return true; } bool DR1801UVCodeplug::GroupListBankElement::link(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0,j=0; i(gl.index())) { errMsg(err) << "Cannot link group list at index " << i << ". Group list not defined."; return false; } if (! gl.linkGroupListObj(ctx.get(gl.index()), ctx, err)) { errMsg(err) << "Cannot link group list at index " << i << "."; return false; } } return true; } bool DR1801UVCodeplug::GroupListBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int n = std::min(Limit::groupListCount(), ctx.count()); setGroupListCount(n); for (unsigned int i=0; i(i), ctx, err)) { errMsg(err) << "Cannot encode group list '" << ctx.get(i)->name() << "' at index " << i << "."; return false; } lst.setIndex(i); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::GroupListElement * ******************************************************************************************** */ DR1801UVCodeplug::GroupListElement::GroupListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::GroupListElement::GroupListElement(uint8_t *ptr) : Element(ptr, GroupListElement::size()) { // pass... } void DR1801UVCodeplug::GroupListElement::clear() { memset(_data, 0, _size); } bool DR1801UVCodeplug::GroupListElement::isValid() const { return 0 != getUInt16_le(Offset::index()); } unsigned int DR1801UVCodeplug::GroupListElement::index() const { return getUInt16_le(Offset::index())-1; } void DR1801UVCodeplug::GroupListElement::setIndex(unsigned int index) { setUInt16_le(Offset::index(), index+1); } unsigned int DR1801UVCodeplug::GroupListElement::count() const { return getUInt16_le(Offset::count()); } void DR1801UVCodeplug::GroupListElement::setCount(unsigned int n) { n = std::min(Limit::members(), n); setUInt16_le(Offset::count(), n); } bool DR1801UVCodeplug::GroupListElement::hasMemberIndex(unsigned int n) const { return 0 != getUInt16_le(Offset::members() + n*0x02); } unsigned int DR1801UVCodeplug::GroupListElement::memberIndex(unsigned int n) const { return getUInt16_le(Offset::members() + n*0x02) - 1; } void DR1801UVCodeplug::GroupListElement::setMemberIndex(unsigned int n, unsigned int index) { setUInt16_le(Offset::members() + n*0x02, index+1); } void DR1801UVCodeplug::GroupListElement::clearMemberIndex(unsigned int n) { setUInt16_le(Offset::members() + n*0x02, 0); } RXGroupList * DR1801UVCodeplug::GroupListElement::toGroupListObj(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); // Simply derive name from index return new RXGroupList(QString("Group List %1").arg(index()+1)); } bool DR1801UVCodeplug::GroupListElement::linkGroupListObj(RXGroupList *list, Context &ctx, const ErrorStack &err) const { if (! isValid()) return false; for (unsigned int i=0; i(memberIndex(i))) { errMsg(err) << "Member index " << memberIndex(i) << " is not known."; return false; } list->addContact(ctx.get(memberIndex(i))); } return true; } bool DR1801UVCodeplug::GroupListElement::encode(RXGroupList *list, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) unsigned int n = std::min(Limit::members(), (unsigned int)list->count()); setCount(n); for (unsigned int i=0; icontact(i))); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::ZoneBankElement * ******************************************************************************************** */ DR1801UVCodeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr) : Element(ptr, ZoneBankElement::size()) { // pass... } void DR1801UVCodeplug::ZoneBankElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::ZoneBankElement::zoneCount() const { return getUInt8(Offset::zoneCount()); } void DR1801UVCodeplug::ZoneBankElement::setZoneCount(unsigned int count) { count = std::min(Limit::zoneCount(), count); setUInt8(Offset::zoneCount(), count); } unsigned int DR1801UVCodeplug::ZoneBankElement::upZoneIndex() const { return getUInt16_le(Offset::upZoneIndex()); } void DR1801UVCodeplug::ZoneBankElement::setUpZoneIndex(unsigned int index) { setUInt16_le(Offset::upZoneIndex(), index); } unsigned int DR1801UVCodeplug::ZoneBankElement::downZoneIndex() const { return getUInt16_le(Offset::downZoneIndex()); } void DR1801UVCodeplug::ZoneBankElement::setDownZoneIndex(unsigned int index) { setUInt16_le(Offset::downZoneIndex(), index); } DR1801UVCodeplug::ZoneElement DR1801UVCodeplug::ZoneBankElement::zone(unsigned int index) const { return ZoneElement(_data + Offset::zones() + index*ZoneElement::size()); } bool DR1801UVCodeplug::ZoneBankElement::decode(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0,j=0; izone(i)); if (! zone.isValid()) continue; j++; Zone *obj = zone.toZoneObj(ctx, err); if (nullptr == obj) { errMsg(err) << "Cannot create zone at index " << i << "."; return false; } // Store zone in context ctx.add(obj, zone.index()); // Store zone in config ctx.config()->zones()->add(obj); } return true; } bool DR1801UVCodeplug::ZoneBankElement::link(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0, j=0; izone(i)); if (! zone.isValid()) continue; j++; if (! ctx.has(zone.index())) { errMsg(err) << "Cannot link zone at index " << i << ", not defined."; return false; } if (! zone.linkZoneObj(ctx.get(zone.index()), ctx, err)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } } return true; } bool DR1801UVCodeplug::ZoneBankElement::encode(Context &ctx, const ErrorStack &err) { setZoneCount(ctx.count()); for (unsigned int i=0; i(); i++) { ZoneElement zone = this->zone(i); if (! zone.encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode zone bank."; return false; } zone.setIndex(i); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::ZoneElement * ******************************************************************************************** */ DR1801UVCodeplug::ZoneElement::ZoneElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::ZoneElement::ZoneElement(uint8_t *ptr) : Element(ptr, ZoneElement::size()) { // pass... } void DR1801UVCodeplug::ZoneElement::clear() { memset(_data, 0, _size); } bool DR1801UVCodeplug::ZoneElement::isValid() const { // Read name-length and channel count return (0 != getUInt8(Offset::nameLength())) && (0 != getUInt8(Offset::numEntries())); } QString DR1801UVCodeplug::ZoneElement::name() const { uint8_t n = getUInt8(Offset::nameLength()); return readASCII(Offset::name(), n, 0x00); } void DR1801UVCodeplug::ZoneElement::setName(const QString &name) { uint8_t n = std::min(qsizetype(32), name.length()); setUInt8(Offset::nameLength(), n); writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } unsigned int DR1801UVCodeplug::ZoneElement::index() const { return getUInt16_le(Offset::index()); } void DR1801UVCodeplug::ZoneElement::setIndex(unsigned int index) { setUInt16_le(Offset::index(), index); } unsigned int DR1801UVCodeplug::ZoneElement::numEntries() const { return getUInt8(Offset::numEntries()); } unsigned int DR1801UVCodeplug::ZoneElement::entryIndex(unsigned int n) { n = std::min(Limit::memberCount(), n); return getUInt16_le(Offset::members()+2*n); } void DR1801UVCodeplug::ZoneElement::setEntryIndex(unsigned int n, unsigned int index) { n = std::min(Limit::memberCount(), n); setUInt16_le(Offset::members() + 2*n, index); } Zone * DR1801UVCodeplug::ZoneElement::toZoneObj(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! isValid()) { errMsg(err) << "Cannot create zone from invalid zone element."; return nullptr; } return new Zone(name()); } bool DR1801UVCodeplug::ZoneElement::linkZoneObj(Zone *obj, Context &ctx, const ErrorStack &err) { if (! isValid()) { errMsg(err) << "Cannot link zone using invalid zone element."; return false; } for (unsigned int i=0; i(entryIndex(i))) { errMsg(err) << "Channel with index " << entryIndex(i) << " not defined."; return false; } obj->A()->add(ctx.get(entryIndex(i))); } return true; } bool DR1801UVCodeplug::ZoneElement::encode(Zone *zone, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) setName(zone->name()); unsigned int n = std::min(Limit::memberCount(), (unsigned int)zone->A()->count()); setUInt8(Offset::numEntries(), n); for (unsigned int i=0; iA()->get(i)->as())); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::SettingsElement * ******************************************************************************************** */ DR1801UVCodeplug::SettingsElement::SettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::SettingsElement::SettingsElement(uint8_t *ptr) : Element(ptr, SettingsElement::size()) { // psas... } void DR1801UVCodeplug::SettingsElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::SettingsElement::dmrID() const { return getUInt24_le(Offset::dmrID()); } void DR1801UVCodeplug::SettingsElement::setDMRID(unsigned int id) { setUInt24_le(Offset::dmrID(), id); } DR1801UVCodeplug::SettingsElement::PowerSaveMode DR1801UVCodeplug::SettingsElement::powerSaveMode() const { if (0x00 == getUInt8(Offset::powerSaveEnabled())) { // Power save disabled return PowerSaveMode::Off; } return (PowerSaveMode)getUInt8(Offset::powerSaveMode()); } void DR1801UVCodeplug::SettingsElement::setPowerSaveMode(PowerSaveMode mode) { setUInt8(Offset::powerSaveEnabled(), PowerSaveMode::Off != mode ? 0x01 : 0x00); setUInt8(Offset::powerSaveMode(), (uint8_t) mode); } Level DR1801UVCodeplug::SettingsElement::voxSensitivity() const { if (0x00 == getUInt8(Offset::voxEnabled())) { return Level::null(); } return Level::fromValue(getUInt8(Offset::voxSensitivity()), Limit::vox()); } void DR1801UVCodeplug::SettingsElement::setVOXSensitivity(Level sens) { if (sens.isNull()) { setUInt8(Offset::voxEnabled(), 0x00); } else { setUInt8(Offset::voxEnabled(), 0x01); setUInt8(Offset::voxSensitivity(), sens.mapTo(Limit::vox())); } } Interval DR1801UVCodeplug::SettingsElement::voxDelay() const { return Interval::fromMilliseconds(getUInt8(Offset::voxDelay())*500); } void DR1801UVCodeplug::SettingsElement::setVOXDelay(Interval delay) { if (! delay.isFinite()) setUInt8(Offset::voxDelay(), 0); setUInt8(Offset::voxDelay(), delay.milliseconds()/500); } bool DR1801UVCodeplug::SettingsElement::encryptionEnabled() const { return 0x01 == getUInt8(Offset::encryptionEnabled()); } void DR1801UVCodeplug::SettingsElement::enableEncryption(bool enable) { setUInt8(Offset::encryptionEnabled(), enable ? 0x01 : 0x00); } bool DR1801UVCodeplug::SettingsElement::keyLockEnabled() const { return 0x01 == getUInt8(Offset::keyLockEnabled()); } void DR1801UVCodeplug::SettingsElement::enableKeyLock(bool enable) { setUInt8(Offset::keyLockEnabled(), enable ? 0x01 : 0x00); } unsigned int DR1801UVCodeplug::SettingsElement::keyLockDelay() const { return getUInt8(Offset::keyLockDelay()); } void DR1801UVCodeplug::SettingsElement::setKeyLockDelay(unsigned int sec) { setUInt8(Offset::keyLockDelay(), sec); } bool DR1801UVCodeplug::SettingsElement::lockSideKey1() const { return getBit(Offset::lockSideKey1().byte, Offset::lockSideKey1().bit); } void DR1801UVCodeplug::SettingsElement::enableLockSideKey1(bool enable) { setBit(Offset::lockSideKey1().byte, Offset::lockSideKey1().bit, enable); } bool DR1801UVCodeplug::SettingsElement::lockSideKey2() const { return getBit(Offset::lockSideKey2().byte, Offset::lockSideKey2().bit); } void DR1801UVCodeplug::SettingsElement::enableLockSideKey2(bool enable) { setBit(Offset::lockSideKey2().byte, Offset::lockSideKey2().bit, enable); } bool DR1801UVCodeplug::SettingsElement::lockPTT() const { return getBit(Offset::lockPTT().byte, Offset::lockPTT().bit); } void DR1801UVCodeplug::SettingsElement::enableLockPTT(bool enable) { setBit(Offset::lockPTT().byte, Offset::lockPTT().bit, enable); } DR1801UVCodeplug::SettingsElement::Language DR1801UVCodeplug::SettingsElement::language() const { return (Language) getUInt8(Offset::language()); } void DR1801UVCodeplug::SettingsElement::setLanguage(Language lang) { setUInt8(Offset::language(), (uint8_t)lang); } DR1801UVCodeplug::SettingsElement::SquelchMode DR1801UVCodeplug::SettingsElement::squelchMode() const { return (SquelchMode) getUInt8(Offset::squelchMode()); } void DR1801UVCodeplug::SettingsElement::setSquelchMode(SquelchMode mode) { setUInt8(Offset::squelchMode(), (uint8_t)mode); } bool DR1801UVCodeplug::SettingsElement::rogerTonesEnabled() const { return 0x01 == getUInt8(Offset::rogerTonesEnabled()); } void DR1801UVCodeplug::SettingsElement::enableRogerTones(bool enable) { setUInt8(Offset::rogerTonesEnabled(), enable ? 0x01 : 0x00); } bool DR1801UVCodeplug::SettingsElement::dmrCallOutToneEnabled() const { return getBit(Offset::dmrCallOutToneEnabled()); } void DR1801UVCodeplug::SettingsElement::enableDMRCallOutTone(bool enable) { setBit(Offset::dmrCallOutToneEnabled(), enable); } bool DR1801UVCodeplug::SettingsElement::fmCallOutToneEnabled() const { return getBit(Offset::fmCallOutToneEnabled()); } void DR1801UVCodeplug::SettingsElement::enableFMCallOutTone(bool enable) { setBit(Offset::fmCallOutToneEnabled(), enable); } bool DR1801UVCodeplug::SettingsElement::dmrVoiceEndToneEnabled() const { return getBit(Offset::dmrVoiceEndToneEnabled()); } void DR1801UVCodeplug::SettingsElement::enableDMRVoiceEndTone(bool enable) { setBit(Offset::dmrVoiceEndToneEnabled(), enable); } bool DR1801UVCodeplug::SettingsElement::fmVoiceEndToneEnabled() const { return getBit(Offset::fmVoiceEndToneEnabled()); } void DR1801UVCodeplug::SettingsElement::enableFMVoiceEndTone(bool enable) { setBit(Offset::fmVoiceEndToneEnabled(), enable); } bool DR1801UVCodeplug::SettingsElement::dmrCallEndToneEnabled() const { return getBit(Offset::dmrCallEndToneEnabled()); } void DR1801UVCodeplug::SettingsElement::enableDMRCallEndTone(bool enable) { setBit(Offset::dmrCallEndToneEnabled(), enable); } bool DR1801UVCodeplug::SettingsElement::messageToneEnabled() const { return getBit(Offset::messageToneEnabled()); } void DR1801UVCodeplug::SettingsElement::enableMessageTone(bool enable) { setBit(Offset::messageToneEnabled(), enable); } DR1801UVCodeplug::SettingsElement::RingTone DR1801UVCodeplug::SettingsElement::ringTone() const { return (RingTone) getUInt8(Offset::ringTone()); } void DR1801UVCodeplug::SettingsElement::setRingTone(RingTone tone) { setUInt8(Offset::ringTone(), (uint8_t)tone); } QString DR1801UVCodeplug::SettingsElement::radioName() const { return readASCII(Offset::radioName(), Limit::radioNameLength(), 0x00); } void DR1801UVCodeplug::SettingsElement::setRadioName(const QString &name) { writeASCII(Offset::radioName(), name, Limit::radioNameLength(), 0x00); } float DR1801UVCodeplug::SettingsElement::reverseBurstFrequency() const { return float(getUInt16_le(Offset::reverseBurstFrequency()))/10; } void DR1801UVCodeplug::SettingsElement::setReverseBurstFrequency(float Hz) { setUInt16_le(Offset::reverseBurstFrequency(), Hz*10); } DR1801UVCodeplug::SettingsElement::BacklightTime DR1801UVCodeplug::SettingsElement::backlightTime() const { return (BacklightTime) getUInt8(Offset::backlightTime()); } void DR1801UVCodeplug::SettingsElement::setBacklightTime(BacklightTime time) { setUInt8(Offset::backlightTime(), (uint8_t)time); } bool DR1801UVCodeplug::SettingsElement::campandingEnabled() const { return 0x01 == getUInt8(Offset::campandingEnabled()); } void DR1801UVCodeplug::SettingsElement::enableCampanding(bool enable) { setUInt8(Offset::campandingEnabled(), enable ? 0x01 : 0x00); } DR1801UVCodeplug::SettingsElement::TuningMode DR1801UVCodeplug::SettingsElement::tuningModeUp() const { return (TuningMode) getUInt8(Offset::tuningModeUp()); } void DR1801UVCodeplug::SettingsElement::setTuningModeUp(TuningMode mode) { setUInt8(Offset::tuningModeUp(), (uint8_t)mode); } DR1801UVCodeplug::SettingsElement::TuningMode DR1801UVCodeplug::SettingsElement::tuningModeDown() const { return (TuningMode) getUInt8(Offset::tunigModeDown()); } void DR1801UVCodeplug::SettingsElement::setTuningModeDown(TuningMode mode) { setUInt8(Offset::tunigModeDown(), (uint8_t)mode); } DR1801UVCodeplug::SettingsElement::DisplayMode DR1801UVCodeplug::SettingsElement::displayMode() const { return (DisplayMode)getUInt8(Offset::displayMode()); } void DR1801UVCodeplug::SettingsElement::setDisplayMode(DisplayMode mode) { setUInt8(Offset::displayMode(), (uint8_t)mode); } DR1801UVCodeplug::SettingsElement::DualWatchMode DR1801UVCodeplug::SettingsElement::dualWatchMode() const { return (DualWatchMode) getUInt8(Offset::dualWatchMode()); } void DR1801UVCodeplug::SettingsElement::setDualWatchMode(DualWatchMode mode) { setUInt8(Offset::dualWatchMode(), (uint8_t)mode); } DR1801UVCodeplug::SettingsElement::ScanMode DR1801UVCodeplug::SettingsElement::scanMode() const { return (ScanMode) getUInt8(Offset::scanMode()); } void DR1801UVCodeplug::SettingsElement::setScanMode(ScanMode mode) { setUInt8(Offset::scanMode(), (uint8_t)mode); } BootSettings::BootDisplay DR1801UVCodeplug::SettingsElement::bootScreen() const { switch ((BootScreen) getUInt8(Offset::bootScreen())) { case BootScreen::Text: return BootSettings::BootDisplay::Text; case BootScreen::Picture: return BootSettings::BootDisplay::Image; } return BootSettings::BootDisplay::Logo; } void DR1801UVCodeplug::SettingsElement::setBootScreen(BootSettings::BootDisplay mode) { switch (mode) { case BootSettings::BootDisplay::Text: setUInt8(Offset::bootScreen(), (uint8_t)BootScreen::Text); break; case BootSettings::BootDisplay::Logo: case BootSettings::BootDisplay::Image: setUInt8(Offset::bootScreen(), (uint8_t)BootScreen::Picture); break; } } QString DR1801UVCodeplug::SettingsElement::bootLine1() const { return readASCII(Offset::bootLine1(), Limit::bootLineLength(), 0x00); } void DR1801UVCodeplug::SettingsElement::setBootLine1(const QString &line) { writeASCII(Offset::bootLine1(), line, Limit::bootLineLength(), 0x00); } QString DR1801UVCodeplug::SettingsElement::bootLine2() const { return readASCII(Offset::bootLine2(), Limit::bootLineLength(), 0x00); } void DR1801UVCodeplug::SettingsElement::setBootLine2(const QString &line) { writeASCII(Offset::bootLine2(), line, Limit::bootLineLength(), 0x00); } bool DR1801UVCodeplug::SettingsElement::ledEnabled() const { return 0x01 == getUInt8(Offset::ledEnabled()); } void DR1801UVCodeplug::SettingsElement::enableLED(bool enabled) { setUInt8(Offset::ledEnabled(), enabled ? 0x01 : 0x00); } unsigned int DR1801UVCodeplug::SettingsElement::loneWorkerResponseTime() const { return getUInt8(Offset::loneWorkerResponseTime()); } void DR1801UVCodeplug::SettingsElement::setLoneWorkerResponseTime(unsigned int sec) { setUInt8(Offset::loneWorkerResponseTime(), sec); } unsigned int DR1801UVCodeplug::SettingsElement::loneWorkerReminderTime() const { return getUInt8(Offset::loneWorkerReminderTime()); } void DR1801UVCodeplug::SettingsElement::setLoneWorkerReminderTime(unsigned int sec) { setUInt8(Offset::loneWorkerReminderTime(), sec); } bool DR1801UVCodeplug::SettingsElement::bootPasswordEnabled() const { return getBit(Offset::bootPasswordEnabled()); } QString DR1801UVCodeplug::SettingsElement::bootPassword() const { return readASCII(Offset::bootPassword(), Limit::bootPasswordLength(), 0x00); } void DR1801UVCodeplug::SettingsElement::setBootPassword(const QString &passwd) { setBit(Offset::bootPasswordEnabled(), true); setUInt8(Offset::boolPasswordLength(), std::min(Limit::bootPasswordLength(), (unsigned int)passwd.length())); writeASCII(Offset::bootPassword(), passwd, Limit::bootPasswordLength(), 0x00); } void DR1801UVCodeplug::SettingsElement::clearBootPassword() { setBit(Offset::bootPasswordEnabled(), false); setUInt8(Offset::boolPasswordLength(), 0); writeASCII(Offset::bootPassword(), "", Limit::bootPasswordLength(), 0x00); } bool DR1801UVCodeplug::SettingsElement::progPasswordEnabled() const { return getBit(Offset::progPasswordEnabled()); } QString DR1801UVCodeplug::SettingsElement::progPassword() const { return readASCII(Offset::progPassword(), Limit::progPasswordLength(), 0x00); } void DR1801UVCodeplug::SettingsElement::setProgPassword(const QString &passwd) { setBit(Offset::progPasswordEnabled(), true); setUInt8(Offset::progPasswordLength(), std::min(Limit::progPasswordLength(), (unsigned int)passwd.length())); writeASCII(Offset::progPassword(), passwd, Limit::progPasswordLength(), 0x00); } void DR1801UVCodeplug::SettingsElement::clearProgPassword() { setBit(Offset::progPasswordEnabled(), false); setUInt8(Offset::progPasswordLength(), 0); writeASCII(Offset::progPassword(), "", Limit::progPasswordLength(), 0x00); } bool DR1801UVCodeplug::SettingsElement::decode(Config *config, const ErrorStack &err) { Q_UNUSED(err); // Store radio ID auto idx = config->radioIDs()->add(new DMRRadioID(radioName(), dmrID())); config->settings()->setDefaultId(config->radioIDs()->get(idx)->as()); // Handle VOX settings. config->settings()->audio()->setVox(voxSensitivity()); config->settings()->audio()->setVOXDelay(voxDelay()); // Handle tone settings config->settings()->tone()->enableRingtone(RingTone::Off != ringTone()); config->settings()->tone()->enableSMSTone(messageToneEnabled()); // Handle boot settings config->settings()->boot()->setBootDisplay(bootScreen()); config->settings()->boot()->setMessage1(bootLine1()); config->settings()->boot()->setMessage2(bootLine2()); config->settings()->boot()->enableBootPassword(bootPasswordEnabled()); config->settings()->boot()->setBootPassword(bootPassword()); return true; } bool DR1801UVCodeplug::SettingsElement::encode(Config *config, const ErrorStack &err) { // Store radio ID DMRRadioID *id = config->settings()->defaultId(); if (nullptr == id) { errMsg(err) << "Cannot encode radio ID and name. No default DMR radio ID."; return false; } setRadioName(id->name()); setDMRID(id->number()); setVOXSensitivity(config->settings()->audio()->vox()); setVOXDelay(config->settings()->audio()->voxDelay()); // Handle tone settings setRingTone(config->settings()->tone()->ringtoneEnabled() ? RingTone::RingTone1 : RingTone::Off); enableMessageTone(config->settings()->tone()->smsToneEnabled()); // Encode boot settings setBootScreen(config->settings()->boot()->bootDisplay()); setBootLine1(config->settings()->boot()->message1()); setBootLine2(config->settings()->boot()->message2()); if (config->settings()->boot()->bootPasswordEnabled()) setBootPassword(config->settings()->boot()->bootPassword()); else clearBootPassword(); return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::ScanListBankElement * ******************************************************************************************** */ DR1801UVCodeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr) : Element(ptr, ScanListBankElement::size()) { // pass... } void DR1801UVCodeplug::ScanListBankElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::ScanListBankElement::scanListCount() const { return getUInt8(Offset::scanListCount()); } void DR1801UVCodeplug::ScanListBankElement::setScanListCount(unsigned int count) { count = std::min(Limit::scanListCount(), count); setUInt8(Offset::scanListCount(), count); } DR1801UVCodeplug::ScanListElement DR1801UVCodeplug::ScanListBankElement::scanList(unsigned int index) const { return ScanListElement(_data + Offset::scanLists() + index*ScanListElement::size()); } bool DR1801UVCodeplug::ScanListBankElement::decode(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0, j=0; iscanlists()->add(obj); } return true; } bool DR1801UVCodeplug::ScanListBankElement::link(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0,j=0; i(sl.index())) { errMsg(err) << "Cannot link scan list at index " << i << ". Scan list not defined."; return false; } if (! sl.linkScanListObj(ctx.get(sl.index()), ctx, err)) { errMsg(err) << "Cannot link scan list at index " << i << "."; return false; } } return true; } bool DR1801UVCodeplug::ScanListBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int n = std::min(Limit::scanListCount(), ctx.count()); setScanListCount(n); for (unsigned int i=0; iscanList(i); if (! scan.encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode scan list '" << ctx.get(i)->name() << " at index " << i << "."; return false; } scan.setIndex(i); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::ScanListElement * ******************************************************************************************** */ DR1801UVCodeplug::ScanListElement::ScanListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::ScanListElement::ScanListElement(uint8_t *ptr) : Element(ptr, ScanListElement::size()) { // pass... } void DR1801UVCodeplug::ScanListElement::clear() { memset(_data, 0, _size); } bool DR1801UVCodeplug::ScanListElement::isValid() const { return 0 != getUInt8(Offset::index()); // Check index } unsigned int DR1801UVCodeplug::ScanListElement::index() const { return getUInt8(Offset::index())-1; } void DR1801UVCodeplug::ScanListElement::setIndex(unsigned int idx) { setUInt8(Offset::index(), idx+1); } unsigned int DR1801UVCodeplug::ScanListElement::entryCount() const { return getUInt8(Offset::memberCount()); } void DR1801UVCodeplug::ScanListElement::setEntryCount(unsigned int num) { setUInt8(Offset::memberCount(), num); } DR1801UVCodeplug::ScanListElement::PriorityChannel DR1801UVCodeplug::ScanListElement::priorityChannel1() const { return (PriorityChannel) getUInt8(Offset::priorityChannel1()); } void DR1801UVCodeplug::ScanListElement::setPriorityChannel1(PriorityChannel mode) { setUInt8(Offset::priorityChannel1(), (uint8_t) mode); } unsigned int DR1801UVCodeplug::ScanListElement::priorityChannel1Index() const { return getUInt16_le(Offset::priorityChannel1Index()); } void DR1801UVCodeplug::ScanListElement::setPriorityChannel1Index(unsigned int index) { setUInt16_le(Offset::priorityChannel1Index(), index); } DR1801UVCodeplug::ScanListElement::PriorityChannel DR1801UVCodeplug::ScanListElement::priorityChannel2() const { return (PriorityChannel) getUInt8(Offset::priorityChannel2()); } void DR1801UVCodeplug::ScanListElement::setPriorityChannel2(PriorityChannel mode) { setUInt8(Offset::priorityChannel2(), (uint8_t) mode); } unsigned int DR1801UVCodeplug::ScanListElement::priorityChannel2Index() const { return getUInt16_le(Offset::priorityChannel2Index()); } void DR1801UVCodeplug::ScanListElement::setPriorityChannel2Index(unsigned int index) { setUInt16_le(Offset::priorityChannel2Index(), index); } DR1801UVCodeplug::ScanListElement::RevertChannel DR1801UVCodeplug::ScanListElement::revertChannel() const { return (RevertChannel) getUInt8(Offset::revertChannel()); } void DR1801UVCodeplug::ScanListElement::setRevertChannel(RevertChannel mode) { setUInt8(Offset::revertChannel(), (uint8_t) mode); } unsigned int DR1801UVCodeplug::ScanListElement::revertChannelIndex() const { return getUInt16_le(Offset::revertChannelIndex()); } void DR1801UVCodeplug::ScanListElement::setRevertChannelIndex(unsigned int index) { setUInt16_le(Offset::revertChannelIndex(), index); } QString DR1801UVCodeplug::ScanListElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0x00); } void DR1801UVCodeplug::ScanListElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0x00); } unsigned int DR1801UVCodeplug::ScanListElement::entryIndex(unsigned int n) { return getUInt16_le(Offset::memberIndices() + 2*n); } void DR1801UVCodeplug::ScanListElement::setEntryIndex(unsigned int n, unsigned int index) { setUInt16_le(Offset::memberIndices()+2*n, index); } ScanList * DR1801UVCodeplug::ScanListElement::toScanListObj(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) return new ScanList(name()); } bool DR1801UVCodeplug::ScanListElement::linkScanListObj(ScanList *obj, Context &ctx, const ErrorStack &err) { // Link priority channels. switch (priorityChannel1()) { case PriorityChannel::Selected: obj->setPrimaryChannel(SelectedChannel::get()); break; case PriorityChannel::Fixed: if (! ctx.has(priorityChannel1Index())) { errMsg(err) << "Cannot link to priority channel 1: Channel with index " << priorityChannel1Index() << " not defined."; return false; } obj->setPrimaryChannel(ctx.get(priorityChannel1Index())); break; case PriorityChannel::None: break; } switch (priorityChannel2()) { case PriorityChannel::Selected: obj->setSecondaryChannel(SelectedChannel::get()); break; case PriorityChannel::Fixed: if (! ctx.has(priorityChannel2Index())) { errMsg(err) << "Cannot link to priority channel 2: Channel with index " << priorityChannel2Index() << " not defined."; return false; } obj->setSecondaryChannel(ctx.get(priorityChannel2Index())); break; case PriorityChannel::None: break; } for (unsigned int i=0; i(entryIndex(i))) { errMsg(err) << "Cannot link scan-list entry " << i << ": Channel with index " << entryIndex(i) << " not defined."; return false; } obj->addChannel(ctx.get(entryIndex(i))); } return true; } bool DR1801UVCodeplug::ScanListElement::encode(ScanList *obj, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setName(obj->name()); if (nullptr == obj->primaryChannel()) setPriorityChannel1(PriorityChannel::None); else if (obj->primaryChannel() && (SelectedChannel::get() == obj->primaryChannel())) { setPriorityChannel1(PriorityChannel::Selected); } else { setPriorityChannel1(PriorityChannel::Fixed); setPriorityChannel1Index(ctx.index(obj->primaryChannel())); } if (nullptr == obj->secondaryChannel()) setPriorityChannel2(PriorityChannel::None); else if (obj->secondaryChannel() && (SelectedChannel::get() == obj->secondaryChannel())) { setPriorityChannel2(PriorityChannel::Selected); } else { setPriorityChannel2(PriorityChannel::Fixed); setPriorityChannel2Index(ctx.index(obj->secondaryChannel())); } if (nullptr == obj->revertChannel()) setRevertChannel(RevertChannel::LastActive); else if (obj->revertChannel() && (SelectedChannel::get() == obj->revertChannel())) { setRevertChannel(RevertChannel::Selected); } else { setRevertChannel(RevertChannel::Fixed); setRevertChannelIndex(ctx.index(obj->revertChannel())); } unsigned int n = std::min(Limit::memberCount(), (unsigned int)obj->count()); setEntryCount(n); for (unsigned int i=0; ichannel(i))); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::MessageBankElement * ******************************************************************************************** */ DR1801UVCodeplug::MessageBankElement::MessageBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::MessageBankElement::MessageBankElement(uint8_t *ptr) : Element(ptr, MessageBankElement::size()) { // pass... } void DR1801UVCodeplug::MessageBankElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::MessageBankElement::messageCount() const { return getUInt8(Offset::messageCount()); } void DR1801UVCodeplug::MessageBankElement::setMessageCount(unsigned int count) { setUInt8(Offset::messageCount(), count); } DR1801UVCodeplug::MessageElement DR1801UVCodeplug::MessageBankElement::message(unsigned int n) const { return MessageElement(_data + Offset::messages() + n*MessageElement::size()); } bool DR1801UVCodeplug::MessageBankElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(err); ctx.config()->smsExtension()->smsTemplates()->clear(); for (unsigned int i=0,j=0; isetName(QString("Message %1").arg(msg.index())); sms->setMessage(msg.text()); ctx.config()->smsExtension()->smsTemplates()->add(sms); } return true; } bool DR1801UVCodeplug::MessageBankElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setMessageCount(0); SMSExtension *smsExt = ctx.config()->smsExtension(); unsigned int count = std::min(Limit::messageCount(), (unsigned int)smsExt->smsTemplates()->count()); for (unsigned int i=0; ismsTemplates()->message(i)->message()); msg.setIndex(i+1); } setMessageCount(count); return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::MessageElement * ******************************************************************************************** */ DR1801UVCodeplug::MessageElement::MessageElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::MessageElement::MessageElement(uint8_t *ptr) : Element(ptr, MessageElement::size()) { // pass... } void DR1801UVCodeplug::MessageElement::clear() { memset(_data, 0, _size); } bool DR1801UVCodeplug::MessageElement::isValid() const { return 0x00 != getUInt8(Offset::index()); } unsigned int DR1801UVCodeplug::MessageElement::index() const { return getUInt8(Offset::index())-1; } void DR1801UVCodeplug::MessageElement::setIndex(unsigned int index) { setUInt8(Offset::index(), index+1); } QString DR1801UVCodeplug::MessageElement::text() const { return readASCII(Offset::text(), Limit::textLength(), 0x00); } void DR1801UVCodeplug::MessageElement::setText(const QString &text) { setUInt8(Offset::textLength(), std::min(Limit::textLength(), (unsigned int)text.length())); writeASCII(Offset::text(), text, Limit::textLength(), 0x00); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::KeySettingsElement * ******************************************************************************************** */ DR1801UVCodeplug::KeySettingsElement::KeySettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::KeySettingsElement::KeySettingsElement(uint8_t *ptr) : Element(ptr, KeySettingsElement::size()) { // pass... } void DR1801UVCodeplug::KeySettingsElement::clear() { memset(_data, 0, _size); } DR1801UVCodeplug::KeySettingsElement::Function DR1801UVCodeplug::KeySettingsElement::sideKey1Short() const { return (Function) getUInt8(Offset::sideKey1Short()); } void DR1801UVCodeplug::KeySettingsElement::setSideKey1Short(Function func) { setUInt8(Offset::sideKey1Short(), (uint8_t) func); } DR1801UVCodeplug::KeySettingsElement::Function DR1801UVCodeplug::KeySettingsElement::sideKey1Long() const { return (Function) getUInt8(Offset::sideKey1Long()); } void DR1801UVCodeplug::KeySettingsElement::setSideKey1Long(Function func) { setUInt8(Offset::sideKey1Long(), (uint8_t) func); } DR1801UVCodeplug::KeySettingsElement::Function DR1801UVCodeplug::KeySettingsElement::sideKey2Short() const { return (Function) getUInt8(Offset::sideKey2Short()); } void DR1801UVCodeplug::KeySettingsElement::setSideKey2Short(Function func) { setUInt8(Offset::sideKey2Short(), (uint8_t) func); } DR1801UVCodeplug::KeySettingsElement::Function DR1801UVCodeplug::KeySettingsElement::sideKey2Long() const { return (Function) getUInt8(Offset::sideKey2Long()); } void DR1801UVCodeplug::KeySettingsElement::setSideKey2Long(Function func) { setUInt8(Offset::sideKey2Long(), (uint8_t) func); } DR1801UVCodeplug::KeySettingsElement::Function DR1801UVCodeplug::KeySettingsElement::topKeyShort() const { return (Function) getUInt8(Offset::topKeyShort()); } void DR1801UVCodeplug::KeySettingsElement::setTopKeyShort(Function func) { setUInt8(Offset::topKeyShort(), (uint8_t) func); } DR1801UVCodeplug::KeySettingsElement::Function DR1801UVCodeplug::KeySettingsElement::topKeyLong() const { return (Function) getUInt8(Offset::topKeyLong()); } void DR1801UVCodeplug::KeySettingsElement::setTopKeyLong(Function func) { setUInt8(Offset::topKeyLong(), (uint8_t) func); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::VFOBankElement * ******************************************************************************************** */ DR1801UVCodeplug::VFOBankElement::VFOBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::VFOBankElement::VFOBankElement(uint8_t *ptr) : Element(ptr, VFOBankElement::size()) { // pass... } void DR1801UVCodeplug::VFOBankElement::clear() { memset(_data, 0, _size); } DR1801UVCodeplug::ChannelElement DR1801UVCodeplug::VFOBankElement::vfoA() const { return ChannelElement(_data + Offset::vfoA()); } DR1801UVCodeplug::ChannelElement DR1801UVCodeplug::VFOBankElement::vfoB() const { return ChannelElement(_data + Offset::vfoB()); } QString DR1801UVCodeplug::VFOBankElement::nameA() const { return readASCII(Offset::nameA(), Limit::nameLength(), 0x00); } void DR1801UVCodeplug::VFOBankElement::setNameA(const QString &name) { writeASCII(Offset::nameA(), name, Limit::nameLength(), 0x00); } QString DR1801UVCodeplug::VFOBankElement::nameB() const { return readASCII(Offset::nameB(), Limit::nameLength(), 0x00); } void DR1801UVCodeplug::VFOBankElement::setNameB(const QString &name) { writeASCII(Offset::nameB(), name, Limit::nameLength(), 0x00); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::EncryptionKeyBankElement * ******************************************************************************************** */ DR1801UVCodeplug::EncryptionKeyBankElement::EncryptionKeyBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::EncryptionKeyBankElement::EncryptionKeyBankElement(uint8_t *ptr) : Element(ptr, EncryptionKeyBankElement::size()) { // pass... } void DR1801UVCodeplug::EncryptionKeyBankElement::clear() { memset(_data, 0, _size); } DR1801UVCodeplug::EncryptionKeyElement DR1801UVCodeplug::EncryptionKeyBankElement::key(unsigned int index) const { return EncryptionKeyElement(_data + index*EncryptionKeyElement::size()); } bool DR1801UVCodeplug::EncryptionKeyBankElement::decode(Context &ctx, const ErrorStack &err) const { for (unsigned int i=0; icommercialExtension()->encryptionKeys()->add(obj); } return true; } bool DR1801UVCodeplug::EncryptionKeyBankElement::link(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); // Nothing to do return true; } bool DR1801UVCodeplug::EncryptionKeyBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int n = std::min(Limit::keyCount(), ctx.count()); for (unsigned int i=0; ikey(i); if (i>=n) { key.clear(); continue; } if (! key.encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode DMR encryption key '" << ctx.get(i)->name() << "' at index " << i << "."; return false; } key.setIndex(i); } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::EncryptionKeyElement * ******************************************************************************************** */ DR1801UVCodeplug::EncryptionKeyElement::EncryptionKeyElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::EncryptionKeyElement::EncryptionKeyElement(uint8_t *ptr) : Element(ptr, EncryptionKeyElement::size()) { // pass... } void DR1801UVCodeplug::EncryptionKeyElement::clear() { memset(_data, 0, _size); } bool DR1801UVCodeplug::EncryptionKeyElement::isValid() const { return 0x00 != getUInt8(Offset::index()); } unsigned int DR1801UVCodeplug::EncryptionKeyElement::index() const { return getUInt8(Offset::index())-1; } void DR1801UVCodeplug::EncryptionKeyElement::setIndex(unsigned int index) { setUInt8(Offset::index(), index+1); } unsigned int DR1801UVCodeplug::EncryptionKeyElement::keyLength() const { return getUInt16_le(Offset::length()); } QString DR1801UVCodeplug::EncryptionKeyElement::key() const { return readASCII(Offset::key(), Limit::keyLength(), 0x00); } void DR1801UVCodeplug::EncryptionKeyElement::setKey(const QString &key) { setUInt8(Offset::length(), 8); writeASCII(Offset::key(), key, Limit::keyLength(), 0x00); } EncryptionKey * DR1801UVCodeplug::EncryptionKeyElement::toKeyObj(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); if (! isValid()) { errMsg(err) << "Cannot decode an invalid encryption key."; return nullptr; } BasicEncryptionKey *obj = new BasicEncryptionKey(); if (! obj->fromHex(key(), err)) { errMsg(err) << "Cannot decode key '" << key() << "'."; delete obj; return nullptr; } return obj; } bool DR1801UVCodeplug::EncryptionKeyElement::linkKeyObj(EncryptionKey *obj, Context &ctx, const ErrorStack &err) { Q_UNUSED(obj); Q_UNUSED(ctx); Q_UNUSED(err); // There is nothing to do. return true; } bool DR1801UVCodeplug::EncryptionKeyElement::encode(EncryptionKey *obj, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); if (!obj->is()) { errMsg(err) << "Cannot encode AES encryption key. Not supported by the device."; return false; } BasicEncryptionKey *key = obj->as(); setKey(key->key().toHex()); return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::DTMFSettingsElement * ******************************************************************************************** */ DR1801UVCodeplug::DTMFSettingsElement::DTMFSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::DTMFSettingsElement::DTMFSettingsElement(uint8_t *ptr) : Element(ptr, DR1801UVCodeplug::DTMFSettingsElement::size()) { // pass... } void DR1801UVCodeplug::DTMFSettingsElement::clear() { memset(_data, 0, _size); } QString DR1801UVCodeplug::DTMFSettingsElement::radioID() const { return readASCII(Offset::radioID(), Limit::radioIDLength(), 0x00); } void DR1801UVCodeplug::DTMFSettingsElement::setRadioID(const QString &id) { setUInt8(Offset::radioIDLength(), std::min(Limit::radioIDLength(), (unsigned int)id.length())); writeASCII(Offset::radioID(), id, Limit::radioIDLength(), 0x00); } QString DR1801UVCodeplug::DTMFSettingsElement::killCode() const { return readASCII(Offset::killCode(), Limit::killCodeLength(), 0x00); } void DR1801UVCodeplug::DTMFSettingsElement::setKillCode(const QString &code) { setUInt8(Offset::killCodeLength(), std::min(Limit::killCodeLength(), (unsigned int)code.length())); writeASCII(Offset::killCode(), code, Limit::killCodeLength(), 0x00); } QString DR1801UVCodeplug::DTMFSettingsElement::wakeCode() const { return readASCII(Offset::wakeCode(), Limit::wakeCodeLength(), 0x00); } void DR1801UVCodeplug::DTMFSettingsElement::setWakeCode(const QString &code) { setUInt8(Offset::wakeCodeLength(), std::min(Limit::wakeCodeLength(), (unsigned int)code.length())); writeASCII(Offset::wakeCode(), code, Limit::wakeCodeLength(), 0x00); } DR1801UVCodeplug::DTMFSettingsElement::NonNumber DR1801UVCodeplug::DTMFSettingsElement::delimiter() const { return (NonNumber) getUInt8(Offset::delimiter()); } void DR1801UVCodeplug::DTMFSettingsElement::setDelimiter(NonNumber code) { setUInt8(Offset::delimiter(), (uint8_t)code); } DR1801UVCodeplug::DTMFSettingsElement::NonNumber DR1801UVCodeplug::DTMFSettingsElement::groupCode() const { return (NonNumber) getUInt8(Offset::groupCode()); } void DR1801UVCodeplug::DTMFSettingsElement::setGroupCode(NonNumber code) { setUInt8(Offset::groupCode(), (uint8_t)code); } DR1801UVCodeplug::DTMFSettingsElement::Response DR1801UVCodeplug::DTMFSettingsElement::response() const { return (Response) getUInt8(Offset::response()); } void DR1801UVCodeplug::DTMFSettingsElement::setResponse(Response resp) { setUInt8(Offset::response(), (uint8_t)resp); } unsigned int DR1801UVCodeplug::DTMFSettingsElement::autoResetTime() const { return getUInt8(Offset::autoResetTime()); } void DR1801UVCodeplug::DTMFSettingsElement::setAutoResetTime(unsigned int sec) { setUInt8(Offset::autoResetTime(), sec); } bool DR1801UVCodeplug::DTMFSettingsElement::killWakeEnabled() const { return 0x01 == getUInt8(Offset::killWake()); } void DR1801UVCodeplug::DTMFSettingsElement::enableKillWake(bool enable) { setUInt8(Offset::killWake(), enable ? 0x01 : 0x00); } DR1801UVCodeplug::DTMFSettingsElement::Kill DR1801UVCodeplug::DTMFSettingsElement::killType() const { return (Kill) getUInt8(Offset::killType()); } void DR1801UVCodeplug::DTMFSettingsElement::setKillType(Kill type) { setUInt8(Offset::killType(), (uint8_t)type); } DR1801UVCodeplug::DTMFSystemBankElement DR1801UVCodeplug::DTMFSettingsElement::dtmfSystems() const { return DTMFSystemBankElement(_data+Offset::dtmfSystems()); } DR1801UVCodeplug::DTMFIDBankElement DR1801UVCodeplug::DTMFSettingsElement::dtmfIDs() const { return DTMFIDBankElement(_data + Offset::dtmfIDs()); } DR1801UVCodeplug::PTTIDBankElement DR1801UVCodeplug::DTMFSettingsElement::pttIDs() const { return PTTIDBankElement(_data + Offset::pttIDs()); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::DTMFSystemBankElement * ******************************************************************************************** */ DR1801UVCodeplug::DTMFSystemBankElement::DTMFSystemBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::DTMFSystemBankElement::DTMFSystemBankElement(uint8_t *ptr) : Element(ptr, DTMFSystemBankElement::size()) { // pass... } void DR1801UVCodeplug::DTMFSystemBankElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::DTMFSystemBankElement::systemCount() const { return getUInt8(Offset::systemCount()); } void DR1801UVCodeplug::DTMFSystemBankElement::setSystemCount(unsigned int count) { setUInt8(Offset::systemCount(), count); } DR1801UVCodeplug::DTMFSystemElement DR1801UVCodeplug::DTMFSystemBankElement::system(unsigned int n) const { return DTMFSystemElement(_data + Offset::systems() + n*DTMFSystemElement::size()); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::DTMFSystemElement * ******************************************************************************************** */ DR1801UVCodeplug::DTMFSystemElement::DTMFSystemElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::DTMFSystemElement::DTMFSystemElement(uint8_t *ptr) : Element(ptr, DTMFSystemElement::size()) { // pass... } void DR1801UVCodeplug::DTMFSystemElement::clear() { memset(_data, 0, _size); } bool DR1801UVCodeplug::DTMFSystemElement::sideToneEnabled() const { return 0x01 == getUInt8(Offset::sideTone()); } void DR1801UVCodeplug::DTMFSystemElement::enableSideTone(bool enable) { setUInt8(Offset::sideTone(), enable ? 0x01 : 0x00); } unsigned int DR1801UVCodeplug::DTMFSystemElement::preTime() const { return getUInt16_le(Offset::preTime()); } void DR1801UVCodeplug::DTMFSystemElement::setPreTime(unsigned int ms) { setUInt16_le(Offset::preTime(), ms); } unsigned int DR1801UVCodeplug::DTMFSystemElement::codeDuration() const { return getUInt16_le(Offset::codeDuration()); } void DR1801UVCodeplug::DTMFSystemElement::setCodeDuration(unsigned int ms) { setUInt16_le(Offset::codeDuration(), ms); } unsigned int DR1801UVCodeplug::DTMFSystemElement::codeItervall() const { return getUInt16_le(Offset::codeIntervall()); } void DR1801UVCodeplug::DTMFSystemElement::setCodeItervall(unsigned int ms) { setUInt16_le(Offset::codeIntervall(), ms); } unsigned int DR1801UVCodeplug::DTMFSystemElement::resetTime() const { return getUInt16_le(Offset::resetTime()); } void DR1801UVCodeplug::DTMFSystemElement::setResetTime(unsigned int ms) { setUInt16_le(Offset::resetTime(), ms); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::DTMFIDBankElement * ******************************************************************************************** */ DR1801UVCodeplug::DTMFIDBankElement::DTMFIDBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::DTMFIDBankElement::DTMFIDBankElement(uint8_t *ptr) : Element(ptr, DTMFIDBankElement::size()) { // pass... } void DR1801UVCodeplug::DTMFIDBankElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::DTMFIDBankElement::idCount() const { return getUInt8(Offset::idCount()); } void DR1801UVCodeplug::DTMFIDBankElement::setIDCount(unsigned int n) { setUInt8(Offset::idCount(), n); } DR1801UVCodeplug::DTMFIDElement DR1801UVCodeplug::DTMFIDBankElement::id(unsigned int n) const { return DTMFIDElement(_data + Offset::ids() + n*DTMFIDElement::size()); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::DTMFIDElement * ******************************************************************************************** */ QVector DR1801UVCodeplug::DTMFIDElement::_bin_dtmf_tab = { '0','1','2','3','4','5','6','7','8','9','A','B','C','D','*','#' }; DR1801UVCodeplug::DTMFIDElement::DTMFIDElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::DTMFIDElement::DTMFIDElement(uint8_t *ptr) : Element(ptr, DR1801UVCodeplug::DTMFIDElement::size()) { // pass... } void DR1801UVCodeplug::DTMFIDElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::DTMFIDElement::numberLength() const { return getUInt8(Offset::numberLength()); } void DR1801UVCodeplug::DTMFIDElement::setNumberLength(unsigned int len) { setUInt8(Offset::numberLength(), len); } QString DR1801UVCodeplug::DTMFIDElement::number() const { QString number; number.reserve(16); for (unsigned int i=0; isettings()->dmr()->setGroupCallHangTime(callHangTime()); ctx.config()->settings()->dmr()->setPrivateCallHangTime(callHangTime()); switch(smsFormat()) { case SMSFormat::IPData: ctx.config()->smsExtension()->setFormat(SMSExtension::Format::Motorola); break; case SMSFormat::CompressedIP: ctx.config()->smsExtension()->setFormat(SMSExtension::Format::Hytera); break; case SMSFormat::DefinedData: ctx.config()->smsExtension()->setFormat(SMSExtension::Format::DMR); break; } return true; } bool DR1801UVCodeplug::DMRSettingsElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setCallHangTime(ctx.config()->settings()->dmr()->groupCallHangTime()); switch(ctx.config()->smsExtension()->format()) { case SMSExtension::Format::Motorola: setSMSFormat(SMSFormat::IPData); break; case SMSExtension::Format::Hytera: setSMSFormat(SMSFormat::CompressedIP); break; case SMSExtension::Format::DMR: setSMSFormat(SMSFormat::DefinedData); break; } return true; } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::OneTouchSettingsElement * ******************************************************************************************** */ DR1801UVCodeplug::OneTouchSettingsElement::OneTouchSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::OneTouchSettingsElement::OneTouchSettingsElement(uint8_t *ptr) : Element(ptr, OneTouchSettingsElement::size()) { // pass... } void DR1801UVCodeplug::OneTouchSettingsElement::clear() { memset(_data, 0, _size); } unsigned int DR1801UVCodeplug::OneTouchSettingsElement::settingsCount() const { return Limit::settingsCount(); } DR1801UVCodeplug::OneTouchSettingElement DR1801UVCodeplug::OneTouchSettingsElement::setting(unsigned int n) const { return OneTouchSettingElement(_data + Offset::settings() + n*OneTouchSettingElement::size()); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug::OneTouchSettingElement * ******************************************************************************************** */ DR1801UVCodeplug::OneTouchSettingElement::OneTouchSettingElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } DR1801UVCodeplug::OneTouchSettingElement::OneTouchSettingElement(uint8_t *ptr) : Element(ptr, OneTouchSettingElement::size()) { // pass... } void DR1801UVCodeplug::OneTouchSettingElement::clear() { memset(_data, 0, _size); } bool DR1801UVCodeplug::OneTouchSettingElement::isValid() const { return Element::isValid() && (Type::Disabled != type()); } bool DR1801UVCodeplug::OneTouchSettingElement::hasContact() const { return 0 != getUInt16_le(Offset::contactIndex()); } unsigned int DR1801UVCodeplug::OneTouchSettingElement::contactIndex() const { return getUInt16_le(Offset::contactIndex())-1; } void DR1801UVCodeplug::OneTouchSettingElement::setContactIndex(unsigned int index) { setUInt16_le(Offset::contactIndex(), index+1); } void DR1801UVCodeplug::OneTouchSettingElement::clearContact() { setUInt16_le(Offset::contactIndex(), 0); } DR1801UVCodeplug::OneTouchSettingElement::Action DR1801UVCodeplug::OneTouchSettingElement::action() const { return (Action) getUInt8(Offset::action()); } void DR1801UVCodeplug::OneTouchSettingElement::setAction(Action action) { setUInt8(Offset::action(), (uint8_t) action); } bool DR1801UVCodeplug::OneTouchSettingElement::hasMessage() const { return 0 != getUInt8(Offset::messageIndex()); } unsigned int DR1801UVCodeplug::OneTouchSettingElement::messageIndex() const { return getUInt8(Offset::messageIndex())-1; } void DR1801UVCodeplug::OneTouchSettingElement::setMessageIndex(unsigned int index) { setUInt8(Offset::messageIndex(), index+1); } void DR1801UVCodeplug::OneTouchSettingElement::clearMessage() { setUInt8(Offset::messageIndex(), 0); } DR1801UVCodeplug::OneTouchSettingElement::Type DR1801UVCodeplug::OneTouchSettingElement::type() const { return (Type) getUInt8(Offset::type()); } void DR1801UVCodeplug::OneTouchSettingElement::setType(Type type) { setUInt8(Offset::type(), (uint8_t)type); } bool DR1801UVCodeplug::OneTouchSettingElement::hasDTMFID() const { return 0 != getUInt8(Offset::dtmfIDIndex()); } unsigned int DR1801UVCodeplug::OneTouchSettingElement::dtmfIDIndex() const { return getUInt8(Offset::dtmfIDIndex())-1; } void DR1801UVCodeplug::OneTouchSettingElement::setDTMFIDIndex(unsigned int index) { setUInt8(Offset::dtmfIDIndex(), index+1); } void DR1801UVCodeplug::OneTouchSettingElement::clearDTMFIDIndex() { setUInt8(Offset::dtmfIDIndex(), 0); } /* ******************************************************************************************** * * Implementation of DR1801UVCodeplug * ******************************************************************************************** */ DR1801UVCodeplug::DR1801UVCodeplug(QObject *parent) : Codeplug(parent) { addImage("BTECH DR-1801UV Codeplug"); image(0).addElement(0, Offset::size()); } Config * DR1801UVCodeplug::preprocess(Config *config, const ErrorStack &err) const { Config *copy = Codeplug::preprocess(config, err); if (nullptr == copy) { errMsg(err) << "Cannot pre-process DR1801A6 codeplug."; return nullptr; } // Remove all AM & M17 channels ObjectFilterVisitor amFilter{AMChannel::staticMetaObject, M17Channel::staticMetaObject}; if (! amFilter.process(copy, err)) { errMsg(err) << "Remove AM & M17 channels."; delete copy; return nullptr; } // Split dual-zones into two. ZoneSplitVisitor splitter; if (! splitter.process(copy, err)) { errMsg(err) << "Cannot pre-process DR1801A6 codeplug."; delete copy; return nullptr; } return copy; } bool DR1801UVCodeplug::postprocess(Config *config, const ErrorStack &err) const { if (! Codeplug::postprocess(config, err)) { errMsg(err) << "Cannot post-process DR1801A6 codeplug."; return false; } // Merge split zones into one. ZoneMergeVisitor merger; if (! merger.process(config, err)) { errMsg(err) << "Cannot post-process DR1801A6 codeplug."; return false; } return true; } bool DR1801UVCodeplug::index(Config *config, Context &ctx, const ErrorStack &err) const { Q_UNUSED(err) // All indices as 0-based. That is, the first channel gets index 0 etc. // There must be a default DMR radio ID. if (nullptr == ctx.config()->settings()->defaultId()) { errMsg(err) << "No default DMR radio ID specified."; errMsg(err) << "Cannot index codeplug for encoding for the BTECH DR-1801UV."; return false; } // Map radio IDs for (int i=0; iradioIDs()->count(); i++) { if (ctx.config()->radioIDs()->get(i)->is()) ctx.add(ctx.config()->radioIDs()->get(i)->as(), i); } // Map digital and DTMF contacts for (int i=0, d=0; icontacts()->count(); i++) { if (config->contacts()->contact(i)->is()) { ctx.add(config->contacts()->contact(i)->as(), d); d++; } } // Map rx group lists for (int i=0; irxGroupLists()->count(); i++) ctx.add(config->rxGroupLists()->list(i), i); // Map channels for (int i=0; ichannelList()->count(); i++) ctx.add(config->channelList()->channel(i), i); // Map zones for (int i=0; izones()->count(); i++) ctx.add(config->zones()->zone(i), i); // Map scan lists for (int i=0; iscanlists()->count(); i++) ctx.add(config->scanlists()->scanlist(i), i); return true; } bool DR1801UVCodeplug::encode(Config *config, const Flags &flags, const ErrorStack &err) { Q_UNUSED(flags); Context ctx(config); if (! index(config, ctx, err)) { errMsg(err) << "Cannot encode codeplug."; return false; } if (! encodeElements(ctx, err)) { errMsg(err) << "Cannot encode codeplug."; return false; } return true; } bool DR1801UVCodeplug::decode(Config *config, const ErrorStack &err) { Context ctx(config); if (! decodeElements(ctx, err)) { errMsg(err) << "Cannot decode elements."; return false; } if (! linkElements(ctx, err)) { errMsg(err) << "Cannot decode elements."; return false; } return true; } bool DR1801UVCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { if (! ChannelBankElement(data(Offset::channelBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode channel elements."; return false; } if (! ContactBankElement(data(Offset::contactBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode contact elements."; return false; } if (! GroupListBankElement(data(Offset::groupListBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode group list elements."; return false; } if (! ZoneBankElement(data(Offset::zoneBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode zone elements."; return false; } if (! MessageBankElement(data(Offset::messageBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode preset messages."; return false; } if (! SettingsElement(data(Offset::settings())).decode(ctx.config(), err)) { errMsg(err) << "Cannot decode settings element."; return false; } if (! DMRSettingsElement(data(Offset::dmrSettings())).decode(ctx, err)) { errMsg(err) << "Cannot decode DMR settings element."; return false; } if (! ScanListBankElement(data(Offset::scanListBank())).decode(ctx, err)) { errMsg(err) << "Cannot decode scan list elements."; return false; } if (! EncryptionKeyBankElement(data(Offset::encryptionKeyBank())).decode(ctx, err)) { logWarn() << "Cannot decode encryption keys:\n" << err.format(" "); //errMsg(err) << "Cannot decode encryption keys."; //return false; } return true; } bool DR1801UVCodeplug::linkElements(Context &ctx, const ErrorStack &err) { if (! ChannelBankElement(data(Offset::channelBank())).link(ctx, err)) { errMsg(err) << "Cannot link channels."; return false; } if (! ContactBankElement(data(Offset::contactBank())).link(ctx, err)) { errMsg(err) << "Cannot link contacts."; return false; } if (! GroupListBankElement(data(Offset::groupListBank())).link(ctx, err)) { errMsg(err) << "Cannot link group lists."; return false; } if (! ZoneBankElement(data(Offset::zoneBank())).link(ctx, err)) { errMsg(err) << "Cannot link zones."; return false; } if (! ScanListBankElement(data(Offset::scanListBank())).link(ctx, err)) { errMsg(err) << "Cannot link scan lists."; return false; } return true; } bool DR1801UVCodeplug::encodeElements(Context &ctx, const ErrorStack &err) { if (! SettingsElement(data(Offset::settings())).encode(ctx.config(), err)) { errMsg(err) << "Cannot encode settings element."; return false; } if (! DMRSettingsElement(data(Offset::dmrSettings())).encode(ctx, err)) { errMsg(err) << "Cannot encode DMR settings element."; return false; } if (! ZoneBankElement(data(Offset::zoneBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode zones."; return false; } if (! MessageBankElement(data(Offset::messageBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode messages."; return false; } if (! ContactBankElement(data(Offset::contactBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode contacts."; return false; } if (! ScanListBankElement(data(Offset::scanListBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode scan lists."; return false; } if (! ChannelBankElement(data(Offset::channelBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode channels."; return false; } if (! GroupListBankElement(data(Offset::groupListBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode group lists."; return false; } if (! EncryptionKeyBankElement(data(Offset::encryptionKeyBank())).encode(ctx, err)) { errMsg(err) << "Cannot encode encryption keys."; return false; } return true; } ================================================ FILE: lib/dr1801uv_codeplug.hh ================================================ #ifndef DR1801UVCODEPLUG_HH #define DR1801UVCODEPLUG_HH #include "channel.hh" #include "codeplug.hh" #include "contact.hh" #include "bootsettings.hh" class Zone; /** Implements the binary codeplug representation of a BTECH DR-1801UV (a.k.a, BF-1801-A6). * * This device is kind of a re-release of the BTECH DM-1801. However, the hardware and firmware * is completely different. It is just the chassis that remained the same. This version uses the * Auctus A6 radio-on-a-chip as its main component. * * @section dr1801ver Matching firmware versions * This class implements the codeplug for the firmware version @c 1.10. The codeplug format usually * does not change much with firmware revisions, in particular not with older radios. * * @section dr1801cpl Codeplug structure within radio * The memory representation of the codeplug within the radio is simply a single large block of * data, read and written entirely. The total size of the codeplug is 01dd90h bytes. * * * * * * * * * * * * * * * * * * * * *
Start End Size Content
0x00000 0x00300 0x00300 Some unknown settings, not * configurable through the CPS. Likely some sort of calibration data. Must be conserved.
0x00300 0x003bc 0x000bc Some information about the radio. * Like serial number, firmware version, etc and timestamp.
0x003b4 0x00418 0x00064 General settings, see * @c DR1801UVCodeplug::SettingsElement.
0x00418 0x04110 0x00000 Zone bank, see * @c DR1801UVCodeplug::ZoneBankElement.
0x04110 0x04274 0x00164 Message bank, see * @c DR1801UVCodeplug::MessageBankElement.
0x04334 0x0a338 0x06008 Contact bank, see * @c DR1801UVCodeplug::ContactBankElement.
0x0a338 0x0a65c 0x00324 Scan-list bank, see * @c DR1801UVCodeplug::ScanListBankElement.
0x0a65c 0x1c660 0x12004 Channel bank, see * @c DR1801UVCodeplug::ChannelBankElement.
0x1c6c4 0x1c6dc 0x0018 Key settings, see * @c DR1801UVCodeplug::KeySettingsElement.
0x1c6dc 0x1d7e0 0x01104 Group list bank, see * @c DR1801UVCodeplug::GroupListBankElement.
0x1d7e0 0x1d858 0x00078 Encryption keys, see * @c DR1801UVCodeplug::EncryptionKeyBankElement.
0x1d858 0x1daf4 0x0029c DTMF signaling settings, see * @c DR1801UVCodeplug::DTMFSettingsElement.
0x1daf4 0x1dbb8 0x00c4 Alarm settings, see * @c DR1801UVCodeplug::AlarmSettingsBankElement.
0x1dbb8 0x1dbc8 0x0010 DMR settings, see * @c DR1801UVCodeplug::DMRSettingsElement.
0x1dbc8 0x1dbf0 0x00028 One-touch settings, see * @c DR1801UVCodeplug::OneTouchSettingsElement.
0x1dbf0 0x1dd00 0x00110 Some unknown settings.
0x1dd00 0x1dd90 0x00090 VFO channels, see * @c DR1801UVCodeplug::VFOBankElement.
* * @ingroup dr1801uv */ class DR1801UVCodeplug : public Codeplug { Q_OBJECT public: /** Implements the binary encoding of the channels settings. * * Memory representation of the channel settings (0034h bytes): * @verbinclude dr1801uv_channelelement.txt */ class ChannelElement: public Element { public: /** Possible channel types. */ enum class Type { FM = 1, DMR = 3 }; /** Possible power settings. */ enum class Power { Low = 0, High = 1 }; /** Possible values for the admid criterion. */ enum class Admit { Always = 0, ColorCode_or_Tone = 1, ChannelFree = 2 }; /** Possible time-slot values. */ enum class TimeSlot { TS1 = 1, TS2 = 2 }; /** Possible FM signaling modes. */ enum class SignalingMode { None = 0, DTMF = 1 }; /** Possible band width settings. */ enum class Bandwidth { Narrow = 1, Wide = 2 }; /** Possible subtone types. */ enum class SubToneType { None = 0, CTCSS = 1, DCS = 2 }; /** Possible DCS modes. */ enum class DCSMode { Normal = 0, Inverted = 1 }; protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructor from pointer. */ ChannelElement(uint8_t *ptr); bool isValid() const; void clear(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00034; } /** Returns the 0-based index of the channel. */ virtual unsigned int index() const; /** Sets the index. */ virtual void setIndex(unsigned int idx); /** Returns the channel type. */ virtual Type channelType() const; /** Sets the channel type. */ virtual void setChannelType(Type type); /** Returns the power settings of the channel. */ virtual Channel::Power power() const; /** Sets the power for the channel. */ virtual void setPower(Channel::Power pwr); /** Returns the RX frequency in MHz. */ virtual Frequency rxFrequency() const; /** Sets the RX frequency in MHz. */ virtual void setRXFrequency(Frequency MHz); /** Returns the TX frequency in MHz. */ virtual Frequency txFrequency() const; /** Sets the TX frequency in MHz. */ virtual void setTXFrequency(Frequency MHz); /** Returns @c true if a contact index is set. */ virtual bool hasTransmitContact() const; /** Returns the contact index. */ virtual unsigned int transmitContactIndex() const; /** Sets the transmit contact index. */ virtual void setTransmitContactIndex(unsigned int index); /** Clears the contact index. */ virtual void clearTransmitContactIndex(); /** Returns the admit criterion. */ virtual Admit admitCriterion() const; /** Sets the admit criterion. */ virtual void setAdmitCriterion(Admit admit); /** Returns the color code (0-15). */ virtual unsigned int colorCode() const; /** Sets the color code (0-15). */ virtual void setColorCode(unsigned int cc); /** Returns the time slot. */ virtual DMRChannel::TimeSlot timeSlot() const; /** Sets the time slot. */ virtual void setTimeSlot(DMRChannel::TimeSlot ts); /** Returns @c true if an ecryption key index is set. */ virtual bool hasEncryptionKey() const; /** Returns the encryption key index. */ virtual unsigned int encryptionKeyIndex() const; /** Sets the encryption key index. */ virtual void setEncryptionKeyIndex(unsigned int index); /** Clears the encryption key index. */ virtual void clearEncryptionKeyIndex(); /** Returns @c true if dual-capacity direct mode is enabled. */ virtual bool dcdm() const; /** Enables/disables dual-capacity direct mode. */ virtual void enableDCDM(bool enable); /** Returns @c true if private-call confirmation is enabled. */ virtual bool confirmPrivateCall() const; /** Enables/disables private-call confirmation. */ virtual void enablePrivateCallConfirmation(bool enable); /** Returns the FM signaling mode. */ virtual SignalingMode signalingMode() const; /** Sets the FM signaling mode. */ virtual void setSignalingMode(SignalingMode mode); /** Returns @c true if the alarm system index is set. */ virtual bool hasAlarmSystem() const; /** Returns the index of the alarm system. */ virtual unsigned int alarmSystemIndex() const; /** Sets the alarm system index. */ virtual void setAlarmSystemIndex(unsigned int index); /** Clears the alarm system index. */ virtual void clearAlarmSystemIndex(); /** Returns the band width for FM channels. */ virtual FMChannel::Bandwidth bandwidth() const; /** Sets the band width for FM channels. */ virtual void setBandwidth(FMChannel::Bandwidth bw); /** Returns @c true, if the auto-scan is enabled. */ virtual bool autoScanEnabled() const; /** Enables/disables auto-scan. */ virtual void enableAutoScan(bool enable); /** Returns @c true if a scan list is assigned. */ virtual bool hasScanList() const; /** Returns the scan list index. */ virtual unsigned int scanListIndex() const; /** Sets the scan list index. */ virtual void setScanListIndex(unsigned int index); /** Clears the scan list index. */ virtual void clearScanListIndex(); /** Returns the RX CTCSS/DCS signaling. */ virtual SelectiveCall rxTone() const; /** Sets the RX CTCSS/DCS signaling. */ virtual void setRXTone(const SelectiveCall &code); /** Returns the TX CTCSS/DCS signaling. */ virtual SelectiveCall txTone() const; /** Sets the TX CTCSS/DCS signaling. */ virtual void setTXTone(const SelectiveCall &code); /** Returns @c true if talkaround is enabled. */ virtual bool talkaround() const; /** Enables/disables talkaround. */ virtual void enableTalkaround(bool enable); /** Returns @c true if a PTT ID is set. */ virtual bool hasPTTID() const; /** Returns the PTT-ID index. */ virtual unsigned int pttIDIndex() const; /** Sets the PTT-ID index. */ virtual void setPTTIDIndex(unsigned int idx); /** Clears the PTT ID. */ virtual void clearPTTID(); /** Returns @c true if a group list is assigned. */ virtual bool hasGroupList() const; /** Returns the group-list index. */ virtual unsigned int groupListIndex() const; /** Sets the group-list index. */ virtual void setGroupListIndex(unsigned int index); /** Clears the group list index. */ virtual void clearGroupListIndex(); /** Returns @c true if lone-worker is enabled. */ virtual bool loneWorker() const; /** Enables/disables lone-worker. */ virtual void enableLoneWorker(bool enable); /** Constructs a channel object. */ virtual Channel *toChannelObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links the channel object. */ virtual bool linkChannelObj(Channel *channel, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes the given channel. */ virtual bool encode(Channel *channel, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Some offsets within the codeplug. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int index() { return 0x0000; } static constexpr unsigned int channelType() { return 0x0002; } static constexpr unsigned int power() { return 0x0003; } static constexpr unsigned int rxFrequency() { return 0x0004; } static constexpr unsigned int txFrequency() { return 0x0008; } static constexpr unsigned int transmitContactIndex() { return 0x000c; } static constexpr unsigned int admitCriterion() { return 0x000e; } static constexpr unsigned int colorCode() { return 0x0010; } static constexpr unsigned int timeSlot() { return 0x0011; } static constexpr unsigned int encryptionKeyIndex() { return 0x0014; } static constexpr Bit dcdm() { return {0x0015, 1} ; } static constexpr Bit confirmPivateCall() { return {0x0015, 0}; } static constexpr unsigned int signalingMode() { return 0x0016; } static constexpr unsigned int alarmSystemIndex() { return 0x0018; } static constexpr unsigned int bandwidth() { return 0x0019; } static constexpr unsigned int autoScan() { return 0x001a; } static constexpr unsigned int scanListIndex() { return 0x001b; } static constexpr unsigned int rxSubtoneCode() { return 0x001c; } static constexpr unsigned int rxSubtoneType() { return 0x001e; } static constexpr unsigned int rxDCSMode() { return 0x001f; } static constexpr unsigned int txSubtoneCode() { return 0x0020; } static constexpr unsigned int txSubtoneType() { return 0x0022; } static constexpr unsigned int txDCSMode() { return 0x0023; } static constexpr unsigned int talkaround() { return 0x0025; } static constexpr unsigned int pttIDIndex() { return 0x0028; } static constexpr unsigned int groupListIndex() { return 0x002a; } static constexpr unsigned int loneWorker() { return 0x002f; } /// @endcond }; }; /** Implements the binary encoding of the channel bank. * * Holds up to 1024 @c DR1801UVCodeplug::ChannelElement. * * Memory representation of the channel bank (12004h bytes): * @verbinclude dr1801uv_channelbankelement.txt */ class ChannelBankElement: public Element { protected: /** Hidden constructor. */ ChannelBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ChannelBankElement(uint8_t *ptr); void clear(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x12004; } /** Returns the number of channels. */ virtual unsigned int channelCount() const; /** Sets the number of channels. */ virtual void setChannelCount(unsigned int count); /** Returns a reference to the channel element that the given index. */ virtual ChannelElement channel(unsigned int index) const; /** Returns the name of the channel at the given index. */ virtual QString channelName(unsigned int index) const; /** Sets the name of the channel at the given index. */ virtual void setChannelName(unsigned int index, const QString &name); /** Decodes all defined channels. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links channels. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes all channels. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Limits of some elements. */ struct Limit { /** Returns the maximum number of channels. */ static constexpr unsigned int channelCount() { return 1024; } /** Returns the maximum length of a channel name. */ static constexpr unsigned int channelNameLength() { return 0x00014; } }; protected: /** Offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int channelCount() { return 0x00000; } static constexpr unsigned int channel() { return 0x00004; } static constexpr unsigned int channelName() { return 0x0d004; } /// @endcond }; }; /** Implements the binary encoding of a contact. * * Memory representation of contact (0018h bytes): * @verbinclude dr1801uv_contactelement.txt */ class ContactElement: public Element { public: /** Possible call types. */ enum class CallType { AllCall = 0x20, PrivateCall = 0x40, GroupCall = 0x80 }; protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactElement(uint8_t *ptr); bool isValid() const; void clear(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00018; } /** Returns @c true if the contact has a successor. */ virtual bool hasSuccessor() const; /** Returns the index of the next element. */ virtual uint16_t successorIndex() const; /** Sets successor index. */ virtual void setSuccessorIndex(uint16_t index); /** Clears the successor index. */ virtual void clearSuccessorIndex(); /** Returns the number. */ virtual uint32_t dmrID() const; /** Sets the number. */ virtual void setDMRID(uint32_t id); /** Returns the call type. */ virtual DMRContact::Type type() const; /** Sets the call type. */ virtual void setCallType(DMRContact::Type type); /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString &name); /** Constructs a DMR contact object from this contact elmeent. */ virtual DMRContact *toContactObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links the DMR contact object. */ virtual bool linkContactObj(DMRContact *contact, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the contact. */ virtual bool encode(DMRContact *contact, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** Maximum length of the contact name. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Defines offsets within the element. */ struct Offset : public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int successorIndex() { return 0x0000; } static constexpr unsigned int nameLength() { return 0x0002; } static constexpr unsigned int dmrID() { return 0x0004; } static constexpr unsigned int callType() { return 0x0007; } static constexpr unsigned int name() { return 0x0008; } /// @endcond }; }; /** Implements the binary encoding of the contact bank. * * The bank holds the list of all contacts defined. See @c DR1801UVCodeplug::ContactElement for * details. * * Memory representation of the contact bank (6008h bytes): * @verbinclude dr1801uv_contactbankelement.txt */ class ContactBankElement: public Element { protected: /** Hidden constructor. */ ContactBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactBankElement(uint8_t *ptr); void clear(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x06004; } /** Returns the number of contacts. */ virtual unsigned int contactCount() const; /** Sets the number of contacts. */ virtual void setContactCount(unsigned int count); /** Returns the index of the first contact. */ virtual unsigned int firstIndex() const; /** Sets the index of the first element. */ virtual void setFirstIndex(unsigned int index); /** Returns a reference to the n-th contact. */ virtual ContactElement contact(unsigned int index) const; /** Decodes all contacts and stores them into the given context and config. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links all contacts. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes all contacts. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some Limits.*/ struct Limit { /** The maximum number of contacts. */ static constexpr unsigned int contactCount() { return 1024; } }; protected: /** Offsets within the element. */ struct Offset : public Element::Offset{ /// @cond DO_NOT_DOCUMENT static constexpr unsigned int contactCount() { return 0x0000; } static constexpr unsigned int firstIndex() { return 0x0002; } static constexpr unsigned int contacts() { return 0x0004; } /// @endcond }; }; /** Implements the binary encoding of a group list. * * Memory representation of group list (44h bytes): * @verbinclude dr1801uv_grouplistelement.txt */ class GroupListElement: public Element { protected: /** Hidden constructor. */ GroupListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListElement(uint8_t *ptr); bool isValid() const; void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x00044; } /** Returns the index of the group list. */ virtual unsigned int index() const; /** Sets the index of the group list. */ virtual void setIndex(unsigned int index); /** Returns the number of elements in the list. */ virtual unsigned int count() const; /** Sets the number of elements in the list. */ virtual void setCount(unsigned int n); /** Returns @c true if the n-th member index is set. */ virtual bool hasMemberIndex(unsigned int n) const; /** Returns the n-th member index. */ virtual unsigned int memberIndex(unsigned int n) const; /** Sets the n-th member index. */ virtual void setMemberIndex(unsigned int n, unsigned int index); /** Clears the n-th member index. */ virtual void clearMemberIndex(unsigned int n); /** Constructs a group list object from this elmeent. */ virtual RXGroupList *toGroupListObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links the group list object. */ virtual bool linkGroupListObj(RXGroupList *list, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links the group list object. */ virtual bool encode(RXGroupList *list, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Some offset within the codeplug. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } static constexpr unsigned int index() { return 0x0002; } static constexpr unsigned int members() { return 0x0004; } /// @endcond }; /** Some limits. */ struct Limit { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int members() { return 10; } /// @endcond }; }; /** Implements the binary encoding of the group-list bank. * * Memory representation of the group-list bank (??h bytes): * @verbinclude dr1801uv_grouplistbankelement.txt */ class GroupListBankElement: public Element { protected: /** Hidden constructor. */ GroupListBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListBankElement(uint8_t *ptr); void clear(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x01104; } /** Returns the number of group lists defined. */ virtual unsigned int groupListCount() const; /** Sets the number of group lists. */ virtual void setGroupListCount(unsigned int count); /** Returns a reference to the group list at the given index. */ virtual GroupListElement groupList(unsigned int index) const; /** Decodes all group lists. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links all group lists. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes all group lists. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum number of group lists. */ static constexpr unsigned int groupListCount() { return 64; } }; protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int groupListCount() { return 0x0000; } static constexpr unsigned int groupLists() { return 0x00004; } /// @endcond }; }; /** Implements the binary encoding of a zone. * * Memory representation of zone (68h bytes): * @verbinclude dr1801uv_zoneelement.txt */ class ZoneElement: public Element { protected: /** Hidden constructor. */ ZoneElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ZoneElement(uint8_t *ptr); bool isValid() const; void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x00068; } /** Returns the name of the zone. */ virtual QString name() const; /** Sets the name of the zone. */ virtual void setName(const QString &name); /** Returns the number of entries. */ virtual unsigned int numEntries() const; /** Returns the channel index of the n-th entry. */ virtual unsigned int entryIndex(unsigned int n); /** Sets the n-th entry index. */ virtual void setEntryIndex(unsigned int n, unsigned int index); /** Returns the index of the zone. */ virtual unsigned int index() const; /** Sets the index of the zone. */ virtual void setIndex(unsigned int index); /** Constructs a zone object from this element. */ virtual Zone *toZoneObj(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the zone object. */ virtual bool linkZoneObj(Zone *obj, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the zone. */ virtual bool encode(Zone *obj, Context &ctx, const ErrorStack &err=ErrorStack()); /** Some limits. */ struct Limit { /** The maximum name length. */ static constexpr unsigned int nameLength() { return 32; } /** The maximum number of channels in the zone. */ static constexpr unsigned int memberCount() { return 32; } }; protected: /** Some offset within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int nameLength() { return 0x0020; } static constexpr unsigned int numEntries() { return 0x0022; } static constexpr unsigned int index() { return 0x0024; } static constexpr unsigned int members() { return 0x0028; } /// @endcond }; }; /** Implements the binary encoding of the zone bank. * * Memory representation of the zone bank (3cf8h bytes): * @verbinclude dr1801uv_zonebankelement.txt */ class ZoneBankElement: public Element { protected: /** Hidden constructor. */ ZoneBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ZoneBankElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x03cf8; } /** Returns the number of zones. */ virtual unsigned int zoneCount() const; /** Sets the number of zones. */ virtual void setZoneCount(unsigned int count); /** Returns the so-called up-zone index. */ virtual unsigned int upZoneIndex() const; /** Sets the so-called up-zone index. */ virtual void setUpZoneIndex(unsigned int index); /** Returns the so-called down-zone index. */ virtual unsigned int downZoneIndex() const; /** Sets the so-called down-zone index. */ virtual void setDownZoneIndex(unsigned int index); /** Returns a reference to the n-th zone. */ virtual ZoneElement zone(unsigned int index) const; /** Decodes all zones. */ virtual bool decode(Context &ctx, const ErrorStack &err = ErrorStack()) const; /** Links all zones. */ virtual bool link(Context &ctx, const ErrorStack &err = ErrorStack()) const; /** Encodes all zones. */ virtual bool encode(Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum number of zones. */ static constexpr unsigned int zoneCount() { return 150; } }; protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int zoneCount() { return 0x0000; } static constexpr unsigned int upZoneIndex() { return 0x0002; } static constexpr unsigned int downZoneIndex() { return 0x0004; } static constexpr unsigned int zones() { return 0x0008; } /// @endcond }; }; /** Implements the binary encoding of the settings element. * * Memory representation of settings element (64h bytes): * @verbinclude dr1801uv_settingselement.txt */ class SettingsElement: public Element { public: /** Possible power-save modes. */ enum class PowerSaveMode { Off = 0, Save50 = 1, Save25 = 2, Save12 = 3 }; /** Possible UI languages. */ enum class Language { SimplifiedChinese = 0, English = 1 }; /** Possible squelch modes. */ enum class SquelchMode { Normal = 0, Silent = 1 }; /** Possible ring tone variants. */ enum class RingTone { Off = 0, RingTone1 = 1, RingTone2 = 2, RingTone3 = 3, RingTone4 = 4, RingTone5 = 5, RingTone6 = 6, RingTone7 = 7, RingTone8 = 8, RingTone9 = 9, RingTone10 = 10, RingTone11 = 11, RingTone12 = 12, RingTone13 = 13, RingTone14 = 14, RingTone15 = 15, RingTone16 = 16, RingTone17 = 17, RingTone18 = 18, RingTone19 = 19, RingTone20 = 20 }; /** Possible backlight time settings. */ enum class BacklightTime { Infinite = 0, Off = 1, On5s = 2, On10s = 3 }; /** Possible tuning modes. */ enum class TuningMode { Channel = 0, VFO = 1 }; /** Possible display modes. */ enum class DisplayMode { Number = 0, Name = 1, Frequency = 2 }; /** Possible dual-watch modes. */ enum class DualWatchMode { Off = 0, DoubleDouble = 1, DoubleSingle = 2 }; /** Possible scan modes. */ enum class ScanMode { Time = 0, Carrier = 1, Search = 2 }; /** Possible boot screen modes. */ enum class BootScreen { Picture = 0, Text = 1 }; protected: /** Hidden constructor. */ SettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ SettingsElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x00064; } /** Returns the radios DMR ID. */ virtual unsigned int dmrID() const; /** Sets the radios DMR ID. */ virtual void setDMRID(unsigned int id); /** Returns the the power-save mode. */ virtual PowerSaveMode powerSaveMode() const; /** Sets the power-save mode. */ virtual void setPowerSaveMode(PowerSaveMode mode); /** Returns the VOX sensitivity [0,10]. * 0 means VOX off. */ virtual Level voxSensitivity() const; /** Sets the VOX sensitivity [0,10]. * 0 means VOX off. */ virtual void setVOXSensitivity(Level sens); /** Returns the VOX delay in ms. */ virtual Interval voxDelay() const; /** Sets the VOX delay in ms. */ virtual void setVOXDelay(Interval ms); /** Returns @c true if encryption is enabled. */ virtual bool encryptionEnabled() const; /** Enables/disables encryption globally. */ virtual void enableEncryption(bool enable); /** Returns @c true if the key-lock is enabled. */ virtual bool keyLockEnabled() const; /** Enable/disable key-lock. */ virtual void enableKeyLock(bool enable); /** Returns the key-lock delay in seconds. */ virtual unsigned int keyLockDelay() const; /** Sets the key-lock delay in seconds. */ virtual void setKeyLockDelay(unsigned int sec); /** Returns @c true if the side-key 1 gets locked too. */ virtual bool lockSideKey1() const; /** Enables/disables locking the side-key 1. */ virtual void enableLockSideKey1(bool enable); /** Returns @c true if the side-key 2 gets locked too. */ virtual bool lockSideKey2() const; /** Enables/disables locking the side-key 2. */ virtual void enableLockSideKey2(bool enable); /** Returns @c true if the PTT gets locked too. */ virtual bool lockPTT() const; /** Enables/disables locking the PTT. */ virtual void enableLockPTT(bool enable); /** Returns the UI language. */ virtual Language language() const; /** Sets the UI language. */ virtual void setLanguage(Language lang); /** Returns the squelch mode. */ virtual SquelchMode squelchMode() const; /** Sets the squelch mode. */ virtual void setSquelchMode(SquelchMode mode); /** Returns @c true, if the roger tones are enabled. */ virtual bool rogerTonesEnabled() const; /** Enables/disables roger tones. */ virtual void enableRogerTones(bool enable); /** Returns @c true if the DMR call out roger tone is enabled. */ virtual bool dmrCallOutToneEnabled() const; /** Enables/disables the DMR call out roger tone. */ virtual void enableDMRCallOutTone(bool enable); /** Returns @c true if the DMR voice end roger tone is enabled. */ virtual bool dmrVoiceEndToneEnabled() const; /** Enables/disables the DMR voice end roger tone. */ virtual void enableDMRVoiceEndTone(bool enable); /** Returns @c true if the DMR call end roger tone is enabled. */ virtual bool dmrCallEndToneEnabled() const; /** Enables/disables the DMR call end roger tone. */ virtual void enableDMRCallEndTone(bool enable); /** Returns @c true if the FM voice end roger tone is enabled. */ virtual bool fmVoiceEndToneEnabled() const; /** Enables/disables the FM voice end roger tone. */ virtual void enableFMVoiceEndTone(bool enable); /** Returns @c true if the FM call out roger tone is enabled. */ virtual bool fmCallOutToneEnabled() const; /** Enables/disables the FM call out roger tone. */ virtual void enableFMCallOutTone(bool enable); /** Returns @c true if the message tone is enabled. */ virtual bool messageToneEnabled() const; /** Enables/disables message tone. */ virtual void enableMessageTone(bool enable); /** Returns the ringtone. */ virtual RingTone ringTone() const; /** Sets the ringtone. */ virtual void setRingTone(RingTone tone); /** Returns the radio name. */ virtual QString radioName() const; /** Sets the radio name. */ virtual void setRadioName(const QString &name); /** Returns the reverse burst frequency in Hz. */ virtual float reverseBurstFrequency() const; /** Sets the reverse burst frequency in Hz. */ virtual void setReverseBurstFrequency(float Hz); /** Returns the backlight time settings. */ virtual BacklightTime backlightTime() const; /** Sets the backlight time. */ virtual void setBacklightTime(BacklightTime time); /** Returns @c true, if campanding is enabled. */ virtual bool campandingEnabled() const; /** Enables/disables campanding. */ virtual void enableCampanding(bool enable); /** Returns the tuning mode up-direction. */ virtual TuningMode tuningModeUp() const; /** Sets the tuning mode up-direction. */ virtual void setTuningModeUp(TuningMode mode); /** Returns the tuning mode down-direction. */ virtual TuningMode tuningModeDown() const; /** Sets the tuning mode down-direction. */ virtual void setTuningModeDown(TuningMode mode); /** Returns the display mode. */ virtual DisplayMode displayMode() const; /** Sets the display mode. */ virtual void setDisplayMode(DisplayMode mode); /** Returns the dual-watch mode. */ virtual DualWatchMode dualWatchMode() const; /** Sets the dual-watch mode. */ virtual void setDualWatchMode(DualWatchMode mode); /** Returns the scan mode. */ virtual ScanMode scanMode() const; /** Sets the scan mode. */ virtual void setScanMode(ScanMode mode); /** Returns the boot-screen mode. */ virtual BootSettings::BootDisplay bootScreen() const; /** Sets the boot-screen mode. */ virtual void setBootScreen(BootSettings::BootDisplay mode); /** Returns the boot-screen line 1. */ virtual QString bootLine1() const; /** Sets the boot-screen line 1. */ virtual void setBootLine1(const QString &line); /** Returns the boot-screen line 2. */ virtual QString bootLine2() const; /** Sets the boot-screen line 2. */ virtual void setBootLine2(const QString &line); /** Returns @c true if the LED is enabled. */ virtual bool ledEnabled() const; /** Enables/disables the LED. */ virtual void enableLED(bool enabled); /** Returns the lone-worker response time in seconds. */ virtual unsigned int loneWorkerResponseTime() const; /** Sets the lone-worker response time in seconds. */ virtual void setLoneWorkerResponseTime(unsigned int sec); /** Returns the lone-worker reminder time in seconds. */ virtual unsigned int loneWorkerReminderTime() const; /** Sets the lone-worker resminder time in seconds. */ virtual void setLoneWorkerReminderTime(unsigned int sec); /** Returns @c true if the boot password is enabled. */ virtual bool bootPasswordEnabled() const; /** Returns the boot password. */ virtual QString bootPassword() const; /** Sets and enables boot password. */ virtual void setBootPassword(const QString &passwd); /** Clears and disables boot password. */ virtual void clearBootPassword(); /** Returns @c true if the programming password is enabled. */ virtual bool progPasswordEnabled() const; /** Returns the programming password. */ virtual QString progPassword() const; /** Sets and enables programming password. */ virtual void setProgPassword(const QString &passwd); /** Clears and disables programming password. */ virtual void clearProgPassword(); /** Updates configuration. */ virtual bool decode(Config *config, const ErrorStack &err=ErrorStack()); /** Encode from config. */ virtual bool encode(Config *config, const ErrorStack &err=ErrorStack()); protected: /** Some offsets within the element. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int dmrID() { return 0x0000; } static constexpr unsigned int powerSaveEnabled() { return 0x0008; } static constexpr unsigned int powerSaveMode() { return 0x0009; } static constexpr unsigned int voxSensitivity() { return 0x000a; } static constexpr unsigned int voxDelay() { return 0x000c; } static constexpr unsigned int encryptionEnabled() { return 0x000d; } static constexpr unsigned int keyLockDelay() { return 0x000e; } static constexpr Bit lockPTT() { return {0x000f, 0}; } static constexpr Bit lockSideKey1() { return {0x000f, 1}; } static constexpr Bit lockSideKey2() { return {0x000f, 2}; } static constexpr unsigned int language() { return 0x0010; } static constexpr unsigned int squelchMode() { return 0x0011; } static constexpr unsigned int rogerTonesEnabled() { return 0x0013; } static constexpr unsigned int keyLockEnabled() { return 0x0017; } static constexpr unsigned int ringTone() { return 0x0016; } static constexpr unsigned int radioName() { return 0x0018; } static constexpr Bit dmrCallOutToneEnabled() { return {0x0028, 1}; } static constexpr Bit fmCallOutToneEnabled() { return {0x0028, 2}; } static constexpr Bit dmrVoiceEndToneEnabled() { return {0x0028, 3}; } static constexpr Bit fmVoiceEndToneEnabled() { return {0x0028, 4}; } static constexpr Bit dmrCallEndToneEnabled() { return {0x0028, 5}; } static constexpr Bit messageToneEnabled() { return {0x0028, 6}; } static constexpr unsigned int reverseBurstFrequency() { return 0x002c; } static constexpr unsigned int backlightTime() { return 0x002f; } static constexpr unsigned int voxEnabled() { return 0x0030; } static constexpr unsigned int campandingEnabled() { return 0x0032; } static constexpr unsigned int tuningModeUp() { return 0x0036; } static constexpr unsigned int tunigModeDown() { return 0x0037; } static constexpr unsigned int displayMode() { return 0x003c; } static constexpr unsigned int dualWatchMode() { return 0x003d; } static constexpr unsigned int scanMode() { return 0x003e; } static constexpr unsigned int bootScreen() { return 0x003f; } static constexpr unsigned int bootLine1() { return 0x0040; } static constexpr unsigned int bootLine2() { return 0x0048; } static constexpr unsigned int ledEnabled() { return 0x0050; } static constexpr unsigned int loneWorkerResponseTime() { return 0x0051; } static constexpr unsigned int loneWorkerReminderTime() { return 0x005c; } static constexpr Bit progPasswordEnabled() { return {0x0052, 0}; } static constexpr Bit bootPasswordEnabled() { return {0x0052, 1}; } static constexpr unsigned int progPasswordLength() { return 0x0053; } static constexpr unsigned int progPassword() { return 0x0054; } static constexpr unsigned int boolPasswordLength() { return 0x005d; } static constexpr unsigned int bootPassword() { return 0x005e; } /// @endcond }; public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum radio name length. */ static constexpr unsigned int radioNameLength() { return 16; } /** Maximum boot-text lines length. */ static constexpr unsigned int bootLineLength() { return 8; } /** Maximum boot password length. */ static constexpr unsigned int bootPasswordLength() { return 6; } /** Maximum programming password length. */ static constexpr unsigned int progPasswordLength() { return 6; } /** VOX sensitivity levels. */ static constexpr Range vox() { return {1, 3}; } }; }; /** Implements the binary encoding of a scan list element. * * Memory representation of a scan list element (50h bytes): * @verbinclude dr1801uv_scanlistelement.txt */ class ScanListElement: public Element { public: /** Possible priority channel modes. */ enum class PriorityChannel { None = 0, Fixed = 1, Selected = 2 }; /** Possible revert channel modes. */ enum class RevertChannel { LastActive = 0, Fixed = 1, Selected = 2 }; protected: /** Hidden constructor. */ ScanListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ScanListElement(uint8_t *ptr); bool isValid() const; void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x00050; } /** Returns the index of the scan list. */ virtual unsigned int index() const; /** Sets the index of the scan list. */ virtual void setIndex(unsigned int idx); /** Returns the number of entries. */ virtual unsigned int entryCount() const; /** Sets the number of entries. */ virtual void setEntryCount(unsigned int num); /** Returns the priority channel 1 setting. */ virtual PriorityChannel priorityChannel1() const; /** Sets the priority channel 1 setting. */ virtual void setPriorityChannel1(PriorityChannel mode); /** Returns the priority channel 1 index. */ virtual unsigned int priorityChannel1Index() const; /** Sets the priority channel 1 index. */ virtual void setPriorityChannel1Index(unsigned int index); /** Returns the priority channel 2 setting. */ virtual PriorityChannel priorityChannel2() const; /** Sets the priority channel 2 setting. */ virtual void setPriorityChannel2(PriorityChannel mode); /** Returns the priority channel 2 index. */ virtual unsigned int priorityChannel2Index() const; /** Sets the priority channel 2 index. */ virtual void setPriorityChannel2Index(unsigned int index); /** Returns the revert channel setting. */ virtual RevertChannel revertChannel() const; /** Sets the revert channel setting. */ virtual void setRevertChannel(RevertChannel mode); /** Returns the revert channel index. */ virtual unsigned int revertChannelIndex() const; /** Sets the revert channel index. */ virtual void setRevertChannelIndex(unsigned int index); /** Returns the name of the scan list. */ virtual QString name() const; /** Sets the name of the scan list. */ virtual void setName(const QString &name); /** Returns the n-th entry index. */ virtual unsigned int entryIndex(unsigned int n); /** Sets the n-th entry index. */ virtual void setEntryIndex(unsigned int n, unsigned int index); /** Constructs a scan-list object from this element. */ virtual ScanList *toScanListObj(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the scan-list object. */ virtual bool linkScanListObj(ScanList *obj, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the scan list. */ virtual bool encode(ScanList *obj, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NO_DOCUMENT static constexpr unsigned int index() { return 0x0000; } static constexpr unsigned int name() { return 0x0010; } static constexpr unsigned int priorityChannel1() { return 0x0002; } static constexpr unsigned int priorityChannel1Index() { return 0x0004; } static constexpr unsigned int priorityChannel2() { return 0x0003; } static constexpr unsigned int priorityChannel2Index() { return 0x0006; } static constexpr unsigned int revertChannel() { return 0x0008; } static constexpr unsigned int revertChannelIndex() { return 0x000a; } static constexpr unsigned int memberCount() { return 0x0001; } static constexpr unsigned int memberIndices() { return 0x0030; } /// @endcond }; public: /** Some limits. */ struct Limit { /** Maximum length of the name. */ static constexpr unsigned int nameLength() { return 32; } /** Maximum number of channels in scan list. */ static constexpr unsigned int memberCount() { return 16; } }; }; /** Implements the binary encoding of the scan-list bank. * * Holds up to 10 @c DR1801UVCodeplug::ScanListElement. * * Memory representation of the scan list bank (324h bytes): * @verbinclude dr1801uv_scanlistbankelement.txt */ class ScanListBankElement: public Element { protected: /** Hidden constructor. */ ScanListBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ScanListBankElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x00324; } /** Returns the number of scan lists. */ virtual unsigned int scanListCount() const; /** Sets the number of scan lists. */ virtual void setScanListCount(unsigned int count); /** Returns a reference to the n-th scan list. */ virtual ScanListElement scanList(unsigned int index) const; /** Decodes all scan lists. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links the scan lists. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes all scan lists. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int scanListCount() { return 0x0000; } static constexpr unsigned int scanLists() { return 0x0004; } /// @endcond }; public: /** Some limits. */ struct Limit { /** Maximum number of scan lists. */ static constexpr unsigned int scanListCount() { return 10; } }; }; /** Implements the binary representation of a single message. * * Memory representation of the message element (44h bytes): * @verbinclude dr1801uv_messageelement.txt */ class MessageElement: public Element { protected: /** Hidden constructor. */ MessageElement(uint8_t *ptr, size_t size); public: /** Constructor. */ MessageElement(uint8_t *ptr); void clear(); bool isValid() const; /** The size of the element. */ static constexpr unsigned int size() { return 0x0044; } /** Returns the index of the message. */ virtual unsigned int index() const; /** Sets the index of the message. */ virtual void setIndex(unsigned int index); /** Returns the message text. */ virtual QString text() const; /** Sets the message text. */ virtual void setText(const QString &text); protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int index() { return 0x0000; } static constexpr unsigned int textLength() { return 0x0001; } static constexpr unsigned int text() { return 0x0004; } /// @endcond }; public: /** Some limits. */ struct Limit { /** The maximum message length. */ static constexpr unsigned int textLength() { return 64; } }; }; /** Implements the binary encoding of the preset message bank element. * * The message bank contains all messages defined. See @c DR1801UVCodeplug::MessageElement for * details. * * Memory representation of the message bank element (164h bytes): * @verbinclude dr1801uv_messagebankelement.txt */ class MessageBankElement: public Element { protected: /** Hidden constructor. */ MessageBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ MessageBankElement(uint8_t *ptr); void clear(); /** Size of the element. */ static constexpr unsigned int size() { return 0x00164; } /** Returns the number of elements in the bank. */ virtual unsigned int messageCount() const; /** Sets the number of messages. */ virtual void setMessageCount(unsigned int count); /** Returns a reference to the n-th message. */ virtual MessageElement message(unsigned int n) const; /** Decodes all scan lists. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes all scan lists. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { static constexpr unsigned int messageCount() { return 8; } ///< Maximum number of messages. }; protected: /** Offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int messageCount() { return 0x0000; } static constexpr unsigned int messages() { return 0x0004; } /// @endcond }; }; /** Implements the binary encoding of the key settings. * * Memory representation of the message bank element (0018h bytes): * @verbinclude dr1801uv_keysettingselement.txt */ class KeySettingsElement: public Element { public: /** Possible key functions. */ enum class Function { None = 0, ToggleAlertTones = 1, EmergencyOn = 2, EmergencyOff = 3, TogglePower = 4, Monitor = 5, DeleteNuisance = 6, OneTouch1 = 7, OneTouch2 = 8, OneTouch3 = 9, OneTouch4 = 10, OneTouch5 = 11, ToggleTalkaround = 13, ToggleScan = 14, ToggleEncryption = 15, ToggleVOX = 16, ZoneSelect = 17, ToggleLoneWorker = 19, PhoneExit = 20 }; protected: /** Hidden constructor. */ KeySettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ KeySettingsElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x00018; } /** Returns the function for the side-key 1, short press. */ virtual Function sideKey1Short() const; /** Sets the function for the side-key 1, short press. */ virtual void setSideKey1Short(Function func); /** Returns the function for the side-key 1, long press. */ virtual Function sideKey1Long() const; /** Sets the function for the side-key 1, long press. */ virtual void setSideKey1Long(Function func); /** Returns the function for the side-key 2, short press. */ virtual Function sideKey2Short() const; /** Sets the function for the side-key 2, short press. */ virtual void setSideKey2Short(Function func); /** Returns the function for the side-key 2, long press. */ virtual Function sideKey2Long() const; /** Sets the function for the side-key 2, long press. */ virtual void setSideKey2Long(Function func); /** Returns the function for the top-key, short press. */ virtual Function topKeyShort() const; /** Sets the function for the top-key, short press. */ virtual void setTopKeyShort(Function func); /** Returns the function for the top-key, long press. */ virtual Function topKeyLong() const; /** Sets the function for the side-key, long press. */ virtual void setTopKeyLong(Function func); protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int sideKey1Short() { return 0x0001; } static constexpr unsigned int sideKey1Long() { return 0x0002; } static constexpr unsigned int sideKey2Short() { return 0x0005; } static constexpr unsigned int sideKey2Long() { return 0x0006; } static constexpr unsigned int topKeyShort() { return 0x0009; } static constexpr unsigned int topKeyLong() { return 0x000a; } /// @endcond }; }; /** Implements the binary encoding of the VFO settings. * * Memory representation of the VFO settings (0090h bytes): * @verbinclude dr1801uv_vfobankelement.txt */ class VFOBankElement: public Element { protected: /** Hidden constructor. */ VFOBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ VFOBankElement(uint8_t *ptr); void clear(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0090; } /** Returns a reference to the VFO A settings. */ virtual ChannelElement vfoA() const; /** Returns a reference to the VFO A settings. */ virtual ChannelElement vfoB() const; /** Returns the name of the first VFO. */ virtual QString nameA() const; /** Sets the name for the first VFO. */ virtual void setNameA(const QString &name); /** Returns the name of the second VFO. */ virtual QString nameB() const; /** Sets the name for the second VFO. */ virtual void setNameB(const QString &name); public: /** Some limits for the element. */ struct Limit { /** The maximum length of the VFO names (not set). */ static constexpr unsigned int nameLength() { return 20; } }; protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int vfoA() { return 0x0000; } static constexpr unsigned int vfoB() { return 0x0034; } static constexpr unsigned int nameA() { return 0x0068; } static constexpr unsigned int nameB() { return 0x007c; } /// @endcond }; }; /** Implements the binary encoding of an encryption key. * * Memory representation of the encryption key bank (000ch bytes): * @verbinclude dr1801uv_encryptionkeyelement.txt */ class EncryptionKeyElement: public Element { protected: /** Hidden constructor. */ EncryptionKeyElement(uint8_t *ptr, size_t size); public: /** Constructor. */ EncryptionKeyElement(uint8_t *ptr); void clear(); bool isValid() const; /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x000c; } /** Returns the index of the key. */ virtual unsigned int index() const; /** Sets the index of the key. */ virtual void setIndex(unsigned int index); /** Returns the length of the key. */ virtual unsigned int keyLength() const; /** Returns the key as a string. */ virtual QString key() const; /** Sets the key. */ virtual void setKey(const QString &key); /** Creates a key object for this element. */ virtual EncryptionKey *toKeyObj(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the key object. */ virtual bool linkKeyObj(EncryptionKey *obj, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the key. */ virtual bool encode(EncryptionKey *obj, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the element. */ struct Limit { /** The maximum length of the key. */ static constexpr unsigned int keyLength() { return 8; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int index() { return 0x0000; } static constexpr unsigned int length() { return 0x0001; } static constexpr unsigned int key() { return 0x0004; } /// @endcond }; }; /** Implements the binary encoding of the encryption keys. * * Memory representation of the encryption key bank (0078h bytes): * @verbinclude dr1801uv_encryptionkeybankelement.txt */ class EncryptionKeyBankElement: public Element { protected: /** Hidden constructor. */ EncryptionKeyBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ EncryptionKeyBankElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0078;} /** Returns a reference to the encryption key. */ virtual EncryptionKeyElement key(unsigned int index) const; /** Decodes the all encryption keys defined. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links all encryption keys. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes all keys. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the key bank. */ struct Limit { /** The number of keys. */ static constexpr unsigned int keyCount() { return 10; } }; }; /** Implements the DTMF system. * * Memory representation of the DTMF system (000ch bytes): * @verbinclude dr1801uv_dtmfsystemelement.txt */ class DTMFSystemElement: public Element { protected: /** Hidden constructor. */ DTMFSystemElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFSystemElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0c; } /** Returns @c true if the side-tone is enabled. */ virtual bool sideToneEnabled() const; /** Enables/disable side-tone. */ virtual void enableSideTone(bool enable); /** Returns the pre-time in milliseconds. */ virtual unsigned int preTime() const; /** Sets the pre-time in milliseconds. */ virtual void setPreTime(unsigned int ms); /** Returns the code duration in milliseconds. */ virtual unsigned int codeDuration() const; /** Sets the code duration in milliseconds. */ virtual void setCodeDuration(unsigned int ms); /** Returns the code interval in milliseconds. */ virtual unsigned int codeItervall() const; /** Sets the code interval in milliseconds. */ virtual void setCodeItervall(unsigned int ms); /** Returns the reset time in milliseconds. */ virtual unsigned int resetTime() const; /** Sets the reset time in milliseconds. */ virtual void setResetTime(unsigned int ms); protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int sideTone() { return 0x0000; } static constexpr unsigned int preTime() { return 0x0002; } static constexpr unsigned int codeDuration() { return 0x0004; } static constexpr unsigned int codeIntervall() { return 0x0006; } static constexpr unsigned int resetTime() { return 0x0008; } /// @endcond }; }; /** Implements the DTMF systems bank. Holding all defined DTMF systems * (see @c DTMFSystemElement). * * Memory representation of the DTMF system bank (0034h bytes): * @verbinclude dr1801uv_dtmfsystembankelement.txt */ class DTMFSystemBankElement: public Element { protected: /** Hidden constructor. */ DTMFSystemBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFSystemBankElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x34; } /** Returns the number of DTMF systems. */ virtual unsigned int systemCount() const; /** Sets the number of DTMF systems. */ virtual void setSystemCount(unsigned int count); /** Returns a reference to the n-th system. */ virtual DTMFSystemElement system(unsigned int n) const; public: /** Some limits. */ struct Limit { /** The maximum number of DTMF systems. */ static constexpr unsigned int systemCount() { return 4; } }; protected: /** Some offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int systemCount() { return 0x0000; } static constexpr unsigned int systems() { return 0x0004; } /// @endcond }; }; /** Implements the DTMF ID. * * Memory representation of the DTMF ID (014h bytes): * @verbinclude dr1801uv_dtmfidelement.txt */ class DTMFIDElement: public Element { protected: /** Hidden constructor. */ DTMFIDElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFIDElement(uint8_t *ptr); void clear(); /** Size of the element. */ static constexpr unsigned int size() { return 0x0014; } public: /** Returns the DTMF code/number. */ virtual QString number() const; /** Sets the DTMF code/number. */ virtual void setNumber(const QString &number); /** Some limits. */ struct Limit { /** The maximum number of digits of the number. */ static constexpr unsigned int numberLength() { return 16; } }; protected: /** Returns the length of the number. */ virtual unsigned int numberLength() const; /** Sets the number length. */ virtual void setNumberLength(unsigned int len); /** Some internal offset within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int numberLength() { return 0x0000; } static constexpr unsigned int number() { return 0x0004; } /// @endcond }; /** Translation table. */ static QVector _bin_dtmf_tab; }; /** Implements the DTMF ID bank. Holding all defined DTMF IDS * (@c see DTMFIDElement). * * Memory representation of the DTMF ID bank (0144h bytes): * @verbinclude dr1801uv_dtmfidbankelement.txt */ class DTMFIDBankElement: public Element { protected: /** Hidden constructor. */ DTMFIDBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFIDBankElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0144; } /** Returns the number of IDs encoded. */ virtual unsigned int idCount() const; /** Sets the ID count. */ virtual void setIDCount(unsigned int n); /** Returns a reference to the n-th ID. */ virtual DTMFIDElement id(unsigned int n) const; public: /** Some limits. */ struct Limit { /** The maximum number of IDs. */ static constexpr unsigned int idCount() { return 16; } }; protected: /** Some offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int idCount() { return 0x0000; } static constexpr unsigned int ids() { return 0x0004; } /// @endcond }; }; /** Implements the PTT ID. * * Memory representation of the PTT ID (014h bytes): * @verbinclude dr1801uv_pttidelement.txt */ class PTTIDElement: public Element { public: /** Possible modes of transmission. */ enum class Transmit { None = 0, Start = 1, End = 2, Both = 3 }; /** Possible ID modes. */ enum class IDMode { Forbidden = 0, Each = 1, Once = 2 }; protected: /** Hidden constructor. */ PTTIDElement(uint8_t *ptr, size_t size); public: /** Constructor. */ PTTIDElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x14; } /** Returns @c true, if the DTMF system is set. */ virtual bool hasDTMFSystem() const; /** Returns the DTMF system index. */ virtual unsigned int dtmfSystemIndex() const; /** Sets the DTMF system index. */ virtual void setDTMFSystemIndex(unsigned int index); /** Clears the DTMF system. */ virtual void clearDTMFSystem(); /** Returns the ID transmission mode. */ virtual Transmit transmitMode() const; /** Sets the ID transmission mode. */ virtual void setTransmitMode(Transmit mode); /** Returns the ID mode. */ virtual IDMode idMode() const; /** Sets the ID mode. */ virtual void setIDMode(IDMode mode); /** Returns @c true if the BOT DTMF ID is set. */ virtual bool hasBOTID() const; /** Returns the BOT DTMF ID index. */ virtual unsigned int botIDIndex() const; /** Sets the BOT DTMF ID index. */ virtual void setBOTIDIndex(unsigned int index); /** Clears the BOT DTMF ID index. */ virtual void clearBOTID(); /** Returns @c true if the EOT DTMF ID is set. */ virtual bool hasEOTID() const; /** Returns the EOT DTMF ID index. */ virtual unsigned int eotIDIndex() const; /** Sets the EOT DTMF ID index. */ virtual void setEOTIDIndex(unsigned int index); /** Clears the EOT DTMF ID index. */ virtual void clearEOTID(); protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int dtmfSystemIndex() { return 0x0000; } static constexpr unsigned int transmitMode() { return 0x0001; } static constexpr unsigned int idMode() { return 0x0002; } static constexpr unsigned int botIDIndex() { return 0x0003; } static constexpr unsigned int eotIDIndex() { return 0x0004; } /// @endcond }; }; /** Implements the encoding of the DTMF PTT IDs. Holding all PTT IDs * (see @c PTTIDElement). * * Memory representation of the PTT ID bank (0104h bytes): * @verbinclude dr1801uv_pttidbankelement.txt */ class PTTIDBankElement: public Element { protected: /** Hidden constructor. */ PTTIDBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ PTTIDBankElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0104; } /** Returns the number of PTT IDs defined. */ virtual unsigned int idCount() const; /** Set the number of PTT IDs. */ virtual void setPTTIDCount(unsigned int n); /** Returns a reference to the n-th PTT ID. */ virtual PTTIDElement pttID(unsigned int n); public: /** Some limits. */ struct Limit { /** The maximum number of PTT IDs. */ static constexpr unsigned int idCount() { return 16; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int idCount() { return 0x0000; } static constexpr unsigned int ids() { return 0x0004; } /// @endcond }; }; /** Implements the binary encoding of the DTMF signaling settings. * * Memory representation of the settings (029ch bytes): * @verbinclude dr1801uv_dtmfsettingselement.txt */ class DTMFSettingsElement: public Element { public: /** Possible DTMF non-number codes. Usually used as separator and group codes. */ enum class NonNumber { None = 0, A=0xa, B=0xb, C=0xc, D=0xd, Asterisk=0xe, Gate=0xf }; /** Possible responses. */ enum class Response { None=0, Reminder=1, Reply=2, Both=3 }; /** Possible kill actions. */ enum class Kill { DisableTX = 0, DisableRXandTX = 1 }; protected: /** Hidden constructor. */ DTMFSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFSettingsElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x029c; } /** Returns the DTMF radio ID as a string. */ virtual QString radioID() const; /** Sets the DTMF radio ID as a string. */ virtual void setRadioID(const QString &id); /** Returns the DTMF kill code as a string. */ virtual QString killCode() const; /** Sets the DTMF kill code as a string. */ virtual void setKillCode(const QString &code); /** Returns the DTMF wake code as a string. */ virtual QString wakeCode() const; /** Sets the DTMF wake code as a string. */ virtual void setWakeCode(const QString &code); /** Returns the delimiter code. */ virtual NonNumber delimiter() const; /** Sets the delimiter code. */ virtual void setDelimiter(NonNumber code); /** Returns the group code. */ virtual NonNumber groupCode() const; /** Sets the group code. */ virtual void setGroupCode(NonNumber code); /** Returns the decode response. */ virtual Response response() const; /** Sets the decode response. */ virtual void setResponse(Response resp); /** Returns the auto-reset time in seconds [5,60s]. */ virtual unsigned int autoResetTime() const; /** Set the auto-reset time in seconds. */ virtual void setAutoResetTime(unsigned int sec); /** Returns @c true if the kill/wake decoding is endabled. */ virtual bool killWakeEnabled() const; /** Enables/disables the kill/wake decoding. */ virtual void enableKillWake(bool enable); /** Returns the kill type. */ virtual Kill killType() const; /** Sets the kill type. */ virtual void setKillType(Kill type); /** Returns a reference to the DTMF systems. */ virtual DTMFSystemBankElement dtmfSystems() const; /** Returns a reference to the DTMF IDs. */ virtual DTMFIDBankElement dtmfIDs() const; /** Returns a reference to the PTT ID bank. */ virtual PTTIDBankElement pttIDs() const; public: /** Some limits. */ struct Limit { /** The maximum length of the radio ID. */ static constexpr unsigned int radioIDLength() { return 5; } /** The maximum length of the kill code. */ static constexpr unsigned int killCodeLength() { return 6; } /** The maximum length of the wake code. */ static constexpr unsigned int wakeCodeLength() { return 6; } }; protected: /** Internal offsets within the settings element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int radioID() { return 0x0000; } static constexpr unsigned int radioIDLength() { return 0x0005; } static constexpr unsigned int killCode() { return 0x0008; } static constexpr unsigned int killCodeLength() { return 0x000e; } static constexpr unsigned int wakeCode() { return 0x0010; } static constexpr unsigned int wakeCodeLength() { return 0x0016; } static constexpr unsigned int delimiter() { return 0x0018; } static constexpr unsigned int groupCode() { return 0x0019; } static constexpr unsigned int response() { return 0x001a; } static constexpr unsigned int autoResetTime() { return 0x001b; } static constexpr unsigned int killWake() { return 0x001c; } static constexpr unsigned int killType() { return 0x001d; } static constexpr unsigned int dtmfSystems() { return 0x0020; } static constexpr unsigned int dtmfIDs() { return 0x0054; } static constexpr unsigned int pttIDs() { return 0x0198; } /// @endcond }; }; /** Implements the binary encoding of the alarm system. * * Memory representation of the alarm system (0018h bytes): * @verbinclude dr1801uv_alarmsystembankelement.txt */ class AlarmSystemElement: public Element { protected: /** Hidden constructor. */ AlarmSystemElement(uint8_t *ptr, size_t size); public: /** Constructor. */ AlarmSystemElement(uint8_t *ptr); void clear(); bool isValid() const; /** The size of the alarm system element. */ static constexpr unsigned int size() { return 0x0018; } /** Returns the index of the system. */ virtual unsigned int index() const; /** Sets the index of the element. */ virtual void setIndex(unsigned int index); /** Clears the index. */ virtual void clearIndex(); /** Returns @c true if the alarm is enabled. */ virtual bool alarmEnabled() const; /** Enables/disables the alarm. */ virtual void enableAlarm(bool enable); /** Returns @c true if no alarm channel is specified. */ virtual bool noAlarmChannel() const; /** Returns @c true if the alarm channel is the current channel. */ virtual bool alarmChannelIsSelected() const; /** Returns the index of the alarm channel. */ virtual unsigned int alarmChannelIndex() const; /** Sets the alarm channel index. */ virtual void setAlarmChannelIndex(unsigned int index); /** Sets the alarm channel to the selected channel. */ virtual void setAlarmChannelSelected(); /** Clears the alarm channel. */ virtual void clearAlarmChannel(); /** Returns the name. */ virtual QString name() const; /** Sets the system name. */ virtual void setName(const QString &name); public: /** Some limits. */ struct Limit { /** The maximum name length. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int index() { return 0x0000; } static constexpr unsigned int alarmEnabled() { return 0x0001; } static constexpr unsigned int alarmChannelIndex() { return 0x0004; } static constexpr unsigned int name() { return 0x0008; } /// @endcond }; }; /** Implements the binary encoding of the alarm system bank. * * Holding all alarm systems, see @c AlarmSystemElement. * * Memory representation of the alarm system bank (00c4h bytes): * @verbinclude dr1801uv_alarmsystembankelement.txt */ class AlarmSystemBankElement: public Element { protected: /** Hidden constructor. */ AlarmSystemBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ AlarmSystemBankElement(uint8_t *ptr); void clear(); /** The size of the alarm system bank. */ static constexpr unsigned int size() { return 0x00c4; } /** Returns the number of alarm systems defined. */ virtual unsigned int alarmSystemCount() const; /** Sets the number of alarm systems. */ virtual void setAlarmSystemCount(unsigned int n); /** Returns a reference to the n-th alarm system. */ virtual AlarmSystemElement alarmSystem(unsigned int n) const; public: /** Some limits. */ struct Limit { /** The maximum number of alarm systems. */ static constexpr unsigned int alarmSystemCount() { return 8; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int alarmSystemCount() { return 0x0000; } static constexpr unsigned int alarmSystems() { return 0x0004; } /// @endcond }; }; /** Implements the binary encoding of the DMR settings. * * Memory representation of the DMR settings (0010h bytes): * @verbinclude dr1801uv_dmrsettingselement.txt */ class DMRSettingsElement: public Element { public: /** Possible SMS encodings. */ enum class SMSFormat { CompressedIP = 0, DefinedData = 1, IPData = 2 }; protected: /** Hidden constructor. */ DMRSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DMRSettingsElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0010; } /** Returns the hold-time in seconds. */ virtual Interval callHangTime() const; /** Sets the hold-time in seconds. */ virtual void setCallHangTime(const Interval &dur); /** Returns the remote-listen duration in seconds. */ virtual unsigned int remoteListen() const; /** Sets the remote-listen duration in seconds. */ virtual void setRemoteListen(unsigned int sec); /** Returns the active wait period in ms. */ virtual unsigned int activeWait() const; /** Sets the active wait period in ms. */ virtual void setActiveWait(unsigned int ms); /** Returns the active resend count. */ virtual unsigned int activeResend() const; /** Sets the active resend count. */ virtual void setActiveResend(unsigned int count); /** Returns the pre-send count. */ virtual unsigned int preSend() const; /** Sets the pre-send count. */ virtual void setPreSend(unsigned int count); /** Returns the voice head count. */ virtual unsigned int voiceHead() const; /** Sets the voice head count. */ virtual void setVoiceHead(unsigned int count); /** Returns the SMS format. */ virtual SMSFormat smsFormat() const; /** Sets the SMS format. */ virtual void setSMSFormat(SMSFormat format); /** Returns @c true if the kill encoding is enabled. */ virtual bool killEnc() const; /** Enables kill encoding. */ virtual void enableKillEnc(bool enable); /** Returns @c true if the kill decoding is enabled. */ virtual bool killDec() const; /** Enables kill decoding. */ virtual void enableKillDec(bool enable); /** Returns @c true if the active encoding is enabled. */ virtual bool activeEnc() const; /** Enables active encoding. */ virtual void enableActiveEnc(bool enable); /** Returns @c true if the active decoding is enabled. */ virtual bool activeDec() const; /** Enables active decoding. */ virtual void enableActiveDec(bool enable); /** Returns @c true if the check encoding is enabled. */ virtual bool checkEnc() const; /** Enables check encoding. */ virtual void enableCheckEnc(bool enable); /** Returns @c true if the check decoding is enabled. */ virtual bool checkDec() const; /** Enables check decoding. */ virtual void enableCheckDec(bool enable); /** Returns @c true if the call alter encoding is enabled. */ virtual bool callAlterEnc() const; /** Enables call alter encoding. */ virtual void enableCallAlterEnc(bool enable); /** Returns @c true if the call alter decoding is enabled. */ virtual bool callAlterDec() const; /** Enables call alter decoding. */ virtual void enableCallAlterDec(bool enable); /** Returns @c true if the remote monitor encoding is enabled. */ virtual bool remoteMonitorEnc() const; /** Enables remote monitor encoding. */ virtual void enableRemoteMonitorEnc(bool enable); /** Returns @c true if the remote monitor decoding is enabled. */ virtual bool remoteMonitorDec() const; /** Enables remote monitor decoding. */ virtual void enableRemoteMonitorDec(bool enable); /** Decodes the DMR settings. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes all keys. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: public Element::Limit { /** The range of hold time. */ static constexpr Range holdTime() { return {Interval::fromSeconds(1), Interval::fromSeconds(90)}; } /** The range of remote listen duration. */ static constexpr Range remoteListen() { return {10, 120}; } /** The range of active wait period. */ static constexpr Range activeWait() { return {120, 600}; } /** The range of active resend count. */ static constexpr Range activeResend() { return {1, 10}; } /** The range of pre-send count. */ static constexpr Range preSend() { return {4, 15}; } /** The range of voice head count. */ static constexpr Range voiceHead() { return {0, 20}; } }; protected: /** Internal offsets within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int holdTime() { return 0x0000; } static constexpr unsigned int remoteListen() { return 0x0001; } static constexpr unsigned int activeWait() { return 0x0002; } static constexpr unsigned int activeResend() { return 0x0003; } static constexpr unsigned int preSend() { return 0x0004; } static constexpr unsigned int killEnc() { return 0x0005; } static constexpr unsigned int activeEnc() { return 0x0006; } static constexpr unsigned int checkEnc() { return 0x0007; } static constexpr unsigned int killDec() { return 0x0008; } static constexpr unsigned int activeDec() { return 0x0009; } static constexpr unsigned int checkDec() { return 0x000a; } static constexpr unsigned int smsFormat() { return 0x000b; } static constexpr unsigned int voiceHead() { return 0x000c; } static constexpr Bit callAlterEnc() { return {0x000d, 0}; } static constexpr Bit callAlterDec() { return {0x000d, 1}; } static constexpr Bit remoteMonitorEnc() { return {0x000d, 2}; } static constexpr Bit remoteMonitorDec() { return {0x000d, 3}; } /// @endcond }; }; /** Implements the binary encoding of a one-touch setting. * * Memory representation of the one-touch setting (0008h bytes): * @verbinclude dr1801uv_onetouchsettingelement.txt */ class OneTouchSettingElement: public Element { public: /** Possible actions to perform. */ enum class Action { Call = 0, Message = 1 }; /** Possible one-touch types. */ enum class Type { Disabled = 0, DMR=1, FM=2 }; protected: /** Hidden constructor. */ OneTouchSettingElement(uint8_t *ptr, size_t size); public: /** Constructor. */ OneTouchSettingElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0008; } /** Returns @c true if the setting is enabled. */ bool isValid() const; /** Returns @c true if a contact is set. */ virtual bool hasContact() const; /** Returns the DMR contact index. */ virtual unsigned int contactIndex() const; /** Sets the contact index. */ virtual void setContactIndex(unsigned int index); /** Clears the contact. */ virtual void clearContact(); /** Returns the one-touch action. */ virtual Action action() const; /** Sets the one-touch action. */ virtual void setAction(Action action); /** Returns @c true, if a message is set. */ virtual bool hasMessage() const; /** Returns the message index. */ virtual unsigned int messageIndex() const; /** Sets the message index. */ virtual void setMessageIndex(unsigned int index); /** Clears the message. */ virtual void clearMessage(); /** Returns the type of the one-touch setting. */ virtual Type type() const; /** Sets the type of the one-touch setting. */ virtual void setType(Type type); /** Returns @c true if a DTMF ID is set. */ virtual bool hasDTMFID() const; /** Returns the DTMF ID index. */ virtual unsigned int dtmfIDIndex() const; /** Sets the DTMF ID index. */ virtual void setDTMFIDIndex(unsigned int index); /** Clears the DTMF ID index. */ virtual void clearDTMFIDIndex(); protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int contactIndex() { return 0x0000; } static constexpr unsigned int action() { return 0x0002; } static constexpr unsigned int messageIndex() { return 0x0003; } static constexpr unsigned int type() { return 0x0004; } static constexpr unsigned int dtmfIDIndex() { return 0x0005; } /// @endcond }; }; /** Implements the binary encoding of the one-touch settings. * * Holding all one-touch settings, see @c OneTouchSettingElement. * * Memory representation of the one-touch settings (0028h bytes): * @verbinclude dr1801uv_onetouchsettingselement.txt */ class OneTouchSettingsElement: public Element { protected: /** Hidden constructor. */ OneTouchSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ OneTouchSettingsElement(uint8_t *ptr); void clear(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0028; } /** Returns the number of one-touch settings. */ virtual unsigned int settingsCount() const; /** Returns a reference to the n-th one-touch setting. */ virtual OneTouchSettingElement setting(unsigned int n) const; public: /** Some limits. */ struct Limit { /** Returns the maximum number of one-touch settings. */ static constexpr unsigned int settingsCount() { return 5; } }; protected: /** Some internal offset. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int settings() { return 0x0000; } /// @endcond }; }; public: /** Default constructor. */ explicit DR1801UVCodeplug(QObject *parent = nullptr); Config * preprocess(Config *config, const ErrorStack &err) const; bool postprocess(Config *config, const ErrorStack &err) const; bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const; bool encode(Config *config, const Flags &flags, const ErrorStack &err=ErrorStack()); bool decode(Config *config, const ErrorStack &err=ErrorStack()); protected: /** Decode codeplug elements. */ virtual bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link decoded elements. */ virtual bool linkElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode all elements. */ virtual bool encodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Defines the offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int size() { return 0x1dd90; } static constexpr unsigned int settings() { return 0x003b4; } static constexpr unsigned int zoneBank() { return 0x00418; } static constexpr unsigned int messageBank() { return 0x04110; } static constexpr unsigned int contactBank() { return 0x04334; } static constexpr unsigned int scanListBank() { return 0x0a338; } static constexpr unsigned int channelBank() { return 0x0a65c; } static constexpr unsigned int keySettings() { return 0x1c6c4; } static constexpr unsigned int groupListBank() { return 0x1c6dc; } static constexpr unsigned int encryptionKeyBank() { return 0x1d7e0; } static constexpr unsigned int dtmfSettings() { return 0x1d858; } static constexpr unsigned int alarmSettings() { return 0x1daf4; } static constexpr unsigned int dmrSettings() { return 0x1dbb8; } static constexpr unsigned int vfoBank() { return 0x1dd00; } /// @endcond }; }; #endif // DR1801UVCODEPLUG_HH ================================================ FILE: lib/dr1801uv_filereader.cc ================================================ #include "dr1801uv_filereader.hh" #include #include #define SEGMENT0_ADDR 0x00000000 #define SEGMENT0_SIZE 0x0001dd90 bool DR1801UVFileReader::read(const QString &filename, DR1801UVCodeplug *codeplug, const ErrorStack &err) { // Check file properties QFileInfo info(filename); if (! info.exists()) { errMsg(err) << "Cannot open file '" << filename << "': File does not exisist."; return false; } if (0x1dd90 != info.size()) { errMsg(err) << "Cannot read codeplug file '" << filename << "': File size is not 1dd90h bytes."; return false; } // Open file QFile file(filename); if (! file.open(QFile::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; return false; } // Read file content char *ptr = (char *)codeplug->data(SEGMENT0_ADDR); size_t n = SEGMENT0_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } return true; } ================================================ FILE: lib/dr1801uv_filereader.hh ================================================ #ifndef DR1801UVFILEREADER_HH #define DR1801UVFILEREADER_HH #include "dr1801uv_codeplug.hh" /** Methods to read manufacturer codeplug files. * * The file format of the stock CPS is pretty simple. It is a one-to-one dump of the codeplug * data as written to the device. This makes the decoding of the manufacturer codeplug files very * easy. * * @ingroup dr1801uv */ class DR1801UVFileReader { public: /** Reads manufacturer codeplug file into given codeplug object. * @param filename Specifies the file to read. * @param codeplug Specifies the codeplug object to store read codeplug. * @param err On error, contains an error message. * @returns @c true on success and @c false on error. */ static bool read(const QString &filename, DR1801UVCodeplug *codeplug, const ErrorStack &err=ErrorStack()); }; #endif // GD77FILEREADER_HH ================================================ FILE: lib/dr1801uv_interface.cc ================================================ #include "dr1801uv_interface.hh" #include #include "logger.hh" #include "dr1801uv.hh" // Identifies itself as "Prolific USB-serial controller" (chip in cable). #define USB_VID 0x067b #define USB_PID 0x23a3 #define WRITE_CODEPLUG_OFFSET 0x00000304 #define WRITE_CODEPLUG_SIZE 0x0001da8c /* ********************************************************************************************* * * Implementation of DR1801UVInterface::PrepareReadRequest * ********************************************************************************************* */ DR1801UVInterface::PrepareReadRequest::PrepareReadRequest(uint32_t baudrate) : baudrate(qToBigEndian(baudrate)) { // pass... } void DR1801UVInterface::PrepareReadRequest::setBaudrate(uint32_t baudrate) { this->baudrate = qToBigEndian(baudrate); } /* ********************************************************************************************* * * Implementation of DR1801UVInterface::PrepareReadResponse * ********************************************************************************************* */ bool DR1801UVInterface::PrepareReadResponse::isSuccessful() const { return 0x01 == success; } uint32_t DR1801UVInterface::PrepareReadResponse::getSize() const { return qFromBigEndian(size); } /* ********************************************************************************************* * * Implementation of DR1801UVInterface::PrepareWriteRequest * ********************************************************************************************* */ DR1801UVInterface::PrepareWriteRequest::PrepareWriteRequest(uint32_t s, uint32_t speed, uint16_t crc=0) : _unknown0(qToBigEndian((uint16_t)0x0001)), size(qToBigEndian(s)), checksum(crc), baudRate(qToBigEndian(speed)) { // pass... } void DR1801UVInterface::PrepareWriteRequest::updateCRC(const uint8_t *data, size_t length) { uint16_t *ptr = (uint16_t*)data; for (unsigned int i=0; i progress, const ErrorStack &err) { if (! isOpen()) { errMsg(err) << "Cannot read codeplug from device: Interface not open."; return false; } if (IDLE != _state) { errMsg(err) << "Cannot read codeplug from device: Interface not in idle state. " << "State=" << _state << "."; return false; } PrepareReadResponse resp; if (! prepareReading(ReadSpeed, resp, err)) { errMsg(err) << "Cannot start reading the codeplug from " << _identifier << "."; _state = ERROR; return false; } unsigned int bytesToTransfer = resp.getSize(), total = bytesToTransfer; if (! startReading(err)) { errMsg(err) << "Cannot start reading the codeplug form " << _identifier << "."; _state = ERROR; return false; } if (codeplug.image(0).element(0).memSize() != bytesToTransfer) { errMsg(err) << "Codeplug size mismatch! Expected " << codeplug.image(0).element(0).memSize() << " radio sends " << bytesToTransfer << "."; _state = ERROR; return false; } if (progress) progress(0, total); logDebug() << "Start reading " << bytesToTransfer << "b of codeplug memory."; unsigned int offset = 0; while (bytesToTransfer) { unsigned n = std::min(256U, bytesToTransfer); if (! AuctusA6Interface::read(codeplug.image(0).data(offset), n, 2000, err)) { errMsg(err) << "Cannot read from device '" << portName() << "'."; _state = ERROR; return false; } offset += n; bytesToTransfer -= n; if (progress) progress(offset, total); } // Set the baud-rate back to 9600 logDebug() << "Reset baudrate to " << DefaultSpeed << "."; if (! this->setBaudRate(DefaultSpeed)) { errMsg(err) << "Cannot set baud-rate of serial port '" << portName() << "'."; return false; } QThread::msleep(250); _state = IDLE; return true; } bool DR1801UVInterface::writeCodeplug( const Codeplug &codeplug, std::function progress, const ErrorStack &err) { if (! isOpen()) { errMsg(err) << "Cannot write codeplug to device: Interface not open."; return false; } if (IDLE != _state) { errMsg(err) << "Cannot write codeplug to device: Interface not in idle state. " << "State=" << _state << "."; return false; } if (! enterProgrammingMode(err)) { errMsg(err) << "Cannot write codeplug."; return false; } // Compute CRC over codeplug uint16_t crc = 0; unsigned int offset = WRITE_CODEPLUG_OFFSET, bytesToTransfer = WRITE_CODEPLUG_SIZE, total = bytesToTransfer; for (unsigned int i=0; i DR1801UVInterface::detect(bool saveOnly) { // if non-save devices are allowed, search for all USB AMC-CDC devices. if (! saveOnly) return USBSerial::detect(); // otherwise, return none return QList(); } bool DR1801UVInterface::getDeviceInfo(QString &info, const ErrorStack &err) { uint8_t response[255], respLen = 255; if (! send_receive(REQUEST_INFO, nullptr, 0, response, respLen, err)) { errMsg(err) << "Cannot request device information."; _state = ERROR; return false; } // check status byte if (1 > respLen) { errMsg(err) << "Invalid response length."; errMsg(err) << "Cannot request device information."; _state = ERROR; return false; } if (0x01 != response[0]) { errMsg(err) << "Device returned no success. Expected 01h, got " << QString::number(response[0],16) << "h."; errMsg(err) << "Cannot request device information."; _state = ERROR; return false; } if (1 < respLen) { logDebug() << QByteArray((const char *)response+1, respLen-1); info = QString::fromLatin1((char *)(response+1), respLen-1); } return true; } /* ********************************************************************************************* * * Internal methods communicating with the device. * ********************************************************************************************* */ bool DR1801UVInterface::enterProgrammingMode(const ErrorStack &err) { uint8_t response[255], respLen=255; if (! send_receive(ENTER_PROGRAMMING_MODE, nullptr, 0, response, respLen, err)) { errMsg(err) << "Cannot enter programming mode."; _state = ERROR; return false; } if ((1 != respLen) || (0x01 != response[0])) { errMsg(err) << "Unexpected response from radio. Expected 01h, got " << QString::number(response[0], 16) << "h."; errMsg(err) << "Cannot enter programming mode."; return false; } return true; } bool DR1801UVInterface::checkProgrammingPassword(const ErrorStack &err) { // No argument -> no password uint8_t request[] = {0x00}, reqLen=sizeof(request), response[255], respLen=255; if (! send_receive(CHECK_PROG_PASSWORD, request, reqLen, response, respLen, err)) { errMsg(err) << "Cannot enter programming mode."; _state = ERROR; return false; } if ((1 != respLen) || (0x02 != response[0])) { errMsg(err) << "Unexpected response from radio. Expected 02h, got " << QString::number(response[0], 16) << "h."; errMsg(err) << "Cannot enter programming mode."; return false; } return true; } bool DR1801UVInterface::prepareReading(uint32_t baudrate, PrepareReadResponse &response, const ErrorStack &err) { PrepareReadRequest request(baudrate); uint8_t respSize = sizeof(response); if (! send_receive(PREPARE_CODEPLUG_READ, (uint8_t *)&request, sizeof(request), (uint8_t *)&response, respSize, err)) { errMsg(err) << "Cannot prepare reading of codeplug."; return false; } if ((sizeof(PrepareReadResponse) != respSize) || (! response.isSuccessful())) { errMsg(err) << "Prepare reading of codeplug failed."; return false; } logDebug() << "Set baudrate to " << baudrate << "."; if (! this->setBaudRate(baudrate)) { errMsg(err) << "Cannot set baud-rate of serial port '" << portName() << "'."; return false; } QThread::msleep(1000); return true; } bool DR1801UVInterface::prepareWriting(uint32_t size, uint32_t baudrate, uint16_t crc, const ErrorStack &err) { PrepareWriteRequest request(size, baudrate, crc); PrepareReadResponse response; uint8_t respSize = sizeof(request); if (! send_receive(PREPARE_CODEPLUG_WRITE, (uint8_t *)&request, sizeof(request), (uint8_t *)&response, respSize, err)) { errMsg(err) << "Cannot prepare writing of codeplug."; return false; } if ((sizeof(PrepareWriteResponse) != respSize) || (! response.isSuccessful())) { errMsg(err) << "Prepare writing of codeplug failed."; return false; } logDebug() << "Set baudrate to " << baudrate << "."; if (! this->setBaudRate(baudrate)) { errMsg(err) << "Cannot set baud-rate of serial port '" << portName() << "'."; return false; } QThread::msleep(1000); _state = WRITE_THROUGH; return true; } bool DR1801UVInterface::receiveWriteACK(const ErrorStack &err) { // Wait for device response uint16_t rcode = CODEPLUG_WRITTEN; CodeplugWriteResponse response; uint8_t size = sizeof(CodeplugWriteResponse); if (! receive(rcode, (uint8_t*) &response, size, err)) { errMsg(err) << "Cannot complete codeplug write."; return false; } if (((rcode & 0x7fff) != CODEPLUG_WRITTEN) || (! response.isSuccessful())) { errMsg(err) << "Cannot complete codeplug write."; return false; } // Set the baud-rate back to default speed logDebug() << "Reset baudrate to " << DefaultSpeed; if (! this->setBaudRate(DefaultSpeed)) { errMsg(err) << "Cannot set baud-rate of serial port '" << portName() << "'."; return false; } QThread::msleep(1000); _state = IDLE; return true; } bool DR1801UVInterface::startReading(const ErrorStack &err) { if (! send(START_READ_DATA, 0, 0, err)) { errMsg(err) << "Cannot initialize codeplug read."; return false; } flush(); _state = READ_THROUGH; return true; } ================================================ FILE: lib/dr1801uv_interface.hh ================================================ #ifndef DR1801UVINTERFACE_HH #define DR1801UVINTERFACE_HH #include "auctus_a6_interface.hh" #include // Forward declarations class Codeplug; /** Implements the actual interface to the DR-1801UV, which builds upon the @c AuctusA6Interface. * * @ingroup dr1801uv */ class DR1801UVInterface : public AuctusA6Interface { Q_OBJECT public: /** Constructs an interface to the BTECH DR-1801UV from the specifeid USB descriptor. */ DR1801UVInterface(const USBDeviceDescriptor &descriptor, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); RadioInfo identifier(const ErrorStack &err); /** Reads the codeplug from the device blocking. */ bool readCodeplug(Codeplug &codeplug, std::function progress, const ErrorStack &err=ErrorStack()); /** Writes the codeplug to the device blocking. */ bool writeCodeplug(const Codeplug &codeplug, std::function progress, const ErrorStack &err=ErrorStack()); bool read_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool read_finish(const ErrorStack &err=ErrorStack()); bool write_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool write_finish(const ErrorStack &err=ErrorStack()); public: /** Returns some information about this interface. */ static USBDeviceInfo interfaceInfo(); /** Tries to find all interfaces connected AnyTone radios. */ static QList detect(bool saveOnly=true); protected: /** Some default speeds. */ enum DefaultTransferSpeed { /** Initial speed, used to send commands. */ DefaultSpeed = QSerialPort::Baud9600, /** Speed for reading the codeplug. */ ReadSpeed = QSerialPort::Baud115200, /** Speed for writing the codeplug. */ WriteSpeed = QSerialPort::Baud9600 }; /** Implemented commands. */ enum Command { REQUEST_INFO = 0x0000, ///< Returns some information about the device. ENTER_PROGRAMMING_MODE = 0x0104, ///< Puts the device into the programming mode. CHECK_PROG_PASSWORD = 0x002b, ///< Checks the programming password. PREPARE_CODEPLUG_READ = 0x0100, ///< Sets baud rate and prepares codeplug read. START_READ_DATA = 0x0101, ///< Actually starts reading the codeplug. PREPARE_CODEPLUG_WRITE = 0x0102, ///< Prepares writing the codeplug. CODEPLUG_WRITTEN = 0x0103 ///< Send by the device once the codeplug was written. }; /** Request to set transfer speed and load codeplug into RAM for transfer. */ struct Q_PACKED PrepareReadRequest { /** The transfer speed. */ uint32_t baudrate; /** Constructor. */ PrepareReadRequest(uint32_t baudrate); /** Sets the baudrate. */ void setBaudrate(uint32_t baudrate); }; /** Response to a @c PREPARE_CODEPLUG_READ command. * Contains some information about the codeplug to read. */ struct Q_PACKED PrepareReadResponse { uint8_t success; ///< If successful, set to 0x01. uint32_t size; ///< Contains the size of the codeplug in big-endian. uint8_t _unknown[10]; ///< Some additional information. /** Returns @c true, if the operation was successful. */ bool isSuccessful() const; /** Returns the codeplug size in bytes. */ uint32_t getSize() const; }; /** Request to prepare a codeplug write. * Contains some information about the codeplug to write and transfer speed to use. */ struct Q_PACKED PrepareWriteRequest { uint16_t _unknown0; ///< Just set to 0x0001 uint32_t size; ///< Contains the size of the codeplug in big-endian. uint16_t checksum; ///< A checksum over the codeplug to be written. uint32_t baudRate; ///< The baud rate for the transfer. /** Constructor. */ PrepareWriteRequest(uint32_t size, uint32_t speed, uint16_t crc); /** Updates the crc with the data. */ void updateCRC(const uint8_t *data, size_t length); }; /** Response to a prepare-write request. Just contains a status word. */ struct Q_PACKED PrepareWriteResponse { uint16_t responseCode; ///< Response code. /** Returns @c true, if the operation was successful. */ bool isSuccessful() const; }; /** Response to a codeplug write. Just contains a status word. */ struct Q_PACKED CodeplugWriteResponse { uint8_t success; ///< Response code. uint16_t unknown; ///< Some unknown data. /** Returns @c true, if the operation was successful. */ bool isSuccessful() const; }; /** Puts the device into programming mode. */ bool enterProgrammingMode(const ErrorStack &err=ErrorStack()); /** Reads some information about the device. */ bool getDeviceInfo(QString &info, const ErrorStack &err=ErrorStack()); /** Checks the if a programming password is set. */ bool checkProgrammingPassword(const ErrorStack &err=ErrorStack()); /** Prepares reading the codeplug. */ bool prepareReading(uint32_t baudrate, PrepareReadResponse &response, const ErrorStack &err=ErrorStack()); /** Starts the read operation. Once the operation is complete, the device will close the * connection. */ bool startReading(const ErrorStack &err=ErrorStack()); /** Repares the codeplug write. */ bool prepareWriting(uint32_t size, uint32_t baudrate, uint16_t crc, const ErrorStack &err=ErrorStack()); /** Receives the ACK for writing the codeplug. */ bool receiveWriteACK(const ErrorStack &err=ErrorStack()); protected: /** Holds the device identifier, once read. */ QString _identifier; }; #endif // DR1801UVINTERFACE_HH ================================================ FILE: lib/dr1801uv_limits.cc ================================================ #include "dr1801uv_limits.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "scanlist.hh" #include "zone.hh" #include "gpssystem.hh" #include "roamingzone.hh" #include "dr1801uv_codeplug.hh" DR1801UVLimits::DR1801UVLimits(QObject *parent) : RadioLimits(true, parent) { // Define limits for call-sign DB _hasCallSignDB = false; _callSignDBImplemented = false; _numCallSignDBEntries = 0; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; add("settings", new RadioLimitItem { { "introLine1", new RadioLimitString( -1, DR1801UVCodeplug::SettingsElement::Limit::bootLineLength(), RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString( -1, DR1801UVCodeplug::SettingsElement::Limit::bootLineLength(), RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::High) } }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(DR1801UVCodeplug::SettingsElement::Limit::bootPasswordLength(), RadioLimitIssue::Critical) } } } /// @todo check default radio ID. } ); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList{ { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString( 1, DR1801UVCodeplug::SettingsElement::Limit::radioNameLength(), RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } } ); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, DR1801UVCodeplug::ContactBankElement::Limit::contactCount(), new RadioLimitObject { { "name", new RadioLimitString( 1, DR1801UVCodeplug::ContactElement::Limit::nameLength(), RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum { (unsigned) DMRContact::PrivateCall, (unsigned) DMRContact::GroupCall, (unsigned) DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, -1, -1, new RadioLimitIgnored() } } ); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, DR1801UVCodeplug::GroupListBankElement::Limit::groupListCount(), new RadioLimitObject { { "name", new RadioLimitString(1, -1, RadioLimitString::ASCII) }, // Name is not encoded. { "contacts", new RadioLimitGroupCallRefList(1, 32) } }) ); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, DR1801UVCodeplug::ChannelBankElement::Limit::channelCount(), new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString( 1, DR1801UVCodeplug::ChannelBankElement::Limit::channelNameLength(), RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored(nullptr, RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString( 1, DR1801UVCodeplug::ChannelBankElement::Limit::channelNameLength(), RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::High), }}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, true)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored()}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } ) ); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, DR1801UVCodeplug::ZoneBankElement::Limit::zoneCount(), new RadioLimitObject { { "name", new RadioLimitString( 1, DR1801UVCodeplug::ZoneElement::Limit::nameLength(), RadioLimitString::Unicode) }, { "A", new RadioLimitRefList( 0, DR1801UVCodeplug::ZoneElement::Limit::memberCount(), Channel::staticMetaObject) }, { "B", new RadioLimitRefList(0, 0, Channel::staticMetaObject) }, { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions } ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, DR1801UVCodeplug::ScanListBankElement::Limit::scanListCount(), new RadioLimitObject{ { "name", new RadioLimitString( 1, DR1801UVCodeplug::ScanListElement::Limit::nameLength(), RadioLimitString::Unicode) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList( 0, DR1801UVCodeplug::ScanListBankElement::Limit::scanListCount(), Channel::staticMetaObject) } }) ); /* Define limits for positioning systems. */ add("positioning", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); } ================================================ FILE: lib/dr1801uv_limits.hh ================================================ #ifndef DR1801UVLIMITS_HH #define DR1801UVLIMITS_HH #include "radiolimits.hh" /** Implements the configuration limits for the BTECH DR-1801UV. * @ingroup dr1801uv */ class DR1801UVLimits : public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit DR1801UVLimits(QObject *parent=nullptr); }; #endif // DR1801UV ================================================ FILE: lib/dummyfilereader.cc ================================================ #include "dummyfilereader.hh" bool DummyFileReader::read(const QString &filename, Codeplug *codeplug, const ErrorStack &err) { Q_UNUSED(codeplug); Q_UNUSED(filename); errMsg(err) << "Cannot read manufacturer codeplug file: not implemented yet."; return false; } ================================================ FILE: lib/dummyfilereader.hh ================================================ #ifndef DUMMYFILEREADER_HH #define DUMMYFILEREADER_HH #include "errorstack.hh" class Codeplug; /** Just a class implementing the filereader "interface" that fails. */ class DummyFileReader { public: /** Does not read the specified file. Just returns an error. */ static bool read(const QString &filename, Codeplug *codeplug, const ErrorStack &err=ErrorStack()); }; #endif // DUMMYFILEREADER_HH ================================================ FILE: lib/encryptionextension.cc ================================================ #include "encryptionextension.hh" #include "logger.hh" /* ********************************************************************************************* * * Implementation of EncryptionKey * ********************************************************************************************* */ EncryptionKey::EncryptionKey(QObject *parent) : ConfigObject(parent) { // pass... } void EncryptionKey::clear() { _key.clear(); } bool EncryptionKey::fromHex(const QString &hex, const ErrorStack &err) { return setKey(QByteArray::fromHex(hex.toLocal8Bit()), err); } QString EncryptionKey::toHex() const { return _key.toHex(); } const QByteArray & EncryptionKey::key() const { return _key; } bool EncryptionKey::setKey(const QByteArray &key, const ErrorStack &err) { if (key.isEmpty()) { errMsg(err) << "Cannot set empty encryption key."; return false; } if (_key == key) return true; _key = key; emit modified(this); return true; } /* ********************************************************************************************* * * Implementation of BasicEncryptionKey * ********************************************************************************************* */ BasicEncryptionKey::BasicEncryptionKey(QObject *parent) : EncryptionKey(parent) { // pass... } ConfigItem * BasicEncryptionKey::clone() const { BasicEncryptionKey *key = new BasicEncryptionKey(); if (! key->copy(*this)) { key->deleteLater(); return nullptr; } return key; } YAML::Node BasicEncryptionKey::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = EncryptionKey::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["dmr"] = node; return type; } bool BasicEncryptionKey::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse basic encryption key: Expected object with one child."; return false; } YAML::Node key = node.begin()->second; return EncryptionKey::parse(key, ctx, err); } /* ********************************************************************************************* * * Implementation of ARC4EncryptionKey * ********************************************************************************************* */ ARC4EncryptionKey::ARC4EncryptionKey(QObject *parent) : EncryptionKey(parent) { // pass... } ConfigItem * ARC4EncryptionKey::clone() const { ARC4EncryptionKey *key = new ARC4EncryptionKey(); if (! key->copy(*this)) { key->deleteLater(); return nullptr; } return key; } bool ARC4EncryptionKey::fromHex(const QString &hex, const ErrorStack &err) { if (10 != hex.size()) { errMsg(err) << "Cannot set RC4 (enhanced) ecryption key to '" << hex << "': Not a 40bit key."; return false; } return EncryptionKey::fromHex(hex); } bool ARC4EncryptionKey::setKey(const QByteArray &key, const ErrorStack &err) { if (5 != key.size()) { errMsg(err) << "Cannot set RC4 (enhanced) ecryption key: Not a 40bit key."; return false; } return EncryptionKey::setKey(key, err); } YAML::Node ARC4EncryptionKey::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = EncryptionKey::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["rc4"] = node; return type; } bool ARC4EncryptionKey::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse basic encryption key: Expected object with one child."; return false; } YAML::Node key = node.begin()->second; return EncryptionKey::parse(key, ctx, err); } /* ********************************************************************************************* * * Implementation of AESEncryptionKey * ********************************************************************************************* */ AESEncryptionKey::AESEncryptionKey(QObject *parent) : EncryptionKey(parent) { // pass... } ConfigItem * AESEncryptionKey::clone() const { AESEncryptionKey *key = new AESEncryptionKey(); if (! key->copy(*this)) { key->deleteLater(); return nullptr; } return key; } bool AESEncryptionKey::setKey(const QByteArray &key, const ErrorStack &err) { if (16 > key.size()) { errMsg(err) << "Cannot set AES ecryption key to '" << key.toHex() << "': Key smaller than 128bit."; return false; } return EncryptionKey::setKey(key, err); } YAML::Node AESEncryptionKey::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = EncryptionKey::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["aes"] = node; return type; } bool AESEncryptionKey::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse enhanced encryption key: Expected object with one child."; return false; } YAML::Node key = node.begin()->second; return EncryptionKey::parse(key, ctx, err); } /* ********************************************************************************************* * * Implementation of EncryptionKeys * ********************************************************************************************* */ EncryptionKeys::EncryptionKeys(QObject *parent) : ConfigObjectList({BasicEncryptionKey::staticMetaObject, ARC4EncryptionKey::staticMetaObject, AESEncryptionKey::staticMetaObject}, parent) { // pass... } int EncryptionKeys::add(ConfigObject *obj, int row, bool unique) { if ((nullptr == obj) || (! obj->is())) { logError() << "Cannot add nullptr or non-encryption key objects to key list."; return -1; } return ConfigObjectList::add(obj, row, unique); } EncryptionKey * EncryptionKeys::key(int index) const { if (index >= count()) return nullptr; return get(index)->as(); } ConfigItem * EncryptionKeys::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) return nullptr; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create encryption key: Expected object with one child."; return nullptr; } QString type = QString::fromStdString(node.begin()->first.as()); if (("basic" == type) || ("dmr" == type)) { return new BasicEncryptionKey(); } else if ("rc4" == type) { return new ARC4EncryptionKey(); } else if ("aes" == type) { return new AESEncryptionKey(); } errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create encryption key: Unknown type '" << type << "'."; return nullptr; } ================================================ FILE: lib/encryptionextension.hh ================================================ #ifndef ENCRYPTIONEXTENSION_HH #define ENCRYPTIONEXTENSION_HH #include "configobject.hh" /** Base class of all encryption keys. * @ingroup conf */ class EncryptionKey: public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "key") /** The key representation as a hex string. */ Q_PROPERTY(QString key READ toHex WRITE fromHex) protected: /** Hidden constructor. */ explicit EncryptionKey(QObject *parent=nullptr); public: void clear(); /** Creates a key from the given hex-string. */ virtual bool fromHex(const QString &hex, const ErrorStack &err=ErrorStack()); /** Converts a key to a hex string. */ virtual QString toHex() const; /** Returns the binary key. */ const QByteArray &key() const; /** Sets the binary key. */ virtual bool setKey(const QByteArray &key, const ErrorStack &err=ErrorStack()); protected: /** Holds the key data. * The size depends on the key type. */ QByteArray _key; }; /** Represents a DMR (basic) encryption key. * * This is a variable sized key used for the DMR basic encryption method. * * @ingroup conf */ class BasicEncryptionKey: public EncryptionKey { Q_OBJECT Q_CLASSINFO("description", "A basic DMR encryption key.") Q_CLASSINFO("longDescription", "This is a variable sized pre-shared key that can be used to encrypt/decrypt traffic " "on DMR channels. Encryption is forbidden in HAM radio context!") public: /** Empty constructor. */ Q_INVOKABLE explicit BasicEncryptionKey(QObject *parent=nullptr); ConfigItem *clone() const; public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); }; /** Represents an (enhanced) ARC4 encryption key. * * This is a 40bit key used for the DMR enhanced encryption method. * * @ingroup conf */ class ARC4EncryptionKey: public EncryptionKey { Q_OBJECT Q_CLASSINFO("description", "An enhanced DMR encryption key.") Q_CLASSINFO("longDescription", "This is a 40bit pre-shared RC4 key, that can be used to encrypt/decrypt traffic on " "DMR channels. Encryption is forbidden in HAM radio context!") public: /** Empty constructor. */ Q_INVOKABLE explicit ARC4EncryptionKey(QObject *parent=nullptr); ConfigItem *clone() const; bool fromHex(const QString &hex, const ErrorStack &err=ErrorStack()); bool setKey(const QByteArray &key, const ErrorStack &err); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); }; /** Represents a variable size AES (enhanced) encryption key. * * This is a 128-256bit key used for the DMR enhanced encryption method. * * @ingroup conf */ class AESEncryptionKey: public EncryptionKey { Q_OBJECT Q_CLASSINFO("description", "An AES (advanced) DMR encryption key.") Q_CLASSINFO("longDescription", "This is a variable sized (usually 128-256bit) pre-shared key that can be used to " "encrypt/decrypt traffic on DMR channels. Encryption is forbidden in HAM radio " "context!") public: /** Empty constructor. */ Q_INVOKABLE explicit AESEncryptionKey(QObject *parent=nullptr); ConfigItem *clone() const; bool setKey(const QByteArray &key, const ErrorStack &err); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); }; Q_DECLARE_OPAQUE_POINTER(AESEncryptionKey*) /** The list of encryption keys. * * This list holds all encryption keys defined within the codeplug. * * @warning Please note that this is a commercial feature and forbidden for HAM radio use. * * @ingroup conf */ class EncryptionKeys: public ConfigObjectList { Q_OBJECT public: /** Empty constructor. */ explicit EncryptionKeys(QObject *parent=nullptr); int add(ConfigObject *obj, int row=-1, bool unique=true); /** Returns the key the given index. */ EncryptionKey *key(int index) const; ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // ENCRYPTIONEXTENSION_HH ================================================ FILE: lib/errorstack.cc ================================================ #include "errorstack.hh" #include #include /* ********************************************************************************************* * * Implementation of ErrorStack::Message * ********************************************************************************************* */ ErrorStack::Message::Message() : _file(), _line(0), _message() { // pass... } ErrorStack::Message::Message(const QString &file, unsigned line, const QString &message) : _file(file), _line(line), _message(message) { // pass... } const QString & ErrorStack::Message::file() const { return _file; } unsigned ErrorStack::Message::line() const { return _line; } const QString & ErrorStack::Message::message() const { return _message; } QString ErrorStack::Message::format() const { return QString("In %1:%2: %3").arg(QFileInfo(file()).fileName()).arg(line()).arg(message()); } /* ********************************************************************************************* * * Implementation of ErrorStack::MessageStream * ********************************************************************************************* */ ErrorStack::MessageStream::MessageStream(const ErrorStack &stack, const QString &file, unsigned line) : QTextStream(&_message), _stack(stack), _file(file), _line(line), _message() { // pass... } ErrorStack::MessageStream::~MessageStream() { _stack.push(Message(_file, _line, _message)); LogMessage(LogMessage::ERROR, _file, _line) << _message; } /* ********************************************************************************************* * * Implementation of ErrorStack::Stack * ********************************************************************************************* */ ErrorStack::Stack::Stack() noexcept : _refcount(1), _errorMessageStack() { // pass... } void ErrorStack::Stack::push(const Message &msg) { _errorMessageStack.push_front(msg); } void ErrorStack::Stack::push(const Stack &other) { if (other.isEmpty()) return; for (int i=(other.count()-1); i>=0; i--) { push(other.message(i)); } } bool ErrorStack::Stack::isEmpty() const { return 0 == count(); } unsigned ErrorStack::Stack::count() const { return _errorMessageStack.count(); } const ErrorStack::Message & ErrorStack::Stack::message(unsigned i) const { return _errorMessageStack.at(i); } QString ErrorStack::Stack::format(const QString &indent) const { QString res; if (isEmpty()) return res; res += indent + message(0).format(); for (unsigned i=1; iref()) { // pass... } ErrorStack::~ErrorStack() { _stack->unref(); _stack = nullptr; } ErrorStack & ErrorStack::operator =(const ErrorStack &other) { _stack->unref(); _stack = other._stack->ref(); return *this; } bool ErrorStack::isEmpty() const { return _stack->isEmpty(); } unsigned ErrorStack::count() const { return _stack->count(); } const ErrorStack::Message & ErrorStack::message(unsigned i) const { return _stack->message(i); } void ErrorStack::push(const Message &msg) const { _stack->push(msg); } void ErrorStack::take(const ErrorStack &other) const { _stack->push(*other._stack); other._stack->clear(); } QString ErrorStack::format(const QString &indent) const { return _stack->format(indent); } ================================================ FILE: lib/errorstack.hh ================================================ #ifndef ERRORSTACK_HH #define ERRORSTACK_HH #include #include #include /** Implements a stack of error messages to provide a pretty formatted error traceback. * * This class is intended to be used like: * @code * class MyClass: public ErrorStack * { * * // [...] * * bool someMethod(const ErrorStack &err=ErrorStack()) { * // [...] * if (someError) { * errMsg() << "Some error happened!"; * return false; * } * // [...] * } * * // [...] * } * @endcode * * The error message can then be obtained using the public methods. E.g., * @code * // [...] * MyClass instance; * ErrorStack err; * * if (! instance.someMethod(err)) { * QString msg = err.formatErrorMessages(); * // [] * } * @endcode * @ingroup log */ class ErrorStack { public: class Stack; /** Represents a single error message. That is, a tuple of file, line and message. */ class Message { public: /** Empty constructor. */ Message(); /** Constructor from file, line and message. */ Message(const QString &file, unsigned line, const QString &message); /** Returns the file name. */ const QString &file() const; /** Returns the line within the file. */ unsigned line() const; /** Returns the error message. */ const QString &message() const; /** Formats the error messaege. */ QString format() const; protected: /** Holds the file path. */ QString _file; /** Holds the line. */ unsigned _line; /** Holds the error message. */ QString _message; }; /** A helper class to assemble error messages as streams. Use the @c errorMessage macro. */ class MessageStream: public QTextStream { public: /** Constructor. */ MessageStream(const ErrorStack &stack, const QString &file, unsigned line); /** Destructor, puts the message on the stack. */ virtual ~MessageStream(); protected: /** Holds a weak reference to the error stack to put the message on. */ const ErrorStack &_stack; /** The file path. */ QString _file; /** The line number. */ unsigned _line; /** The message buffer. */ QString _message; }; /** The actual error message stack. */ class Stack { public: /** Empty constructor. */ Stack() noexcept; public: /** Returns @c true if there are any error messages. */ bool isEmpty() const; /** Returns the number of error messages. */ unsigned count() const; /** Returns a specific error message. */ const Message &message(unsigned i) const; /** Returns a formatted string of error messages. */ QString format(const QString &indent=" ") const; /** Adds an error message to the stack. */ void push(const Message &msg); /** Adds the error messages from another stack. */ void push(const Stack &other); /** Clears the error stack. */ void clear(); /** Returns a new reference to the stack. */ Stack *ref(); /** Dereferences a stack, this decreases the ref count. When 0 is reached, the stack is * destroyed. */ void unref(); private: /** Reference counter. */ unsigned _refcount; /** Holds the stack of error messages. */ QList _errorMessageStack; }; public: /** Default constructor. */ ErrorStack() noexcept; /** Copy constructor. */ ErrorStack(const ErrorStack &other); /** Destructor. */ ~ErrorStack(); /** Copy assignment. */ ErrorStack &operator= (const ErrorStack &other); /** Returns @c true, if the stack is empty. */ bool isEmpty() const; /** Returns the number of elements on the stack. */ unsigned count() const; /** Returns the i-th message from the stack. */ const Message &message(unsigned i) const; /** Pushes a message on the stack. */ void push(const Message &msg) const; /** Takes all messages from the other stack. */ void take(const ErrorStack &other) const; /** Returns a formatted string of error messages. */ QString format(const QString &indent=" ") const; protected: /** A reference to the actual message stack. */ Stack *_stack; }; /** Utility macro to assemble a message stream. */ #define errMsg(stack) (ErrorStack::MessageStream(stack, __FILE__, __LINE__)) #endif // ERRORSTACK_HH ================================================ FILE: lib/frequency.cc ================================================ #include "frequency.hh" #include "logger.hh" #include inline const QString HzString = QStringLiteral("Hz"); inline const QString KHzString = QStringLiteral("kHz"); inline const QString MHzString = QStringLiteral("MHz"); inline const QString GHzString = QStringLiteral("GHz"); /* ******************************************************************************************** * * Implementation of FrequencyBase * ******************************************************************************************** */ FrequencyBase::FrequencyBase(const FrequencyBase &other) : _frequency(other._frequency) { // pass... } FrequencyBase & FrequencyBase::operator =(const FrequencyBase &other) { _frequency = other._frequency; return *this; } QString FrequencyBase::format(Unit unit) const { switch (unit) { case Unit::Auto: if (10000LL > std::abs(_frequency)) { return format(Unit::Hz); } else if (10000000LL > std::abs(_frequency)) { return format(Unit::kHz); } else if (10000000000LL > std::abs(_frequency)) { return format(Unit::MHz); } return format(Unit::GHz); case Unit::Hz: return QString("%1 Hz").arg(inHz()); case Unit::kHz: return QString("%1 kHz").arg(inkHz(), 0, 'g', 6); case Unit::MHz: return QString("%1 MHz").arg(inMHz(), 0, 'g', 9); case Unit::GHz: return QString("%1 GHz").arg(inGHz(), 0, 'g', 12); } return ""; } bool FrequencyBase::parse(const QString &value, Qt::CaseSensitivity caseSensitivity) { QRegularExpression::PatternOption patternOption = caseSensitivity == Qt::CaseInsensitive ? QRegularExpression::PatternOption::CaseInsensitiveOption : QRegularExpression::PatternOption::NoPatternOption; QRegularExpression re(R"(\s*(\+|-)?\s*([0-9]+)(?:\.([0-9]*)|)\s*([kMG]|[kMG]?Hz|)\s*)", patternOption); QRegularExpressionMatch match = re.match(value); if (!match.hasMatch()) return false; bool isNegative = ("-" == match.captured(1)); auto unit = unitFromString(match.captured(4)); QString decimals = match.captured(3); QString leading = match.captured(2); _frequency = leading.toUInt(); switch (unit) { case Unit::kHz: { _frequency *= 1000ULL; unsigned long long factor = 100ULL; for (int i=0; i3) && (decimals[3].digitValue()>=5)) _frequency += 1; } break; case Unit::MHz: { _frequency *= 1000000ULL; unsigned long long factor = 100000ULL; for (int i=0; i6) && (decimals[6].digitValue()>=5)) _frequency += 1; } break; case Unit::GHz: { _frequency *= 1000000000ULL; unsigned long long factor = 100000000; for (int i=0; i9) && (decimals[9].digitValue()>=5)) _frequency+=1; } break; case Unit::Hz: default: ; // Do Nothing } if (isNegative) _frequency = -_frequency; return true; } FrequencyBase::Unit FrequencyBase::unit() const { qint64 absFreq = std::abs(_frequency); if (absFreq >= 1000000000LL) return Unit::GHz; else if (absFreq >= 1000000LL) return Unit::MHz; else if (absFreq >= 1000LL) return Unit::kHz; else return Unit::Hz; } FrequencyBase::Unit FrequencyBase::unitFromString(const QString& input) const { Unit result; if (input.length() == 0) { return Unit::MHz; } auto multiplier = input[0].toLower(); if (multiplier == 'k') { result = Unit::kHz; } else if (multiplier == 'm') { result = Unit::MHz; } else if (multiplier == 'g') { result = Unit::GHz; } else { result = Unit::Hz; } return result; } bool FrequencyBase::isMultipleOf(Unit u) const { switch(u) { case Unit::kHz: return (_frequency % 1000LL) == 0; case Unit::MHz: return (_frequency % 1000000LL) == 0; case Unit::GHz: return (_frequency % 1000000000LL) == 0; case Unit::Hz: //fallthrough default: return true; } return false; // Unreachable } QString FrequencyBase::unitName(Unit unit) { switch(unit) { case Unit::kHz: return KHzString; case Unit::MHz: return MHzString; case Unit::GHz: return GHzString; case Unit::Hz: //fallthrough default: return HzString; } return QString(); } /* ******************************************************************************************** * * Implementation of FrequencyOffset * ******************************************************************************************** */ FrequencyOffset::FrequencyOffset() : FrequencyBase(0) { // pass... } FrequencyOffset::FrequencyOffset(const FrequencyOffset &other) : FrequencyBase(other) { // pass... } FrequencyOffset & FrequencyOffset::operator =(const FrequencyOffset &other) { FrequencyBase::operator=(other); return *this; } FrequencyOffset FrequencyOffset::fromString(const QString &freq) { FrequencyOffset f; if (! f.parse(freq)) { logWarn() << "Cannot parse offset '" << freq << "'."; } return f; } /* ******************************************************************************************** * * Implementation of Frequency * ******************************************************************************************** */ Frequency::Frequency(quint64 Hz) : FrequencyBase(Hz) { // pass... } Frequency::Frequency() : FrequencyBase(0) { // pass... } Frequency::Frequency(const Frequency &other) : FrequencyBase(other) { // pass... } Frequency & Frequency::operator =(const Frequency &other) { FrequencyBase::operator=(other); return *this; } Frequency Frequency::operator+(const FrequencyOffset &offset) const { if ((0 > offset.inHz()) && (std::abs(offset.inHz()) > _frequency)) return Frequency(); return Frequency(_frequency + offset.inHz()); } FrequencyOffset Frequency::operator-(const Frequency &other) const { return FrequencyOffset(_frequency - other._frequency); } Frequency Frequency::fromString(const QString &freq) { Frequency f; if (! f.parse(freq)) { logWarn() << "Cannot parse frequency '" << freq << "'."; } return f; } ================================================ FILE: lib/frequency.hh ================================================ #ifndef FREQUENCY_HH #define FREQUENCY_HH #include #include #include #include #include /** Common base for all frequencies. That is frequencies (positive) and frequency offsets * (positive and negative). To this end, the type stores frequencies in Hz as a signed value. */ struct FrequencyBase { public: /** Frequency units. */ enum class Unit { Auto, Hz, kHz, MHz, GHz }; protected: /** Hidden constructor from offset in Hz. */ FrequencyBase(qint64 hz) : _frequency(hz) {} public: /** Copy constructor. */ FrequencyBase(const FrequencyBase &other); /** Assignment. */ FrequencyBase &operator = (const FrequencyBase &other); /** Returns @c true of the frequency is negative. */ inline bool isNegative() const { return 0 > _frequency; } /** Returns @c true of the frequency is positive. */ inline bool isPositive() const { return 0 < _frequency; } /** Returns @c true of the frequency is zero. */ inline bool isZero() const { return 0 == _frequency; } /** Format the frequency. */ QString format(Unit unit = Unit::Auto) const; /** Parses a frequency. */ bool parse(const QString &value, Qt::CaseSensitivity caseSensitivity = Qt::CaseInsensitive); inline long long inHz() const { return _frequency; } ///< Unit conversion. inline double inkHz() const { return double(_frequency)/1e3; } ///< Unit conversion. inline double inMHz() const { return double(_frequency)/1e6; } ///< Unit conversion. inline double inGHz() const { return double(_frequency)/1e9; } ///< Unit conversion. /** Returns the most appropriate unit for the frequency value. */ Unit unit() const; /** Returns unit as type base on string input */ Unit unitFromString(const QString& input) const; /** Checks if frequency is a multiple of unit. */ bool isMultipleOf(Unit unit) const; /** Helper for string conversion of unit. */ static QString unitName(Unit unit); protected: /** The actual frequency in Hz. */ qint64 _frequency; }; struct Frequency; /** Helper type to represent frequency differences aka offsets. * @ingroup utils */ struct FrequencyOffset: public FrequencyBase { friend struct Frequency; protected: /** Hidden constructor. */ FrequencyOffset(qint64 Hz) : FrequencyBase(Hz) { } public: /** Default constructor. */ FrequencyOffset(); /** Copy constructor. */ FrequencyOffset(const FrequencyOffset &other); /** Assignment. */ FrequencyOffset &operator = (const FrequencyOffset &other); /** Invert an offset */ inline FrequencyOffset invert() const { return FrequencyOffset(this->_frequency * -1); } /** Returns a positive frequency offset. */ inline FrequencyOffset abs() const { return FrequencyOffset(std::abs(this->_frequency)); } /** Parses an offset. */ static FrequencyOffset fromString(const QString &freq); static inline FrequencyOffset fromHz(qint64 Hz) { return FrequencyOffset(Hz); } ///< Unit conversion. static inline FrequencyOffset fromkHz(double kHz) { return FrequencyOffset(kHz*1e3); } ///< Unit conversion. static inline FrequencyOffset fromMHz(double MHz) { return FrequencyOffset(MHz*1e6); } ///< Unit conversion. static inline FrequencyOffset fromGHz(double GHz) { return FrequencyOffset(GHz*1e9); } ///< Unit conversion. }; /** Helper type to encode frequencies without any rounding error. * This is a specialization of @c FrequencyBase, enforcing a positive integer. * @ingroup utils */ struct Frequency: public FrequencyBase { protected: /** Hidden constructor from frequency in Hz. */ Frequency(quint64 hz); public: /** Default constructor. */ Frequency(); /** Copy constructor. */ Frequency(const Frequency &other); /** Assignment. */ Frequency &operator = (const Frequency &other); inline bool operator< (const Frequency &other) const { ///< Comparison. return _frequency < other._frequency; } inline bool operator <=(const Frequency &other) const { ///< Comparison. return _frequency <= other._frequency; } inline bool operator== (const Frequency &other) const { ///< Comparison. return _frequency == other._frequency; } inline bool operator != (const Frequency &other) const { ///< Comparison. return _frequency != other._frequency; } inline bool operator> (const Frequency &other) const { ///< Comparison. return _frequency > other._frequency; } inline bool operator >=(const Frequency &other) const { ///< Comparison. return _frequency >= other._frequency; } /** Frequency arithmetic. May result in an invalid frequency, if the negative offset is larger * than the frequency. */ Frequency operator+(const FrequencyOffset &offset) const; /** Frequency arithmetic. May result in an invalid frequency, if the negative offset is larger * than the frequency. */ Frequency &operator+=(const FrequencyOffset &offset); FrequencyOffset operator-(const Frequency &other) const; ///< Frequency arithmetic. /** Pareses a frequency. */ static Frequency fromString(const QString &freq); inline quint64 inHz() const { return _frequency; } ///< Unit conversion. static inline Frequency fromHz(quint64 Hz) { return Frequency(Hz); } ///< Unit conversion. static inline Frequency fromkHz(double kHz) { return Frequency(kHz*1e3); } ///< Unit conversion. static inline Frequency fromMHz(double MHz) { return Frequency(MHz*1e6); } ///< Unit conversion. static inline Frequency fromGHz(double GHz) { return Frequency(GHz*1e9); } ///< Unit conversion. public: /** Searches for the nearest frequency and returns an associated value. */ template class MapNearest { protected: typedef QList> container; typedef typename container::const_iterator const_iterator; public: /** Constructs a nearest frequency mapping. */ MapNearest(const QList> &items) : _items(items) { // Sort items ascending. std::sort(_items.begin(), _items.end(), compItem); } /** Searches for the nearest item to the given frequency. */ const_iterator find(const Frequency &f) const { auto lower = std::lower_bound(_items.begin(), _items.end(), f, compItemValue); if (_items.begin() == lower) return lower; auto upper = lower--; if (_items.end() == upper) return lower; if ((f.inHz()-lower->first.inHz()) < (upper->first.inHz()-f.inHz())) return lower; return upper; } /** Returns the value associated with the nearest item for the given frequency. */ T value(const Frequency &f) const { return find(f)->second; } /** Returns the frequency of the nearest item for the given frequency. */ Frequency key(const Frequency &f) const { return find(f)->first; } protected: /** Comparison of items by frequency. */ inline static bool compItem(const QPair &a, const QPair &b) { return a.first < b.first; } /** Comparison of item and frequency. */ inline static bool compItemValue(const QPair &a, const Frequency &b) { return a.first < b; } protected: /** All items. */ container _items; }; }; Q_DECLARE_METATYPE(Frequency) namespace YAML { /** Implements the conversion to and from YAML::Node. */ template<> struct convert { /** Serializes the frequency. */ static Node encode(const Frequency& rhs) { return Node(rhs.format().toStdString()); } /** Parses the frequency. */ static bool decode(const Node& node, Frequency& rhs) { if (! node.IsScalar()) return false; return rhs.parse(QString::fromStdString(node.as()), Qt::CaseSensitive); } }; } #endif // FREQUENCY_HH ================================================ FILE: lib/gd73.cc ================================================ #include "gd73.hh" #include "gd73_limits.hh" #include "logger.hh" #include "config.hh" #define BSIZE 0x35 RadioLimits * GD73::_limits = nullptr; GD73::GD73(GD73Interface *device, QObject *parent) : Radio(parent), _name("Radioddity GD-73"), _dev(device), _codeplugFlags(), _config(nullptr), _codeplug() { if (_dev) _dev->setParent(this); } const QString & GD73::name() const { return _name; } const RadioLimits & GD73::limits() const { if (nullptr == _limits) _limits = new GD73Limits(); return *_limits; } const Codeplug & GD73::codeplug() const { return _codeplug; } Codeplug & GD73::codeplug() { return _codeplug; } RadioInfo GD73::defaultRadioInfo() { return RadioInfo( RadioInfo::GD73, "gd73", "GD-73", "Radioddity", {GD73Interface::interfaceInfo()}); } bool GD73::startDownload(const TransferFlags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; _task = StatusDownload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } start(); return true; } bool GD73::startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; if (! (_config = config)) return false; _task = StatusUpload; _codeplugFlags = flags; if (flags.blocking()) { this->run(); return (StatusIdle == _task); } _errorStack = err; this->start(); return true; } bool GD73::startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(selection); errMsg(err) << "Radio does not support a callsign DB."; return false; } bool GD73::startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(flags); errMsg(err) << "Satellite config upload is not implemented yet."; return false; } void GD73::run() { if (StatusDownload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit downloadError(this); return; } if (! download()) { _dev->read_finish(); _dev->reboot(); _dev->close(); _task = StatusError; emit downloadError(this); return; } _task = StatusIdle; _dev->reboot(); _dev->close(); emit downloadFinished(this, &codeplug()); _config = nullptr; } else if (StatusUpload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if (! upload()) { _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadCallsigns == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if(! uploadCallsigns()) { _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _task = StatusIdle; _dev->reboot(); _dev->close(); emit uploadComplete(this); } } bool GD73::download() { emit downloadStarted(); unsigned btot = 0; for (int n=0; nread_start(0,0,_errorStack)) return false; unsigned bcount = 0; for (int n=0; nread(0, (b0+i)*BSIZE, codeplug().data((b0+i)*BSIZE), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot download codeplug."; return false; } emit downloadProgress(float(bcount*100)/btot); } } _dev->read_finish(_errorStack); return true; } bool GD73::upload() { emit uploadStarted(); unsigned btot = 0; for (int n=0; nread_start(0,0,_errorStack)) return false; // If codeplug gets updated, download codeplug from device first: for (int n=0; nread(0, addr, codeplug().data(addr), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot upload codeplug."; return false; } emit uploadProgress(float(bcount*50)/btot); } } _dev->read_finish(_errorStack); } // Encode config into codeplug if (! codeplug().encode(_config, _codeplugFlags, _errorStack)) { errMsg(_errorStack) << "Codeplug upload failed."; return false; } if (! _dev->write_start(0, 0, _errorStack)) return false; // then, upload modified codeplug bcount = 0; for (int n=0; nwrite(0, addr, codeplug().data(addr), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot upload codeplug."; return false; } emit uploadProgress(50+float(bcount*50)/btot); } } _dev->write_finish(_errorStack); return true; } bool GD73::uploadCallsigns() { return false; } ================================================ FILE: lib/gd73.hh ================================================ /** @defgroup gd73 Radioddity GD-73 * Device specific classes for Radioddity GD-73A and GD-73E. * * \image html gd73.webp "GD-73" width=200px * \image latex gd73.webp "GD-73" width=200px * * @ingroup radioddity */ #ifndef GD73_HH #define GD73_HH #include "radio.hh" #include "gd73_interface.hh" #include "gd73_codeplug.hh" /** Implements an USB interface to the Radioddity GD-73 UHF 2W DMR (Tier I&II) radio. * * @ingroup gd73 */ class GD73 : public Radio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit GD73(GD73Interface *device=nullptr, QObject *parent=nullptr); const QString &name() const; const RadioLimits &limits() const; const Codeplug &codeplug() const; Codeplug &codeplug(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); public slots: /** Starts the download of the codeplug and derives the generic configuration from it. */ bool startDownload(const TransferFlags &flags, const ErrorStack &err=ErrorStack()); /** Derives the device-specific codeplug from the generic configuration and uploads that * codeplug to the radio. */ bool startUpload(Config *config, const Codeplug::Flags &flags = Codeplug::Flags(), const ErrorStack &err=ErrorStack()); /** Encodes the given user-database and uploads it to the device. */ bool startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection=CallsignDB::Flags(), const ErrorStack &err=ErrorStack()); bool startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err); protected: /** Thread main routine, performs all blocking IO operations for codeplug up- and download. */ void run(); private: virtual bool download(); virtual bool upload(); virtual bool uploadCallsigns(); protected: /** The device identifier. */ QString _name; /** The interface to the radio. */ GD73Interface *_dev; /** Holds the flags to control assembly and upload of code-plugs. */ Codeplug::Flags _codeplugFlags; /** The generic configuration. */ Config *_config; /** The codeplug. */ GD73Codeplug _codeplug; private: /** Holds the singleton instance of the radio limits for this radio. */ static RadioLimits *_limits; }; #endif // GD77_HH ================================================ FILE: lib/gd73_codeplug.cc ================================================ #include "gd73_codeplug.hh" #include "config.hh" #include "intermediaterepresentation.hh" #include "logger.hh" QVector _ctcss_codes = { SelectiveCall(62.5), SelectiveCall(67.0), SelectiveCall(69.3), SelectiveCall(71.9), SelectiveCall(74.4), SelectiveCall(77.0), SelectiveCall(79.7), SelectiveCall(82.5), SelectiveCall(85.4), SelectiveCall(88.5), SelectiveCall(91.5), SelectiveCall(94.8), SelectiveCall(97.4), SelectiveCall(100.0), SelectiveCall(103.5), SelectiveCall(107.2), SelectiveCall(110.9), SelectiveCall(114.8), SelectiveCall(118.8), SelectiveCall(123.0), SelectiveCall(127.3), SelectiveCall(131.8), SelectiveCall(136.5), SelectiveCall(141.3), SelectiveCall(146.2), SelectiveCall(151.4), SelectiveCall(156.7), SelectiveCall(159.8), SelectiveCall(162.2), SelectiveCall(165.5), SelectiveCall(167.9), SelectiveCall(171.3), SelectiveCall(173.8), SelectiveCall(177.3), SelectiveCall(179.9), SelectiveCall(183.5), SelectiveCall(186.2), SelectiveCall(189.9), SelectiveCall(192.8), SelectiveCall(196.6), SelectiveCall(199.5), SelectiveCall(203.5), SelectiveCall(206.5), SelectiveCall(210.7), SelectiveCall(218.1), SelectiveCall(225.7), SelectiveCall(229.1), SelectiveCall(233.6), SelectiveCall(241.8), SelectiveCall(250.3), SelectiveCall(254.1) }; QVector _dcs_codes = { 23, 25, 26, 31, 32, 36, 43, 47, 51, 53, 54, 65, 71, 72, 73, 74, 114, 115, 116, 122, 125, 131, 132, 134, 143, 145, 152, 155, 156, 162, 165, 172, 174, 205, 212, 223, 225, 226, 243, 244, 245, 246, 251, 252, 255, 261, 263, 265, 266, 271, 274, 306, 311, 315, 325, 331, 332, 343, 346, 351, 356, 364, 365, 371, 411, 412, 413, 423, 431, 432, 445, 446, 452, 454, 455, 462, 464, 465, 466, 503, 506, 516, 523, 526, 532, 546, 565, 606, 612, 624, 627, 631, 632, 645, 654, 662, 703, 712, 723, 731, 732, 734, 743, 754 }; /* ********************************************************************************************* * * Implementation of GD73Codeplug::InformationElement * ********************************************************************************************* */ GD73Codeplug::InformationElement::InformationElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::InformationElement::InformationElement(uint8_t *ptr) : Element(ptr, InformationElement::size()) { // pass... } FrequencyRange GD73Codeplug::InformationElement::frequencyRange() const { switch (getUInt8(Offset::frequencyRange())) { case 0x00: return FrequencyRange {Frequency::fromMHz(406.1), Frequency::fromMHz(470.0)}; break; case 0x01: return FrequencyRange {Frequency::fromMHz(446.0), Frequency::fromMHz(446.995)}; break; case 0x02: return FrequencyRange {Frequency::fromMHz(400.0), Frequency::fromMHz(470.0)}; break; } return FrequencyRange(); } void GD73Codeplug::InformationElement::setFrequencyRange(const FrequencyRange &range) { if ((Frequency::fromMHz(446.0)<=range.lower) && (range.upper <=Frequency::fromMHz(446.995))) setUInt8(Offset::frequencyRange(), 0x01); else if ((range.lower>=Frequency::fromMHz(406.1)) && (range.upper <=Frequency::fromMHz(470.0))) setUInt8(Offset::frequencyRange(), 0x00); else setUInt8(Offset::frequencyRange(), 0x02); } QDateTime GD73Codeplug::InformationElement::timestamp() const { int year = ((int)getUInt8(Offset::dateCentury()))*100 + getUInt8(Offset::dateYear()); return QDateTime(QDate(year, getUInt8(Offset::dateMonth()), getUInt8(Offset::dateDay())), QTime(getUInt8(Offset::dateHour()), getUInt8(Offset::dateMinute()), getUInt8(Offset::dateSecond()))); } void GD73Codeplug::InformationElement::setTimestamp(const QDateTime ×tamp) { setUInt8(Offset::dateCentury(), timestamp.date().year()/100); setUInt8(Offset::dateYear(), timestamp.date().year()%100); setUInt8(Offset::dateMonth(), timestamp.date().month()); setUInt8(Offset::dateDay(), timestamp.date().day()); setUInt8(Offset::dateHour(), timestamp.time().hour()); setUInt8(Offset::dateMinute(), timestamp.time().minute()); setUInt8(Offset::dateSecond(), timestamp.time().second()); } QString GD73Codeplug::InformationElement::serial() const { return readASCII(Offset::serial(), Limit::serial(), 0x00); } QString GD73Codeplug::InformationElement::modelName() const { return readASCII(Offset::modelName(), Limit::modelName(), 0x00); } QString GD73Codeplug::InformationElement::deviceID() const { return readASCII(Offset::deviceID(), Limit::deviceID(), 0x00); } QString GD73Codeplug::InformationElement::modelNumber() const { return readASCII(Offset::modelNumber(), Limit::modelNumber(), 0x00); } QString GD73Codeplug::InformationElement::softwareVersion() const { return readASCII(Offset::softwareVersion(), Limit::softwareVersion(), 0x00); } /* ********************************************************************************************* * * Implementation of GD73Codeplug::SettingsElement::KeyFunction * ********************************************************************************************* */ uint8_t GD73Codeplug::SettingsElement::KeyFunction::encode(RadioddityButtonSettingsExtension::Function func) { switch (func) { case RadioddityButtonSettingsExtension::Function::None: return None; case RadioddityButtonSettingsExtension::Function::RadioEnable: return RadioEnable; case RadioddityButtonSettingsExtension::Function::RadioCheck: return RadioCheck; case RadioddityButtonSettingsExtension::Function::RadioDisable: return RadioDisable; case RadioddityButtonSettingsExtension::Function::PowerLevel: return PowerLevel; case RadioddityButtonSettingsExtension::Function::ToggleMonitor: return Monitor; case RadioddityButtonSettingsExtension::Function::EmergencyOn: return EmergencyOn; case RadioddityButtonSettingsExtension::Function::EmergencyOff: return EmergencyOff; case RadioddityButtonSettingsExtension::Function::ZoneSelect: return ZoneSwitch; case RadioddityButtonSettingsExtension::Function::ToggleScan: return ToggleScan; case RadioddityButtonSettingsExtension::Function::ToggleVox: return ToggleVOX; case RadioddityButtonSettingsExtension::Function::OneTouch1: return OneTouch1; case RadioddityButtonSettingsExtension::Function::OneTouch2: return OneTouch2; case RadioddityButtonSettingsExtension::Function::OneTouch3: return OneTouch3; case RadioddityButtonSettingsExtension::Function::OneTouch4: return OneTouch4; case RadioddityButtonSettingsExtension::Function::OneTouch5: return OneTouch5; case RadioddityButtonSettingsExtension::Function::ToggleTalkaround: return ToggleTalkaround; case RadioddityButtonSettingsExtension::Function::ToggleLoneWorker: return LoneWorker; case RadioddityButtonSettingsExtension::Function::TBST: return TBST; case RadioddityButtonSettingsExtension::Function::CallSwell: return CallSwell; default: break; } return None; } RadioddityButtonSettingsExtension::Function GD73Codeplug::SettingsElement::KeyFunction::decode(uint8_t code) { switch((Code) code) { case None: return RadioddityButtonSettingsExtension::Function::None; case RadioEnable: return RadioddityButtonSettingsExtension::Function::RadioEnable; case RadioCheck: return RadioddityButtonSettingsExtension::Function::RadioCheck; case RadioDisable: return RadioddityButtonSettingsExtension::Function::RadioDisable; case PowerLevel: return RadioddityButtonSettingsExtension::Function::PowerLevel; case Monitor: return RadioddityButtonSettingsExtension::Function::ToggleMonitor; case EmergencyOn: return RadioddityButtonSettingsExtension::Function::EmergencyOn; case EmergencyOff: return RadioddityButtonSettingsExtension::Function::EmergencyOff; case ZoneSwitch: return RadioddityButtonSettingsExtension::Function::ZoneSelect; case ToggleScan: return RadioddityButtonSettingsExtension::Function::ToggleScan; case ToggleVOX: return RadioddityButtonSettingsExtension::Function::ToggleVox; case OneTouch1: return RadioddityButtonSettingsExtension::Function::OneTouch1; case OneTouch2: return RadioddityButtonSettingsExtension::Function::OneTouch2; case OneTouch3: return RadioddityButtonSettingsExtension::Function::OneTouch3; case OneTouch4: return RadioddityButtonSettingsExtension::Function::OneTouch4; case OneTouch5: return RadioddityButtonSettingsExtension::Function::OneTouch5; case ToggleTalkaround: return RadioddityButtonSettingsExtension::Function::ToggleTalkaround; case LoneWorker: return RadioddityButtonSettingsExtension::Function::ToggleLoneWorker; case TBST: return RadioddityButtonSettingsExtension::Function::TBST; case CallSwell: return RadioddityButtonSettingsExtension::Function::CallSwell; default: break; } return RadioddityButtonSettingsExtension::Function::None; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::SettingsElement * ********************************************************************************************* */ GD73Codeplug::SettingsElement::SettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::SettingsElement::SettingsElement(uint8_t *ptr) : Element(ptr, SettingsElement::size()) { // pass... } QString GD73Codeplug::SettingsElement::name() const { return readUnicode(Offset::name(), Limit::name(), 0x0000); } void GD73Codeplug::SettingsElement::setName(const QString &name) { writeUnicode(Offset::name(), name, Limit::name(), 0x0000); } unsigned int GD73Codeplug::SettingsElement::dmrID() const { return getUInt32_le(Offset::dmrId()); } void GD73Codeplug::SettingsElement::setDMRID(unsigned int id) { setUInt32_le(Offset::dmrId(), id); } GD73Codeplug::SettingsElement::Language GD73Codeplug::SettingsElement::language() const { return (Language)getUInt8(Offset::language()); } void GD73Codeplug::SettingsElement::setLanguage(Language lang) { setUInt8(Offset::language(), (unsigned int)lang); } Level GD73Codeplug::SettingsElement::vox() const { return Level::fromValue(getUInt8(Offset::voxLevel()), Limit::vox()); } void GD73Codeplug::SettingsElement::setVOX(Level level) { setUInt8(Offset::voxLevel(), level.mapTo(Limit::vox())); } Level GD73Codeplug::SettingsElement::squelch() const { return Level::fromValue(getUInt8(Offset::squelchLevel()), Limit::squelch()); } void GD73Codeplug::SettingsElement::setSquelch(Level level) { setUInt8(Offset::squelchLevel(), level.mapTo(Limit::squelch())); } bool GD73Codeplug::SettingsElement::totIsSet() const { return 0 != getUInt8(Offset::tot()); } Interval GD73Codeplug::SettingsElement::tot() const { unsigned int n = getUInt8(Offset::tot()); if (0 == n) return Interval::fromSeconds(0); return Interval::fromSeconds(n*10+20); } void GD73Codeplug::SettingsElement::setTOT(const Interval &interval) { Interval intv = Limit::tot().map(interval); if (intv.seconds()<20) setUInt8(Offset::tot(), 0); setUInt8(Offset::tot(), (intv.seconds()-20)/10); } void GD73Codeplug::SettingsElement::clearTOT() { setUInt8(Offset::tot(), 0); } bool GD73Codeplug::SettingsElement::txInterruptedEnabled() const { return 0 != getUInt8(Offset::txInterrupt()); } void GD73Codeplug::SettingsElement::enableTXInterrupt(bool enable) { setUInt8(Offset::txInterrupt(), enable ? 0x01 : 0x00); } bool GD73Codeplug::SettingsElement::powerSaveEnabled() const { return 0x00 != getUInt8(Offset::powerSave()); } void GD73Codeplug::SettingsElement::enablePowerSave(bool enable) { setUInt8(Offset::powerSave(), enable ? 0x01 : 0x00); } Interval GD73Codeplug::SettingsElement::powerSaveTimeout() const { return Interval::fromSeconds(getUInt8(Offset::powerSaveTimeout())); } void GD73Codeplug::SettingsElement::setPowerSaveTimeout(const Interval &interval) { Interval intv = Limit::powerSaveTimeout().map(interval); setUInt8(Offset::powerSaveTimeout(), intv.seconds()); } bool GD73Codeplug::SettingsElement::readLockEnabled() const { return 0x00 != getUInt8(Offset::readLockEnable()); } void GD73Codeplug::SettingsElement::enableReadLock(bool enable) { setUInt8(Offset::readLockEnable(), enable ? 0x01 : 0x00); } QString GD73Codeplug::SettingsElement::readLockPin() const { return readASCII(Offset::readLockPin(), Limit::pin(), 0x00); } void GD73Codeplug::SettingsElement::setReadLockPin(const QString &pin) { writeASCII(Offset::readLockPin(), pin, Limit::pin(), 0x00); } bool GD73Codeplug::SettingsElement::writeLockEnabled() const { return 0x00 != getUInt8(Offset::writeLockEnable()); } void GD73Codeplug::SettingsElement::enableWriteLock(bool enable) { setUInt8(Offset::writeLockEnable(), enable ? 0x01 : 0x00); } QString GD73Codeplug::SettingsElement::writeLockPin() const { return readASCII(Offset::writeLockPin(), Limit::pin(), 0x00); } void GD73Codeplug::SettingsElement::setWriteLockPin(const QString &pin) { writeASCII(Offset::writeLockPin(), pin, Limit::pin(), 0x00); } GD73Codeplug::SettingsElement::ChannelDisplayMode GD73Codeplug::SettingsElement::channelDisplayMode() const { return (ChannelDisplayMode)getUInt8(Offset::channelDisplayMode()); } void GD73Codeplug::SettingsElement::setChannelDisplayMode(ChannelDisplayMode mode) { setUInt8(Offset::channelDisplayMode(), (unsigned int)mode); } Level GD73Codeplug::SettingsElement::dmrMicGain() const { return Level::fromValue(getUInt8(Offset::dmrMicGain())+1, Limit::micGain()); } void GD73Codeplug::SettingsElement::setDMRMicGain(Level gain) { setUInt8(Offset::dmrMicGain(), gain.mapTo(Limit::micGain())-1); } Level GD73Codeplug::SettingsElement::fmMicGain() const { return Level::fromValue(getUInt8(Offset::fmMicGain())+1, Limit::micGain()); } void GD73Codeplug::SettingsElement::setFMMicGain(Level gain) { setUInt8(Offset::fmMicGain(), gain.mapTo(Limit::micGain())-1); } Interval GD73Codeplug::SettingsElement::loneWorkerResponseTimeout() const { return Interval::fromMinutes(getUInt16_le(Offset::loneWorkerResponseTimeout())); } void GD73Codeplug::SettingsElement::setLoneWorkerResponseTimeout(const Interval &interval) { Interval intv = Limit::loneWorkerResponse().map(interval); setUInt16_le(Offset::loneWorkerResponseTimeout(), intv.minutes()); } Interval GD73Codeplug::SettingsElement::loneWorkerRemindPeriod() const { return Interval::fromSeconds(getUInt16_le(Offset::loneWorkerReminderPeriod())); } void GD73Codeplug::SettingsElement::setLoneWorkerRemindPeriod(const Interval &interval) { Interval intv = Limit::loneWorkerRemindPeriod().map(interval); setUInt16_le(Offset::loneWorkerReminderPeriod(), intv.seconds()); } BootSettings::BootDisplay GD73Codeplug::SettingsElement::bootDisplayMode() const { switch ((BootDisplayMode)getUInt8(Offset::bootDisplayMode())) { case BootDisplayMode::Off: return BootSettings::BootDisplay::Logo; case BootDisplayMode::Text: return BootSettings::BootDisplay::Text; case BootDisplayMode::Both: case BootDisplayMode::Image: return BootSettings::BootDisplay::Image; } return BootSettings::BootDisplay::Logo; } void GD73Codeplug::SettingsElement::setBootDisplayMode(BootSettings::BootDisplay mode) { switch (mode) { case BootSettings::BootDisplay::Logo: setUInt8(Offset::bootDisplayMode(), (unsigned int)BootDisplayMode::Off); break; case BootSettings::BootDisplay::Text: setUInt8(Offset::bootDisplayMode(), (unsigned int)BootDisplayMode::Text); break; case BootSettings::BootDisplay::Image: setUInt8(Offset::bootDisplayMode(), (unsigned int)BootDisplayMode::Image); break; } } QString GD73Codeplug::SettingsElement::bootTextLine1() const { return readUnicode(Offset::bootTextLine1(), Limit::bootTextLine(), 0x0000); } void GD73Codeplug::SettingsElement::setBootTextLine1(const QString &line) { writeUnicode(Offset::bootTextLine1(), line, Limit::bootTextLine(), 0x0000); } QString GD73Codeplug::SettingsElement::bootTextLine2() const { return readUnicode(Offset::bootTextLine2(), Limit::bootTextLine(), 0x0000); } void GD73Codeplug::SettingsElement::setBootTextLine2(const QString &line) { writeUnicode(Offset::bootTextLine2(), line, Limit::bootTextLine(), 0x0000); } bool GD73Codeplug::SettingsElement::keyToneEnabled() const { return 0x00 != getUInt8(Offset::keyToneEnable()); } void GD73Codeplug::SettingsElement::enableKeyTone(bool enable) { setUInt8(Offset::keyToneEnable(), enable ? 0x01 : 0x00); } Level GD73Codeplug::SettingsElement::keyToneVolume() const { return Level::fromValue(getUInt8(Offset::keyToneVolume()), Limit::toneVolume()); } void GD73Codeplug::SettingsElement::setKeyToneVolume(Level vol) { setUInt8(Offset::keyToneVolume(), vol.mapTo(Limit::toneVolume())); } bool GD73Codeplug::SettingsElement::lowBatteryToneEnabled() const { return 0x00 != getUInt8(Offset::lowBatToneEnable()); } void GD73Codeplug::SettingsElement::enableLowBatteryTone(bool enable) { setUInt8(Offset::lowBatToneEnable(), enable ? 0x01 : 0x00); } unsigned int GD73Codeplug::SettingsElement::lowBatteryToneVolume() const { return getUInt8(Offset::lowBatToneVolume()); } void GD73Codeplug::SettingsElement::setLowBatteryToneVolume(unsigned int vol) { setUInt8(Offset::lowBatToneVolume(), Level::fromValue(vol).mapTo(Limit::toneVolume())); } Interval GD73Codeplug::SettingsElement::longPressDuration() const { return Interval::fromMilliseconds(500*((unsigned int)getUInt8(Offset::longPressDuration()))); } void GD73Codeplug::SettingsElement::setLongPressDuration(const Interval &interval) { Interval intv = Limit::longPressDuration().map(interval); setUInt8(Offset::longPressDuration(), intv.milliseconds()/500); } RadioddityButtonSettingsExtension::Function GD73Codeplug::SettingsElement::keyFunctionLongPressP1() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1LongPress())); } void GD73Codeplug::SettingsElement::setKeyFunctionLongPressP1(RadioddityButtonSettingsExtension::Function function) { setUInt8(Offset::progFuncKey1LongPress(), KeyFunction::encode(function)); } RadioddityButtonSettingsExtension::Function GD73Codeplug::SettingsElement::keyFunctionShortPressP1() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey1ShortPress())); } void GD73Codeplug::SettingsElement::setKeyFunctionShortPressP1(RadioddityButtonSettingsExtension::Function function) { setUInt8(Offset::progFuncKey1ShortPress(), KeyFunction::encode(function)); } RadioddityButtonSettingsExtension::Function GD73Codeplug::SettingsElement::keyFunctionLongPressP2() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2LongPress())); } void GD73Codeplug::SettingsElement::setKeyFunctionLongPressP2(RadioddityButtonSettingsExtension::Function function) { setUInt8(Offset::progFuncKey2LongPress(), KeyFunction::encode(function)); } RadioddityButtonSettingsExtension::Function GD73Codeplug::SettingsElement::keyFunctionShortPressP2() const { return KeyFunction::decode(getUInt8(Offset::progFuncKey2ShortPress())); } void GD73Codeplug::SettingsElement::setKeyFunctionShortPressP2(RadioddityButtonSettingsExtension::Function function) { setUInt8(Offset::progFuncKey2ShortPress(), KeyFunction::encode(function)); } GD73Codeplug::OneTouchSettingElement GD73Codeplug::SettingsElement::oneTouch(unsigned int n) { return OneTouchSettingElement( _data+Offset::oneTouchSettings() + n*Offset::betweenOneTouchSettings()); } bool GD73Codeplug::SettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Create radio ID DMRRadioID *radioID = new DMRRadioID(name(), dmrID()); ctx.config()->radioIDs()->add(radioID); ctx.config()->settings()->setDefaultId(radioID); ctx.add(radioID, 0); // Apply settings ctx.config()->settings()->boot()->setMessage1(bootTextLine1()); ctx.config()->settings()->boot()->setMessage2(bootTextLine2()); // Audio settings ctx.config()->settings()->audio()->setMicGain(dmrMicGain()); if (dmrMicGain() == fmMicGain()) ctx.config()->settings()->audio()->disableFMMicGain(); else ctx.config()->settings()->audio()->setFMMicGain(fmMicGain()); ctx.config()->settings()->audio()->setSquelch(squelch()); ctx.config()->settings()->audio()->setVox(vox()); if (! totIsSet()) ctx.config()->settings()->disableTOT(); else ctx.config()->settings()->setTOT(tot()); ctx.config()->settings()->boot()->setBootDisplay(bootDisplayMode()); ctx.config()->settings()->boot()->enableBootPassword(readLockEnabled()); ctx.config()->settings()->boot()->setBootPassword(readLockPin()); if (! keyToneEnabled()) ctx.config()->settings()->tone()->disableKeyTone(); else ctx.config()->settings()->tone()->setKeyToneVolume(keyToneVolume()); // Get/add radioddity settings extension RadiodditySettingsExtension *ext = ctx.config()->settings()->radioddityExtension(); if (nullptr == ext) ctx.config()->settings()->setRadioddityExtension(ext = new RadiodditySettingsExtension()); ext->setLoneWorkerResponseTime(loneWorkerResponseTimeout()); ext->setLoneWorkerReminderPeriod(loneWorkerRemindPeriod()); ext->enableTXInterrupt(txInterruptedEnabled()); switch(language()) { case Language::Chinese: ext->setLanguage(RadiodditySettingsExtension::Language::Chinese); break; case Language::English: ext->setLanguage(RadiodditySettingsExtension::Language::English); break; } ext->enablePowerSaveMode(powerSaveEnabled()); ext->setPowerSaveDelay(powerSaveTimeout()); ext->buttons()->setLongPressDuration(longPressDuration()); ext->buttons()->setFuncKey1Short(keyFunctionShortPressP1()); ext->buttons()->setFuncKey1Long(keyFunctionLongPressP1()); ext->buttons()->setFuncKey2Short(keyFunctionShortPressP2()); ext->buttons()->setFuncKey2Long(keyFunctionLongPressP2()); ext->tone()->enableLowBatteryWarn(lowBatteryToneEnabled()); ext->tone()->setLowBatteryWarnVolume(lowBatteryToneVolume()); if (writeLockEnabled()) ext->boot()->setProgPassword(writeLockPin()); else ext->boot()->setProgPassword(""); return true; } bool GD73Codeplug::SettingsElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Get default radio ID if (nullptr == ctx.config()->settings()->defaultId()) { errMsg(err) << "Cannot encode default radio DMR ID, none is set."; return false; } setDMRID(ctx.config()->settings()->defaultId()->number()); setName(ctx.config()->settings()->defaultId()->name()); // Apply settings setBootTextLine1(ctx.config()->settings()->boot()->message1()); setBootTextLine2(ctx.config()->settings()->boot()->message2()); setDMRMicGain(ctx.config()->settings()->audio()->micGain()); setFMMicGain(ctx.config()->settings()->audio()->micGain()); if (ctx.config()->settings()->audio()->fmMicGainEnabled()) setFMMicGain(ctx.config()->settings()->audio()->fmMicGain()); setSquelch(ctx.config()->settings()->audio()->squelch()); setVOX(ctx.config()->settings()->audio()->vox()); enableKeyTone(ctx.config()->settings()->tone()->keyToneEnabled()); if (ctx.config()->settings()->tone()->keyToneEnabled()) setKeyToneVolume(ctx.config()->settings()->tone()->keyToneVolume()); if (ctx.config()->settings()->totDisabled()) clearTOT(); else setTOT(ctx.config()->settings()->tot()); setLanguage(Language::English); setUInt8(0x003c, 0x01); setUInt8(0x003e, 0x01); setBootDisplayMode(ctx.config()->settings()->boot()->bootDisplay()); enableReadLock(ctx.config()->settings()->boot()->bootPasswordEnabled()); setReadLockPin(ctx.config()->settings()->boot()->bootPassword()); // Get/add radioddity settings extension RadiodditySettingsExtension *ext = ctx.config()->settings()->radioddityExtension(); if (nullptr == ext) return true; setLoneWorkerResponseTimeout(ext->loneWorkerResponseTime()); setLoneWorkerRemindPeriod(ext->loneWorkerReminderPeriod()); enableTXInterrupt(ext->txInterrupt()); switch(ext->language()) { case RadiodditySettingsExtension::Language::Chinese: setLanguage(Language::Chinese); break; case RadiodditySettingsExtension::Language::English: setLanguage(Language::English); break; } enablePowerSave(ext->powerSaveMode()); setPowerSaveTimeout(ext->powerSaveDelay()); setLongPressDuration(ext->buttons()->longPressDuration()); setKeyFunctionShortPressP1(ext->buttons()->funcKey1Short()); setKeyFunctionLongPressP1(ext->buttons()->funcKey1Long()); setKeyFunctionShortPressP2(ext->buttons()->funcKey2Short()); setKeyFunctionLongPressP2(ext->buttons()->funcKey2Long()); enableLowBatteryTone(ext->tone()->lowBatteryWarn()); setLowBatteryToneVolume(ext->tone()->lowBatteryWarnVolume()); if (! ext->boot()->progPassword().isEmpty()) { setWriteLockPin(ext->boot()->progPassword()); enableWriteLock(true); } else { enableWriteLock(false); } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::OneTouchSettingElement * ********************************************************************************************* */ GD73Codeplug::OneTouchSettingElement::OneTouchSettingElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::OneTouchSettingElement::OneTouchSettingElement(uint8_t *ptr) : Element(ptr, OneTouchSettingElement::size()) { // pass... } /* ********************************************************************************************* * * Implementation of GD73Codeplug::DMRSettingsElement * ********************************************************************************************* */ GD73Codeplug::DMRSettingsElement::DMRSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::DMRSettingsElement::DMRSettingsElement(uint8_t *ptr) : Element(ptr, DMRSettingsElement::size()) { // pass... } Interval GD73Codeplug::DMRSettingsElement::callHangTime() const { return Interval::fromSeconds(getUInt8(Offset::callHangTime())+1); } void GD73Codeplug::DMRSettingsElement::setCallHangTime(const Interval &intv) { setUInt8(Offset::callHangTime(), Limit::callHangTime().map(intv).seconds()-1); } Interval GD73Codeplug::DMRSettingsElement::activeWaitTime() const { return Interval::fromMilliseconds(120 + ((unsigned int)getUInt8(Offset::activeWaitTime()))*5); } void GD73Codeplug::DMRSettingsElement::setActiveWaitTime(const Interval &intv) { setUInt8(Offset::activeWaitTime(), (Limit::activeWaitTime().map(intv).milliseconds()-120)/5); } unsigned int GD73Codeplug::DMRSettingsElement::activeRetries() const { return getUInt8(Offset::activeRetries()); } void GD73Codeplug::DMRSettingsElement::setActiveRetries(unsigned int count) { setUInt8(Offset::activeRetries(), Limit::activeRetires().map(count)); } unsigned int GD73Codeplug::DMRSettingsElement::txPreambles() const { return getUInt8(Offset::txPreambles()); } void GD73Codeplug::DMRSettingsElement::setTXPreambles(unsigned int count) { setUInt8(Offset::txPreambles(), Limit::txPreambles().map(count)); } bool GD73Codeplug::DMRSettingsElement::decodeDisableRadioEnabled() const { return 0x00 != getUInt8(Offset::decodeDisableRadio()); } void GD73Codeplug::DMRSettingsElement::enableDecodeDisableRadio(bool enable) { setUInt8(Offset::decodeDisableRadio(), enable ? 0x01 : 0x00); } bool GD73Codeplug::DMRSettingsElement::decodeRadioCheckEnabled() const { return 0x00 != getUInt8(Offset::decodeCheckRadio()); } void GD73Codeplug::DMRSettingsElement::enableDecodeRadioCheck(bool enable) { setUInt8(Offset::decodeCheckRadio(), enable ? 0x01 : 0x00); } bool GD73Codeplug::DMRSettingsElement::decodeEnableRadioEnabled() const { return 0x00 != getUInt8(Offset::decodeEnableRadio()); } void GD73Codeplug::DMRSettingsElement::enableDecodeEnableRadio(bool enable) { setUInt8(Offset::decodeEnableRadio(), enable ? 0x01 : 0x00); } bool GD73Codeplug::DMRSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); ctx.config()->settings()->dmr()->setPrivateCallHangTime(callHangTime()); ctx.config()->settings()->dmr()->setGroupCallHangTime(callHangTime()); return true; } bool GD73Codeplug::DMRSettingsElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setCallHangTime(std::max(ctx.config()->settings()->dmr()->privateCallHangTime(), ctx.config()->settings()->dmr()->groupCallHangTime())); return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::EncryptionKeyElement * ********************************************************************************************* */ GD73Codeplug::EncryptionKeyElement::EncryptionKeyElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::EncryptionKeyElement::EncryptionKeyElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void GD73Codeplug::EncryptionKeyElement::clear() { setKeySize(0); } bool GD73Codeplug::EncryptionKeyElement::isValid() const { return Element::isValid() && (0 != keySize()); } unsigned int GD73Codeplug::EncryptionKeyElement::keySize() const { return getUInt8(Offset::size())*4; } void GD73Codeplug::EncryptionKeyElement::setKeySize(unsigned int size) { setUInt8(Offset::size(), size/4); } BasicEncryptionKey * GD73Codeplug::EncryptionKeyElement::createEncryptionKey(const ErrorStack &err) const { if (! isValid()) return nullptr; BasicEncryptionKey *key = new BasicEncryptionKey(); if (! key->setKey(QByteArray((const char*)_data+Offset::key(), keySize()/8))) { errMsg(err) << "Cannot decode encryption key of size " << keySize() << "."; delete key; return nullptr; } return key; } bool GD73Codeplug::EncryptionKeyElement::encodeEncryptionKey(BasicEncryptionKey *key, const ErrorStack &err) { unsigned int size = key->key().size()*8; if (size > 32) { errMsg(err) << "Key size of " << size << " exceeds 32bit."; return false; } setKeySize(size); memcpy(_data+Offset::key(), key->key().constData(), key->key().size()); return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::EncryptionKeyBankElement * ********************************************************************************************* */ GD73Codeplug::EncryptionKeyBankElement::EncryptionKeyBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::EncryptionKeyBankElement::EncryptionKeyBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } bool GD73Codeplug::EncryptionKeyBankElement::createEncryptionKeys(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; isetName(QString("Basic Key %1").arg(i+1)); ctx.add(key, i); } return true; } bool GD73Codeplug::EncryptionKeyBankElement::encodeEncryptionKeys(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i= ctx.count()) continue; if (! keyElement.encodeEncryptionKey(ctx.get(i))) { errMsg(err) << "Cannot encode " << i+1 << "-th encryption key."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::MessageElement * ********************************************************************************************* */ GD73Codeplug::MessageElement::MessageElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::MessageElement::MessageElement(uint8_t *ptr) : Element(ptr, GD73Codeplug::MessageElement::size()) { // pass... } QString GD73Codeplug::MessageElement::text() const { unsigned int length = std::min(Limit::messageLength(), (unsigned int)getUInt8(Offset::size())); return readUnicode(Offset::text(), length, 0x0000); } void GD73Codeplug::MessageElement::setText(const QString &message) { unsigned int length = std::min(Limit::messageLength(), (unsigned int)message.length()); writeUnicode(Offset::text(), message, length, 0x0000); setUInt8(Offset::size(), length); } bool GD73Codeplug::MessageElement::encode(SMSTemplate *message, const ErrorStack &err) { Q_UNUSED(err); setText(message->message()); return true; } SMSTemplate * GD73Codeplug::MessageElement::decode(const ErrorStack &err) { Q_UNUSED(err); SMSTemplate *sms = new SMSTemplate(); sms->setName("Message"); sms->setMessage(text()); return sms; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::MessageBankElement * ********************************************************************************************* */ GD73Codeplug::MessageBankElement::MessageBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::MessageBankElement::MessageBankElement(uint8_t *ptr) : Element(ptr, MessageBankElement::size()) { // pass... } unsigned int GD73Codeplug::MessageBankElement::memberCount() const { return getUInt8(Offset::memberCount()); } void GD73Codeplug::MessageBankElement::setMemberCount(unsigned int count) { setUInt8(Offset::memberCount(), std::min(Limit::memberCount(), count)); } GD73Codeplug::MessageElement GD73Codeplug::MessageBankElement::message(unsigned int i) { i = std::min(i, memberCount()-1); return MessageElement(_data + Offset::members() + i*Offset::betweenMembers()); } bool GD73Codeplug::MessageBankElement::decode(SMSExtension *ext, const ErrorStack &err) { ext->smsTemplates()->clear(); for (unsigned int i=0; isetName(QString("Message %1").arg(i+1)); ext->smsTemplates()->add(sms); } return true; } bool GD73Codeplug::MessageBankElement::encode(const SMSExtension *ext, const ErrorStack &err) { setMemberCount(0); unsigned int count = std::min((unsigned int) ext->smsTemplates()->count(), Limit::memberCount()); for (unsigned int i=0; ismsTemplates()->get(i)->as(), err)) { errMsg(err) << "Cannot encode " << i+1 << "-th preset message."; return false; } } setMemberCount(count); return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::ContactBankElement * ********************************************************************************************* */ GD73Codeplug::ContactBankElement::ContactBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::ContactBankElement::ContactBankElement(uint8_t *ptr) : Element(ptr, ContactBankElement::size()) { // pass... } bool GD73Codeplug::ContactBankElement::createContacts(Context &ctx, const ErrorStack &err) { unsigned int count = std::min((unsigned int)getUInt16_le(Offset::contactCount()), Limit::contactCount()); for (unsigned int i=0; icontacts()->add(contactObj); ctx.add(contactObj, i); } return true; } bool GD73Codeplug::ContactBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::contactCount()); setUInt16_le(Offset::contactCount(), count); for (unsigned int i=0; i(i)) { errMsg(err) << "Contact at index " << i << " not indexed."; errMsg(err) << "Cannot encode contact bank."; return false; } DMRContact *contactObj = ctx.get(i); if (! contact.encode(contactObj, ctx, err)) { errMsg(err) << "Cannot encode contact at index " << i << "."; errMsg(err) << "Cannot encode contact bank."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::ContactElement * ********************************************************************************************* */ GD73Codeplug::ContactElement::ContactElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::ContactElement::ContactElement(uint8_t *ptr) : Element(ptr, ContactElement::size()) { // pass... } QString GD73Codeplug::ContactElement::name() const { return readUnicode(Offset::name(), Limit::nameLength(), 0x0000); } void GD73Codeplug::ContactElement::setName(const QString &name) { writeUnicode(Offset::name(), name, Limit::nameLength(), 0x0000); } DMRContact::Type GD73Codeplug::ContactElement::type() const { switch (getUInt8(Offset::type())) { case 0: return DMRContact::GroupCall; case 1: return DMRContact::PrivateCall; case 2: return DMRContact::AllCall; } return DMRContact::PrivateCall; } void GD73Codeplug::ContactElement::setType(DMRContact::Type type) { switch (type) { case DMRContact::GroupCall: setUInt8(Offset::type(), 0); break; case DMRContact::PrivateCall: setUInt8(Offset::type(), 1); break; case DMRContact::AllCall: setUInt8(Offset::type(), 2); break; } } unsigned int GD73Codeplug::ContactElement::id() const { return getUInt32_le(Offset::id()); } void GD73Codeplug::ContactElement::setID(unsigned int id) { setUInt32_le(Offset::id(), id); } DMRContact * GD73Codeplug::ContactElement::toContact(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return new DMRContact(type(), name(), id()); } bool GD73Codeplug::ContactElement::encode(const DMRContact *contact, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); setType(contact->type()); setName(contact->name()); setID(contact->number()); return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::GroupListBankElement * ********************************************************************************************* */ GD73Codeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr) : Element(ptr, GroupListBankElement::size()) { // pass... } bool GD73Codeplug::GroupListBankElement::createGroupLists(Context &ctx, const ErrorStack &err) { unsigned int count = std::min((unsigned int)getUInt8(Offset::memberCount()), Limit::memberCount()); for (unsigned int i=0; irxGroupLists()->add(glObj); ctx.add(glObj, i); } return true; } bool GD73Codeplug::GroupListBankElement::linkGroupLists(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::memberCount()); for (unsigned int i=0; i(i); if (! gl.linkGroupList(glObj, ctx, err)) { errMsg(err) << "Cannot link " << i << "-th group list."; return false; } } return true; } bool GD73Codeplug::GroupListBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::memberCount()); setUInt8(Offset::memberCount(), count); for (unsigned int i=0; i(i)) { errMsg(err) << "Group list at index " << i << " is not indexed."; return false; } if (!gl.encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode group list at index " << i << "."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::GroupListElement * ********************************************************************************************* */ GD73Codeplug::GroupListElement::GroupListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::GroupListElement::GroupListElement(uint8_t *ptr) : Element(ptr, GroupListElement::size()) { // pass... } QString GD73Codeplug::GroupListElement::name() const { return readUnicode(Offset::name(), Limit::nameLength(), 0x0000); } void GD73Codeplug::GroupListElement::setName(const QString &name) { writeUnicode(Offset::name(), name, Limit::nameLength(), 0x0000); } unsigned int GD73Codeplug::GroupListElement::members() const { return getUInt8(Offset::memberCount()); } bool GD73Codeplug::GroupListElement::hasMember(unsigned int i) const { return 0 != getUInt16_le(Offset::members() + i*Offset::betweenMembers()); } unsigned int GD73Codeplug::GroupListElement::memberIndex(unsigned int i) const { return getUInt16_le(Offset::members() + i*Offset::betweenMembers())-1; } RXGroupList * GD73Codeplug::GroupListElement::toGroupList(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return new RXGroupList(name()); } bool GD73Codeplug::GroupListElement::linkGroupList(RXGroupList *lst, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); unsigned int count = std::min(members(), Limit::memberCount()); for (unsigned int i=0; i(idx)) lst->addContact(ctx.get(idx)); else logWarn() << "Cannot link group list '" << lst->name() << "': Cannot resolve contact at index " << idx << "."; } return true; } bool GD73Codeplug::GroupListElement::encode(RXGroupList *lst, Context &ctx, const ErrorStack &err) { unsigned int count = std::min((unsigned int)lst->count(), Limit::memberCount()); setName(lst->name()); setUInt8(Offset::memberCount(), count); for (unsigned int i=0; i ctx.index(lst->contact(i))) { errMsg(err) << "Cannot resolve index of contact '" << lst->contact(i)->name() << "'."; return false; } setUInt16_le(Offset::members() + i*Offset::betweenMembers(), ctx.index(lst->contact(i))+1); } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::ChannelBankElement * ********************************************************************************************* */ GD73Codeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr) : Element(ptr, ChannelBankElement::size()) { // pass... } bool GD73Codeplug::ChannelBankElement::createChannels(Context &ctx, const ErrorStack &err) { unsigned int count = std::min((unsigned int)getUInt8(Offset::channelCount()), Limit::channelCount()); for (unsigned int i=0; ichannelList()->add(chObj); ctx.add(chObj, i); } return true; } bool GD73Codeplug::ChannelBankElement::linkChannels(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::channelCount()); for (unsigned int i=0; i(i); if (! ch.linkChannel(chObj, ctx, err)) { errMsg(err) << "Cannot link channel at index " << i << "."; return false; } } return true; } bool GD73Codeplug::ChannelBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::channelCount()); setUInt16_le(Offset::channelCount(), count); for (unsigned int i=0; i(i); if (! ch.encode(chObj, ctx, err)) { errMsg(err) << "Cannot encode channel at index " << i << "."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::ChannelElement * ********************************************************************************************* */ GD73Codeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::ChannelElement::ChannelElement(uint8_t *ptr) : Element(ptr, ChannelElement::size()) { // pass... } QString GD73Codeplug::ChannelElement::name() const { return readUnicode(Offset::name(), Limit::nameLength(), 0x0000); } void GD73Codeplug::ChannelElement::setName(const QString &name) { writeUnicode(Offset::name(), name, Limit::nameLength(), 0x0000); } FMChannel::Bandwidth GD73Codeplug::ChannelElement::bandwidth() const { switch (getUInt8(Offset::bandwidth())) { case 0: return FMChannel::Bandwidth::Narrow; case 1: return FMChannel::Bandwidth::Wide; } return FMChannel::Bandwidth::Narrow; } void GD73Codeplug::ChannelElement::setBandwidth(FMChannel::Bandwidth bandwidth) { switch (bandwidth) { case FMChannel::Bandwidth::Narrow: setUInt8(Offset::bandwidth(), 0); break; case FMChannel::Bandwidth::Wide: setUInt8(Offset::bandwidth(), 1); break; } } bool GD73Codeplug::ChannelElement::hasScanListIndex() const { return 0 != getUInt8(Offset::scanList()); } unsigned int GD73Codeplug::ChannelElement::scanListIndex() const { return getUInt8(Offset::scanList())-1; } void GD73Codeplug::ChannelElement::setScanListIndex(unsigned int idx) { setUInt8(Offset::scanList(), idx+1); } void GD73Codeplug::ChannelElement::clearScanListIndex() { setUInt8(Offset::scanList(), 0); } GD73Codeplug::ChannelElement::Type GD73Codeplug::ChannelElement::type() const { return (Type) getUInt8(Offset::channelType()); } void GD73Codeplug::ChannelElement::setType(Type type) { setUInt8(Offset::channelType(), (unsigned int)type); } bool GD73Codeplug::ChannelElement::talkaroundEnabled() const { return 0x00 != getUInt8(Offset::talkaround()); } void GD73Codeplug::ChannelElement::enableTalkaround(bool enable) { setUInt8(Offset::talkaround(), enable ? 0x01 : 0x00); } bool GD73Codeplug::ChannelElement::rxOnly() const { return 0x00 != getUInt8(Offset::rxOnly()); } void GD73Codeplug::ChannelElement::enableRXOnly(bool enable) { setUInt8(Offset::rxOnly(), enable ? 0x01 : 0x00); } bool GD73Codeplug::ChannelElement::scanAutoStartEnabled() const { return 0x00 != getUInt8(Offset::scanAutoStart()); } void GD73Codeplug::ChannelElement::enableScanAutoStart(bool enable) { setUInt8(Offset::scanAutoStart(), enable ? 0x01 : 0x00); } Frequency GD73Codeplug::ChannelElement::rxFrequency() const { return Frequency::fromHz(getUInt32_le(Offset::rxFrequency())); } void GD73Codeplug::ChannelElement::setRXFrequency(const Frequency &f) { setUInt32_le(Offset::rxFrequency(), f.inHz()); } Frequency GD73Codeplug::ChannelElement::txFrequency() const { return Frequency::fromHz(getUInt32_le(Offset::txFrequency())); } void GD73Codeplug::ChannelElement::setTXFrequency(const Frequency &f) { setUInt32_le(Offset::txFrequency(), f.inHz()); } bool GD73Codeplug::ChannelElement::hasDTMFPTTSettingsIndex() const { return 0x00 != getUInt8(Offset::dtmfPTTSettingsIndex()); } unsigned int GD73Codeplug::ChannelElement::dtmfPTTSettingsIndex() const { return getUInt8(Offset::dtmfPTTSettingsIndex())-1; } void GD73Codeplug::ChannelElement::setDTMFPTTSettingsIndex(unsigned int idx) { setUInt8(Offset::dtmfPTTSettingsIndex(), idx+1); } void GD73Codeplug::ChannelElement::clearDTMFPTTSettingsIndex() { setUInt8(Offset::dtmfPTTSettingsIndex(), 0); } Channel::Power GD73Codeplug::ChannelElement::power() const { switch (getUInt8(Offset::power())) { case 0: return Channel::Power::Low; case 1: return Channel::Power::High; } return Channel::Power::Low; } void GD73Codeplug::ChannelElement::setPower(Channel::Power power) { switch (power) { case Channel::Power::Min: case Channel::Power::Low: case Channel::Power::Mid: setUInt8(Offset::power(), 0); break; case Channel::Power::High: case Channel::Power::Max: setUInt8(Offset::power(), 1); break; } } GD73Codeplug::ChannelElement::Admit GD73Codeplug::ChannelElement::admit() const { return (Admit) getUInt8(Offset::admid()); } void GD73Codeplug::ChannelElement::setAdmit(Admit admit) { setUInt8(Offset::admid(), (unsigned int) admit); } SelectiveCall GD73Codeplug::ChannelElement::rxTone() const { int mode = getUInt8(Offset::rxToneMode()); int ctcss_code = getUInt8(Offset::rxCTCSS()); int dcs_code = getUInt8(Offset::rxDCS()); if (0 == mode) return SelectiveCall(); if (1 == mode) { if (ctcss_code >= _ctcss_codes.size()) return SelectiveCall(); return _ctcss_codes[ctcss_code]; } if (dcs_code >= _dcs_codes.size()) return SelectiveCall(); return SelectiveCall(_dcs_codes[dcs_code], 3 == mode); } void GD73Codeplug::ChannelElement::setRXTone(const SelectiveCall &code) { int mode = 0, ctcss_code = 0, dcs_code = 0; if (code.isCTCSS()) { mode = 1; ctcss_code = _ctcss_codes.indexOf(code); } else if (code.isDCS()) { if (code.isInverted()) mode = 3; else mode = 2; dcs_code = _dcs_codes.indexOf(code.octalCode()); } setUInt8(Offset::rxToneMode(), mode); setUInt8(Offset::rxCTCSS(), ctcss_code); setUInt8(Offset::rxDCS(), dcs_code); } SelectiveCall GD73Codeplug::ChannelElement::txTone() const { int mode = getUInt8(Offset::txToneMode()); int ctcss_code = getUInt8(Offset::txCTCSS()); int dcs_code = getUInt8(Offset::txDCS()); if (0 == mode) return SelectiveCall(); if (1 == mode) { if (ctcss_code >= _ctcss_codes.size()) return SelectiveCall(); return _ctcss_codes[ctcss_code]; } if (dcs_code >= _dcs_codes.size()) return SelectiveCall(); return SelectiveCall(_dcs_codes[dcs_code], 3 == mode); } void GD73Codeplug::ChannelElement::setTXTone(const SelectiveCall &code) { int mode = 0, ctcss_code = 0, dcs_code = 0; if (code.isCTCSS()) { mode = 1; ctcss_code = _ctcss_codes.indexOf(code); } else if (code.isDCS()) { if (code.isInverted()) mode = 3; else mode = 2; dcs_code = _dcs_codes.indexOf(code.octalCode()); } setUInt8(Offset::txToneMode(), mode); setUInt8(Offset::txCTCSS(), ctcss_code); setUInt8(Offset::txDCS(), dcs_code); } DMRChannel::TimeSlot GD73Codeplug::ChannelElement::timeSlot() const { switch (getUInt8(Offset::timeslot())) { case 0: case 2: return DMRChannel::TimeSlot::TS1; case 1: case 3: return DMRChannel::TimeSlot::TS2; } return DMRChannel::TimeSlot::TS1; } void GD73Codeplug::ChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { switch (ts) { case DMRChannel::TimeSlot::TS1: setUInt8(Offset::timeslot(), 0); break; case DMRChannel::TimeSlot::TS2: setUInt8(Offset::timeslot(), 1); break; } } unsigned int GD73Codeplug::ChannelElement::colorCode() const { return getUInt8(Offset::colorcode()); } void GD73Codeplug::ChannelElement::setColorCode(unsigned int cc) { setUInt8(Offset::colorcode(), std::min(15u, cc)); } bool GD73Codeplug::ChannelElement::groupListMatchesContact() const { return 0 == getUInt8(Offset::groupListIndex()); } bool GD73Codeplug::ChannelElement::groupListAllMatch() const { return 1 == getUInt8(Offset::groupListIndex()); } unsigned int GD73Codeplug::ChannelElement::groupListIndex() const { return getUInt8(Offset::groupListIndex())-2; } void GD73Codeplug::ChannelElement::setGroupListIndex(unsigned int idx) { setUInt8(Offset::groupListIndex(), idx+2); } void GD73Codeplug::ChannelElement::setGroupListAllMatch() { setUInt8(Offset::groupListIndex(), 1); } void GD73Codeplug::ChannelElement::setGroupListMatchesContact() { setUInt8(Offset::groupListIndex(), 0); } bool GD73Codeplug::ChannelElement::hasTXContact() const { return 0 != getUInt16_le(Offset::contactIndex()); } unsigned int GD73Codeplug::ChannelElement::txContactIndex() const { return getUInt16_le(Offset::contactIndex())-1; } void GD73Codeplug::ChannelElement::setTXContactIndex(unsigned int idx) { setUInt16_le(Offset::contactIndex(), idx+1); } void GD73Codeplug::ChannelElement::clearTXContactIndex() { setUInt16_le(Offset::contactIndex(), 0); } bool GD73Codeplug::ChannelElement::hasEmergencySystemIndex() const { return 0 != getUInt8(Offset::emergencySystemIndex()); } unsigned int GD73Codeplug::ChannelElement::emergencySystemIndex() const { return getUInt8(Offset::emergencySystemIndex())-1; } void GD73Codeplug::ChannelElement::setEmergencySystemIndex(unsigned int idx) { setUInt8(Offset::emergencySystemIndex(), idx+1); } void GD73Codeplug::ChannelElement::clearEmergencySystemIndex() { setUInt8(Offset::emergencySystemIndex(), 0); } bool GD73Codeplug::ChannelElement::hasEncryptionKeyIndex() const { return 0 != getUInt8(Offset::encryptionKeyIndex()); } unsigned int GD73Codeplug::ChannelElement::encryptionKeyIndex() const { return getUInt8(Offset::encryptionKeyIndex())-1; } void GD73Codeplug::ChannelElement::setEncryptionKeyIndex(unsigned int idx) { setUInt8(Offset::encryptionKeyIndex(), idx+1); } void GD73Codeplug::ChannelElement::clearEncryptionKeyIndex() { setUInt8(Offset::encryptionKeyIndex(), 0); } Channel * GD73Codeplug::ChannelElement::toChannel(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) Channel *ch = nullptr; if (Type::FM == type()) { FMChannel *fm = new FMChannel(); ch = fm; switch (admit()) { case Admit::Always: fm->setAdmit(FMChannel::Admit::Always); break; case Admit::CC_CTCSS: fm->setAdmit(FMChannel::Admit::Tone); break; case Admit::Free: fm->setAdmit(FMChannel::Admit::Free); break; } fm->setBandwidth(bandwidth()); fm->setSquelchDefault(); fm->setRXTone(rxTone()); fm->setTXTone(txTone()); fm->extended()->enableTalkaround(talkaroundEnabled()); } else if (Type::DMR == type()) { DMRChannel *dmr = new DMRChannel(); ch = dmr; switch (admit()) { case Admit::Always: dmr->setAdmit(DMRChannel::Admit::Always); break; case Admit::CC_CTCSS: dmr->setAdmit(DMRChannel::Admit::ColorCode); break; case Admit::Free: dmr->setAdmit(DMRChannel::Admit::Free); break; } dmr->setColorCode(colorCode()); dmr->setTimeSlot(timeSlot()); dmr->setRadioId(DefaultRadioID::get()); dmr->extended()->enableTalkaround(talkaroundEnabled()); } ch->setName(name()); ch->setRXFrequency(rxFrequency()); ch->setTXFrequency(txFrequency()); ch->setRXOnly(rxOnly()); ch->setPower(power()); return ch; } bool GD73Codeplug::ChannelElement::linkChannel(Channel *ch, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); if (Type::DMR == type()) { DMRChannel *dmr = ch->as(); if (hasTXContact()) { if (ctx.has(txContactIndex())) dmr->setContact(ctx.get(txContactIndex())); else logWarn() << "Cannot link channel '" << name() << "', cannot resolve contact index " << txContactIndex() << "."; } if ((! groupListAllMatch()) && (! groupListMatchesContact())) { if (ctx.has(groupListIndex())) dmr->setGroupList(ctx.get(groupListIndex())); else logWarn() << "Cannot link channel '" << name() << "', cannot resolve group list index " << groupListIndex() << "."; } if (hasEncryptionKeyIndex()) { // Check if already defined if (! ctx.has(encryptionKeyIndex())) { errMsg(err) << "Cannot link channel '" << name() << "', cannot resolve encryption key index " << encryptionKeyIndex() << "."; return false; } // set... if (nullptr == dmr->commercialExtension()) dmr->setCommercialExtension(new CommercialChannelExtension()); dmr->commercialExtension()->setEncryptionKey( ctx.get(encryptionKeyIndex())); } } if (hasScanListIndex()) { if (ctx.has(scanListIndex())) ch->setScanList(ctx.get(scanListIndex())); else logWarn() << "Cannot link channel '" << name() << "', cannot resolve scanlist index " << scanListIndex() << "."; } return true; } bool GD73Codeplug::ChannelElement::encode(Channel *ch, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) // Encode common stuff setName(ch->name()); setRXFrequency(ch->rxFrequency()); setTXFrequency(ch->txFrequency()); enableRXOnly(ch->rxOnly()); if (! ch->scanListRef()->isNull()) setScanListIndex(ctx.index(ch->scanList())); setPower(ch->power()); // Dispatch by type if (ch->is()) { DMRChannel *dmr = ch->as(); setType(ChannelElement::Type::DMR); if (! dmr->contactRef()->isNull()) setTXContactIndex(ctx.index(dmr->contact())); if (dmr->groupListRef()->isNull()) setGroupListAllMatch(); else setGroupListIndex(ctx.index(dmr->groupList())); switch (dmr->admit()) { case DMRChannel::Admit::Always: setAdmit(Admit::Always); break; case DMRChannel::Admit::ColorCode: setAdmit(Admit::CC_CTCSS); break; case DMRChannel::Admit::Free: setAdmit(Admit::Free); break; } setColorCode(dmr->colorCode()); setTimeSlot(dmr->timeSlot()); if (CommercialChannelExtension *ext = dmr->commercialExtension()) if ((! ext->encryptionKeyRef()->isNull()) && (0 <= ctx.index(ext->encryptionKey())) ) setEncryptionKeyIndex(ctx.index(ext->encryptionKey())); enableTalkaround(dmr->extended()->talkaround()); } else if (ch->is()) { FMChannel *fm = ch->as(); setType(ChannelElement::Type::FM); switch (fm->admit()) { case FMChannel::Admit::Always: setAdmit(Admit::Always); break; case FMChannel::Admit::Tone: setAdmit(Admit::CC_CTCSS); break; case FMChannel::Admit::Free: setAdmit(Admit::Free); break; } setBandwidth(fm->bandwidth()); setRXTone(fm->rxTone()); setTXTone(fm->txTone()); enableTalkaround(fm->extended()->talkaround()); } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::ZoneBankElement * ********************************************************************************************* */ GD73Codeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr) : Element(ptr, ZoneBankElement::size()) { // pass... } bool GD73Codeplug::ZoneBankElement::createZones(Context &ctx, const ErrorStack &err) { unsigned int count = std::min((unsigned int)getUInt8(Offset::zoneCount()), Limit::zoneCount()); for (unsigned int i=0; izones()->add(zoneObj); ctx.add(zoneObj, i); } return true; } bool GD73Codeplug::ZoneBankElement::linkZones(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::zoneCount()); for (unsigned int i=0; i(i); if (! zone.linkZone(zoneObj, ctx, err)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } } return true; } bool GD73Codeplug::ZoneBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::zoneCount()); setUInt8(Offset::zoneCount(), count); for (unsigned int i=0; i(i); if (! zone.encode(zoneObj, ctx, err)) { errMsg(err) << "Cannot encode zone at index " << i << "."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::ZoneElement * ********************************************************************************************* */ GD73Codeplug::ZoneElement::ZoneElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::ZoneElement::ZoneElement(uint8_t *ptr) : Element(ptr, ZoneElement::size()) { // pass... } QString GD73Codeplug::ZoneElement::name() const { return readUnicode(Offset::name(), Limit::nameLength(), 0x0000); } void GD73Codeplug::ZoneElement::setName(const QString &name) { writeUnicode(Offset::name(), name, Limit::nameLength(), 0x0000); } Zone * GD73Codeplug::ZoneElement::toZone(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return new Zone(name()); } bool GD73Codeplug::ZoneElement::linkZone(Zone *zone, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) unsigned int count = std::min((unsigned int)getUInt8(Offset::channeCount()), Limit::channelCount()); for (unsigned int i=0; i(index-1)) zone->A()->add(ctx.get(index-1)); else logWarn() << "Cannot link zone '" << zone->name() << "': Channel at index " << (index-1) << " not known."; } return true; } bool GD73Codeplug::ZoneElement::encode(Zone *zone, Context &ctx, const ErrorStack &err) { unsigned int count = std::min((unsigned int)zone->A()->count(), Limit::channelCount()); setUInt8(Offset::channeCount(), count); setName(zone->name()); for (unsigned int i=0; i ctx.index(zone->A()->get(i)->as())) { errMsg(err) << "Cannot find index for channel " << zone->A()->get(i)->name() << " in zone " << zone->name() << "."; return false; } setUInt16_le(Offset::channelIndices() + i*Offset::betweenChannelIndices(), ctx.index(zone->A()->get(i)->as())+1); } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::ScanListBankElement * ********************************************************************************************* */ GD73Codeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr) : Element(ptr, ScanListBankElement::size()) { // pass... } bool GD73Codeplug::ScanListBankElement::createScanLists(Context &ctx, const ErrorStack &err) { unsigned int count = std::min((unsigned int)getUInt8(Offset::memberCount()), Limit::memberCount()); for (unsigned int i=0; iscanlists()->add(lstObj); ctx.add(lstObj, i); } return true; } bool GD73Codeplug::ScanListBankElement::linkScanLists(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::memberCount()); for (unsigned int i=0; i(i); if (! lst.linkScanList(lstObj, ctx, err)) { errMsg(err) << "Cannot link scan list at index " << i << "."; return false; } } return true; } bool GD73Codeplug::ScanListBankElement::encode(Context &ctx, const ErrorStack &err) { unsigned int count = std::min(ctx.count(), Limit::memberCount()); setUInt8(Offset::memberCount(), count); for (unsigned int i=0; i(i); if (! lst.encode(lstObj, ctx, err)) { errMsg(err) << "Cannot encode scan list at index " << i << "."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug::ScanListElement * ********************************************************************************************* */ GD73Codeplug::ScanListElement::ScanListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } GD73Codeplug::ScanListElement::ScanListElement(uint8_t *ptr) : Element(ptr, ScanListElement::size()) { // pass... } QString GD73Codeplug::ScanListElement::name() const { return readUnicode(Offset::name(), Limit::nameLength(), 0x0000); } void GD73Codeplug::ScanListElement::setName(const QString &name) { writeUnicode(Offset::name(), name, Limit::nameLength(), 0x0000); } GD73Codeplug::ScanListElement::ChannelMode GD73Codeplug::ScanListElement::primaryChannelMode() const { return (ChannelMode)getUInt8(Offset::priChannel1Mode()); } void GD73Codeplug::ScanListElement::setPrimaryChannelMode(ChannelMode mode) { setUInt8(Offset::priChannel1Mode(), (unsigned int)mode); } bool GD73Codeplug::ScanListElement::hasPrimaryZoneIndex() const { return 0 != getUInt8(Offset::priChannel1Zone()); } unsigned int GD73Codeplug::ScanListElement::primaryZoneIndex() const { return getUInt8(Offset::priChannel1Zone())-1; } void GD73Codeplug::ScanListElement::setPrimaryZoneIndex(unsigned int idx) { setUInt8(Offset::priChannel1Zone(), idx+1); } void GD73Codeplug::ScanListElement::clearPrimaryZoneIndex() { setUInt8(Offset::priChannel1Zone(), 0); } bool GD73Codeplug::ScanListElement::hasPrimaryChannelIndex() const { return 0 != getUInt8(Offset::priChannel1Channel()); } unsigned int GD73Codeplug::ScanListElement::primaryChannelIndex() const { return getUInt8(Offset::priChannel1Channel())-1; } void GD73Codeplug::ScanListElement::setPrimaryChannelIndex(unsigned int idx) { setUInt8(Offset::priChannel1Channel(), idx+1); } void GD73Codeplug::ScanListElement::clearPrimaryChannelIndex() { setUInt8(Offset::priChannel1Channel(), 0); } GD73Codeplug::ScanListElement::ChannelMode GD73Codeplug::ScanListElement::secondaryChannelMode() const { return (ChannelMode)getUInt8(Offset::priChannel2Mode()); } void GD73Codeplug::ScanListElement::setSecondaryChannelMode(ChannelMode mode) { setUInt8(Offset::priChannel2Mode(), (unsigned int)mode); } bool GD73Codeplug::ScanListElement::hasSecondaryZoneIndex() const { return 0 != getUInt8(Offset::priChannel2Zone()); } unsigned int GD73Codeplug::ScanListElement::secondaryZoneIndex() const { return getUInt8(Offset::priChannel2Zone())-1; } void GD73Codeplug::ScanListElement::setSecondaryZoneIndex(unsigned int idx) { setUInt8(Offset::priChannel2Zone(), idx+1); } void GD73Codeplug::ScanListElement::clearSecondaryZoneIndex() { setUInt8(Offset::priChannel2Zone(), 0); } bool GD73Codeplug::ScanListElement::hasSecondaryChannelIndex() const { return 0 != getUInt8(Offset::priChannel2Channel()); } unsigned int GD73Codeplug::ScanListElement::secondaryChannelIndex() const { return getUInt8(Offset::priChannel2Channel())-1; } void GD73Codeplug::ScanListElement::setSecondaryChannelIndex(unsigned int idx) { setUInt8(Offset::priChannel2Channel(), idx+1); } void GD73Codeplug::ScanListElement::clearSecondaryChannelIndex() { setUInt8(Offset::priChannel2Channel(), 0); } GD73Codeplug::ScanListElement::ChannelMode GD73Codeplug::ScanListElement::revertChannelMode() const { return (ChannelMode)getUInt8(Offset::txChannelMode()); } void GD73Codeplug::ScanListElement::setRevertChannelMode(ChannelMode mode) { setUInt8(Offset::txChannelMode(), (unsigned int)mode); } bool GD73Codeplug::ScanListElement::hasRevertZoneIndex() const { return 0 != getUInt8(Offset::txChannelZone()); } unsigned int GD73Codeplug::ScanListElement::revertZoneIndex() const { return getUInt8(Offset::txChannelZone())-1; } void GD73Codeplug::ScanListElement::setRevertZoneIndex(unsigned int idx) { setUInt8(Offset::txChannelZone(), idx+1); } void GD73Codeplug::ScanListElement::clearRevertZoneIndex() { setUInt8(Offset::txChannelZone(), 0); } bool GD73Codeplug::ScanListElement::hasRevertChannelIndex() const { return 0 != getUInt8(Offset::txChannelChannel()); } unsigned GD73Codeplug::ScanListElement::revertChannelIndex() const { return getUInt8(Offset::txChannelChannel())-1; } void GD73Codeplug::ScanListElement::setRevertChannelIndex(unsigned int idx) { setUInt8(Offset::txChannelChannel(), idx+1); } void GD73Codeplug::ScanListElement::clearRevertChannelIndex() { setUInt8(Offset::txChannelChannel(), 0); } Interval GD73Codeplug::ScanListElement::rxHoldTime() const { return Interval::fromMilliseconds(500*getUInt8(Offset::holdTime())); } void GD73Codeplug::ScanListElement::setRXHoldTime(const Interval &interval) { setUInt8(Offset::holdTime(), Limit::holdTime().map(interval).milliseconds()/500); } Interval GD73Codeplug::ScanListElement::txHoldTime() const { return Interval::fromMilliseconds(500*getUInt8(Offset::txHoldTime())); } void GD73Codeplug::ScanListElement::setTXHoldTime(const Interval &interval) { setUInt8(Offset::txHoldTime(), Limit::holdTime().map(interval).milliseconds()/500); } ScanList * GD73Codeplug::ScanListElement::toScanList(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return new ScanList(name()); } bool GD73Codeplug::ScanListElement::linkScanList(ScanList *lst, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) if ((ChannelMode::Fixed == primaryChannelMode()) && hasPrimaryChannelIndex()) { if (ctx.has(primaryChannelIndex())) lst->setPrimaryChannel(ctx.get(primaryChannelIndex())); else logWarn() << "Cannot link scan list '" << lst->name() << "': Cannot resolve primary channel index " << primaryChannelIndex() << "."; } else if (ChannelMode::Selected == primaryChannelMode()) { lst->setPrimaryChannel(SelectedChannel::get()); } if ((ChannelMode::Fixed == secondaryChannelMode()) && hasSecondaryChannelIndex()) { if (ctx.has(secondaryChannelIndex())) lst->setSecondaryChannel(ctx.get(secondaryChannelIndex())); else logWarn() << "Cannot link scan list '" << lst->name() << "': Cannot resolve secondary channel index " << secondaryChannelIndex() << "."; } else if (ChannelMode::Selected == secondaryChannelMode()) { lst->setSecondaryChannel(SelectedChannel::get()); } if ((ChannelMode::Fixed == revertChannelMode()) && hasRevertChannelIndex()) { if (ctx.has(revertChannelIndex())) lst->setRevertChannel(ctx.get(revertChannelIndex())); else logWarn() << "Cannot link scan list '" << lst->name() << "': Cannot resolve revert channel index " << revertChannelIndex() << "."; } else if (ChannelMode::Selected == revertChannelMode()) { lst->setRevertChannel(SelectedChannel::get()); } unsigned int count = std::min((unsigned int)getUInt8(Offset::memberCount()), Limit::memberCount()); for (unsigned int i=0; i(index-1)) lst->addChannel(ctx.get(index-1)); else logWarn() << "Cannot link scan list '" << lst->name() << "': Cannot resolve member index" << index-1 << "."; } return true; } bool GD73Codeplug::ScanListElement::encode(ScanList *lst, Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setName(lst->name()); if (! lst->primaryChannelRef()->isNull()) { if (SelectedChannel::get() == lst->primaryChannel()) { setPrimaryChannelMode(ChannelMode::Selected); } else { setPrimaryChannelMode(ChannelMode::Fixed); setPrimaryChannelIndex(ctx.index(lst->primaryChannel())); } } if (! lst->secondaryChannelRef()->isNull()) { if (SelectedChannel::get() == lst->secondaryChannel()) { setSecondaryChannelMode(ChannelMode::Selected); } else { setSecondaryChannelMode(ChannelMode::Fixed); setSecondaryChannelIndex(ctx.index(lst->secondaryChannel())); } } if (! lst->revertChannelRef()->isNull()) { if (SelectedChannel::get() == lst->revertChannel()) { setRevertChannelMode(ChannelMode::Selected); } else { setRevertChannelMode(ChannelMode::Fixed); setRevertChannelIndex(ctx.index(lst->revertChannel())); } } unsigned int count = std::min((unsigned int)lst->count(), Limit::memberCount()); setUInt8(Offset::memberCount(), count); for (unsigned int i=0; ichannel(i))+1); } return true; } /* ********************************************************************************************* * * Implementation of GD73Codeplug * ********************************************************************************************* */ GD73Codeplug::GD73Codeplug(QObject *parent) : Codeplug{parent} { addImage("Radioddity GD-73A/E codeplug"); image(0).addElement(0x000000, 0x22014); } Config * GD73Codeplug::preprocess(Config *config, const ErrorStack &err) const { Config *copy = Codeplug::preprocess(config, err); if (nullptr == copy) { errMsg(err) << "Cannot pre-process codeplug for GD73A/E."; return nullptr; } // Remove all AM & M17 channels ObjectFilterVisitor amFilter{AMChannel::staticMetaObject, M17Channel::staticMetaObject}; if (! amFilter.process(copy, err)) { errMsg(err) << "Remove AM & M17 channels."; delete copy; return nullptr; } ZoneSplitVisitor splitter; if (! splitter.process(copy, err)) { errMsg(err) << "Cannot pre-process codeplug for GD73A/E."; return nullptr; } return copy; } bool GD73Codeplug::postprocess(Config *config, const ErrorStack &err) const { if (! Codeplug::postprocess(config, err)) { errMsg(err) << "Cannot post-process codeplug for GD73A/E."; return false; } ZoneMergeVisitor merger; if (! merger.process(config, err)) { errMsg(err) << "Cannot post-process codeplug for GD73A/E."; return false; } return true; } bool GD73Codeplug::index(Config *config, Context &ctx, const ErrorStack &err) const { // There must be a default DMR radio ID. if (nullptr == ctx.config()->settings()->defaultId()) { errMsg(err) << "No default DMR radio ID specified."; errMsg(err) << "Cannot index codeplug for encoding for the Radioddity GD73."; return false; } // Map radio IDs for (int i=0; iradioIDs()->count(); i++) { if (ctx.config()->radioIDs()->get(i)->is()) ctx.add(ctx.config()->radioIDs()->get(i)->as(), i); } // Map digital and DTMF contacts for (int i=0, d=0, a=0; icontacts()->count(); i++) { if (ctx.config()->contacts()->contact(i)->is()) { ctx.add(ctx.config()->contacts()->contact(i)->as(), d); d++; } else if (ctx.config()->contacts()->contact(i)->is()) { ctx.add(ctx.config()->contacts()->contact(i)->as(), a); a++; } } // Map rx group lists for (int i=0; irxGroupLists()->count(); i++) ctx.add(ctx.config()->rxGroupLists()->list(i), i); // Map channels for (int i=0; ichannelList()->count(); i++) { if (ctx.config()->channelList()->get(i)->is() || ctx.config()->channelList()->get(i)->is()) ctx.add(ctx.config()->channelList()->channel(i), i); } // Map zones for (int i=0; izones()->count(); i++) ctx.add(config->zones()->zone(i), i); // Map scan lists for (int i=0; iscanlists()->count(); i++) ctx.add(config->scanlists()->scanlist(i), i); // Handle encryption keys) if (nullptr != config->commercialExtension()) { for (int i=0, j=0; icommercialExtension()->encryptionKeys()->count(); i++) { EncryptionKey *key = config->commercialExtension()->encryptionKeys()->key(i); // Can only encode basic encryption keys if (! key->is()) continue; ctx.add(key->as(), j++); } } return true; } bool GD73Codeplug::encode(Config *config, const Flags &flags, const ErrorStack &err) { Q_UNUSED(flags); Context ctx(config); if (! index(config, ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } if (! encodeTimestamp(ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } if (! encodeMessages(ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } if (! encodeSettings(ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } if (! encodeContacts(ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } if (! encodeGroupLists(ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } if (! encodeEncryptionKeys(ctx, err)) { errMsg(err) << "Cannot encode encryption keys for Radioddity GD73."; return false; } if (! encodeChannels(ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } if (! encodeZones(ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } if (! encodeScanLists(ctx, err)) { errMsg(err) << "Cannot encode codeplug for Radioddity GD73."; return false; } return true; } bool GD73Codeplug::decode(Config *config, const ErrorStack &err) { Context ctx(config); if (! decodeTimestamp(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! createMessages(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! decodeSettings(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! createContacts(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! createDTMFContacts(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! createGroupLists(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! createEncryptionKeys(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! createChannels(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! createZones(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! createScanLists(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! linkGroupLists(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! linkChannels(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! linkZones(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } if (! linkScanLists(ctx, err)) { errMsg(err) << "Cannot decode codeplug."; return false; } return true; } bool GD73Codeplug::decodeTimestamp(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) InformationElement info(data(Offset::timestamp())); if (! info.isValid()) { errMsg(err) << "Cannot parse info element @" << Qt::hex << Offset::timestamp() << "."; return false; } // Nothing to do here. return true; } bool GD73Codeplug::encodeTimestamp(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) InformationElement info(data(Offset::timestamp())); info.setTimestamp(QDateTime::currentDateTimeUtc()); info.setFrequencyRange(FrequencyRange {Frequency::fromMHz(400.0), Frequency::fromMHz(470.0)}); return true; } bool GD73Codeplug::createMessages(Context &ctx, const ErrorStack &err) { if (! MessageBankElement(data(Offset::messages())).decode(ctx.config()->smsExtension())) { errMsg(err) << "Cannot decode preset text messages."; return false; } return true; } bool GD73Codeplug::encodeMessages(Context &ctx, const ErrorStack &err) { if (! MessageBankElement(data(Offset::messages())).encode(ctx.config()->smsExtension())) { errMsg(err) << "Cannot encode preset text messages."; return false; } return true; } bool GD73Codeplug::decodeSettings(Context &ctx, const ErrorStack &err) { SettingsElement settings(data(Offset::settings())); if (! settings.updateConfig(ctx, err)) { errMsg(err) << "Cannot decode settings element."; return false; } DMRSettingsElement dmrSettings(data(Offset::dmrSettings())); if (! dmrSettings.updateConfig(ctx, err)) { errMsg(err) << "Cannot decode DMR settings element."; return false; } return true; } bool GD73Codeplug::encodeSettings(Context &ctx, const ErrorStack &err) { SettingsElement settings(data(Offset::settings())); if (! settings.encode(ctx, err)) { errMsg(err) << "Cannot encode settings element."; return false; } DMRSettingsElement dmrSettings(data(Offset::dmrSettings())); if (! dmrSettings.encode(ctx, err)) { errMsg(err) << "Cannot encode DMR settings element."; return false; } return true; } bool GD73Codeplug::createContacts(Context &ctx, const ErrorStack &err) { ContactBankElement bank(data(Offset::contacts())); if (! bank.createContacts(ctx, err)) { errMsg(err) << "Cannot create contacts."; return false; } return true; } bool GD73Codeplug::encodeContacts(Context &ctx, const ErrorStack &err) { ContactBankElement bank(data(Offset::contacts())); if (! bank.encode(ctx, err)) { errMsg(err) << "Cannot encode contacts."; return false; } return true; } bool GD73Codeplug::createDTMFContacts(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return true; } bool GD73Codeplug::createGroupLists(Context &ctx, const ErrorStack &err) { if (! GroupListBankElement(data(Offset::groupLists())).createGroupLists(ctx, err)) { errMsg(err) << "Cannot decode group lists."; return false; } return true; } bool GD73Codeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { if (! GroupListBankElement(data(Offset::groupLists())).linkGroupLists(ctx, err)) { errMsg(err) << "Cannot link group lists."; return false; } return true; } bool GD73Codeplug::encodeGroupLists(Context &ctx, const ErrorStack &err) { if (! GroupListBankElement(data(Offset::groupLists())).encode(ctx, err)) { errMsg(err) << "Cannot encode group lists."; return false; } return true; } bool GD73Codeplug::createEncryptionKeys(Context &ctx, const ErrorStack &err) { if (! EncryptionKeyBankElement(data(Offset::encryptionKeys())).createEncryptionKeys(ctx, err)) { errMsg(err) << "Cannot create encryption keys."; return false; } return true; } bool GD73Codeplug::encodeEncryptionKeys(Context &ctx, const ErrorStack &err) { if (! EncryptionKeyBankElement(data(Offset::encryptionKeys())).encodeEncryptionKeys(ctx, err)) { errMsg(err) << "Cannot encode encryption keys."; return false; } return true; } bool GD73Codeplug::createChannels(Context &ctx, const ErrorStack &err) { if (! ChannelBankElement(data(Offset::channels())).createChannels(ctx, err)) { errMsg(err) << "Cannot create channels."; return false; } return true; } bool GD73Codeplug::linkChannels(Context &ctx, const ErrorStack &err) { if (! ChannelBankElement(data(Offset::channels())).linkChannels(ctx, err)) { errMsg(err) << "Cannot link channels."; return false; } return true; } bool GD73Codeplug::encodeChannels(Context &ctx, const ErrorStack &err) { if (! ChannelBankElement(data(Offset::channels())).encode(ctx, err)) { errMsg(err) << "Cannot encode channels."; return false; } return true; } bool GD73Codeplug::createZones(Context &ctx, const ErrorStack &err) { if (! ZoneBankElement(data(Offset::zones())).createZones(ctx, err)) { errMsg(err) << "Cannot create zones."; return false; } return true; } bool GD73Codeplug::linkZones(Context &ctx, const ErrorStack &err) { if (! ZoneBankElement(data(Offset::zones())).linkZones(ctx, err)) { errMsg(err) << "Cannot link zones."; return false; } return true; } bool GD73Codeplug::encodeZones(Context &ctx, const ErrorStack &err) { if (! ZoneBankElement(data(Offset::zones())).encode(ctx, err)) { errMsg(err) << "Cannot encode zones."; return false; } return true; } bool GD73Codeplug::createScanLists(Context &ctx, const ErrorStack &err) { if (! ScanListBankElement(data(Offset::scanLists())).createScanLists(ctx, err)) { errMsg(err) << "Cannot create scan-lists."; return false; } return true; } bool GD73Codeplug::linkScanLists(Context &ctx, const ErrorStack &err) { if (! ScanListBankElement(data(Offset::scanLists())).linkScanLists(ctx, err)) { errMsg(err) << "Cannot link scan-lists."; return false; } return true; } bool GD73Codeplug::encodeScanLists(Context &ctx, const ErrorStack &err) { if (! ScanListBankElement(data(Offset::scanLists())).encode(ctx, err)) { errMsg(err) << "Cannot encode scan-lists."; return false; } return true; } ================================================ FILE: lib/gd73_codeplug.hh ================================================ #ifndef GD73CODEPLUG_HH #define GD73CODEPLUG_HH #include "codeplug.hh" #include "interval.hh" #include "ranges.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "channel.hh" #include "zone.hh" #include "bootsettings.hh" #include "radioddity_extensions.hh" class SMSTemplate; class SMSExtension; /** Represents, encodes and decodes the device specific codeplug for a Radioddity GD-73. * * * * * * * * * * * * * * * * * * *
Start End Size Content
First segment 0x00000-0x22014
0x00000 0x00061 0x0061 Basic info, see * @c GD73Codeplug::InformationElement
0x00061 0x0010b 0x00aa Radio settings, see * @c GD73Codeplug::SettingsElement
0x0010b 0x00d4c 0x0c41 Zone bank, see * @c GD73Codeplug::ZoneBankElement
0x00d4c 0x1254e 0x11802 Channel bank, see * @c GD73Codeplug::ChannelBankElement
0x125ff 0x1c201 0x9c02 Contact bank, see * @c GD73Codeplug::ContactBankElement
0x1c201 0x21310 0x510f Group list bank, see * @c GD73Codeplug::GroupListBankElement
0x21310 0x21911 0x0601 Scan list bank, see * @c GD73Codeplug::ScanListBankElement
0x21911 0x2191f 0x000e DMR settings, see * @c GD73Codeplug::DMRSettingsElement
0x2191f 0x2196f 0x0050 16 encryption keys, see * @c GD73Codeplug::EncryptionKeyBankElement
0x2196f 0x21e80 0x0511 Message bank, see * @c GD73Codeplug::MessageBankElement
0x21e80 0x21e94 0x0014 4 DTMF systems, see * @c GD73Codeplug::DTMFSystemBankElement
0x21e94 0x21f24 0x0090 16 DTMF numbers, see * @c GD73Codeplug::DTMFNumberBankElement
0x21f24 0x21fc4 0x00a0 32 DTMF PTT settings, see * @c GD73Codeplug::DTMFPTTSettingBankElement
0x21fc4 0x22014 0x0050 Unused, filled with 0x00
* * @ingroup gd73 */ class GD73Codeplug : public Codeplug { Q_OBJECT public: /** Implements the information element. * * Memory representation of the element (size 000h bytes): * @verbinclude gd73_timestamp.txt */ class InformationElement: public Element { protected: /** Hidden constructor. */ InformationElement(uint8_t *ptr, size_t size); public: /** Constructor. */ InformationElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0061; } /** Returns the frequency range, supported by the radio. */ FrequencyRange frequencyRange() const; /** Overrides the frequency range settings. */ void setFrequencyRange(const FrequencyRange &range); /** Returns the timestamp of the last programming. */ QDateTime timestamp() const; /** Sets the timestamp of the last programming. */ void setTimestamp(const QDateTime ×tamp); /** Returns the serial number as a string. */ QString serial() const; /** Returns the model name. */ QString modelName() const; /** Returns the device id. */ QString deviceID() const; /** Returns the model number as a string. */ QString modelNumber() const; /** Returns the software version as a string. */ QString softwareVersion() const; public: /** Some limits. */ struct Limit { /** Maximum length of serial number. */ static constexpr unsigned int serial() { return 16; } /** Maximum length of model name. */ static constexpr unsigned int modelName() { return 16; } /** Maximum length of device id. */ static constexpr unsigned int deviceID() { return 16; } /** Maximum length of model number. */ static constexpr unsigned int modelNumber() { return 16; } /** Maximum length of software version. */ static constexpr unsigned int softwareVersion() { return 16; } }; protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int frequencyRange() { return 0x0000; } static constexpr unsigned int dateCentury() { return 0x0001; } static constexpr unsigned int dateYear() { return 0x0002; } static constexpr unsigned int dateMonth() { return 0x0003; } static constexpr unsigned int dateDay() { return 0x0004; } static constexpr unsigned int dateHour() { return 0x0005; } static constexpr unsigned int dateMinute() { return 0x0006; } static constexpr unsigned int dateSecond() { return 0x0007; } static constexpr unsigned int serial() { return 0x0011; } static constexpr unsigned int modelName() { return 0x0021; } static constexpr unsigned int deviceID() { return 0x0031; } static constexpr unsigned int modelNumber() { return 0x0041; } static constexpr unsigned int softwareVersion() { return 0x0051; } /// @endcond }; }; /** Implements one of the 5 one-touch settings elements. * * Memory representation of the element (size 0005h bytes): * @verbinclude gd73_one_touch_element.txt */ class OneTouchSettingElement: public Element { public: /** Possible one-touch actions. */ enum class Action { Call = 0, Message = 1 }; protected: /** Hidden constructor. */ OneTouchSettingElement(uint8_t *ptr, size_t size); public: /** Constructor. */ OneTouchSettingElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0005; } protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int contact() { return 0x0001; } static constexpr unsigned int action() { return 0x0003; } static constexpr unsigned int message() { return 0x0001; } /// @endcond }; }; /** Implements the radio settings. * * Memory representation within the binary codeplug (size: 00aah): * @verbinclude gd73_settings_element.txt. */ class SettingsElement: public Element { public: /** Possible channel display modes. */ enum class ChannelDisplayMode { Name = 0, Frequency = 1 }; /** Possible boot display modes. */ enum class BootDisplayMode { Off = 0, Text = 1, Image = 2, Both = 3 }; /** Possible programmable key function. */ struct KeyFunction { public: /** Encodes the given function. */ static uint8_t encode(RadioddityButtonSettingsExtension::Function func); /** Decodes the given function code. */ static RadioddityButtonSettingsExtension::Function decode(uint8_t code); protected: /** Possible function codes. */ enum Code { None=0, RadioEnable=1, RadioCheck=2, RadioDisable=3, PowerLevel=4, Monitor=5, EmergencyOn=6, EmergencyOff=7, ZoneSwitch=8, ToggleScan=9, ToggleVOX=10, OneTouch1=11, OneTouch2=12, OneTouch3=13, OneTouch4=14, OneTouch5=15, ToggleTalkaround=16, LoneWorker=17, TBST=18, CallSwell=19 }; }; /** Possible languages. */ enum class Language { Chinese=0, English=1 }; protected: /** Hidden constructor. */ SettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ SettingsElement(uint8_t *ptr); /** Returns the size of the settings element. */ static constexpr unsigned int size() { return 0x00aa; } /** Returns the radio name. */ QString name() const; /** Sets the radio name. */ void setName(const QString &name); /** Returns the radio ID. */ unsigned int dmrID() const; /** Sets the radio ID. */ void setDMRID(unsigned int id); /** Returns the menu language. */ Language language() const; /** Sets the menu language. */ void setLanguage(Language lang); /** Returns the VOX level [0,10]. */ Level vox() const; /** Sets the VOX level [0,10]. */ void setVOX(Level level); /** Returns the squelch level [0,10]. */ Level squelch() const; /** Sets the squelch level [0,10]. */ void setSquelch(Level level); /** Returns @c true, if a transmit time-out is set. */ bool totIsSet() const; /** Returns the transmit time-out. */ Interval tot() const; /** Sets the transmit time-out. */ void setTOT(const Interval &interval); /** Disables transmit time-out. */ void clearTOT(); /** Returns @c true if the TX interrupt is enabled. */ bool txInterruptedEnabled() const; /** Enables/disables the TX interrupt. */ void enableTXInterrupt(bool enable); /** Returns @c true if power save is enabled. */ bool powerSaveEnabled() const; /** Enables/disables power save. */ void enablePowerSave(bool enable); /** Returns the power-save time-out. */ Interval powerSaveTimeout() const; /** Sets the power-save time-out. */ void setPowerSaveTimeout(const Interval &interval); /** Returns @c true, if the read lock is enabled. */ bool readLockEnabled() const; /** Enables/disables read lock. */ void enableReadLock(bool enable); /** Returns the read-lock pin (1-6 digits as ASCII). */ QString readLockPin() const; /** Sets the read-lock pin (1-6 digits as ASCII). */ void setReadLockPin(const QString &pin); /** Returns @c true, if the write lock is enabled. */ bool writeLockEnabled() const; /** Enables/disables write lock. */ void enableWriteLock(bool enable); /** Returns the write-lock pin (1-6 digits as ASCII). */ QString writeLockPin() const; /** Sets the write-lock pin (1-6 digits as ASCII). */ void setWriteLockPin(const QString &pin); /** Returns the channel display mode. */ ChannelDisplayMode channelDisplayMode() const; /** Sets the channel display mode. */ void setChannelDisplayMode(ChannelDisplayMode mode); /** Returns the DMR microphone gain [1,10]. */ Level dmrMicGain() const; /** Sets the DMR microphone gain [1,10]. */ void setDMRMicGain(Level gain); /** Returns the FM microphone gain [1,10]. */ Level fmMicGain() const; /** Sets the FM microphone gain [1,10]. */ void setFMMicGain(Level gain); /** Returns the lone-worker response time-out. */ Interval loneWorkerResponseTimeout() const; /** Sets the lone-worker response time-out. */ void setLoneWorkerResponseTimeout(const Interval &interval); /** Returns the lone-worker remind period. */ Interval loneWorkerRemindPeriod() const; /** Sets the lone-worker remind period. */ void setLoneWorkerRemindPeriod(const Interval &interval); /** Returns the boot display mode. */ BootSettings::BootDisplay bootDisplayMode() const; /** Sets the boot display mode. */ void setBootDisplayMode(BootSettings::BootDisplay mode); /** Returns the first line of the boot text. */ QString bootTextLine1() const; /** Sets the first line of the boot text. */ void setBootTextLine1(const QString &line); /** Returns the second line of the boot text. */ QString bootTextLine2() const; /** Sets the second line of the boot text. */ void setBootTextLine2(const QString &line); /** Returns @c true if the key tones are enabled. */ bool keyToneEnabled() const; /** Enables/disables the key tones. */ void enableKeyTone(bool enable); /** Returns the key-tone volume [0-13]. */ Level keyToneVolume() const; /** Sets the key-tone volume. */ void setKeyToneVolume(Level vol); /** Returns @c true if the low-battery warn tone is enabled. */ bool lowBatteryToneEnabled() const; /** Enables/disables the low-battery warn tone. */ void enableLowBatteryTone(bool enable); /** Returns the low-battery warn-tone volume [0-13]. */ unsigned int lowBatteryToneVolume() const; /** Sets the low-battery warn-tone volume. */ void setLowBatteryToneVolume(unsigned int vol); /** Returns the long-press duration. */ Interval longPressDuration() const; /** Sets the long-press duration. */ void setLongPressDuration(const Interval &interval); /** Long-press function of programmable key 1. */ RadioddityButtonSettingsExtension::Function keyFunctionLongPressP1() const; /** Sets the long-press function of the programmable key 1. */ void setKeyFunctionLongPressP1(RadioddityButtonSettingsExtension::Function function); /** Short-press function of programmable key 1. */ RadioddityButtonSettingsExtension::Function keyFunctionShortPressP1() const; /** Sets the short-press function of the programmable key 1. */ void setKeyFunctionShortPressP1(RadioddityButtonSettingsExtension::Function function); /** Long-press function of programmable key 2. */ RadioddityButtonSettingsExtension::Function keyFunctionLongPressP2() const; /** Sets the long-press function of the programmable key 2. */ void setKeyFunctionLongPressP2(RadioddityButtonSettingsExtension::Function function); /** Short-press function of programmable key 2. */ RadioddityButtonSettingsExtension::Function keyFunctionShortPressP2() const; /** Sets the short-press function of the programmable key 2. */ void setKeyFunctionShortPressP2(RadioddityButtonSettingsExtension::Function function); /** Returns the n-th one-touch setting. */ OneTouchSettingElement oneTouch(unsigned int n); /** Updates the given config. */ bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the settings from the given config */ bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits of the settings. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int name() { return 16; } /** Valid VOX sensitivity levels. */ static constexpr Range vox() { return {1, 4}; } /** Valid squelch sensitivity levels. */ static constexpr Range squelch() { return {1, 9}; } /** Valid range for mic gains. */ static constexpr Range micGain() { return {1, 6}; } /** Transmit time-out range. */ static constexpr TimeRange tot() { return TimeRange{Interval::fromSeconds(20), Interval::fromSeconds(500)}; } /** Power-save timeout. */ static constexpr TimeRange powerSaveTimeout() { return TimeRange{Interval::fromSeconds(10), Interval::fromSeconds(60)}; } /** Maximum read/write lock pin size. */ static constexpr unsigned int pin() { return 6; } /** Lone-worker response time-out range. */ static constexpr TimeRange loneWorkerResponse() { return TimeRange{Interval::fromMinutes(1), Interval::fromMinutes(480)}; } /** Lone-worker remind period range. */ static constexpr TimeRange loneWorkerRemindPeriod() { return TimeRange{Interval::fromSeconds(10), Interval::fromSeconds(200)}; } /** Maximum length of the boot text lines. */ static constexpr unsigned int bootTextLine() { return 16; } /** Value range for tone-volumes. */ static constexpr Range toneVolume() { return {0,13}; } /** Long-press duration range. */ static constexpr TimeRange longPressDuration() { return TimeRange{Interval::fromSeconds(0), Interval::fromSeconds(31)}; } }; protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int dmrId() { return 0x0020; } static constexpr unsigned int language() { return 0x0024; } static constexpr unsigned int voxLevel() { return 0x0026; } static constexpr unsigned int squelchLevel() { return 0x0027; } static constexpr unsigned int tot() { return 0x0028; } static constexpr unsigned int txInterrupt() { return 0x0029; } static constexpr unsigned int powerSave() { return 0x002a; } static constexpr unsigned int powerSaveTimeout() { return 0x002b; } static constexpr unsigned int readLockEnable() { return 0x002c; } static constexpr unsigned int writeLockEnable() { return 0x002d; } static constexpr unsigned int channelDisplayMode() { return 0x002f; } static constexpr unsigned int readLockPin() { return 0x0030; } static constexpr unsigned int writeLockPin() { return 0x0036; } static constexpr unsigned int dmrMicGain() { return 0x003d; } static constexpr unsigned int fmMicGain() { return 0x003f; } static constexpr unsigned int loneWorkerResponseTimeout() { return 0x0040; } static constexpr unsigned int loneWorkerReminderPeriod() { return 0x0042; } static constexpr unsigned int bootDisplayMode() { return 0x0043; } static constexpr unsigned int bootTextLine1() { return 0x0044; } static constexpr unsigned int bootTextLine2() { return 0x0064; } static constexpr unsigned int keyToneEnable() { return 0x0084; } static constexpr unsigned int keyToneVolume() { return 0x0085; } static constexpr unsigned int lowBatToneEnable() { return 0x0086; } static constexpr unsigned int lowBatToneVolume() { return 0x0087; } static constexpr unsigned int longPressDuration() { return 0x0088; } static constexpr unsigned int progFuncKey1ShortPress() { return 0x008b; } static constexpr unsigned int progFuncKey1LongPress() { return 0x008c; } static constexpr unsigned int progFuncKey2ShortPress() { return 0x008d; } static constexpr unsigned int progFuncKey2LongPress() { return 0x008e; } static constexpr unsigned int oneTouchSettings() { return 0x0090; } static constexpr unsigned int betweenOneTouchSettings() { return OneTouchSettingElement::size(); } /// @endcond }; }; /** Implements a single zone within the binary codeplug. * * Memory representation of the zone (size 0031h): * @verbinclude gd73_zone_element.txt */ class ZoneElement: public Element { protected: /** Hidden constructor. */ ZoneElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ZoneElement(uint8_t *ptr); /** Returns the size of the zone element. */ static constexpr unsigned int size() { return 0x0031; } /** Returns the name of the zone. */ QString name() const; /** Sets the name of the zone. */ void setName(const QString &name); /** Decodes the zone element. */ Zone *toZone(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the decoded zone */ bool linkZone(Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given zone. */ bool encode(Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the element. */ struct Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 8; } /** Maximum number of channels per zone. */ static constexpr unsigned int channelCount() { return 16; } }; protected: /** Internal offsets within the zone element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int channeCount() { return 0x0010; } static constexpr unsigned int channelIndices() { return 0x0011; } static constexpr unsigned int betweenChannelIndices() { return 0x0002; } /// @endcond }; }; /** Implements the bank of zones. * * See also @c GD73Codeplug::ZoneElement. * * Memory representation of the zone bank (size ????h bytes): * @verbinclude gd73_zone_bank.txt */ class ZoneBankElement: public Element { protected: /** Hidden constructor. */ ZoneBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ZoneBankElement(uint8_t *ptr); /** Returns the size of the zone bank element. */ static constexpr unsigned int size() { return 0x0c41; } /** Creates all encoded zones, also updates the context. */ bool createZones(Context &ctx, const ErrorStack &err); /** Links all decoded zones. */ bool linkZones(Context &ctx, const ErrorStack &err); /** Encodess all zones. */ bool encode(Context &ctx, const ErrorStack &err); public: /** Some limits for the zone bank. */ struct Limit { /** Maximum number of zones. */ static constexpr unsigned int zoneCount() { return 64; } }; protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int zoneCount() { return 0x0000; } static constexpr unsigned int zones() { return 0x0001; } static constexpr unsigned int betweenZones() { return ZoneElement::size(); } /// @endcond }; }; /** Implements an FM/DMR channel. * * Memory representation of the channel (size ): * @verbinclude gd73_channel_element.txt */ class ChannelElement: public Element { protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Possible channel types. */ enum class Type { FM = 0, DMR = 1 }; /** Possible admit criteria. */ enum class Admit { Always = 0, CC_CTCSS = 1, Free=2 }; public: /** Constructor. */ ChannelElement(uint8_t *ptr); /** Returns the size of the channel element. */ static constexpr unsigned int size() { return 0x0046; } /** Returns the name of the channel. */ QString name() const; /** Sets the channel name. */ void setName(const QString &name); /** Returns the bandwidth of the channel. */ FMChannel::Bandwidth bandwidth() const; /** Sets the bandwidth. */ void setBandwidth(FMChannel::Bandwidth bandwidth); /** Returns @c true, if a scan list index is set. */ bool hasScanListIndex() const; /** Returns the index of the scan list. */ unsigned int scanListIndex() const; /** Sets the scan list index. */ void setScanListIndex(unsigned int idx); /** Clears the scan list index. */ void clearScanListIndex(); /** Returns the channel type. */ Type type() const; /** Sets the channel type. */ void setType(Type type); /** Returns @c true if talkaround is enabled. */ bool talkaroundEnabled() const; /** Enable/disable talkaround. */ void enableTalkaround(bool enable); /** Returns @c true if RX only is enabled. */ bool rxOnly() const; /** Enables/disables RX only. */ void enableRXOnly(bool enable); /** Returns @c true if scan auto-start is enabled. */ bool scanAutoStartEnabled() const; /** Enables/disables scan auto-start. */ void enableScanAutoStart(bool enable); /** Returns the RX frequency. */ Frequency rxFrequency() const; /** Sets the RX frequency. */ void setRXFrequency(const Frequency &f); /** Returns the TX frequency. */ Frequency txFrequency() const; /** Sets the TX frequency. */ void setTXFrequency(const Frequency &f); /** Returns @c true if channel has DTMF PTT settings index. */ bool hasDTMFPTTSettingsIndex() const; /** Returns the DTMF PTT settings index. */ unsigned int dtmfPTTSettingsIndex() const; /** Sets the DTMF PTT settings index. */ void setDTMFPTTSettingsIndex(unsigned int idx); /** Resets the DTMF PTT settings index. */ void clearDTMFPTTSettingsIndex(); /** Returns the power setting. */ Channel::Power power() const; /** Sets the power. */ void setPower(Channel::Power power); /** Returns the admit criterion. */ Admit admit() const; /** Sets the admit criterion. */ void setAdmit(Admit admit); /** Returns the RX tone. */ SelectiveCall rxTone() const; /** Sets the RX tone. */ void setRXTone(const SelectiveCall &code); /** Returns the TX tone. */ SelectiveCall txTone() const; /** Sets the TX tone. */ void setTXTone(const SelectiveCall &code); /** Returns the time slot. */ DMRChannel::TimeSlot timeSlot() const; /** Sets the time slot. */ void setTimeSlot(DMRChannel::TimeSlot ts); /** Returns the color code. */ unsigned int colorCode() const; /** Sets the color code. */ void setColorCode(unsigned int cc); /** Returns @c true, if group list matches current TX contact. */ bool groupListMatchesContact() const; /** Returns @c true, if no group list match is needed (monitor). */ bool groupListAllMatch() const; /** Returns the group list index. */ unsigned int groupListIndex() const; /** Sets the group list index. */ void setGroupListIndex(unsigned int idx); /** Enables, that no group list match is needed (monitor). */ void setGroupListAllMatch(); /** Enables, that the group list matches the current TX contact. */ void setGroupListMatchesContact(); /** Returns @c true, if the transmit contact is set. */ bool hasTXContact() const; /** Returns the tx contact index. */ unsigned int txContactIndex() const; /** Sets the transmit contact index. */ void setTXContactIndex(unsigned int idx); /** Clears the transmit contact index. */ void clearTXContactIndex(); /** Returns @c true if an emergency system index is set. */ bool hasEmergencySystemIndex() const; /** Returns the emergency system index. */ unsigned int emergencySystemIndex() const; /** Sets the emergency system index. */ void setEmergencySystemIndex(unsigned int idx); /** Clears the emergency system index. */ void clearEmergencySystemIndex(); /** Returns @c true if an encryption key index is set. */ bool hasEncryptionKeyIndex() const; /** Returns the encryption key index. */ unsigned int encryptionKeyIndex() const; /** Sets the encryption key index. */ void setEncryptionKeyIndex(unsigned int idx); /** Clears the encryption key index. */ void clearEncryptionKeyIndex(); /** Decodes the channel. */ Channel *toChannel(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links decoded channel. */ bool linkChannel(Channel *ch, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given channel. */ bool encode(Channel *ch, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the channel. */ struct Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Internal used offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int bandwidth() { return 0x0020; } static constexpr unsigned int scanList() { return 0x0021; } static constexpr unsigned int channelType() { return 0x0022; } static constexpr unsigned int talkaround() { return 0x0023; } static constexpr unsigned int rxOnly() { return 0x0024; } static constexpr unsigned int scanAutoStart() { return 0x0026; } static constexpr unsigned int rxFrequency() { return 0x0027; } static constexpr unsigned int txFrequency() { return 0x002b; } static constexpr unsigned int dtmfPTTSettingsIndex() { return 0x002f; } static constexpr unsigned int power() { return 0x0030; } static constexpr unsigned int admid() { return 0x0031; } static constexpr unsigned int rxToneMode() { return 0x0034; } static constexpr unsigned int rxCTCSS() { return 0x0035; } static constexpr unsigned int rxDCS() { return 0x0036; } static constexpr unsigned int txToneMode() { return 0x0037; } static constexpr unsigned int txCTCSS() { return 0x0038; } static constexpr unsigned int txDCS() { return 0x0039; } static constexpr unsigned int timeslot() { return 0x003c; } static constexpr unsigned int colorcode() { return 0x003d; } static constexpr unsigned int groupListIndex() { return 0x003e; } static constexpr unsigned int contactIndex() { return 0x0040; } static constexpr unsigned int emergencySystemIndex() { return 0x0042; } static constexpr unsigned int encryptionKeyIndex() { return 0x0044; } /// @endcond }; }; /** Implements the bank of channels within the binary codeplug. * * See @c GD73Codeplug::ChannelElement for details on how the channels are encoded. * * Memory representation of the channel bank (size 11802h bytes): * @verbinclude gd73_channel_bank.txt */ class ChannelBankElement: public Element { protected: /** Hidden constructor. */ ChannelBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ChannelBankElement(uint8_t *ptr); /** Returns the size of the channel bank. */ static constexpr unsigned int size() { return 0x11802; } /** Creates the encoded channels, also updates context. */ bool createChannels(Context &ctx, const ErrorStack &err); /** Link all decoded channels. */ bool linkChannels(Context &ctx, const ErrorStack &err); /** Encodes all indexed channels. */ bool encode(Context &ctx, const ErrorStack &err); public: /** Some limits for the channel bank. */ struct Limit { /** Maximum number of channels. */ static constexpr unsigned int channelCount() { return 1024; } }; protected: /** Internal offsets within the bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int channelCount() { return 0x0000; } static constexpr unsigned int channels() { return 0x0002; } static constexpr unsigned int betweenChannels() { return ChannelElement::size(); } /// @endcond }; }; /** Implements the contact element. * * Memory representation of the contact (size 9c02h bytes): * @verbinclude gd73_contact_bank.txt */ class ContactElement: public Element { protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x025; } /** Returns the name of the contact. */ QString name() const; /** Sets the name of the contact. */ void setName(const QString &name); /** Returns the contact type. */ DMRContact::Type type() const; /** Sets the contact type. */ void setType(DMRContact::Type type); /** Returns the DMR ID. */ unsigned int id() const; /** Sets the DMR ID. */ void setID(unsigned int id); /** Decodes the contact. */ DMRContact *toContact(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given contact. */ bool encode(const DMRContact *contact, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum name length. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets within the contact. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int type() { return 0x0020; } static constexpr unsigned int id() { return 0x0021; } /// @endcond }; }; /** Implements the contact bank within the codeplug. * * See @c GD73Codeplug::ContactElement for contact encoding. * * Memory representation of the contact bank (size 9c02h bytes): * @verbinclude gd73_contact_bank.txt */ class ContactBankElement: public Element { protected: /** Hidden constructor. */ ContactBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x9c02; } /** Adds all encoded contacts, also updates the context */ bool createContacts(Context &ctx, const ErrorStack &err); /** Encodes all defined contacts. */ bool encode(Context &ctx, const ErrorStack &err); public: /** Some limits. */ struct Limit { /** The maximum number of contacts. */ static constexpr unsigned int contactCount() { return 1024; } }; protected: /** Some internal offsets within the contact bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int contactCount() { return 0x0000; } static constexpr unsigned int contacts() { return 0x0802; } static constexpr unsigned int betweenContacts() { return ContactElement::size(); } /// @endcond }; }; /** Encodes a group list. * * Memory representation of the group list (size 0053h bytes): * @verbinclude gd73_group_list_element.txt */ class GroupListElement: public Element { protected: /** Hidden constructor. */ GroupListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0053; } /** Returns the name of the group list. */ QString name() const; /** Sets the name of the group list. */ void setName(const QString &name); /** Returns the number of entries in the group list. */ unsigned int members() const; /** Returns @c true, if the i-th member is set. */ bool hasMember(unsigned int i) const; /** Returns the i-th member index. */ unsigned int memberIndex(unsigned int i) const; /** Decodes the group list. */ RXGroupList *toGroupList(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the given RX group list. */ bool linkGroupList(RXGroupList *lst, Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the group list. */ bool encode(RXGroupList *lst, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum name length. */ static constexpr unsigned int nameLength() { return 8; } /** The maximum number of members. */ static constexpr unsigned int memberCount() { return 33; } }; protected: /** Some internal offsets within the group list. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int memberCount() { return 0x0010; } static constexpr unsigned int members() { return 0x0011; } static constexpr unsigned int betweenMembers() { return 0x0002; } /// @endcond }; }; /** Encodes the bank of group lists. * * See @c GD73Codeplug::GroupListElement for group list encoding. * * Memory representation of group list bank (size 510fh bytes): * @verbinclude gd73_group_list_bank.txt */ class GroupListBankElement: public Element { protected: /** Hidden constructor. */ GroupListBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x510f; } /** Create all encoded group lists, also update context. */ bool createGroupLists(Context &ctx, const ErrorStack &err); /** Link all decoded group lists. */ bool linkGroupLists(Context &ctx, const ErrorStack &err); /** Encode group lists. */ bool encode(Context &ctx, const ErrorStack &err); public: /** Some limits. */ struct Limit { /** The maximum number of members. */ static constexpr unsigned int memberCount() { return 250; } }; protected: /** Some internal offsets within the group list bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int memberCount() { return 0x0000; } static constexpr unsigned int members() { return 0x0001; } static constexpr unsigned int betweenMembers() { return GroupListElement::size(); } /// @endcond }; }; /** Implements a scan list. * * Memory representation of the scan list (size 005fh bytes): * @verbinclude gd73_scan_list_element.txt */ class ScanListElement: public Element { public: /** Possible priority/revert channel modes. */ enum class ChannelMode { None=0, Fixed=1, Selected=2 }; protected: /** Hidden constructor. */ ScanListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ScanListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x005f; } /** Returns the name of the scan list. */ QString name() const; /** Sets the name of the scan list. */ void setName(const QString &name); /** Returns the primary channel mode. */ ChannelMode primaryChannelMode() const; /** Sets the primary channel mode. */ void setPrimaryChannelMode(ChannelMode mode); /** Returns @c true, if a primary zone is set. */ bool hasPrimaryZoneIndex() const; /** Returns the primary zone index. */ unsigned int primaryZoneIndex() const; /** Sets the primary zone index. */ void setPrimaryZoneIndex(unsigned int idx); /** Clears the primary zone index. */ void clearPrimaryZoneIndex(); /** Returns @c true, if a primary channel is set. */ bool hasPrimaryChannelIndex() const; /** Returns the primary channel index. */ unsigned int primaryChannelIndex() const; /** Sets the primary channel index. */ void setPrimaryChannelIndex(unsigned int idx); /** Clears the primary channel index. */ void clearPrimaryChannelIndex(); /** Returns the secondary channel mode. */ ChannelMode secondaryChannelMode() const; /** Sets the secondary channel mode. */ void setSecondaryChannelMode(ChannelMode mode); /** Returns @c true, if a secondary zone is set. */ bool hasSecondaryZoneIndex() const; /** Returns the secondary zone index. */ unsigned int secondaryZoneIndex() const; /** Sets the secondary zone index. */ void setSecondaryZoneIndex(unsigned int idx); /** Clears the secondary zone index. */ void clearSecondaryZoneIndex(); /** Returns @c true, if a secondary channel is set. */ bool hasSecondaryChannelIndex() const; /** Returns the secondary channel index. */ unsigned int secondaryChannelIndex() const; /** Sets the secondary channel index. */ void setSecondaryChannelIndex(unsigned int idx); /** Clears the secondary channel index. */ void clearSecondaryChannelIndex(); /** Returns the revert channel mode. */ ChannelMode revertChannelMode() const; /** Sets the revert channel mode. */ void setRevertChannelMode(ChannelMode mode); /** Returns @c true, if a revert zone is set. */ bool hasRevertZoneIndex() const; /** Returns the revert zone index. */ unsigned int revertZoneIndex() const; /** Sets the revert zone index. */ void setRevertZoneIndex(unsigned int idx); /** Clears the revert zone index. */ void clearRevertZoneIndex(); /** Returns @c true, if a revert channel is set. */ bool hasRevertChannelIndex() const; /** Returns the revert channel index. */ unsigned int revertChannelIndex() const; /** Sets the revert channel index. */ void setRevertChannelIndex(unsigned int idx); /** Clears the revert channel index. */ void clearRevertChannelIndex(); /** Returns the RX hold time. */ Interval rxHoldTime() const; /** Sets the RX hold time. */ void setRXHoldTime(const Interval &interval); /** Returns the TX hold time. */ Interval txHoldTime() const; /** Sets the TX hold time. */ void setTXHoldTime(const Interval &interval); /** Constructs a ScanList from this element. */ ScanList *toScanList(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links a decoded scan list. */ bool linkScanList(ScanList *lst, Context&ctx, const ErrorStack &err=ErrorStack()); /** Encodes the scan list. */ bool encode(ScanList *lst, Context&ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum name length. */ static constexpr unsigned int nameLength() { return 8; } /** The maximum number of members. */ static constexpr unsigned int memberCount() { return 32; } /** The range of hold times. */ static TimeRange holdTime() { return TimeRange{Interval::fromSeconds(0), Interval::fromSeconds(10)}; } }; protected: /** Some internal offsets within the scan list bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int memberCount() { return 0x0010; } static constexpr unsigned int members() { return 0x0011; } static constexpr unsigned int betweenMembers() { return 0x0002; } static constexpr unsigned int priChannel1Mode() { return 0x0051; } static constexpr unsigned int priChannel2Mode() { return 0x0052; } static constexpr unsigned int priChannel1Zone() { return 0x0053; } static constexpr unsigned int priChannel2Zone() { return 0x0054; } static constexpr unsigned int priChannel1Channel() { return 0x0055; } static constexpr unsigned int priChannel2Channel() { return 0x0057; } static constexpr unsigned int txChannelMode() { return 0x0059; } static constexpr unsigned int txChannelZone() { return 0x005a; } static constexpr unsigned int txChannelChannel() { return 0x005b; } static constexpr unsigned int holdTime() { return 0x005d; } static constexpr unsigned int txHoldTime() { return 0x005e; } /// @endcond }; }; /** Implements the bank of scan lists. * * See @c GD73Codeplug::ScanListElement for the encoding of the single scan lists. * * Memory representation of the scan list bank (size 0601h bytes): * @verbinclude gd73_scan_list_bank.txt */ class ScanListBankElement: public Element { protected: /** Hidden constructor. */ ScanListBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ScanListBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x601; } /** Creates all encoded scan lists, also updates context. */ bool createScanLists(Context &ctx, const ErrorStack &err); /** Links all decoded scan lists. */ bool linkScanLists(Context &ctx, const ErrorStack &err); /** Encodes all scan lists. */ bool encode(Context &ctx, const ErrorStack &err); public: /** Some limits. */ struct Limit { /** The maximum number of members. */ static constexpr unsigned int memberCount() { return 16; } }; protected: /** Some internal offsets within the scan list bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int memberCount() { return 0x0000; } static constexpr unsigned int members() { return 0x0011; } static constexpr unsigned int betweenMembers() { return ScanListElement::size(); } /// @endcond }; }; /** Implements the DMR settings element. * * Memory representation of the settings (size 000eh bytes): * @verbinclude gd73_dmr_settings_element.txt */ class DMRSettingsElement: public Element { protected: /** Hidden constructor. */ DMRSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DMRSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x000e; } /** Returns the call hang time (private & group). */ Interval callHangTime() const; /** Sets the call hang time (private & group). */ void setCallHangTime(const Interval &intv); /** Returns the active wait time. */ Interval activeWaitTime() const; /** Sets the active wait time. */ void setActiveWaitTime(const Interval &interval); /** Returns the number of active reties. */ unsigned int activeRetries() const; /** Sets the number of active retries. */ void setActiveRetries(unsigned int count); /** Returns the number of TX preambles. */ unsigned int txPreambles() const; /** Sets the number of TX preambles. */ void setTXPreambles(unsigned int count); /** Returns @c true, if decoding of 'disable radio' is enabled. */ bool decodeDisableRadioEnabled() const; /** Enables/disables decoding of 'disable radio'. */ void enableDecodeDisableRadio(bool enable); /** Returns @c true, if decoding of 'radio check' is enabled. */ bool decodeRadioCheckEnabled() const; /** Enables/disables decoding of 'radio check'. */ void enableDecodeRadioCheck(bool enable); /** Returns @c true, if decoding of 'enable radio' is enabled. */ bool decodeEnableRadioEnabled() const; /** Enables/disables decoding of 'enable radio'. */ void enableDecodeEnableRadio(bool enable); /** Updates the settings within the config. */ bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the settings from the given config */ bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The range of call hang times. */ static constexpr TimeRange callHangTime() { return TimeRange{ Interval::fromSeconds(1), Interval::fromSeconds(90) }; } /** The range of active wait times. */ static constexpr TimeRange activeWaitTime() { return TimeRange{ Interval::fromMilliseconds(120), Interval::fromMilliseconds(600) }; } /** The range of active retries. */ static constexpr IntRange activeRetires() { return IntRange{ 1, 10}; } /** The maximum number of TX preambles. */ static constexpr IntRange txPreambles() { return IntRange{ 0, 63}; } }; protected: /** Some internal offsets within the scan list bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int callHangTime() { return 0x0000; } static constexpr unsigned int activeWaitTime() { return 0x0001; } static constexpr unsigned int activeRetries() { return 0x0002; } static constexpr unsigned int txPreambles() { return 0x0003; } static constexpr unsigned int decodeDisableRadio() { return 0x0004; } static constexpr unsigned int decodeCheckRadio() { return 0x0005; } static constexpr unsigned int decodeEnableRadio() { return 0x0006; } /// @endcond }; }; /** Implements the encryption key element. * * Memory representation (size 0005h bytes): * @verbinclude gd73_encryption_key_element.txt */ class EncryptionKeyElement: public Element { protected: /** Hidden constructor. */ EncryptionKeyElement(uint8_t *ptr, size_t size); public: /** Constructor. */ EncryptionKeyElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x005; } void clear(); bool isValid() const; /** Returns the key size in bits. */ unsigned int keySize() const; /** Sets the key size in bits. */ void setKeySize(unsigned int size); /** Decodes the encryption key. */ BasicEncryptionKey *createEncryptionKey(const ErrorStack &err=ErrorStack()) const; /** Encodes the encryption key. */ bool encodeEncryptionKey(BasicEncryptionKey *key, const ErrorStack &err=ErrorStack()); protected: /** Internal used offsets within the bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int size() { return 0x0000; } static constexpr unsigned int key() { return 0x0001; } /// @endcond }; }; /** Implements the encryption key-bank. * * Memory representation (size 0050h bytes): * @verbinclude gd73_encryption_key_bank.txt */ class EncryptionKeyBankElement: public Element { protected: /** Hidden constructor. */ EncryptionKeyBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ EncryptionKeyBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0050; } /** Decodes and create encryption keys. */ bool createEncryptionKeys(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes encryption keys. */ bool encodeEncryptionKeys(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum number of keys. */ static constexpr unsigned int keys() { return 16; } }; protected: /** Internal used offsets within the bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int keys() { return 0x0000; } static constexpr unsigned int betweenKeys() { return EncryptionKeyElement::size(); } /// @endcond }; }; /** Implements a message. * Memory representation (size 0051h bytes): * @verbinclude gd73_message_element.txt */ class MessageElement: public Element { protected: /** Hidden constructor. */ MessageElement(uint8_t *ptr, size_t size); public: /** Constructor. */ MessageElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x051; } /** Returns the message text. */ QString text() const; /** Set message text. */ void setText(const QString &message); /** Sets a message element from an SMS message. */ bool encode(SMSTemplate *message, const ErrorStack &err=ErrorStack()); /** Creates a SMS template from this message. */ SMSTemplate *decode(const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum message length. */ static constexpr unsigned int messageLength() { return 40; } }; protected: /** Internal used offsets within the bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int size() { return 0x0000; } static constexpr unsigned int text() { return 0x0001; } /// @endcond }; }; /** Implements the message bank element. * * See @c GD73Codeplug::MessageElement for encoding of the single messages. * * Memory representation of the bank (size 0511h bytes): * @verbinclude gd73_message_bank.txt */ class MessageBankElement: public Element { protected: /** Hidden constructor. */ MessageBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ MessageBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x511; } /** Returns the member count. */ unsigned int memberCount() const; /** Sets the member count. */ void setMemberCount(unsigned int count); /** Returns the i-th message. */ MessageElement message(unsigned int i); /** Updates the SMS extension by decoding all defined messages. */ bool decode(SMSExtension *ext, const ErrorStack &err=ErrorStack()); /** Encodes all messages defined within the SMS extension. */ bool encode(const SMSExtension *ext, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum number of members. */ static constexpr unsigned int memberCount() { return 16; } }; protected: /** Some internal offsets within the message bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int memberCount() { return 0x0000; } static constexpr unsigned int members() { return 0x0001; } static constexpr unsigned int betweenMembers() { return MessageElement::size(); } /// @endcond }; }; /** Implements a single DTMF system. * Memory representation of the system (size 0005h bytes): * @verbinclude gd73_dtmf_system_element.txt */ class DTMFSystemElement: public Element { protected: /** Hidden constructor. */ DTMFSystemElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFSystemElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x005; } public: /** Some limits. */ struct Limit { /** Maximum preamble duration in ms. */ static constexpr Interval preambleDuration() { return Interval::fromMilliseconds(1000); } /** Range for tone duration. */ static constexpr TimeRange toneDuration() { return TimeRange{ Interval::fromMilliseconds(30), Interval::fromMilliseconds(1900) }; } /** Range for pause duration. */ static constexpr TimeRange pauseDuration() { return TimeRange{ Interval::fromMilliseconds(30), Interval::fromMilliseconds(1900) }; } /** Rang of dead time. */ static constexpr TimeRange deadTime() { return TimeRange{ Interval::fromMilliseconds(200), Interval::fromSeconds(33) }; } }; protected: /** Internal used offsets within the bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int sidetone() { return 0x0000; } static constexpr unsigned int preambleDuration() { return 0x0001; } static constexpr unsigned int toneDuration() { return 0x0002; } static constexpr unsigned int pauseDuration() { return 0x0003; } static constexpr unsigned int deadTime() { return 0x0004; } /// @endcond }; }; /** Implements the bank of 4 DTMF systems. * @c GD73Codeplug::DTMFSystemElement for encoding of each system. */ class DTMFSystemBankElement: public Element { protected: /** Hidden constructor. */ DTMFSystemBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFSystemBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0014; } public: /** Some limits. */ struct Limit { /** The number of members. */ static constexpr unsigned int memberCount() { return 4; } }; protected: /** Some internal offsets within the message bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int members() { return 0x0000; } static constexpr unsigned int betweenMembers() { return DTMFSystemElement::size(); } /// @endcond }; }; /** Implements a single DTMF number. * Memory representation of the DTMF number (size 000ah bytes): * @verbinclude gd73_dtmf_code_element.txt */ class DTMFNumberElement: public Element { protected: /** Hidden constructor. */ DTMFNumberElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFNumberElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x000a; } public: /** Some limits. */ struct Limit { /** Maximum number of digita. */ static constexpr unsigned int digits() { return 16; } }; protected: /** Internal used offsets within the bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int count() { return 0x0000; } static constexpr unsigned int digits() { return 0x0001; } /// @endcond }; }; /** Implements the bank of 16 DTMF numbers. * @c GD73Codeplug::DTMFNumberElement for encoding of each system. */ class DTMFNumberBankElement: public Element { protected: /** Hidden constructor. */ DTMFNumberBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFNumberBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0090; } public: /** Some limits. */ struct Limit { /** The number of members. */ static constexpr unsigned int memberCount() { return 16; } }; protected: /** Some internal offsets within the number bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int members() { return 0x0000; } static constexpr unsigned int betweenMembers() { return DTMFNumberElement::size(); } /// @endcond }; }; /** Implements a single DTMF PTT setting. * Memory representation of the DTMF PTT setting (size 0005h bytes): * @verbinclude gd73_dtmf_ptt_settings.txt */ class DTMFPTTSettingElement: public Element { protected: /** Hidden constructor. */ DTMFPTTSettingElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFPTTSettingElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0005; } protected: /** Internal used offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int systemIndex() { return 0x0000; } static constexpr unsigned int pttIDType() { return 0x0001; } static constexpr unsigned int pttIDMode() { return 0x0002; } static constexpr unsigned int connectIDIndex() { return 0x0003; } static constexpr unsigned int disconnectIDIndex() { return 0x0004; } /// @endcond }; }; /** Implements the bank of 32 DTMF PTT settings. * @c GD73Codeplug::DTMFPTTSettingElement for encoding of each element. */ class DTMFPTTSettingBankElement: public Element { protected: /** Hidden constructor. */ DTMFPTTSettingBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFPTTSettingBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00a0; } public: /** Some limits. */ struct Limit { /** The number of members. */ static constexpr unsigned int memberCount() { return 32; } }; protected: /** Some internal offsets within the number bank. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int members() { return 0x0000; } static constexpr unsigned int betweenMembers() { return DTMFPTTSettingElement::size(); } /// @endcond }; }; public: /** Default constructor. */ explicit GD73Codeplug(QObject *parent = nullptr); Config *preprocess(Config *config, const ErrorStack &err=ErrorStack()) const; bool postprocess(Config *config, const ErrorStack &err=ErrorStack()) const; bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const; bool decode(Config *config, const ErrorStack &err=ErrorStack()); bool encode(Config *config, const Flags &flags=Flags(), const ErrorStack &err=ErrorStack()); protected: /** Decodes the time-stamp field. */ virtual bool decodeTimestamp(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the time-stamp field. */ virtual bool encodeTimestamp(Context &ctx, const ErrorStack &err=ErrorStack()); /** Creates messages. */ virtual bool createMessages(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode messages. */ virtual bool encodeMessages(Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes the settings fields (generic & DMR). */ virtual bool decodeSettings(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode settings fields (generic & DMR settings). */ virtual bool encodeSettings(Context &ctx, const ErrorStack &err=ErrorStack()); /** Creates contacts. */ virtual bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode contacts. */ virtual bool encodeContacts(Context &ctx, const ErrorStack &err=ErrorStack()); /** Creates DTMF contacts. */ virtual bool createDTMFContacts(Context &ctx, const ErrorStack &err=ErrorStack()); /** Create group lists. */ virtual bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link group lists. */ virtual bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode group lists. */ virtual bool encodeGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Create encryption keys. */ virtual bool createEncryptionKeys(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode encryption keys. */ virtual bool encodeEncryptionKeys(Context &ctx, const ErrorStack &err=ErrorStack()); /** Create channels. */ virtual bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link channels. */ virtual bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode channels. */ virtual bool encodeChannels(Context &ctx, const ErrorStack &err=ErrorStack()); /** Create zones. */ virtual bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link zones. */ virtual bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode zones. */ virtual bool encodeZones(Context &ctx, const ErrorStack &err=ErrorStack()); /** Create scan lists. */ virtual bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Link zones. */ virtual bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encode zones. */ virtual bool encodeScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Internal used offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int timestamp() { return 0x00000; } static constexpr unsigned int settings() { return 0x00061; } static constexpr unsigned int zones() { return 0x0010b; } static constexpr unsigned int channels() { return 0x00d4c; } static constexpr unsigned int contacts() { return 0x125ff; } static constexpr unsigned int groupLists() { return 0x1c201; } static constexpr unsigned int scanLists() { return 0x21310; } static constexpr unsigned int dmrSettings() { return 0x21911; } static constexpr unsigned int encryptionKeys() { return 0x2191f; } static constexpr unsigned int messages() { return 0x2196f; } static constexpr unsigned int dtmfSystems() { return 0x21e80; } static constexpr unsigned int dtmfNumbers() { return 0x21e94; } static constexpr unsigned int dtmfPTTSettings() { return 0x21f24; } /// @endcond }; }; #endif // GD73CODEPLUG_HH ================================================ FILE: lib/gd73_filereader.cc ================================================ #include "gd73_filereader.hh" #include #include #define SEGMENT0_ADDR 0x00000000 #define SEGMENT0_SIZE 0x00022014 bool GD73FileReader::read(const QString &filename, GD73Codeplug *codeplug, const ErrorStack &err) { // Check file properties QFileInfo info(filename); if (! info.exists()) { errMsg(err) << "Cannot open file '" << filename << "': File does not exisist."; return false; } if (SEGMENT0_SIZE != info.size()) { errMsg(err) << "Cannot read codeplug file '" << filename << "': File size is not " << SEGMENT0_SIZE << " bytes."; return false; } // Open file QFile file(filename); if (! file.open(QFile::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; return false; } // Read file content if (! file.seek(SEGMENT0_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } char *ptr = (char *)codeplug->data(SEGMENT0_ADDR); size_t n = SEGMENT0_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } return true; } ================================================ FILE: lib/gd73_filereader.hh ================================================ #ifndef GD73FILEREADER_HH #define GD73FILEREADER_HH #include "gd73_codeplug.hh" /** Methods to read manufacturer codeplug files. * * The file format of the stock CPS is pretty simple. It is a one-to-one dump of the codeplug * data as written to the device. This makes the decoding of the manufacturer codeplug files very * easy. Some memory regions, however, are not written to the deivice although they are present in * the codeplug file. * * * * *
Start End Size
0x00000 0x22149 0x22149
* * @ingroup gd73 */ class GD73FileReader { public: /** Reads manufacturer codeplug file into given codeplug object. * @param filename Specifies the file to read. * @param codeplug Specifies the codeplug object to store read codeplug. * @param err The error stack. * @returns @c true on success and @c false on error. */ static bool read(const QString &filename, GD73Codeplug *codeplug, const ErrorStack &err=ErrorStack()); }; #endif // GD73FILEREADER_HH ================================================ FILE: lib/gd73_interface.cc ================================================ #include "gd73_interface.hh" #include "logger.hh" #include #define BLOCK_SIZE 0x35 GD73Interface::GD73Interface(const USBDeviceDescriptor &descriptor, const ErrorStack &err, QObject *parent) : C7000Device(descriptor, err, parent), RadioInterface() { Packet request, response; if (nullptr == _dev) { errMsg(err) << "Cannot initialize GD73 interface: C7000 interface not open."; return; } request = Packet(0x01, 0x04); if (! sendRecv(request, response, err)) { errMsg(err) << "Cannot enter programming mode."; C7000Device::close(); } logDebug() << "Entered prog mode. Response: " << response.payload().toHex() << "."; } bool GD73Interface::isOpen() const { return C7000Device::isOpen(); } RadioInfo GD73Interface::identifier(const ErrorStack &err) { Q_UNUSED(err); return RadioInfo::byID(RadioInfo::GD73); } void GD73Interface::close() { C7000Device::close(); } bool GD73Interface::write_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { Q_UNUSED(bank); Q_UNUSED(addr); Q_UNUSED(err); return true; } bool GD73Interface::write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err) { Q_UNUSED(bank); if ((addr%BLOCK_SIZE) || (nbytes!=BLOCK_SIZE)) { errMsg(err) << "Address and size must align with block size of 35h"; return false; } C7000Device::Packet request, response; QByteArray payload; payload.resize(2); *((uint16_t *)payload.data()) = qToLittleEndian((uint16_t)(addr/BLOCK_SIZE)); payload.append((char *)data, nbytes); request = C7000Device::Packet(0x01, 0x00, 0x0f, payload); if (! sendRecv(request, response, err)) { errMsg(err) << "Cannot send write command."; return false; } return true; } bool GD73Interface::write_finish(const ErrorStack &err) { Q_UNUSED(err); return true; } bool GD73Interface::read_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { Q_UNUSED(bank); Q_UNUSED(addr); Q_UNUSED(err); _lastSequence = 0xffff; logDebug() << "Start codeplug read, seqnr=" << Qt::hex << _lastSequence << "h."; return true; } bool GD73Interface::read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err) { Q_UNUSED(bank); if ((addr%BLOCK_SIZE) || (nbytes!=BLOCK_SIZE)) { errMsg(err) << "Address and size must align with block size of 35h"; return false; } //logDebug() << "Read " << nbytes << "bytes from address " << Qt::hex << addr << "h."; uint16_t seqNum = addr/BLOCK_SIZE; if (uint16_t(_lastSequence+1) != seqNum) { errMsg(err) << "Out-of-sequence read: Expected seqnr. " << uint16_t(_lastSequence+1) << " got " << seqNum << "."; return false; } C7000Device::Packet request, response; if (0xffff == _lastSequence) { // Request start-of-read request = C7000Device::Packet(0x01, 0x02); } else { QByteArray payload(2,0); *((uint16_t *)payload.data()) = qToLittleEndian(_lastSequence); request = C7000Device::Packet(0x04, 0x01, 0x0f, payload); } if (! sendRecv(request, response, err)) { errMsg(err) << "Cannot read codeplug from device."; return false; } _lastSequence = qFromLittleEndian(*(uint16_t *)response.payload().data()); memcpy(data, response.payload().data()+2, nbytes); return true; } bool GD73Interface::read_finish(const ErrorStack &err) { Q_UNUSED(err); _lastSequence = 0xffff; return true; } ================================================ FILE: lib/gd73_interface.hh ================================================ #ifndef GD73INTERFACE_HH #define GD73INTERFACE_HH #include "c7000device.hh" #include "radiointerface.hh" /** Implements the communication interface to the GD-73. * @ingroup gd73 */ class GD73Interface: public C7000Device, public RadioInterface { Q_OBJECT public: /** Constructs a new interface to GD73A/E radios. If a matching device was found, @c isOpen * returns @c true. */ GD73Interface(const USBDeviceDescriptor &descriptor, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); bool isOpen() const; RadioInfo identifier(const ErrorStack &err=ErrorStack()); bool write_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool write_finish(const ErrorStack &err=ErrorStack()); bool read_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool read_finish(const ErrorStack &err=ErrorStack()); void close(); protected: /** Name of the radio. */ QString _identifier; /** Last received/send sequence number. */ uint16_t _lastSequence; }; #endif // GD73INTERFACE_HH ================================================ FILE: lib/gd73_limits.cc ================================================ #include "gd73_limits.hh" #include "gd73_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "roamingzone.hh" GD73Limits::GD73Limits(QObject *parent) : RadioLimits(true, parent) { // Define limits for call-sign DB _hasCallSignDB = false; _callSignDBImplemented = false; _numCallSignDBEntries = 0; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, GD73Codeplug::SettingsElement::Limit::bootTextLine(), RadioLimitString::Unicode) }, { "introLine2", new RadioLimitString(-1, GD73Codeplug::SettingsElement::Limit::bootTextLine(), RadioLimitString::Unicode) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitIgnored(RadioLimitIssue::Silent) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString(1, GD73Codeplug::SettingsElement::Limit::name(), RadioLimitString::Unicode) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } /// @todo check default radio ID. }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, GD73Codeplug::ContactBankElement::Limit::contactCount(), new RadioLimitObject { { "name", new RadioLimitString(1, GD73Codeplug::ContactElement::Limit::nameLength(), RadioLimitString::Unicode) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, GD73Codeplug::GroupListBankElement::Limit::memberCount(), new RadioLimitObject { { "name", new RadioLimitString(1, GD73Codeplug::GroupListElement::Limit::nameLength(), RadioLimitString::Unicode) }, { "contacts", new RadioLimitGroupCallRefList(1, GD73Codeplug::GroupListElement::Limit::memberCount()) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, GD73Codeplug::ChannelBankElement::Limit::channelCount(), new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, GD73Codeplug::ChannelElement::Limit::nameLength(), RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(400.), Frequency::fromMHz(470.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(400.), Frequency::fromMHz(470.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored()}, {"openGD77", new RadioLimitIgnored()}, {"tyt", new RadioLimitIgnored()} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, GD73Codeplug::ChannelElement::Limit::nameLength(), RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(400.), Frequency::fromMHz(470.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(400.), Frequency::fromMHz(470.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored()}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, GD73Codeplug::ZoneBankElement::Limit::zoneCount(), new RadioLimitSingleZone( GD73Codeplug::ZoneElement::Limit::channelCount(), { { "name", new RadioLimitString(1, GD73Codeplug::ZoneElement::Limit::nameLength(), RadioLimitString::Unicode) }, { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Check scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, GD73Codeplug::ScanListBankElement::Limit::memberCount(), new RadioLimitObject{ { "name", new RadioLimitString(1, GD73Codeplug::ScanListElement::Limit::nameLength(), RadioLimitString::Unicode) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, GD73Codeplug::ScanListElement::Limit::memberCount(), Channel::staticMetaObject) } })); /* Check encryption keys. */ add("commercial", new RadioLimitItem { {"encryptionKeys", new RadioLimitList( BasicEncryptionKey::staticMetaObject, 0, GD73Codeplug::EncryptionKeyBankElement::Limit::keys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("^[0-9a-fA-F]{2,8}$", RadioLimitIssue::Critical)} })} }); /* Ignore positioning systems. */ add("positioning", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored()) ); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored()) ); } ================================================ FILE: lib/gd73_limits.hh ================================================ #ifndef GD73LIMITS_HH #define GD73LIMITS_HH #include "radiolimits.hh" /** Implements the limits for the Radioddity GD77. * @ingroup gd73 */ class GD73Limits: public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit GD73Limits(QObject *parent=nullptr); }; #endif // GD73LIMITS_HH ================================================ FILE: lib/gd77.cc ================================================ #include "gd77.hh" #include "gd77_limits.hh" #include "logger.hh" #include "config.hh" #define BSIZE 32 RadioLimits * GD77::_limits = nullptr; GD77::GD77(RadioddityInterface *device, QObject *parent) : RadioddityRadio(device, parent), _name("Radioddity GD-77"), _codeplug(), _callsigns() { // pass... } const QString & GD77::name() const { return _name; } const RadioLimits & GD77::limits() const { if (nullptr == _limits) _limits = new GD77Limits(); return *_limits; } const Codeplug & GD77::codeplug() const { return _codeplug; } Codeplug & GD77::codeplug() { return _codeplug; } RadioInfo GD77::defaultRadioInfo() { return RadioInfo( RadioInfo::GD77, "gd77", "GD-77", "Radioddity", {RadioddityInterface::interfaceInfo()}); } bool GD77::startUploadCallsignDB(UserDatabase *db, bool blocking, const CallsignDB::Flags &selection, const ErrorStack &err) { logDebug() << "Start call-sign DB upload to " << name() << "..."; _errorStack = err; if (StatusIdle != _task) { errMsg(err) << "Cannot upload to radio, radio is not idle."; return false; } // Assemble call-sign db from user DB logDebug() << "Encode call-signs into db."; _callsigns.encode(db, selection); _task = StatusUploadCallsigns; if (blocking) { logDebug() << "Upload call-sign DB in this thread (blocking)."; run(); return (StatusIdle == _task); } //if (_dev && _dev->isOpen()) // _dev->moveToThread(this); // start thread for upload logDebug() << "Upload call-sign DB in separate thread."; start(); return true; } bool GD77::uploadCallsigns() { emit uploadStarted(); // Check every segment in the codeplug if (! _callsigns.isAligned(BSIZE)) { errMsg(_errorStack) << "Cannot upload call-sign DB: Not aligned with block-size " << BSIZE << "."; return false; } logDebug() << "Call-sign DB upload started..."; size_t totb = _callsigns.memSize(); unsigned bcount = 0; for (int n=0; n<_callsigns.image(0).numElements(); n++) { unsigned addr = _callsigns.image(0).element(n).address(); unsigned size = _callsigns.image(0).element(n).data().size(); unsigned b0 = addr/BSIZE, nb = size/BSIZE; for (unsigned b=0; b (b0+b)*BSIZE) ? RadioddityInterface::MEMBANK_CALLSIGN_LOWER : RadioddityInterface::MEMBANK_CALLSIGN_UPPER ); if (! _dev->write(bank, ((b0+b)*BSIZE)&0xffff, _callsigns.data((b0+b)*BSIZE, 0), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot write block " << (b0+b) << "."; return false; } emit uploadProgress(float(bcount*100)/totb); } } _dev->write_finish(); return true; } ================================================ FILE: lib/gd77.hh ================================================ /** @defgroup gd77 Radioddity GD-77 * Device specific classes for Radioddity GD-77 and GD-77S. * * \image html gd77.jpg "GD-77" width=200px * \image latex gd77.jpg "GD-77" width=200px * * @ingroup radioddity */ #ifndef GD77_HH #define GD77_HH #include "radioddity_radio.hh" #include "radioddity_interface.hh" #include "gd77_codeplug.hh" #include "gd77_callsigndb.hh" /** Implements an USB interface to the Radioddity GD-77(S) VHF/UHF 5W DMR (Tier I&II) radios. * * @ingroup gd77 */ class GD77 : public RadioddityRadio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit GD77(RadioddityInterface *device=nullptr, QObject *parent=nullptr); const QString &name() const; const RadioLimits &limits() const; const Codeplug &codeplug() const; Codeplug &codeplug(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); public slots: /** Encodes the given user-database and uploads it to the device. */ bool startUploadCallsignDB(UserDatabase *db, bool blocking=false, const CallsignDB::Flags &selection=CallsignDB::Flags(), const ErrorStack &err=ErrorStack()); protected: /** Implements the actual callsign DB upload process. */ bool uploadCallsigns(); protected: /** The device identifier. */ QString _name; /** The codeplug. */ GD77Codeplug _codeplug; /** The actual binary callsign DB representation. */ GD77CallsignDB _callsigns; private: /** Holds the singleton instance of the radio limits for this radio. */ static RadioLimits *_limits; }; #endif // GD77_HH ================================================ FILE: lib/gd77_callsigndb.cc ================================================ #include "gd77_callsigndb.hh" #include "utils.hh" #include "userdatabase.hh" #include "logger.hh" #include #define OFFSET_USERDB 0x00000 #define USERDB_MAX_ENTRIES 10920 #define USERDB_MAX_ENTRIES_PER_BANK 5460 #define BLOCK_SIZE 32 /* ******************************************************************************************** * * Implementation of GD77CallsignDB::userdb_entry_t * ******************************************************************************************** */ GD77CallsignDB::userdb_entry_t::userdb_entry_t() { clear(); } void GD77CallsignDB::userdb_entry_t::clear() { memset(this, 0, sizeof(userdb_entry_t)); } uint32_t GD77CallsignDB::userdb_entry_t::getNumber() const { return decode_dmr_id_bcd_le((uint8_t *)&number); } void GD77CallsignDB::userdb_entry_t::setNumber(uint32_t number) { encode_dmr_id_bcd_le((uint8_t *)&(this->number), number); } QString GD77CallsignDB::userdb_entry_t::getName() const { return decode_ascii((const uint8_t *)name, 7, 0x00); } void GD77CallsignDB::userdb_entry_t::setName(const QString &name) { encode_ascii((uint8_t *)(this->name), name, 7, 0x00); } void GD77CallsignDB::userdb_entry_t::fromEntry(const UserDatabase::User &user) { clear(); setNumber(user.id); setName(user.call); } /* ******************************************************************************************** * * Implementation of GD77CallsignDB::userdb_t * ******************************************************************************************** */ GD77CallsignDB::userdb_t::userdb_t() { clear(); } void GD77CallsignDB::userdb_t::clear() { memcpy(magic, "ID-V001\0", 8); count = 0; } void GD77CallsignDB::userdb_t::setSize(uint32_t n) { count = qToLittleEndian(std::min(n, uint32_t(USERDB_MAX_ENTRIES))); } /* ******************************************************************************************** * * Implementation of OpenGD77CallsignDB * ******************************************************************************************** */ GD77CallsignDB::GD77CallsignDB(QObject *parent) : CallsignDB(parent) { addImage("GD77 call-sign database"); } GD77CallsignDB::~GD77CallsignDB() { // pass... } bool GD77CallsignDB::encode(UserDatabase *calldb, const Flags &selection, const ErrorStack &err) { Q_UNUSED(err) // Limit entries to USERDB_NUM_ENTRIES qint64 n = std::min(calldb->count(), qint64(USERDB_MAX_ENTRIES)); if (selection.hasCountLimit()) n = std::min(n, (qint64)selection.countLimit()); // If there are no entries -> done. if (0 == n) return true; // Select first n entries and sort them in ascending order of their IDs logDebug() << "Select first " << n << " entries out off " << calldb->count() << "."; QVector users; for (unsigned i=0; iuser(i)); logDebug() << "Sort selected w.r.t their ID in ascending order."; std::sort(users.begin(), users.end(), [](const UserDatabase::User &a, const UserDatabase::User &b) { return a.id < b.id; }); // Allocate segment for user db if requested size_t size = align_size(sizeof(userdb_t)+n*sizeof(userdb_entry_t), BLOCK_SIZE); logDebug() << "Allocate 0x" << QString::number(size,16) << " bytes for call-sign DB."; this->image(0).addElement(OFFSET_USERDB, size); // Encode user DB userdb_t *userdb = (userdb_t *)this->data(OFFSET_USERDB); userdb->clear(); userdb->setSize(n); userdb_entry_t *db = (userdb_entry_t *)this->data(OFFSET_USERDB+sizeof(userdb_t), 0); for (unsigned i=0; i /* ******************************************************************************************** * * Implementation of GD77Codeplug::ChannelElement * ******************************************************************************************** */ GD77Codeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : RadioddityCodeplug::ChannelElement(ptr, size) { // pass... } GD77Codeplug::ChannelElement::ChannelElement(uint8_t *ptr) : RadioddityCodeplug::ChannelElement(ptr) { // pass... } void GD77Codeplug::ChannelElement::clear() { RadioddityCodeplug::ChannelElement::clear(); setUInt8(0x0028, 0x00); setARTSMode(ARTS_OFF); setSTEAngle(STE_FREQUENCY); } GD77Codeplug::ChannelElement::ARTSMode GD77Codeplug::ChannelElement::artsMode() const { return ARTSMode(getUInt2(0x0030,0)); } void GD77Codeplug::ChannelElement::setARTSMode(ARTSMode mode) { setUInt2(0x0030, 0, (unsigned)mode); } GD77Codeplug::ChannelElement::STEAngle GD77Codeplug::ChannelElement::steAngle() const { return STEAngle(getUInt2(0x0032,6)); } void GD77Codeplug::ChannelElement::setSTEAngle(STEAngle angle) { setUInt2(0x0032, 6, (unsigned)angle); } GD77Codeplug::ChannelElement::PTTId GD77Codeplug::ChannelElement::pttIDMode() const { return PTTId(getUInt2(0x0032, 2)); } void GD77Codeplug::ChannelElement::setPTTIDMode(PTTId mode) { setUInt2(0x0032, 2, (unsigned)mode); } bool GD77Codeplug::ChannelElement::squelchIsTight() const { return getBit(0x0033, 0); } void GD77Codeplug::ChannelElement::enableTightSquelch(bool enable) { setBit(0x0033, 0, enable); } bool GD77Codeplug::ChannelElement::loneWorker() const { return getBit(0x0033, 4); } void GD77Codeplug::ChannelElement::enableLoneWorker(bool enable) { setBit(0x0033, 4, enable); } bool GD77Codeplug::ChannelElement::autoscan() const { return getBit(0x0033, 5); } void GD77Codeplug::ChannelElement::enableAutoscan(bool enable) { setBit(0x0033, 5, enable); } /* ******************************************************************************************** * * Implementation of GD77Codeplug::ContactElement * ******************************************************************************************** */ GD77Codeplug::ContactElement::ContactElement(uint8_t *ptr, unsigned size) : RadioddityCodeplug::ContactElement(ptr, size) { // pass... } GD77Codeplug::ContactElement::ContactElement(uint8_t *ptr) : RadioddityCodeplug::ContactElement(ptr) { // pass... } void GD77Codeplug::ContactElement::clear() { markValid(false); } bool GD77Codeplug::ContactElement::isValid() const { uint8_t validFlag = getUInt8(Offset::validFlag()); return RadioddityCodeplug::ContactElement::isValid() && (0x00 != validFlag); } void GD77Codeplug::ContactElement::markValid(bool valid) { setUInt8(Offset::validFlag(), valid ? 0xff : 0x00); } bool GD77Codeplug::ContactElement::fromContactObj(const DMRContact *obj, Context &ctx, const ErrorStack &err) { if (! RadioddityCodeplug::ContactElement::fromContactObj(obj, ctx, err)) return false; markValid(); return true; } /* ******************************************************************************************** * * Implementation of GD77Codeplug::ScanListElement * ******************************************************************************************** */ GD77Codeplug::ScanListElement::ScanListElement(uint8_t *ptr, unsigned size) : RadioddityCodeplug::ScanListElement(ptr, size) { // pass... } GD77Codeplug::ScanListElement::ScanListElement(uint8_t *ptr) : RadioddityCodeplug::ScanListElement(ptr) { // pass... } void GD77Codeplug::ScanListElement::clear() { RadioddityCodeplug::ScanListElement::clear(); setBit(0x0f, 0, true); } /* ******************************************************************************************** * * Implementation of GD77Codeplug::ScanListBankElement * ******************************************************************************************** */ GD77Codeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr, unsigned size) : RadioddityCodeplug::ScanListBankElement(ptr, size) { // pass... } GD77Codeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr) : RadioddityCodeplug::ScanListBankElement(ptr, size()) { // pass... } void GD77Codeplug::ScanListBankElement::clear() { memset(_data, 0, Limit::scanListCount()); } uint8_t * GD77Codeplug::ScanListBankElement::get(unsigned n) const { return _data+Offset::scanLists() + n*ScanListElement::size(); } /* ******************************************************************************************** * * Implementation of GD77Codeplug::GroupListElement * ******************************************************************************************** */ GD77Codeplug::GroupListElement::GroupListElement(uint8_t *ptr, unsigned size) : RadioddityCodeplug::GroupListElement(ptr, size) { // pass... } GD77Codeplug::GroupListElement::GroupListElement(uint8_t *ptr) : RadioddityCodeplug::GroupListElement(ptr, size()) { // pass... } bool GD77Codeplug::GroupListElement::linkRXGroupListObj(unsigned int ncnt, RXGroupList *lst, Context &ctx, const ErrorStack &err) const { for (unsigned int i=0; (i(member(i))) { lst->addContact(ctx.get(member(i))); } else { errMsg(err) << "Cannot link group list '" << lst->name() << "': Member index " << member(i) << " does not refer to a digital contact."; return false; } } return true; } bool GD77Codeplug::GroupListElement::fromRXGroupListObj(const RXGroupList *lst, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) setName(lst->name()); int j = 0; // Iterate over all entries in the codeplug for (unsigned int i=0; icount() > j) { // Skip non-private-call entries while((lst->count() > j) && (DMRContact::GroupCall != lst->contact(j)->type())) { logWarn() << "Contact '" << lst->contact(i)->name() << "' in group list '" << lst->name() << "' is not a group call. Skip entry."; j++; } setMember(i, ctx.index(lst->contact(j))); j++; } else { // Clear entry. clearMember(i); } } return false; } /* ******************************************************************************************** * * Implementation of GD77Codeplug::GroupListBankElement * ******************************************************************************************** */ GD77Codeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr, unsigned size) : RadioddityCodeplug::GroupListBankElement(ptr, size) { // pass... } GD77Codeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr) : RadioddityCodeplug::GroupListBankElement(ptr, size()) { // pass... } uint8_t * GD77Codeplug::GroupListBankElement::get(unsigned n) const { if ((Offset::groupLists() + (n+1)*GroupListElement::size())>_size) { logFatal() << "Cannot resolve group list at index " << n << ": Overflow."; return nullptr; } return _data + Offset::groupLists() + n*GroupListElement::size(); } /* ******************************************************************************************** * * Implementation of GD77Codeplug * ******************************************************************************************** */ GD77Codeplug::GD77Codeplug(QObject *parent) : RadioddityCodeplug(parent) { addImage("Radioddity GD77 Codeplug"); image(0).addElement(0x00080, 0x07b80); image(0).addElement(0x08000, 0x16b00); } void GD77Codeplug::clearGeneralSettings() { GeneralSettingsElement(data(Offset::settings())).clear(); } bool GD77Codeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { GeneralSettingsElement el(data(Offset::settings())); if (! flags.updateCodeplug()) el.clear(); return el.fromConfig(ctx, err); } bool GD77Codeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings())).updateConfig(ctx, err); } void GD77Codeplug::clearButtonSettings() { ButtonSettingsElement(data(Offset::buttons())).clear(); } bool GD77Codeplug::encodeButtonSettings(Context &ctx, const Flags &flags, const ErrorStack &err) { Q_UNUSED(flags); return ButtonSettingsElement(data(Offset::buttons())).encode(ctx, err); } bool GD77Codeplug::decodeButtonSettings(Context &ctx, const ErrorStack &err) { return ButtonSettingsElement(data(Offset::buttons())).decode(ctx, err); } void GD77Codeplug::clearMessages() { MessageBankElement(data(Offset::messages())).clear(); } bool GD77Codeplug::encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).encode(ctx, flags, err); } bool GD77Codeplug::decodeMessages(Context &ctx, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).decode(ctx, err); } void GD77Codeplug::clearScanLists() { ScanListBankElement bank(data(Offset::scanListBank())); bank.clear(); for (unsigned int i=0; i= ctx.count()) { bank.enable(i, false); continue; } if (! ScanListElement(bank.get(i)).fromScanListObj(ctx.get(i+1), ctx, err)) return false; bank.enable(i, true); } return true; } bool GD77Codeplug::createScanLists(Context &ctx, const ErrorStack &err) { ScanListBankElement bank(data(Offset::scanListBank())); for (unsigned int i=0; iscanlists()->add(scan); ctx.add(scan, i+1); } return true; } bool GD77Codeplug::linkScanLists(Context &ctx, const ErrorStack &err) { ScanListBankElement bank(data(Offset::scanListBank())); for (unsigned int i=0; i(i+1), ctx, err)) return false; } return true; } void GD77Codeplug::clearChannels() { for (unsigned int b=0,c=0; b()) { if (! el.fromChannelObj(ctx.get(c+1), ctx, err)) { errMsg(err) << "Cannot encode channel " << c << " (" << i << " of bank " << b <<")."; return false; } bank.enable(i,true); } else { el.clear(); bank.enable(i, false); } } } return true; } bool GD77Codeplug::createChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) for (unsigned int b=0,c=0; bchannelList()->add(ch); ctx.add(ch, c+1); } } return true; } bool GD77Codeplug::linkChannels(Context &ctx, const ErrorStack &err) { for (unsigned int b=0,c=0; b(c+1), ctx, err)) { errMsg(err) << "Cannot link channel '" << ctx.get(c+1)->name() << "' at index " << i << "."; return false; } } } return true; } void GD77Codeplug::clearMenuSettings() { MenuSettingsElement(data(Offset::menuSettings())).clear(); } void GD77Codeplug::clearBootSettings() { BootSettingsElement(data(Offset::bootSettings())).clear(); BootTextElement(data(Offset::bootText())).clear(); } bool GD77Codeplug::encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); BootSettingsElement(data(Offset::bootSettings())).encode(ctx, err); BootTextElement(data(Offset::bootText())).fromConfig(ctx, err); return true; } bool GD77Codeplug::decodeBootSettings(Context &ctx, const ErrorStack &err) { BootSettingsElement(data(Offset::bootSettings())).decode(ctx, err); BootTextElement(data(Offset::bootText())).updateConfig(ctx, err); return true; } void GD77Codeplug::clearVFOSettings() { VFOChannelElement(data(Offset::vfoA())).clear(); VFOChannelElement(data(Offset::vfoB())).clear(); } void GD77Codeplug::clearZones() { ZoneBankElement bank(data(Offset::zoneBank())); bank.clear(); for (unsigned int i=0; i(i+1)) { bank.enable(i, false); continue; } // Construct from Zone obj Zone *zone = ctx.get(i+1); z.fromZoneObjA(zone, ctx); bank.enable(i, true); } return true; } bool GD77Codeplug::createZones(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ZoneBankElement bank(data(Offset::zoneBank())); for (unsigned int i=0; izones()->add(zone); ctx.add(zone, i+1); } return true; } bool GD77Codeplug::linkZones(Context &ctx, const ErrorStack &err) { ZoneBankElement bank(data(Offset::zoneBank())); for (unsigned int i=0; i(i+1); if (! z.linkZoneObj(zone, ctx)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } } return true; } void GD77Codeplug::clearContacts() { for (unsigned int i=0; i= ctx.count()) continue; if (! el.fromContactObj(ctx.get(i+1), ctx, err)) { errMsg(err) << "Cannot encode contact at index " << i << "."; return false; } } return true; } bool GD77Codeplug::createContacts(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; icontacts()->add(cont); } return true; } void GD77Codeplug::clearDTMFContacts() { for (unsigned int i=0; i= ctx.count()) continue; el.fromContactObj(ctx.get(i), ctx, err); } return true; } bool GD77Codeplug::createDTMFContacts(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; icontacts()->add(cont); } return true; } void GD77Codeplug::clearGroupLists() { GroupListBankElement bank(data(Offset::groupListBank())); bank.clear(); for (unsigned int i=0; i= ctx.count()) continue; GroupListElement el(bank.get(i)); el.fromRXGroupListObj(ctx.get(i+1), ctx, err); bank.setContactCount(i, ctx.get(i+1)->count()); } return true; } bool GD77Codeplug::createGroupLists(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GroupListBankElement bank(data(Offset::groupListBank())); for (unsigned int i=0; irxGroupLists()->add(list); ctx.add(list, i+1); } return true; } bool GD77Codeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { GroupListBankElement bank(data(Offset::groupListBank())); for (unsigned int i=0; i(i+1), ctx, err)) { errMsg(err) << "Cannot link group list '" << ctx.get(i+1)->name() << "'."; return false; } } return true; } void GD77Codeplug::clearEncryption() { EncryptionElement enc(data(Offset::encryption())); enc.clear(); } bool GD77Codeplug::encodeEncryption(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); clearEncryption(); EncryptionElement enc(data(Offset::encryption())); return enc.fromCommercialExt(ctx.config()->commercialExtension(), ctx, err); } bool GD77Codeplug::createEncryption(Context &ctx, const ErrorStack &err) { EncryptionElement enc(data(Offset::encryption())); if (EncryptionElement::PrivacyType::None == enc.privacyType()) return true; return enc.updateCommercialExt(ctx, err); } bool GD77Codeplug::linkEncryption(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return true; } ================================================ FILE: lib/gd77_codeplug.hh ================================================ #ifndef GD77_CODEPLUG_HH #define GD77_CODEPLUG_HH #include "radioddity_codeplug.hh" #include "signaling.hh" /** Represents, encodes and decodes the device specific codeplug for a Radioddity GD-77. * * The GD-77 & GD-77S codeplugs are almost identical to the Radioddity/Baofeng @c RD5RCodeplug, in fact * the memory layout (see below) and almost all of the single components of the codeplug are encoded in * exactly the same way. Obviously, when Baofeng and Radioddity joint to create the RD5R, * Radioddity provided the firmware. However, there are some small subtle differences between * these two codeplug formats, requiring a separate class for the GD-77. For example, the contacts * and scan-lists swapped the addresses and the @c channel_t encoding analog and digital channels * for the codeplugs are identical except for the squelch settings. Thanks for that! * * @section gd77ver Matching firmware versions * This class implements the codeplug for the firmware version @b 4.03.06. The codeplug format usually * does not change much with firmware revisions, in particular not with older radios. Unfortunately, * it is not possible to detect the firmware version running on the device. Consequenly, only the * newest firmware version is supported. However, older revisions may still work. * * @section gd77cpl Codeplug structure within radio * The memory representation of the codeplug within the radio is divided into two segments. * The first segment starts at the address 0x00080 and ends at 0x07c00 while the second section * starts at 0x08000 and ends at 0x1e300. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Start End Size Content
First segment 0x00080-0x07c00
0x00080 0x000e0 0x0070 ??? Unknown ???
0x000e0 0x000ec 0x000c General settings, see @c RadioddityCodeplug::GeneralSettingsElement.
0x000ec 0x00108 0x0028 ??? Unknown ???
0x00108 0x00128 0x0020 Button settings, see @c RadioddityCodeplug::ButtonSettingsElement.
0x00128 0x01370 0x1248 32 preset message texts, see @c RadioddityCodeplug::MessageBankElement.
0x01370 0x01790 0x0420 ??? Unknown ???
0x01790 0x02dd0 0x1640 64 scan lists, see @c GD77Codeplug::ScanListBankElement and @c GD77Codeplug::ScanListElement
0x02dd0 0x02f88 0x01b8 ??? Unknown ???
0x02f88 0x03388 0x0400 DTMF contacts, see RadioddityCodeplug::DTMFContactElement.
0x03388 0x03780 0x03f8 ??? Unknown ???
0x03780 0x05390 0x1c10 First 128 channels (bank 0), see @c RadioddityCodeplug::ChannelBankElement and GD77Codeplug::ChannelElement
0x05390 0x07518 0x2188 ??? Unknown ???
0x07518 0x07538 0x0020 Boot settings, see @c RadioddityCodeplug::BootSettingsElement.
0x07538 0x07540 0x0008 Menu settings, see @c RadioddityCodeplug::MenuSettingsElement.
0x07540 0x07560 0x0020 2 intro lines, @c RadioddityCodeplug::BootTextElement.
0x07560 0x07590 0x0030 ??? Unknown ???
0x07590 0x075c8 0x0038 VFO A settings @c RadioddityCodeplug::VFOChannelElement
0x075c8 0x07600 0x0038 VFO B settings @c RadioddityCodeplug::VFOChannelElement
0x07600 0x07c00 0x0600 ??? Unknown ???
Second segment 0x08000-0x1e300
0x08000 0x08010 0x0010 ??? Unknown ???
0x08010 0x0af10 0x2f00 68 zones of 80 channels each, see @c RadioddityCodeplug::ZoneBankElement @c RadioddityCodeplug::ZoneElement.
0x0af10 0x0b1b0 0x02a0 ??? Unknown ???
0x0b1b0 0x17620 0xc470 Remaining 896 channels (bank 1-7), see @c RadioddityCodeplug::ChannelBankElement, @c GD77Codeplug::ChannelElement.
0x17620 0x1d620 0x6000 1024 contacts, see @c GD77Codeplug::ContactElement.
0x1d620 0x1eaa0 0x1480 64 RX group lists, see @c GD77Codeplug::GroupListBankElement, @c GD77Codeplug::GroupListElement.
0x1eaa0 0x1eb00 0x0060 ??? Unknown ???
* @ingroup gd77 */ class GD77Codeplug: public RadioddityCodeplug { Q_OBJECT public: /** Channel representation within the binary codeplug. * * Each channel requires 0x38b: * @verbinclude gd77_channel.txt */ class ChannelElement: public RadioddityCodeplug::ChannelElement { public: /** ARTS send. */ enum ARTSMode { ARTS_OFF = 0, ARTS_TX = 1, ARTS_RX = 2, ARTS_BOTH = 3 }; /** STE angle. */ enum STEAngle { STE_FREQUENCY = 0, ///< STE Frequency. STE_120DEG = 1, ///< 120 degree. STE_180DEG = 2, ///< 180 degree. STE_240DEG = 3 ///< 240 degree. }; /** PTT ID send. */ enum PTTId { PTTID_OFF = 0, PTTID_START = 1, PTTID_END = 2, PTTID_BOTH = 3 }; protected: /** Hidden Constructor. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit ChannelElement(uint8_t *ptr); void clear(); /** Returns the ARTS mode. */ virtual ARTSMode artsMode() const; /** Sets the ARTS mode. */ virtual void setARTSMode(ARTSMode mode); /** Returns the STE angle. */ virtual STEAngle steAngle() const; /** Sets the STE angle. */ virtual void setSTEAngle(STEAngle angle); /** Returns the PTT ID mode. */ virtual PTTId pttIDMode() const; /** Sets the PTT ID mode. */ virtual void setPTTIDMode(PTTId mode); /** Returns @c true if the squech type is tight. */ virtual bool squelchIsTight() const; /** Enables/disables tight squelch. */ virtual void enableTightSquelch(bool enable); /** Returns @c true if lone worker is enabled. */ virtual bool loneWorker() const; /** Enables/disables lone worker. */ virtual void enableLoneWorker(bool enable); /** Returns @c true if auto scan is enabled. */ virtual bool autoscan() const; /** Enables/disables auto scan. */ virtual void enableAutoscan(bool enable); }; /** Specific codeplug representation of a DMR contact for the GD77. * * Memory layout of the contact (0x18b): * @verbinclude gd77_contact.txt */ class ContactElement: public RadioddityCodeplug::ContactElement { protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ContactElement(uint8_t *ptr); /** Resets the contact. */ void clear(); /** Returns @c true if the contact is valid. */ bool isValid() const; /** Marks the entry as valid/invalid. */ virtual void markValid(bool valid=true); bool fromContactObj(const DMRContact *obj, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Some internal offsets. */ struct Offset: RadioddityCodeplug::ContactElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int validFlag() { return 0x0017; } /// @endcond }; }; /** Represents an RX group list within the codeplug. * * The group list is encoded as (size 0x50b): * @verbinclude gd77_grouplist.txt */ class GroupListElement: public RadioddityCodeplug::GroupListElement { protected: /** Hidden constructor. */ GroupListElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ GroupListElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0050; } virtual bool linkRXGroupListObj(unsigned int ncnt, RXGroupList *lst, Context &ctx, const ErrorStack &err = ErrorStack()) const; virtual bool fromRXGroupListObj(const RXGroupList *lst, Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits for the group list. */ struct Limit: public RadioddityCodeplug::GroupListElement { static constexpr unsigned int memberCount() { return 32; } ///< Maximum number of entries. }; }; /** Table of RX group lists. * * The RX group list table constsis of a table of number of members per group list and the actual * list of RX group lists. The former also acts as a byte map for valid RX group lists. If 0, the * group list is disabled, if 1 the group list is empty, etc. So the entry is N+1, where N is the * number of entries per group list. * * Encoding of group list table: * @verbinclude gd77_grouplistbank.txt*/ class GroupListBankElement: public RadioddityCodeplug::GroupListBankElement { protected: /** Hidden constructor. */ GroupListBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ GroupListBankElement(uint8_t *ptr); /** The size of the group list bank. */ static constexpr unsigned int size() { return 0x1480; } uint8_t *get(unsigned n) const; public: /** Some limits for the group list bank. */ struct Limit: public RadioddityCodeplug::GroupListBankElement::Limit { static constexpr unsigned int groupListCount() { return 64; } ///< Number of group lists. }; }; /** Represents a single scan list within the GD77 codeplug. * * Encoding of scan list (size: 0x58b): * @verbinclude gd77_scanlist.txt */ class ScanListElement: public RadioddityCodeplug::ScanListElement { protected: /** Hidden constructor. */ ScanListElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ScanListElement(uint8_t *ptr); /** Resets the scan list. */ void clear(); }; /** Bank of scan lists for the GD77. * * Encoding of scan list table (size 0x1640b): * @verbinclude gd77_scanlistbank.txt */ class ScanListBankElement: public RadioddityCodeplug::ScanListBankElement { protected: /** Hidden constructor. */ ScanListBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ ScanListBankElement(uint8_t *ptr); /** The size of the scan list bank. */ static constexpr unsigned int size() { return 0x1640; } /** Clears the scan list bank. */ void clear(); uint8_t *get(unsigned n) const; public: /** Some limits for the scan list bank. */ struct Limit: public RadioddityCodeplug::ScanListBankElement::Limit { static constexpr unsigned int scanListCount() { return 64; } ///< Maximum number of scan lists. }; protected: /** Internal offsets within the element. */ struct Offset: public RadioddityCodeplug::ScanListBankElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bytemap() { return 0x0000; } static constexpr unsigned int scanLists() { return 0x0040; } /// @endcond }; }; public: /** Constructs an empty codeplug for the GD-77. */ explicit GD77Codeplug(QObject *parent=nullptr); public: void clearGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearButtonSettings(); bool encodeButtonSettings(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); bool decodeButtonSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearMessages(); bool encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); bool decodeMessages(Context &ctx, const ErrorStack &err=ErrorStack()); void clearScanLists(); bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearDTMFContacts(); bool encodeDTMFContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createDTMFContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void clearMenuSettings(); void clearBootSettings(); bool encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeBootSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearVFOSettings(); void clearZones(); bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); void clearGroupLists(); bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearEncryption(); bool encodeEncryption(const Flags &flags, Context &ctx, const ErrorStack &err); bool createEncryption(Context &ctx, const ErrorStack &err); bool linkEncryption(Context &ctx, const ErrorStack &err); public: /** Some limits for the GD77 codeplug. */ struct Limit { static constexpr unsigned int dtmfContactCount() { return 32; } ///< Maximum number of DTMF contacts. static constexpr unsigned int channelBankCount() { return 8; } ///< Number of channel banks. static constexpr unsigned int channelCount() { return 1024; } ///< Maximum number of channels. static constexpr unsigned int contactCount() { return 1024; } ///< Maximum number of contacts. }; protected: /** Some internal offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int settings() { return 0x0000e0; } static constexpr unsigned int buttons() { return 0x000108; } static constexpr unsigned int messages() { return 0x000128; } static constexpr unsigned int encryption() { return 0x001370; } static constexpr unsigned int scanListBank() { return 0x001790; } static constexpr unsigned int dtmfContacts() { return 0x002f88; } static constexpr unsigned int channelBank0() { return 0x003780; } // Channels 1-128 static constexpr unsigned int bootSettings() { return 0x007518; } static constexpr unsigned int bootText() { return 0x007540; } static constexpr unsigned int menuSettings() { return 0x007538; } static constexpr unsigned int vfoA() { return 0x007590; } static constexpr unsigned int vfoB() { return 0x0075c8; } static constexpr unsigned int zoneBank() { return 0x008010; } static constexpr unsigned int channelBank1() { return 0x00b1b0; } // Channels 129-1024 static constexpr unsigned int contacts() { return 0x017620; } static constexpr unsigned int groupListBank() { return 0x01d620; } /// @endcond }; }; #endif // GD77_CODEPLUG_HH ================================================ FILE: lib/gd77_filereader.cc ================================================ #include "gd77_filereader.hh" #include #include #define SEGMENT0_ADDR 0x00000080 #define SEGMENT0_SIZE 0x00007b80 #define SEGMENT1_ADDR 0x00008000 #define SEGMENT1_SIZE 0x00016300 bool GD77FileReader::read(const QString &filename, GD77Codeplug *codeplug, const ErrorStack &err) { // Check file properties QFileInfo info(filename); if (! info.exists()) { errMsg(err) << "Cannot open file '" << filename << "': File does not exisist."; return false; } if (131072 != info.size()) { errMsg(err) << "Cannot read codeplug file '" << filename << "': File size is not 131072 bytes."; return false; } // Open file QFile file(filename); if (! file.open(QFile::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; return false; } // Read file content if (! file.seek(SEGMENT0_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } char *ptr = (char *)codeplug->data(SEGMENT0_ADDR); size_t n = SEGMENT0_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } if (! file.seek(SEGMENT1_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } ptr = (char *)codeplug->data(SEGMENT1_ADDR); n = SEGMENT1_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } return true; } ================================================ FILE: lib/gd77_filereader.hh ================================================ #ifndef GD77FILEREADER_HH #define GD77FILEREADER_HH #include "gd77_codeplug.hh" /** Methods to read manufacturer codeplug files. * * The file format of the stock CPS is pretty simple. It is a one-to-one dump of the codeplug * data as written to the device. This makes the decoding of the manufacturer codeplug files very * easy. Some memory regions, however, are not written to the deivice although they are present in * the codeplug file. * * * * * *
Start End Size
0x00080 0x07c00 0x07b80
0x08000 0x1e300 0x16300
* * @ingroup gd77 */ class GD77FileReader { public: /** Reads manufacturer codeplug file into given codeplug object. * @param filename Specifies the file to read. * @param codeplug Specifies the codeplug object to store read codeplug. * @param err The error stack. * @returns @c true on success and @c false on error. */ static bool read(const QString &filename, GD77Codeplug *codeplug, const ErrorStack &err=ErrorStack()); }; #endif // GD77FILEREADER_HH ================================================ FILE: lib/gd77_limits.cc ================================================ #include "gd77_limits.hh" #include "gd77_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "roamingzone.hh" GD77Limits::GD77Limits(QObject *parent) : RadioLimits(true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 10920; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 16, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 16, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(GD77Codeplug::BootSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } /// @todo check default radio ID. }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 1024, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, 0, 32, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "number", new RadioLimitStringRegEx("^[0-9A-Fa-f]+$") } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 76, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1, 32) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 1024, // < up to 1024 channels new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored()}, {"openGD77", new RadioLimitIgnored()}, {"tyt", new RadioLimitIgnored()} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored()}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 16, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Ignore scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 64, new RadioLimitObject{ { "name", new RadioLimitString(1, 15, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 32, Channel::staticMetaObject) } })); /* Check encryption keys. */ add("commercial", new RadioLimitItem { {"encryptionKeys", new RadioLimitList( BasicEncryptionKey::staticMetaObject, 0, RadioddityCodeplug::EncryptionElement::Limit::keyCount(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{8}")} })} }); /* Ignore positioning systems. */ add("positioning", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored()) ); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored()) ); } ================================================ FILE: lib/gd77_limits.hh ================================================ #ifndef GD77LIMITS_HH #define GD77LIMITS_HH #include "radiolimits.hh" /** Implements the limits for the Radioddity GD77. * @ingroup gd77 */ class GD77Limits: public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit GD77Limits(QObject *parent=nullptr); }; #endif // GD77LIMITS_HH ================================================ FILE: lib/gnsssettings.cc ================================================ #include "gnsssettings.hh" #include "utils.hh" GNSSSettings::GNSSSettings(QObject *parent) : ConfigExtension{parent}, _fixedPositionEnabled(false), _fixedPosition(), _systems(System::GPS), _units(Units::Metric) { // pass... } ConfigItem * GNSSSettings::clone() const { auto obj = new GNSSSettings(); if (! obj->copy(*this)) { delete obj; return nullptr; } return obj; } bool GNSSSettings::fixedPositionEnabled() const { return _fixedPosition.isValid() && (_fixedPositionEnabled || _systems.testFlag(System::Fixed)); } void GNSSSettings::enableFixedPosition(bool use) { if (_fixedPositionEnabled == use) return; _fixedPositionEnabled = use; emit modified(this); } const QGeoCoordinate & GNSSSettings::fixedPosition() const { return _fixedPosition; } void GNSSSettings::setFixedPosition(const QGeoCoordinate &pos) { if (_fixedPosition == pos) return; _fixedPosition = pos; emit modified(this); } QString GNSSSettings::fixedPositionLocator() const { return deg2loc(fixedPosition(), 8); } void GNSSSettings::setFixedPositionLocator(const QString &locator) { setFixedPosition(loc2deg(locator)); } GNSSSettings::Systems GNSSSettings::systems() const { return _systems; } void GNSSSettings::setSystems(Systems systems) { if (_systems == systems) return; _systems = systems; emit modified(this); } GNSSSettings::Units GNSSSettings::units() const { return _units; } void GNSSSettings::setUnits(Units units) { if (_units == units) return; _units = units; emit modified(this); } ================================================ FILE: lib/gnsssettings.hh ================================================ #ifndef GNSSSETTINGS_HH #define GNSSSETTINGS_HH #include "configobject.hh" #include /** Some common global GNSS settings. */ class GNSSSettings : public ConfigExtension { Q_OBJECT Q_CLASSINFO("description", "Some common global GNSS settings."); Q_CLASSINFO("fixedPositionEnabledDescription", "If enabled, the fixed position is used instead of the GNSS position.") Q_PROPERTY(bool fixedPositionEnabled READ fixedPositionEnabled WRITE enableFixedPosition FINAL) Q_CLASSINFO("fixedPositionDescription", "Some fixed position (locator).") Q_PROPERTY(QString fixedPosition READ fixedPositionLocator WRITE setFixedPositionLocator FINAL) Q_CLASSINFO("systemsDescription", "Enabled GNSSs.") Q_PROPERTY(Systems systems READ systems WRITE setSystems FINAL) Q_CLASSINFO("unitsDescrption", "Specifies unit system.") Q_PROPERTY(Units units READ units WRITE setUnits FINAL) public: enum class Units { Metric, Archaic }; Q_ENUM(Units) enum class System { Fixed = 0, GPS = 1, Glonass = 2, Galileo = 4, Beidou = 8 }; Q_DECLARE_FLAGS(Systems, System) Q_FLAGS(Systems) public: explicit GNSSSettings(QObject *parent = nullptr); ConfigItem *clone() const override; bool fixedPositionEnabled() const; void enableFixedPosition(bool use); const QGeoCoordinate &fixedPosition() const; void setFixedPosition(const QGeoCoordinate &pos); QString fixedPositionLocator() const; void setFixedPositionLocator(const QString &locator); Systems systems() const; void setSystems(Systems systems); Units units() const; void setUnits(Units units); protected: bool _fixedPositionEnabled; QGeoCoordinate _fixedPosition; Systems _systems; Units _units; }; Q_DECLARE_OPERATORS_FOR_FLAGS(GNSSSettings::Systems) namespace YAML { /** Implements the conversion to and from YAML::Node. */ template<> struct convert { /** Serializes the frequency. */ static Node encode(const QGeoCoordinate& rhs) { if (! rhs.isValid()) return Node(YAML::Null); Node list; list["longitude"] = rhs.longitude(); list["latitude"] = rhs.latitude(); if (std::isfinite(rhs.altitude())) list["altitude"] = rhs.altitude(); return list; } /** Parses the frequency. */ static bool decode(const Node& node, QGeoCoordinate& rhs) { if (node.IsNull()) { rhs = QGeoCoordinate(); return true; } if (! node.IsMap()) return false; if ((!node["longitude"]) || (!node["longitude"].IsScalar())) return false; if ((!node["latitude"]) || (!node["latitude"].IsScalar())) return false; rhs.setLongitude(node["longitude"].as(std::numeric_limits::quiet_NaN())); rhs.setLatitude(node["latitude"].as(std::numeric_limits::quiet_NaN())); if (node["altitude"] && node["altitude"].IsScalar()) rhs.setAltitude(node["altitude"].as(std::numeric_limits::quiet_NaN())); return true; } }; } #endif // GNSSSETTINGS_HH ================================================ FILE: lib/gpssystem.cc ================================================ #include "gpssystem.hh" #include "contact.hh" #include "channel.hh" #include "logger.hh" #include /* ********************************************************************************************* * * Implementation of PositionReportingSystem * ********************************************************************************************* */ PositionReportingSystem::PositionReportingSystem(QObject *parent) : ConfigObject(parent), _period(Interval::infinity()) { // pass... } PositionReportingSystem::PositionReportingSystem(const QString &name, const Interval &period, QObject *parent) : ConfigObject(name, parent), _period(period) { // pass... } PositionReportingSystem::~PositionReportingSystem() { // pass... } bool PositionReportingSystem::periodDisabled() const { return !_period.isFinite(); } Interval PositionReportingSystem::period() const { return _period; } void PositionReportingSystem::setPeriod(const Interval &period) { if (_period == period) return; _period = period; emit modified(this); } void PositionReportingSystem::disablePeriod() { _period = Interval::infinity(); } bool PositionReportingSystem::populate(YAML::Node &node, const ConfigItem::Context &context, const ErrorStack &err) { if (! ConfigObject::populate(node, context, err)) return false; if (! periodDisabled()) node["period"] = _period.format().toStdString(); return true; } bool PositionReportingSystem::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse positioning system: Expected object with one child."; return false; } YAML::Node pos = node.begin()->second; if (pos && (! pos["period"])) { logWarn() << pos.Mark().line << ":" << pos.Mark().column << ": Positioning system has no period."; } else if (pos && pos["period"].IsScalar()) { Interval period; if (! period.parse(QString::fromStdString(pos["period"].as()), Interval::Format::Seconds)) disablePeriod(); else setPeriod(period); } return ConfigObject::parse(pos, ctx, err); } bool PositionReportingSystem::link(const YAML::Node &node, const Context &ctx, const ErrorStack &err) { return ConfigObject::link(node.begin()->second, ctx, err); } void PositionReportingSystem::onReferenceModified() { emit modified(this); } /* ********************************************************************************************* * * Implementation of DMRAPRSSystem * ********************************************************************************************* */ DMRAPRSSystem::DMRAPRSSystem(QObject *parent) : PositionReportingSystem(parent), _contact(), _revertChannel() { // Allow revert channel to take a reference to the SelectedChannel singleton _revertChannel.allow(SelectedChannel::get()->metaObject()); // Register '!selected' tag for revert channel Context::setTag(staticMetaObject.className(), "revert", "!selected", QVariant::fromValue(SelectedChannel::get())); // By default, selected channel is revert channel resetRevertChannel(); // Connect signals connect(&_contact, SIGNAL(modified()), this, SLOT(onReferenceModified())); connect(&_revertChannel, SIGNAL(modified()), this, SLOT(onReferenceModified())); } DMRAPRSSystem::DMRAPRSSystem(const QString &name, DMRContact *contact, DMRChannel *revertChannel, const Interval &period, QObject *parent) : PositionReportingSystem(name, period, parent), _contact(), _revertChannel() { // Allow revert channel to take a reference to the SelectedChannel singleton _revertChannel.allow(SelectedChannel::get()->metaObject()); // Register '!selected' tag for revert channel Context::setTag(staticMetaObject.className(), "revert", "!selected", QVariant::fromValue(SelectedChannel::get())); // Set references. _contact.set(contact); setRevertChannel(revertChannel); // Connect signals connect(&_contact, SIGNAL(modified()), this, SLOT(onReferenceModified())); connect(&_revertChannel, SIGNAL(modified()), this, SLOT(onReferenceModified())); } ConfigItem * DMRAPRSSystem::clone() const { DMRAPRSSystem *sys = new DMRAPRSSystem(); if (! sys->copy(*this)) { sys->deleteLater(); return nullptr; } return sys; } bool DMRAPRSSystem::hasContact() const { return ! _contact.isNull(); } DMRContact * DMRAPRSSystem::contact() const { return _contact.as(); } void DMRAPRSSystem::setContact(DMRContact *contact) { _contact.set(contact); } const DMRContactReference * DMRAPRSSystem::contactRef() const { return &_contact; } DMRContactReference * DMRAPRSSystem::contactRef() { return &_contact; } bool DMRAPRSSystem::hasRevertChannel() const { return _revertChannel.is(); } DMRChannel * DMRAPRSSystem::revertChannel() const { return _revertChannel.as(); } void DMRAPRSSystem::setRevertChannel(DMRChannel *channel) { if (nullptr == channel) resetRevertChannel(); else _revertChannel.set(channel); } void DMRAPRSSystem::resetRevertChannel() { _revertChannel.set(SelectedChannel::get()); } const DMRChannelReference* DMRAPRSSystem::revertChannelRef() const { return &_revertChannel; } DMRChannelReference* DMRAPRSSystem::revertChannelRef() { return &_revertChannel; } YAML::Node DMRAPRSSystem::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = PositionReportingSystem::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["dmr"] = node; return type; } /* ********************************************************************************************* * * Implementation of FMAPRSSystem * ********************************************************************************************* */ FMAPRSSystem::FMAPRSSystem(QObject *parent) : PositionReportingSystem(parent), _channel(), _destination(), _destSSID(0), _source(), _srcSSID(0), _path(), _icon(Icon::None), _message(), _anytone(nullptr) { // Allow revert channel to take a reference to the SelectedChannel singleton _channel.allow(SelectedChannel::get()->metaObject()); // Register '!selected' tag for revert channel Context::setTag(staticMetaObject.className(), "revert", "!selected", QVariant::fromValue(SelectedChannel::get())); // By default, selected channel is revert channel resetRevertChannel(); // Connect to channel reference connect(&_channel, SIGNAL(modified()), this, SLOT(onReferenceModified())); } FMAPRSSystem::FMAPRSSystem(const QString &name, FMChannel *channel, const QString &dest, unsigned destSSID, const QString &src, unsigned srcSSID, const QString &path, Icon icon, const QString &message, const Interval &period, QObject *parent) : PositionReportingSystem(name, period, parent), _channel(), _destination(dest), _destSSID(destSSID), _source(src), _srcSSID(srcSSID), _path(path), _icon(icon), _message(message), _anytone(nullptr) { // Allow revert channel to take a reference to the SelectedChannel singleton _channel.allow(SelectedChannel::get()->metaObject()); // Register '!selected' tag for revert channel Context::setTag(staticMetaObject.className(), "revert", "!selected", QVariant::fromValue(SelectedChannel::get())); // Set revert channel setRevertChannel(channel); // Connect to channel reference connect(&_channel, SIGNAL(modified()), this, SLOT(onReferenceModified())); } bool FMAPRSSystem::copy(const ConfigItem &other) { const FMAPRSSystem *sys = other.as(); if ((nullptr == sys) || (! PositionReportingSystem::copy(other))) return false; _destination = sys->destination(); _destSSID = sys->_destSSID; _source = sys->_source; _srcSSID = sys->_srcSSID; _path = sys->_path; return true; } ConfigItem * FMAPRSSystem::clone() const { FMAPRSSystem *sys = new FMAPRSSystem(); if (! sys->copy(*this)) { sys->deleteLater(); return nullptr; } return sys; } bool FMAPRSSystem::hasRevertChannel() const { return _channel.is(); } FMChannel * FMAPRSSystem::revertChannel() const { return _channel.as(); } void FMAPRSSystem::setRevertChannel(FMChannel *channel) { if (nullptr == channel) resetRevertChannel(); else _channel.set(channel); } void FMAPRSSystem::resetRevertChannel() { _channel.set(SelectedChannel::get()); } const FMChannelReference * FMAPRSSystem::revertChannelRef() const { return &_channel; } FMChannelReference * FMAPRSSystem::revertChannelRef() { return &_channel; } const QString & FMAPRSSystem::destination() const { return _destination; } unsigned FMAPRSSystem::destSSID() const { return _destSSID; } void FMAPRSSystem::setDestination(const QString &call, unsigned ssid) { _destination = call; _destSSID = ssid; } void FMAPRSSystem::setDestination(const QString &call) { _destination = call; } void FMAPRSSystem::setDestSSID(unsigned int ssid) { _destSSID = ssid; } const QString & FMAPRSSystem::source() const { return _source; } unsigned FMAPRSSystem::srcSSID() const { return _srcSSID; } void FMAPRSSystem::setSource(const QString &call, unsigned ssid) { _source = call; _srcSSID = ssid; } void FMAPRSSystem::setSource(const QString &call) { _source = call; } void FMAPRSSystem::setSrcSSID(unsigned ssid) { _srcSSID = ssid; } const QString & FMAPRSSystem::path() const { return _path; } void FMAPRSSystem::setPath(const QString &path) { _path = path.toUpper(); _path.replace(" ",""); } FMAPRSSystem::Icon FMAPRSSystem::icon() const { return _icon; } void FMAPRSSystem::setIcon(Icon icon) { _icon = icon; } const QString & FMAPRSSystem::message() const { return _message; } void FMAPRSSystem::setMessage(const QString &msg) { _message = msg; emit modified(this); } AnytoneFMAPRSSettingsExtension * FMAPRSSystem::anytoneExtension() const { return _anytone; } void FMAPRSSystem::setAnytoneExtension(AnytoneFMAPRSSettingsExtension *ext) { if (_anytone) { _anytone->deleteLater(); _anytone = nullptr; } if (ext) { _anytone = ext; ext->setParent(this); connect(ext, SIGNAL(modified(ConfigItem *)), this, SIGNAL(modified(ConfigItem *))); } } YAML::Node FMAPRSSystem::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = PositionReportingSystem::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; type["aprs"] = node; return type; } bool FMAPRSSystem::populate(YAML::Node &node, const Context &context, const ErrorStack &err) { if (! PositionReportingSystem::populate(node, context, err)) return false; node["destination"] = QString("%1-%2").arg(_destination).arg(_destSSID).toStdString(); node["source"] = QString("%1-%2").arg(_source).arg(_srcSSID).toStdString(); QStringList path; QRegularExpression pattern("([A-Za-z0-9]+-[0-9]+)"); int idx = 0; auto match = pattern.match(_path, idx); while (match.hasMatch()) { path.append(match.captured(1)); idx += match.capturedLength(1); match = pattern.match(_path, idx); } if (path.count()) { YAML::Node list = YAML::Node(YAML::NodeType::Sequence); list.SetStyle(YAML::EmitterStyle::Flow); foreach (QString call, path) { list.push_back(call.toStdString()); } node["path"] = list; } return true; } bool FMAPRSSystem::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse APRS system: Expected object with one child."; return false; } YAML::Node sys = node.begin()->second; if (sys["source"] && sys["source"].IsScalar()) { QString source = QString::fromStdString(sys["source"].as()); QRegularExpression pattern("^([A-Z0-9]+)-(1?[0-9])$"); auto match = pattern.match(source); if (! match.hasMatch()) { errMsg(err) << sys.Mark().line << ":" << sys.Mark().column << ": Cannot parse APRS system: '" << source << "' not a valid source call and SSID."; return false; } setSource(match.captured(1), match.captured(2).toUInt()); } else { errMsg(err) << sys.Mark().line << ":" << sys.Mark().column << ": Cannot parse APRS system: No source call+SSID specified."; return false; } if (sys["destination"] && sys["destination"].IsScalar()) { QString dest = QString::fromStdString(sys["destination"].as()); QRegularExpression pattern("^([A-Z0-9]+)-(1?[0-9])$"); auto match = pattern.match(dest); if (! match.hasMatch()) { errMsg(err) << sys.Mark().line << ":" << sys.Mark().column << ": Cannot parse APRS system: '" << dest << "' not a valid destination call and SSID."; return false; } setDestination(match.captured(1), match.captured(2).toUInt()); } else { errMsg(err) << sys.Mark().line << ":" << sys.Mark().column << ": Cannot parse APRS system: No destination call+SSID specified."; return false; } if (sys["path"] && sys["path"].IsSequence()) { QStringList path; for (YAML::const_iterator it=sys["path"].begin(); it!=sys["path"].end(); it++) { if (it->IsScalar()) path.append(QString::fromStdString(it->as())); } setPath(path.join(",")); } return PositionReportingSystem::parse(node, ctx, err); } /* ********************************************************************************************* * * Implementation of GPSSystems table * ********************************************************************************************* */ PositionReportingSystems::PositionReportingSystems(QObject *parent) : ConfigObjectList(PositionReportingSystem::staticMetaObject, parent) { // pass... } PositionReportingSystem * PositionReportingSystems::system(int idx) const { if (ConfigItem *obj = get(idx)) return obj->as(); return nullptr; } int PositionReportingSystems::add(ConfigObject *obj, int row, bool unique) { if (obj && obj->is()) return ConfigObjectList::add(obj, row, unique); return -1; } ConfigItem * PositionReportingSystems::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) return nullptr; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create positioning system: Expected object with one child."; return nullptr; } QString type = QString::fromStdString(node.begin()->first.as()); if ("dmr" == type) { return new DMRAPRSSystem(); } else if ("aprs"==type) { return new FMAPRSSystem(); } errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create positioning system: Unknown type '" << type << "'."; return nullptr; } ================================================ FILE: lib/gpssystem.hh ================================================ #ifndef GPSSYSTEM_H #define GPSSYSTEM_H #include "configreference.hh" #include #include "anytone_extension.hh" class Config; class DMRContact; class DMRChannel; class FMChannel; /** Base class of the position reporting systems, that is APRS and DMR position reporting system. * @ingroup conf */ class PositionReportingSystem: public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "aprs") /** The update period in seconds. */ Q_PROPERTY(Interval period READ period WRITE setPeriod SCRIPTABLE false) protected: /** Default constructor. */ explicit PositionReportingSystem(QObject *parent=nullptr); /** Hidden constructor. * The PositionReportingSystem class is not instantiated directly, use either @c GPSSystem or * @c APRSSystem instead. * @param name Specifies the name of the system. * @param period Specifies the auto-update period in seconds. * @param parent Specified the QObject parent object. */ PositionReportingSystem(const QString &name, const Interval &period=Interval::fromMinutes(5), QObject *parent=nullptr); public: /** Destructor. */ virtual ~PositionReportingSystem(); /** Returns @c true, if the period is disabled. */ bool periodDisabled() const; /** Returns the update period in seconds. */ Interval period() const; /** Sets the update period in seconds. */ void setPeriod(const Interval &period); /** Disable update period. */ void disablePeriod(); public: bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); bool link(const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack()); protected: bool populate(YAML::Node &node, const ConfigItem::Context &context, const ErrorStack &err=ErrorStack()); protected slots: /** Gets called, whenever a reference is modified. */ void onReferenceModified(); protected: /** Holds the update period in seconds. */ Interval _period; }; /** This class represents a DMR position reporting system within the codeplug. * @ingroup conf */ class DMRAPRSSystem : public PositionReportingSystem { Q_OBJECT /** References the destination contact. */ Q_PROPERTY(DMRContactReference* contact READ contactRef) /** References the revertRef channel. */ Q_PROPERTY(DMRChannelReference* revert READ revertChannelRef) public: /** Default constructor. */ Q_INVOKABLE explicit DMRAPRSSystem(QObject *parent=nullptr); /** Constructor. * * Please note, that a contact needs to be set in order for the GPS system to work properly. * * @param name Specifies the name of the GPS system. * @param contact Specifies the contact, the GPS position is sent to. * @param revertChannel Specifies the channel on which the GPS is sent on. If @c nullptr, the GPS * data is sent on the current channel. * @param period Specifies the update period in seconds. * @param parent Specifies the QObject parent object. */ DMRAPRSSystem(const QString &name, DMRContact *contact=nullptr, DMRChannel *revertChannel = nullptr, const Interval &period=Interval::fromMinutes(5), QObject *parent = nullptr); ConfigItem *clone() const; /** Returns @c true if a contact is set for the GPS system. */ bool hasContact() const; /** Returns the destination contact for the GPS information or @c nullptr if not set. */ DMRContact *contact() const; /** Sets the destination contact for the GPS information. */ void setContact(DMRContact *contactObj); /** Returns the reference to the destination contactRef. */ const DMRContactReference *contactRef() const; /** Returns the reference to the destination contactRef. */ DMRContactReference *contactRef(); /** Returns @c true if the GPS system has a revert channel set. If not, the GPS information will * be sent on the current channel. */ bool hasRevertChannel() const; /** Returns the revert channel for the GPS information or @c nullptr if not set. */ DMRChannel *revertChannel() const; /** Sets the revert channel for the GPS information to be sent on. */ void setRevertChannel(DMRChannel *channel); /** Resets the revert channel to the current channel. */ void resetRevertChannel(); /** Returns a reference to the revertChannelRef channel. */ const DMRChannelReference *revertChannelRef() const; /** Returns a reference to the revertChannelRef channel. */ DMRChannelReference *revertChannelRef(); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); protected: /** Holds the destination contact for the GPS information. */ DMRContactReference _contact; /** Holds the revert channel on which the GPS information is sent on. */ DMRChannelReference _revertChannel; }; /** Represents an APRS system within the generic config. * @ingroup conf */ class FMAPRSSystem: public PositionReportingSystem { Q_OBJECT /** The transmit channel. */ Q_PROPERTY(FMChannelReference* revert READ revertChannelRef) /** The destination call. */ Q_PROPERTY(QString destination READ destination WRITE setDestination SCRIPTABLE false) /** The destination SSID. */ Q_PROPERTY(unsigned int destSSID READ destSSID WRITE setDestSSID SCRIPTABLE false) /** The source call. */ Q_PROPERTY(QString source READ source WRITE setSource SCRIPTABLE false) /** The source SSID. */ Q_PROPERTY(unsigned int srcSSID READ srcSSID WRITE setSrcSSID SCRIPTABLE false) /** The APRS path as a string. */ Q_PROPERTY(QString path READ path WRITE setPath SCRIPTABLE false) /** The APRS icon. */ Q_PROPERTY(Icon icon READ icon WRITE setIcon) /** An optional text message. */ Q_PROPERTY(QString message READ message WRITE setMessage) /** Anytone specific settings. */ Q_PROPERTY(AnytoneFMAPRSSettingsExtension *anytone READ anytoneExtension WRITE setAnytoneExtension) public: static const unsigned PRIMARY_TABLE = (0<<8); ///< Primary icon table flag. static const unsigned SECONDARY_TABLE = (1<<8); ///< Secondary icon table flag. static const unsigned TABLE_MASK = (3<<8); ///< Bitmask for icon table flags. static const unsigned ICON_MASK = 0x7f; ///< Bitmask for the icon table entry. /** All implemented APRS icons. */ enum class Icon { PoliceStation = (PRIMARY_TABLE | 0), None, Digipeater, Phone, DXCluster, HFGateway, SmallPlane, MobileSatelliteStation, WheelChair, Snowmobile, RedCross, BoyScout, Home, X, RedDot, Circle0, Circle1, Circle2, Circle3, Circle4, Circle5, Circle6, Circle7, Circle8, Circle9, Fire, Campground, Motorcycle, RailEngine, Car, FileServer, HCFuture, AidStation, BBS, Canoe, Eyeball = (PRIMARY_TABLE | 36), Tractor, GridSquare, Hotel, TCPIP, School = (PRIMARY_TABLE | 42), Logon, MacOS, NTSStation, Balloon, PoliceCar, TBD, RV, Shuttle, SSTV, Bus, ATV, WXService, Helo, Yacht, Windows, Jogger, Triangle, PBBS, LargePlane, WXStation, DishAntenna, Ambulance, Bike, ICP, FireStation, Horse, FireTruck, Glider, Hospital, IOTA, Jeep, SmallTruck, Laptop, MicE, Node, EOC, Rover, Grid, Antenna, PowerBoat, TruckStop, TruckLarge, Van, Water, XAPRS, Yagi, Shelter }; Q_ENUM(Icon) public: /** Default constructor. */ Q_INVOKABLE explicit FMAPRSSystem(QObject *parent=nullptr); /** Constructor for a APRS system. * @param name Specifies the name of the APRS system. This property is just a name, it does not * affect the radio configuration. * @param channel Specifies the transmit channel. This property is not optional. A transmit * channel must be specified to obtain a working APRS system. * @param dest Specifies the destination call, APRS messages are sent to. Usually 'WIDE3' is a * reasonable setting. * @param destSSID Specifies the destination SSID. Usually 3 is a reasonable choice. * @param src Specifies the source call, usually you call has to be entered here. * @param srcSSID The source SSID, usually 7 is a reasonable choice of handhelds. * @param path Specifies the APRS path string. * @param icon Specifies the map icon to send. * @param message An optional message to send. * @param period Specifies the auto-update period in seconds. * @param parent Specifies the QObject parent object. */ FMAPRSSystem(const QString &name, FMChannel *channel, const QString &dest, unsigned destSSID, const QString &src, unsigned srcSSID, const QString &path="", Icon icon=Icon::Jogger, const QString &message="", const Interval &period=Interval::fromMinutes(5), QObject *parent=nullptr); bool copy(const ConfigItem &other); ConfigItem *clone() const; /** Returns @c true if a revert channel is set. If false, the APRS information is send on the * current channel. */ bool hasRevertChannel() const; /** Returns the transmit channel of the APRS system. */ FMChannel *revertChannel() const; /** Sets the transmit channel of the APRS system. */ void setRevertChannel(FMChannel *revertChannel); /** Resets the revert channel to the current one */ void resetRevertChannel(); /** Returns a reference to the revertChannelRef channel. */ const FMChannelReference *revertChannelRef() const; /** Returns a reference to the revertChannelRef channel. */ FMChannelReference *revertChannelRef(); /** Returns the destination call. */ const QString &destination() const; /** Returns the destination SSID. */ unsigned destSSID() const; /** Sets the destination call and SSID. */ void setDestination(const QString &call, unsigned ssid); /** Sets the destination call. */ void setDestination(const QString &call); /** Sets the destination SSID. */ void setDestSSID(unsigned ssid); /** Returns the source call. */ const QString &source() const; /** Returns the source SSID. */ unsigned srcSSID() const; /** Sets the source call and SSID. */ void setSource(const QString &call, unsigned ssid); /** Sets the source call. */ void setSource(const QString &call); /** Sets the source SSID. */ void setSrcSSID(unsigned ssid); /** Returns the APRS path. */ const QString &path() const; /** Sets the APRS path. */ void setPath(const QString &path); /** Returns the map icon. */ Icon icon() const; /** Sets the map icon. */ void setIcon(Icon icon); /** Returns the optional message. */ const QString &message() const; /** Sets the optional APRS message text. */ void setMessage(const QString &msg); /** Returns the Anytone settings extension, if set. */ AnytoneFMAPRSSettingsExtension *anytoneExtension() const; /** Sets the Anytone settings extension. */ void setAnytoneExtension(AnytoneFMAPRSSettingsExtension *ext); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); protected: bool populate(YAML::Node &node, const Context &context, const ErrorStack &err=ErrorStack()); protected: /** A weak reference to the transmit channel. */ FMChannelReference _channel; /** Holds the destination call. */ QString _destination; /** Holds the destination SSID. */ unsigned _destSSID; /** Holds the source call. */ QString _source; /** Holds the source SSID. */ unsigned _srcSSID; /** Holds the APRS path string. */ QString _path; /** Holds the map icon. */ Icon _icon; /** Holds the optional message. */ QString _message; /** Owns the Anytone settings extension. */ AnytoneFMAPRSSettingsExtension *_anytone; }; /** The list of positioning systems. * @ingroup conf */ class PositionReportingSystems: public ConfigObjectList { Q_OBJECT public: /** Constructs an empty list of GPS systems. */ explicit PositionReportingSystems(QObject *parent=nullptr); /** Returns the positioning system at the specified index. */ PositionReportingSystem *system(int idx) const; int add(ConfigObject *obj, int row=-1, bool unique=true); public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // GPSSYSTEM_H ================================================ FILE: lib/hid_libusb.cc ================================================ #include "hid_libusb.hh" #include "logger.hh" #define HID_INTERFACE 0 // interface index #define TIMEOUT_MSEC 500 // receive timeout #define MAX_RETRY 20 // Number of retries /* ********************************************************************************************* * * Implementation of HIDevice::Descriptor * ********************************************************************************************* */ HIDevice::Descriptor::Descriptor(const USBDeviceInfo &info, uint8_t bus, uint8_t device) : USBDeviceDescriptor(info, USBDeviceHandle(bus, device)) { // pass... } /* ********************************************************************************************* * * Implementation of HIDevice * ********************************************************************************************* */ HIDevice::HIDevice(const USBDeviceDescriptor &descr, const ErrorStack &err, QObject *parent) : QObject(parent), _ctx(nullptr), _dev(nullptr), _transfer(nullptr) { if (USBDeviceInfo::Class::HID != descr.interfaceClass()) { errMsg(err) << "Cannot connect to HID device using a non HID descriptor: " << descr.description() << "."; return; } int error = libusb_init(&_ctx); if (error < 0) { errMsg(err) << "Libusb init failed (" << error << "): " << libusb_strerror((enum libusb_error) error) << "."; return; } int num=0; libusb_device **lst; libusb_device *dev=nullptr; if (0 > (num = libusb_get_device_list(_ctx, &lst))) { errMsg(err) << "Cannot obtain list of USB devices."; libusb_exit(_ctx); _ctx = nullptr; return; } logDebug() << "Try to detect USB HID interface " << descr.description() << "."; USBDeviceHandle addr = descr.device().value(); for (int i=0; (i libusb_get_device_descriptor(lst[i],&usb_descr)) continue; if (descr.vendorId() != usb_descr.idVendor) continue; if (descr.productId() != usb_descr.idProduct) continue; logDebug() << "Matching device found at bus " << addr.bus << ", device " << addr.device << " with vendor ID " << QString::number(usb_descr.idVendor, 16) << " and product ID " << QString::number(usb_descr.idProduct, 16) << "."; libusb_ref_device(lst[i]); dev = lst[i]; } // Unref all devices and free list, matching device was referenced earlier libusb_free_device_list(lst, 1); if (nullptr == dev) { errMsg(err) << "No matching device found: " << descr.description() << "."; libusb_exit(_ctx); _ctx = nullptr; return; } if (0 > (error = libusb_open(dev, &_dev))) { errMsg(err) << "Cannot open device " << descr.description() << ": " << libusb_strerror((enum libusb_error) error) << "."; libusb_unref_device(dev); libusb_exit(_ctx); _ctx = nullptr; } if (libusb_kernel_driver_active(_dev, 0)) { error = libusb_detach_kernel_driver(_dev, 0); if (error < 0) { logWarn() << "Failed to detach kernel from HID interface (" << error << "): " << libusb_strerror((enum libusb_error) error) << ". Device claim will likely fail."; } } error = libusb_claim_interface(_dev, HID_INTERFACE); if (error < 0) { errMsg(err) << "Failed to claim HID interface (" << error << "): " << libusb_strerror((enum libusb_error) error) << "."; libusb_close(_dev); libusb_exit(_ctx); _dev = nullptr; _ctx = nullptr; } } HIDevice::~HIDevice() { close(); } QList HIDevice::detect(uint16_t vid, uint16_t pid) { QList res; int error, num; libusb_context *ctx; if (0 > (error = libusb_init(&ctx))) { logError() << "Libusb init failed (" << error << "): " << libusb_strerror((enum libusb_error) error) << "."; return res; } libusb_device **lst; if (0 == (num = libusb_get_device_list(ctx, &lst))) { logDebug() << "No USB devices found at all."; // unref devices and free list libusb_free_device_list(lst, 1); return res; } logDebug() << "Search for HID interfaces matching VID:PID " << QString::number(vid, 16) << ":" << QString::number(pid, 16) << "."; for (int i=0; (i> 8; if (nbytes > 0) memcpy(buf+4, data, nbytes); nbytes += 4; reply_len = write_read(buf, sizeof(buf), reply, sizeof(reply)); if (reply_len < 0) { err.take(_cbError); return false; } if (reply_len != sizeof(reply)) { errMsg(err) << "Short read: " << reply_len << " bytes instead of " << (int)sizeof(reply) << "!"; return false; } if (reply[0] != 3 || reply[1] != 0 || reply[3] != 0) { errMsg(err) << "Incorrect reply!"; return false; } if (reply[2] != rlength) { errMsg(err) << "Incorrect reply length " << reply[2] << ", expected " << rlength << "."; return false; } memcpy(rdata, reply+4, rlength); return true; } int HIDevice::write_read(const unsigned char *data, unsigned length, unsigned char *reply, unsigned rlength, const ErrorStack &err) { if (! _transfer) { // Allocate transfer descriptor on first invocation. _transfer = libusb_alloc_transfer(0); } libusb_fill_interrupt_transfer( _transfer, _dev, LIBUSB_RECIPIENT_INTERFACE | LIBUSB_ENDPOINT_IN, reply, rlength, read_callback, this, TIMEOUT_MSEC); size_t nretry = 0; again: _nbytes_received = 0; libusb_submit_transfer(_transfer); int result = libusb_control_transfer( _dev, LIBUSB_REQUEST_TYPE_CLASS|LIBUSB_RECIPIENT_INTERFACE|LIBUSB_ENDPOINT_OUT, 0x09/*HID Set_Report*/, (2/*HID output*/ << 8) | 0, HID_INTERFACE, (unsigned char*)data, length, TIMEOUT_MSEC); if (result < 0) { err.take(_cbError); errMsg(err) << "Error " << result << " transmitting data via control transfer: " << libusb_strerror((enum libusb_error) result) << "."; _transfer = nullptr; return -1; } while (_nbytes_received == 0) { result = libusb_handle_events(_ctx); if (result < 0) { /* Break out of this loop only on fatal error.*/ if (result != LIBUSB_ERROR_BUSY && result != LIBUSB_ERROR_TIMEOUT && result != LIBUSB_ERROR_OVERFLOW && result != LIBUSB_ERROR_INTERRUPTED) { err.take(_cbError); errMsg(err) << "Error " <= MAX_RETRY) { logError() << "HID (libusb): Retry limit of " << MAX_RETRY << " exceeded."; } return _nbytes_received; } void HIDevice::read_callback(struct libusb_transfer *t) { HIDevice *self = (HIDevice *)t->user_data; switch (t->status) { case LIBUSB_TRANSFER_COMPLETED: memcpy(self->_receive_buf, t->buffer, t->actual_length); self->_nbytes_received = t->actual_length; break; case LIBUSB_TRANSFER_CANCELLED: self->_nbytes_received = LIBUSB_ERROR_INTERRUPTED; errMsg(self->_cbError) << libusb_error_name(LIBUSB_ERROR_INTERRUPTED); break; case LIBUSB_TRANSFER_NO_DEVICE: self->_nbytes_received = LIBUSB_ERROR_NO_DEVICE; errMsg(self->_cbError) << libusb_error_name(LIBUSB_ERROR_NO_DEVICE); break; case LIBUSB_TRANSFER_TIMED_OUT: self->_nbytes_received = LIBUSB_ERROR_TIMEOUT; errMsg(self->_cbError) << libusb_error_name(LIBUSB_ERROR_TIMEOUT); break; default: self->_nbytes_received = LIBUSB_ERROR_IO; errMsg(self->_cbError) << libusb_error_name(LIBUSB_ERROR_IO); break; } } ================================================ FILE: lib/hid_libusb.hh ================================================ #ifndef HID_MACOS_HH #define HID_MACOS_HH #include #include #include "errorstack.hh" #include "radiointerface.hh" /** Implements the HID radio interface using libusb. * @ingroup rif */ class HIDevice: public QObject { Q_OBJECT public: /** Specialization to address a HI device uniquely. */ class Descriptor: public USBDeviceDescriptor { public: /** Constructor from interface info, bus number and device address. */ Descriptor(const USBDeviceInfo &info, uint8_t bus, uint8_t device); }; public: /** Connects to the device with given vendor and product ID. */ HIDevice(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Destructor. */ virtual ~HIDevice(); /** Returns @c true if the connection is established. */ bool isOpen() const; /** Send command/data to the device and store response in @c rdata. * @param data Pointer to the command/data to send. * @param nbytes The number of bytes to send. * @param rdata Pointer to receive buffer. * @param rlength Size of receive buffer. * @param err Passes an error stack to put error messages on. */ bool hid_send_recv(const unsigned char *data, unsigned nbytes, unsigned char *rdata, unsigned rlength, const ErrorStack &err=ErrorStack()); /** Close connection to device. */ void close(); public: /** Finds all HID interfaces with the specified VID/PID combination. */ static QList detect(uint16_t vid, uint16_t pid); protected: /** Internal used implementation of send_recv(). */ int write_read(const unsigned char *data, unsigned length, unsigned char *reply, unsigned rlength, const ErrorStack &err=ErrorStack()); /** Callback for response data. */ static void read_callback(struct libusb_transfer *t); protected: /** libusb context. */ libusb_context *_ctx; /** libusb device. */ libusb_device_handle *_dev; /** libusb async transfer descriptor. */ struct libusb_transfer *_transfer; /** Receive buffer. */ unsigned char _receive_buf[42]; /** Receive result. */ volatile int _nbytes_received; /** Internal used error stack for the static callback function. */ ErrorStack _cbError; }; #endif // HID_MACOS_HH ================================================ FILE: lib/hid_macos.cc ================================================ #include "hid_macos.hh" #include #include #include #include #include #include #include /* ********************************************************************************************* * * Implementation of HIDevice::Descriptor * ********************************************************************************************* */ HIDevice::Descriptor::Descriptor(const USBDeviceInfo &info, uint32_t locationId, uint16_t device) : USBDeviceDescriptor(info, USBDeviceHandle((locationId>>24), device, locationId)) { // pass... } /* ********************************************************************************************* * * Implementation of HIDevice * ********************************************************************************************* */ HIDevice::HIDevice(const USBDeviceDescriptor &desc, const ErrorStack &err, QObject *parent) : QObject(parent), _dev(nullptr) { // Create the USB HID Manager. _HIDManager = IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDOptionsTypeNone); // Create an empty matching dictionary for filtering USB devices in our HID manager. CFMutableDictionaryRef matchDict = CFDictionaryCreateMutable( kCFAllocatorDefault, 2, &kCFTypeDictionaryKeyCallBacks, &kCFTypeDictionaryValueCallBacks); // Specify the USB device manufacturer and product in our matching dictionary. int locationId = desc.device().value().locationId; CFDictionarySetValue(matchDict, CFSTR(kIOHIDLocationIDKey), CFNumberCreate(kCFAllocatorDefault, kCFNumberIntType, &locationId)); // Apply the matching to our HID manager. IOHIDManagerSetDeviceMatching(_HIDManager, matchDict); CFRelease(matchDict); // The HID manager will use callbacks when specified USB devices are connected/disconnected. IOHIDManagerRegisterDeviceMatchingCallback(_HIDManager, &callback_open, this); IOHIDManagerRegisterDeviceRemovalCallback(_HIDManager, &callback_close, this); // Add the HID manager to the main run loop IOHIDManagerScheduleWithRunLoop(_HIDManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); // Open the HID manager IOReturn IOReturn = IOHIDManagerOpen(_HIDManager, kIOHIDOptionsTypeNone); if (IOReturn != kIOReturnSuccess) { errMsg(err) << "Cannot open HID manager for USB device " << QString::number(locationId,16) << "."; IOHIDManagerUnscheduleFromRunLoop(_HIDManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); _HIDManager = nullptr; return; } // Run loop until device found. for (int k=0; k<4; k++) { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 0); if (_dev) return; usleep(10000); } errMsg(err) << "Cannot open USB device " << QString::number(locationId,16) << "."; IOHIDManagerUnscheduleFromRunLoop(_HIDManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); IOHIDManagerClose(_HIDManager, kIOHIDOptionsTypeNone); _HIDManager = nullptr; } HIDevice::~HIDevice() { if (_dev) close(); if (_HIDManager) { IOHIDManagerUnscheduleFromRunLoop(_HIDManager, CFRunLoopGetMain(), kCFRunLoopDefaultMode); IOHIDManagerClose(_HIDManager, kIOHIDOptionsTypeNone); } } QList HIDevice::detect(uint16_t vid, uint16_t pid) { /* Get the IO registry which has the system information for connected hardware. */ io_registry_entry_t entry = IORegistryGetRootEntry(kIOMasterPortDefault); if (entry == 0) return QList(); /* Get an iterator for the USB plane. */ io_iterator_t iter = 0; kern_return_t kret = IORegistryEntryCreateIterator(entry, kIOUSBPlane, kIORegistryIterateRecursively, &iter); if (kret != KERN_SUCCESS || iter == 0) return QList(); QList devices; io_service_t service = 0; while ((service = IOIteratorNext(iter))) { IOCFPlugInInterface **plug = NULL; IOUSBDeviceInterface **dev = NULL; io_string_t path; SInt32 score = 0; IOReturn ioret; /* Pull out IO Plugins for each iterator. */ kret = IOCreatePlugInInterfaceForService(service, kIOUSBDeviceUserClientTypeID, kIOCFPlugInInterfaceID, &plug, &score); IOObjectRelease(service); if (kret != KERN_SUCCESS || plug == NULL) continue; /* Get an IOUSBDeviceInterface for each USB device from the IO Plugin object. */ ioret = (*plug)->QueryInterface(plug, CFUUIDGetUUIDBytes(kIOUSBDeviceInterfaceID), (void **)&dev); (*plug)->Release(plug); if (ioret != kIOReturnSuccess || dev == NULL) continue; /* Print out the path in the IO Plane the device is at. */ if (IORegistryEntryGetPath(service, kIOServicePlane, path) != KERN_SUCCESS) { (*dev)->Release(dev); continue; } UInt16 dev_vendor, dev_product, dev_addr; UInt32 dev_location; if ( ((*dev)->GetDeviceVendor(dev, &dev_vendor) != kIOReturnSuccess) || (dev_vendor != vid) ) continue; if ( ((*dev)->GetDeviceProduct(dev, &dev_product) != kIOReturnSuccess) || (dev_product != pid) ) continue; if ((*dev)->GetLocationID(dev, &dev_location) != kIOReturnSuccess) continue; if ((*dev)->GetDeviceAddress(dev, &dev_addr) != kIOReturnSuccess) continue; logDebug() << "Found device with vid:pid " << QString::number(vid, 16) << ":" << QString::number(pid, 16) << " at " << QString::number(dev_location, 16) << "."; devices.append(HIDevice::Descriptor( USBDeviceInfo(USBDeviceInfo::Class::HID, vid, pid), dev_location, dev_addr)); /* All done with this device. */ (*dev)->Release(dev); } IOObjectRelease(iter); return devices; } bool HIDevice::isOpen() const { return nullptr != _dev; } // // Send a request to the device. // Store the reply into the rdata[] array. // Terminate in case of errors. // bool HIDevice::hid_send_recv(const unsigned char *data, unsigned nbytes, unsigned char *rdata, unsigned rlength, const ErrorStack &err) { unsigned char buf[42]; unsigned k; IOReturn result; if (! isOpen()) return false; memset(buf, 0, sizeof(buf)); buf[0] = 1; buf[1] = 0; buf[2] = nbytes; buf[3] = nbytes >> 8; if (nbytes > 0) memcpy(buf+4, data, nbytes); nbytes += 4; _nbytes_received = 0; memset(_receive_buf, 0, sizeof(_receive_buf)); uint retrycount = 0; again: // Write to HID device. result = IOHIDDeviceSetReport(_dev, kIOHIDReportTypeOutput, 0, buf, sizeof(buf)); if (result != kIOReturnSuccess) { errMsg(err) << "HID output error: " << result << "!"; return false; } // Run application loop until reply received. CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 0); for (k = 0; _nbytes_received <= 0; k++) { usleep(100); CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, 0); if (k >= 1000) { retrycount++; if (retrycount<100) goto again; errMsg(err) << "HID IO error: Exceeded max. retry count."; return false; } } usleep(100); if (_nbytes_received != sizeof(_receive_buf)) { errMsg(err) << "Short read: " << _nbytes_received << " bytes instead of " << (int)sizeof(_receive_buf) << "!"; return false; } if ((_receive_buf[0] != 3) || (_receive_buf[1] != 0) || (_receive_buf[3] != 0)) { errMsg(err) << "Incorrect reply. Expected {3,0,0}, got {" << int(_receive_buf[0]) << "," << int(_receive_buf[1]) << "," << int(_receive_buf[3]) << "}."; return false; } if (_receive_buf[2] != rlength) { errMsg(err) << "Incorrect reply length " << (int)_receive_buf[2] << ", expected " << rlength << "!"; return false; } memcpy(rdata, _receive_buf+4, rlength); return true; } // // Callback: data is received from the HID device // void HIDevice::callback_input(void *context, IOReturn result, void *sender, IOHIDReportType type, uint32_t reportID, uint8_t *data, CFIndex nbytes) { Q_UNUSED(sender); Q_UNUSED(type); Q_UNUSED(reportID); HIDevice *self = reinterpret_cast(context); if (result != kIOReturnSuccess) { logError() << "HID input error: " << result << "."; self->close(); return; } if (nbytes > CFIndex(sizeof(self->_receive_buf))) { logError() << "Too large HID input: " << (int)nbytes << " bytes!"; self->close(); return; } self->_nbytes_received = nbytes; if (nbytes > 0) memcpy(self->_receive_buf, data, nbytes); } // // Callback: device specified in the matching dictionary has been added // void HIDevice::callback_open(void *context, IOReturn result, void *sender, IOHIDDeviceRef deviceRef) { Q_UNUSED(result); Q_UNUSED(sender); HIDevice *self = reinterpret_cast(context); IOReturn o = IOHIDDeviceOpen(deviceRef, kIOHIDOptionsTypeSeizeDevice); if (o != kIOReturnSuccess) { logError() << "Cannot open HID device!"; return; } // Register input callback. IOHIDDeviceRegisterInputReportCallback(deviceRef, self->_transfer_buf, sizeof(self->_transfer_buf), callback_input, self); self->_dev = deviceRef; } // // Callback: device specified in the matching dictionary has been removed // void HIDevice::callback_close(void *context, IOReturn result, void *sender, IOHIDDeviceRef deviceRef) { Q_UNUSED(result); Q_UNUSED(sender); HIDevice *self = reinterpret_cast(context); // De-register input callback. IOHIDDeviceRegisterInputReportCallback( deviceRef, self->_transfer_buf, sizeof(self->_transfer_buf), NULL, NULL); } // // Close HID device. // void HIDevice::close() { if (! _dev) return; IOHIDDeviceClose(_dev, kIOHIDOptionsTypeNone); _dev = nullptr; } ================================================ FILE: lib/hid_macos.hh ================================================ #ifndef HID_MACOS_HH #define HID_MACOS_HH #include #include #include "errorstack.hh" #include "radiointerface.hh" /** Implements the HID radio interface MacOS X API. * @ingroup rif */ class HIDevice: public QObject { Q_OBJECT public: /** Specialization to address a HI device uniquely. */ class Descriptor: public USBDeviceDescriptor { public: /** Constructor from interface info, bus number and device address. */ Descriptor(const USBDeviceInfo &info, uint32_t locationId, uint16_t device); }; public: /** Opens a connection to the device with given vendor and product ID. */ HIDevice(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Destructor. */ virtual ~HIDevice(); /** Returns @c true if the connection was established. */ bool isOpen() const; /** Send command/data to the device and store response in @c rdata. * @param data Pointer to the command/data to send. * @param nbytes The number of bytes to send. * @param rdata Pointer to receive buffer. * @param rlength Size of receive buffer. * @param err The stack to put error messages on. */ bool hid_send_recv(const unsigned char *data, unsigned nbytes, unsigned char *rdata, unsigned rlength, const ErrorStack &err=ErrorStack()); /** Close connection to device. */ void close(); public: /** Finds all HID interfaces with the specified VID/PID combination. */ static QList detect(uint16_t vid, uint16_t pid); protected: /** Internal callback for response data. */ static void callback_input(void *context, IOReturn result, void *sender, IOHIDReportType type, uint32_t reportID, uint8_t *data, CFIndex nbytes); /** Internal callback for device opend. */ static void callback_open(void *context, IOReturn result, void *sender, IOHIDDeviceRef deviceRef); /** Internal callback for device closed. */ static void callback_close(void *ontext, IOReturn result, void *sender, IOHIDDeviceRef deviceRef); protected: /** Device manager. */ IOHIDManagerRef _HIDManager; /** Device handle. */ volatile IOHIDDeviceRef _dev; /** Device buffer. */ unsigned char _transfer_buf[42]; /** Receive buffer. */ unsigned char _receive_buf[42]; /** Receive result. */ volatile int _nbytes_received = 0; }; #endif // HID_MACOS_HH ================================================ FILE: lib/intermediaterepresentation.cc ================================================ #include "intermediaterepresentation.hh" #include "configobject.hh" #include "zone.hh" #include "encryptionextension.hh" /* ********************************************************************************************* * * Implementation of ZoneSplitVisitor * ********************************************************************************************* */ ZoneSplitVisitor::ZoneSplitVisitor() : Visitor() { // pass... } bool ZoneSplitVisitor::processItem(ConfigItem *item, const ErrorStack &err) { // Skip non-zones if (! item->is()) return Visitor::processItem(item, err); Zone *zone = item->as(); // skip zones with empty B list if (0 == zone->B()->count()) return Visitor::processItem(item, err); // create new zone with B list as A list, clear B list of "old" zone Zone *newZone = new Zone(); while (zone->B()->count()) { newZone->A()->add(zone->B()->get(0)); zone->B()->take(zone->B()->get(0)); } // set names newZone->setName(QString("%1 B").arg(zone->name())); zone->setName(QString("%1 A").arg(zone->name())); // add zone to list of zones ConfigObjectList *lst = qobject_cast(item->parent()); int idx = lst->indexOf(zone); lst->add(newZone, idx+1); return true; } /* ********************************************************************************************* * * Implementation of ZoneMergeVisitor * ********************************************************************************************* */ ZoneMergeVisitor::ZoneMergeVisitor() : Visitor(), _lastZone(nullptr), _mergedZones() { // pass... } bool ZoneMergeVisitor::processList(AbstractConfigObjectList *list, const ErrorStack &err) { if (nullptr == qobject_cast(list)) return Visitor::processList(list, err); if (2 > list->count()) return Visitor::processList(list, err); _mergedZones.clear(); _lastZone = nullptr; if (! processItem(list->get(0), err)) return false; _lastZone = list->get(0)->as(); for (int i=1; icount(); i++) { if (! processItem(list->get(i), err)) return false; _lastZone = list->get(i)->as(); } foreach(Zone *zone, _mergedZones) { list->del(zone); } _mergedZones.clear(); return true; } bool ZoneMergeVisitor::processItem(ConfigItem *item, const ErrorStack &err) { if (! item->is()) return Visitor::processItem(item, err); Zone *currentZone = item->as(); if ((! _lastZone) || (!_lastZone->name().endsWith(" A")) || (0 != _lastZone->B()->count())) return Visitor::processItem(item, err); if ((!currentZone->name().endsWith(" B")) || (0 != currentZone->B()->count())) return Visitor::processItem(item, err); while (currentZone->A()->count()) { ConfigObject *ch = currentZone->A()->get(0); _lastZone->B()->add(ch); currentZone->A()->del(ch); } _lastZone->setName(_lastZone->name().chopped(2)); _mergedZones.append(currentZone); return Visitor::processItem(item); } /* ********************************************************************************************* * * Implementation of AbstractObjectFilterVisitor * ********************************************************************************************* */ AbstractObjectFilterVisitor::AbstractObjectFilterVisitor() : Visitor() { // pass... } bool AbstractObjectFilterVisitor::processProperty(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err) { if (! propIsInstance(prop)) return Visitor::processProperty(item, prop, err); if (prop.read(item).isNull()) return Visitor::processProperty(item, prop, err); if (! prop.isWritable()) return Visitor::processProperty(item, prop, err); ConfigItem *propItem = prop.read(item).value(); if (toRemove(propItem)) { if (prop.write(item, QVariant(prop.metaType()))) return true; errMsg(err) << "Cannot delete instance of " << item->metaObject()->className() << " from property " << prop.name() << "."; return false; } return Visitor::processProperty(item, prop, err); } bool AbstractObjectFilterVisitor::processList(AbstractConfigObjectList *list, const ErrorStack &err) { if (qobject_cast(list)) return Visitor::processList(list, err); ConfigObjectList *objList = qobject_cast(list); if (nullptr == objList) return Visitor::processList(list, err);; QList filtered; for (int i=0; icount(); i++) { if (toRemove(objList->get(i))) filtered.append(objList->get(i)); } foreach (ConfigObject *item, filtered) objList->del(item); return Visitor::processList(list, err); } /* ********************************************************************************************* * * Implementation of ObjectFilterVisitor * ********************************************************************************************* */ ObjectFilterVisitor::ObjectFilterVisitor(const std::initializer_list &types) : AbstractObjectFilterVisitor(), _filter(types) { // pass... } bool ObjectFilterVisitor::toRemove(ConfigItem *item) { foreach (const QMetaObject &meta, _filter) if (item->inherits(meta.className())) return true; return false; } /* ********************************************************************************************* * * Implementation of EncryptionKeyFilterVisitor::Filter * ********************************************************************************************* */ EncryptionKeyFilterVisitor::Filter::Filter(const QMetaObject &meta_type, unsigned int bits) : type(meta_type), minBits(bits), maxBits(bits) { // pass... } EncryptionKeyFilterVisitor::Filter::Filter(const QMetaObject &meta_type, unsigned int min_bits, unsigned int max_bits) : type(meta_type), minBits(min_bits), maxBits(max_bits) { // pass... } bool EncryptionKeyFilterVisitor::Filter::accepts(const EncryptionKey *key) const { return key->inherits(type.className()) && (key->key().size()*8>=minBits) && (key->key().size()*8<=maxBits); } /* ********************************************************************************************* * * Implementation of EncryptionKeyFilterVisitor * ********************************************************************************************* */ EncryptionKeyFilterVisitor::EncryptionKeyFilterVisitor(const std::initializer_list &filter) : AbstractObjectFilterVisitor(), _filter(filter) { // pass... } bool EncryptionKeyFilterVisitor::toRemove(ConfigItem *item) { if (! item->is()) return false; auto key = item->as(); foreach (const Filter &filter, _filter) { if (filter.accepts(key)) return false; } return true; } ================================================ FILE: lib/intermediaterepresentation.hh ================================================ /** @defgroup ir Intermediate representation. * This module collects visitors applied to a codeplug before the actual encoding of the device * specific binary codeplug. This preprocessing step eases the encoding a lot. * * @ingroup config */ #ifndef INTERMEDIATEREPRESENTATION_HH #define INTERMEDIATEREPRESENTATION_HH #include "visitor.hh" #include class Zone; class EncryptionKey; /** Simple visitor that splits Zones having A and B channels into two zones with A-lists only. * This is a pre-processing step for many radios, where zones consists of a single list of channels * and a zone is selected for each VFO separately. * @ingroup ir */ class ZoneSplitVisitor: public Visitor { public: /** Constructor. */ explicit ZoneSplitVisitor(); bool processItem(ConfigItem *item, const ErrorStack &err); }; /** Simple visitor that merges zones. This is the reverse step of the @c ZoneSplitVisitor. * Two subsequent zones are only merged into one, if both zones have empty B lists, the name of the * first zone ends on "... A" and the name of the second ends on "... B". That is, if the two zones * where likely split by qdmr. */ class ZoneMergeVisitor: public Visitor { public: /** Constructor. */ explicit ZoneMergeVisitor(); bool processList(AbstractConfigObjectList *list, const ErrorStack &err); bool processItem(ConfigItem *item, const ErrorStack &err); protected: /** The last zone visited, @c nullptr if the first zone is processed. */ Zone *_lastZone; /** Zones to be removed. */ QList _mergedZones; }; /** Filters instances by a test function. * * This visitor can be used to remove elements from the abstract codeplug, not supported by the * target device. Use @c ObjectFilterVisitor to remove instances of specific classes. */ class AbstractObjectFilterVisitor: public Visitor { protected: /** Hidden constructor. */ explicit AbstractObjectFilterVisitor(); public: bool processProperty(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err); bool processList(AbstractConfigObjectList *list, const ErrorStack &err); protected slots: /** Abstract test function. If this function returns @c true, the item will be removed. */ virtual bool toRemove(ConfigItem *item) = 0; }; /** Filters instance by meta object. * * This visitor can be used to remove elements from the abstract codeplug, not supported by the * target device. */ class ObjectFilterVisitor: public AbstractObjectFilterVisitor { public: /** Constructor from initializer list of Qt meta objects. */ explicit ObjectFilterVisitor(const std::initializer_list &types); protected slots: bool toRemove(ConfigItem *item); protected: /** The list of filtered types. */ QList _filter; }; /** Only keeps those encryption keys, that match a specific type and size. */ class EncryptionKeyFilterVisitor: public AbstractObjectFilterVisitor { public: struct Filter { QMetaObject type; unsigned int minBits; unsigned int maxBits; Filter(const QMetaObject &type, unsigned int bits); Filter(const QMetaObject &type, unsigned int minBits, unsigned int maxBits); bool accepts(const EncryptionKey *key) const; }; public: EncryptionKeyFilterVisitor(const std::initializer_list &filter); protected: bool toRemove(ConfigItem *item); protected: QList _filter; }; #endif // INTERMEDIATEREPRESENTATION_HH ================================================ FILE: lib/interval.cc ================================================ #include "interval.hh" #include QString Interval::format(Format f) const { if (isNull()) return "0"; if (isInfinite()) return "infinite"; switch (f) { case Format::Automatic: if (0 == _duration % 60000UL) return format(Format::Minutes); else if (0 == _duration % 1000UL) return format(Format::Seconds); return format(Format::Milliseconds); case Format::Minutes: return QString("%1 min").arg(_duration/60000UL); case Format::Seconds: return QString("%1 s").arg(_duration/1000UL); case Format::Milliseconds: return QString("%1 ms").arg(_duration); } return QString("%1 ms").arg(_duration); } bool Interval::parse(const QString &value, Format format) { if (value.toLower().startsWith("inf")) { _duration = std::numeric_limits::max(); return true; } QRegularExpression ex(R"(\s*([0-9]+)\s*(min|s|ms|)\s*)"); QRegularExpressionMatch match = ex.match(value); if (! match.hasMatch()) return false; bool hasUnit = match.capturedLength(2); QString unit = match.captured(2); QString dur = match.captured(1); if ((hasUnit && ("s"==unit)) || (!hasUnit && (Format::Seconds == format))) _duration = dur.toULongLong()*1000ULL; else if ((hasUnit && ("min"==unit)) || (!hasUnit && (Format::Minutes == format))) _duration = dur.toULongLong()*60000ULL; else _duration = dur.toULongLong(); return true; } ================================================ FILE: lib/interval.hh ================================================ #ifndef INTERVAL_HH #define INTERVAL_HH #include #include #include /** Represents a time interval. * @ingroup utils */ class Interval { public: /** Possible formats. */ enum class Format { Automatic, Milliseconds, Seconds, Minutes }; protected: /** Constructor from milliseconds. */ constexpr explicit Interval(unsigned long long ms) : _duration(ms) { // pass... } public: /** Default constructor. */ Interval() : _duration(0) { // pass... } /** Copy constructor. */ constexpr Interval(const Interval &other) : _duration(other._duration) { // pass... } inline Interval &operator =(const Interval &other) { ///< Assignment. _duration = other._duration; return *this; } inline bool isNull() const { return 0 == _duration; } ///< Test for 0. /** Test for infinite durations. */ inline bool isInfinite() const { return std::numeric_limits::max() == _duration; } /** Test for finite values. */ inline bool isFinite() const { return (!isNull()) && (!isInfinite()); } inline bool operator ==(const Interval &other) const { ///< Comparison. return _duration == other._duration; } inline bool operator< (const Interval &other) const {///< Comparison. return _duration < other._duration; } inline bool operator<= (const Interval &other) const {///< Comparison. return _duration <= other._duration; } inline bool operator> (const Interval &other) const {///< Comparison. return _duration > other._duration; } inline bool operator>= (const Interval &other) const {///< Comparison. return _duration >= other._duration; } inline unsigned long long milliseconds() const { return _duration; } ///< Unit conversion. inline unsigned long long seconds() const { return _duration/1000ULL; } ///< Unit conversion. inline unsigned long long minutes() const { return _duration/60000ULL; } ///< Unit conversion. static inline constexpr Interval fromMilliseconds(unsigned long long ms) { ///< Unit conversion. return Interval(ms); } static inline constexpr Interval fromSeconds(unsigned long long s) { ///< Unit conversion. return Interval(s*1000ULL); } static inline constexpr Interval fromMinutes(unsigned long long min) { ///< Unit conversion. return Interval(min*60000ULL); } /** Format the frequency. */ QString format(Format f=Format::Automatic) const; /** Parses a frequency. */ bool parse(const QString &value, Format defaultFormat=Format::Milliseconds); public: /** Constructs a null interval. */ static inline Interval null() { return Interval(0); } /** Constructs an infinite interval. */ static inline Interval infinity() { return Interval(std::numeric_limits::max()); } private: /** An interval duration in ms. */ unsigned long long _duration; }; Q_DECLARE_METATYPE(Interval) namespace YAML { /** Implements the conversion to and from YAML::Node. */ template<> struct convert { /** Serializes the interval. */ static Node encode(const Interval& rhs) { return Node(rhs.format().toStdString()); } /** Parses the interval. */ static bool decode(const Node& node, Interval& rhs) { if (!node.IsScalar()) return false; return rhs.parse(QString::fromStdString(node.as())); } }; } #endif // INTERVAL_HH ================================================ FILE: lib/level.cc ================================================ #include "level.hh" Level::Level() : _level(std::numeric_limits::max()) { // pass... } QString Level::format() const{ if (isNull()) return "off"; else if (isInvalid()) return "none"; return QString::number(_level); } bool Level::parse(const QString &value) { if ("off" == value.toLower().simplified()) { _level = 0; return true; } if ("none" == value.toLower().simplified()) { _level = std::numeric_limits::max(); return true; } bool ok; _level = std::min(10U, value.toUInt(&ok)); return ok; } ================================================ FILE: lib/level.hh ================================================ #ifndef LEVEL_HH #define LEVEL_HH #include #include #include #include #include "codeplug.hh" /** Some simple class implementing a [1-10] level setting. Also supports the "values" null and * invalid. */ class Level { protected: /** Constructor from value. */ inline constexpr Level(unsigned int value) : _level(value) {} public: /** Default constructor. */ Level(); inline constexpr Level(const Level &other) : _level(other._level) { // pass... } inline Level &operator =(const Level &other) = default; inline bool isNull() const { return 0 == _level; } ///< Test for 0. /** Test for invalid level. */ inline bool isInvalid() const { return std::numeric_limits::max() == _level; } /** Test for finite values. That is, if the value is neither null nor invalid. */ inline bool isFinite() const { return (!isNull()) && (!isInvalid()); } inline bool operator !=(const Level &other) const { ///< Comparison. return _level != other._level; } inline bool operator ==(const Level &other) const { ///< Comparison. return _level == other._level; } inline bool operator< (const Level &other) const {///< Comparison. return _level < other._level; } inline bool operator<= (const Level &other) const {///< Comparison. return _level <= other._level; } inline bool operator> (const Level &other) const {///< Comparison. return _level > other._level; } inline bool operator>= (const Level &other) const {///< Comparison. return _level >= other._level; } /** Returns the value of the level. */ inline unsigned int value() const { return _level; } inline unsigned int mapTo(const Codeplug::Element::Limit::Range &range) const { if (isNull() || isInvalid()) return 0; return Codeplug::Element::Limit::Range{1,10}.mapTo(range, value()); } /** Format the frequency. */ QString format() const; /** Parses a frequency. */ bool parse(const QString &value); public: /** Constructs null level. */ inline static constexpr Level null() { return Level(0); } /** Constructs an invalid level. */ inline static constexpr Level invalid() { return Level(std::numeric_limits::max()); } /** Constructs a proper level. */ inline static constexpr Level fromValue(unsigned int value, const Codeplug::Element::Limit::Range range={1,10}) { // If 0 is not in normal range -> always may 0 -> 0 (e.g. means off). if ((0 == value) && (0 != range.min)) return Level::null(); return Level(range.mapTo({1,10},value)); } protected: /** The actual level value. */ unsigned int _level; }; Q_DECLARE_METATYPE(Level) namespace YAML { /** Implements the conversion to and from YAML::Node. */ template<> struct convert { /** Serializes the interval. */ static Node encode(const Level& rhs) { return Node(rhs.format().toStdString()); } /** Parses the interval. */ static bool decode(const Node& node, Level& rhs) { // Usually means default level if (node.IsNull()) { rhs = Level::invalid(); return true; } // Fails on non-scalars if (!node.IsScalar()) return false; // Otherwise, parse string. return rhs.parse(QString::fromStdString(node.as())); } }; } #endif // LEVEL_HH ================================================ FILE: lib/libdmrconf.hh ================================================ /** @mainpage libdmrconf -- A Library to program DMR radios. * Libdmrconf is a Qt based library, providing methods to assemble, read and program codeplugs for * cheap Chinese DMR radios. To this end, it aims at providing a common way to describe codeplugs * irrespective of the actual radio being used. * * The aim of this project is, to define a minimal but sufficient generic configuration for all * radios, such that all relevant ham-radio features of the radios can be used. This is a challenge, * as there are many manufacturers, each selling several different models, each having * a variety of different features. In fact, manufacturers provide a separate application to program * each model. The resulting codeplugs are usually stored in a binary format, * similar to the actual codeplug being loaded onto the radio. * * This makes absolutely sense for commercial applications, like radios used by staff at large * events. In these cases, the organizer of the event would buy large quantities of identical radios and * assemble a single codeplug for all radios. * * For ham-radio purposes, however, this incompatibility between models and manufactures is an * issue. Assembling a decent codeplug for a region with several repeaters is cumbersome. As these * applications cannot share codeplugs between models let alone between manufactures, this * cumbersome work has to be repeated for each model, although the basic configuration of these * models like contact, channels & zones remains identical. In fact, many features of these radios * are specific to the commercial application and are not used in ham-radio context. * * A common minimalistic configuration of these radios for ham-radio use would allow for sharing * codeplugs between different models and even manufactures. Hence, the hard work to * assemble a decent codeplug for a particular region only needs to be done once. * * To this end, @c libdmrconf defines a configuration language in text format, that represents the * configuration of the radio and from which the actual binary code-plug for each radio is derived. * If a radio does not support a particular feature within the configuration, it simply gets * ignored. The configuration language as well as classes for reading and writing config files are * documented in the manual. * * @section usage Using libdmrconf * - Using cmake to find libdmrconf * - setting paths * - linking * - include files * * @section interface Interfacing radios * In a first step, detect all USB devices that could be connected radios. Some radios use generic * USB CDC-AMC (USB-to-serial) chips. In these cases be careful, that you do not confuse them with * other hardware connected to the host. So, to detect USB interfaces use the device specific * interfaces, e.g. @c AnytoneInterface::detect(), @c RadioddityInterface::detect() or * @c TyTInterface::detect(). For example * @code * #include * * int main(void) { * // Find any supported devices connected * QList devices = AnytoneInterface::detect() + * TyTInterface::detect() + * RadioddityInterface::detect() + * OpenGD77Interface::detect(); * if (0 == devices.size()) { * std::cerr << "No matching device found."; * return -1; * } * * // As the connected radio may use a generic USB CDC-ACM chip, check if the interface is safe. * if (! devices.first().isSave()) { * std::cerr << "Sorry, the detected device is not save to talk to automatically, " * "check if the chosen device is the correct one."; * return -1; * } * * // Then try to connect to the first found. * // The radio interface is common to all devices. So the remaining code is device independent. * Radio *radio = Radio::detect(devices.first(), RadioInfo(), err); * if (nullptr == radio) { * std::cerr << "Oops:" << err.format(); * return -1; * } * * // Download codeplug * if (! radio->startDownload(true, err)) { * std::cerr << "Error while downloading codeplug:" << err.format(); * delete radio; * return -1; * } * * // Decode codeplug to device independent config * Config config; * if (! radio->codeplug().decode(&config, err)) { * std::cerr << "Cannot decode codeplug:" << err.format(); * delete radio; * return -1; * } * * // Now, manipulate the generic codeplug in config. * * // Verify codeplug with radio * RadioLimitContext issues; * if (! radio->limits().verifyConfig(config, issues)) { * // Dump issues... * delete radio; * return -1; * } * * // Encode and upload codeplug back to the device * if (! radio->startUpload(config, true, Codeplug::Flags(), err)) { * std::err << "Cannot write codeplug: " << err.format(); * delete radio; * return -1; * } * * // Done. * delete radio; * return 0; * } * @endcode * * @section codeplug Reading and writing codeplug files * - Using YAML for reading and writing codeplug files * * @section config Manipulating codeplugs * - Some examples for basic manipulations of the @c Config object. * */ #ifndef __LIBDMRCONF_HH__ #define __LIBDMRCONF_HH__ #include "config.h" #include "config.hh" #include "csvreader.hh" #include "radio.hh" #include "anytone_interface.hh" #include "tyt_interface.hh" #include "opengd77_interface.hh" #include "radioddity_interface.hh" #endif // __LIBDMRCONF_HH__ ================================================ FILE: lib/logger.cc ================================================ #include "logger.hh" #include #include #include /* ********************************************************************************************* * * Implementation of LogMessage * ********************************************************************************************* */ LogMessage::LogMessage(Level level, const QString &file, int line, const QString &message) : QTextStream(), _level(level), _file(file), _line(line), _message(message) { this->setString(&_message); this->seek(_message.size()); } LogMessage::LogMessage(const LogMessage &other) : QTextStream(), _level(other._level), _file(other._file), _line(other._line), _message(other._message) { this->setString(&_message); this->seek(_message.size()); } LogMessage::~LogMessage() { Logger::get().log(*this); } LogMessage::Level LogMessage::level() const { return _level; } const QString & LogMessage::file() const { return _file; } int LogMessage::line() const { return _line; } const QString & LogMessage::message() const { return _message; } /* ********************************************************************************************* * * Implementation of LogHandler * ********************************************************************************************* */ LogHandler::LogHandler(QObject *parent) : QObject(parent) { // pass... } LogHandler::~LogHandler() { // pass... } /* ********************************************************************************************* * * Implementation of Logger * ********************************************************************************************* */ Logger *Logger::_instance = nullptr; Logger::Logger() : QObject(nullptr), _handler(), _lock() { // pass... } Logger::~Logger() { _handler.clear(); } void Logger::log(const LogMessage &msg) { _lock.lock(); foreach (LogHandler *handler, _handler) { handler->handle(msg); } _lock.unlock(); } void Logger::addHandler(LogHandler *handler) { if (nullptr == handler) return; if (_handler.contains(handler)) return; handler->setParent(this); _handler.append(handler); connect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(onHandlerDeleted(QObject*))); } void Logger::remHandler(LogHandler *handler) { if (_handler.contains(handler)) { handler->setParent(nullptr); disconnect(handler, SIGNAL(destroyed(QObject*)), this, SLOT(onHandlerDeleted(QObject*))); } _handler.removeAll(handler); } void Logger::onHandlerDeleted(QObject *obj) { _handler.removeAll(dynamic_cast(obj)); } Logger & Logger::get() { if (nullptr == _instance) { _instance = new Logger(); } return *_instance; } /* ********************************************************************************************* * * Implementation of StreamLogHandler * ********************************************************************************************* */ StreamLogHandler::StreamLogHandler(QTextStream &stream, LogMessage::Level minLevel, bool color, QObject *parent) : LogHandler(parent), _stream(stream), _minLevel(minLevel), _color(color) { // pass... } LogMessage::Level StreamLogHandler::minLevel() const { return _minLevel; } void StreamLogHandler::setMinLevel(LogMessage::Level minLevel) { _minLevel = minLevel; } void StreamLogHandler::handle(const LogMessage &message) { if (message.level() < _minLevel) return; switch (message.level()) { case LogMessage::DEBUG: if (_color) _stream << "\033[37m"; _stream << "Debug "; break; case LogMessage::INFO: if (_color) _stream << "\033[39m"; _stream << "Info "; break; case LogMessage::WARNING: if (_color) _stream << "\033[33m"; _stream << "Warning "; break; case LogMessage::ERROR: if (_color) _stream << "\033[31m"; _stream << "ERROR "; break; case LogMessage::FATAL: if (_color) _stream << "\033[30m\033[101m"; _stream << "FATAL "; break; } QFileInfo finfo(message.file()); _stream << "in " << finfo.dir().dirName() << "/" << finfo.fileName() << "@" << message.line() << ": " << message.message() << "\n"; if (_color) _stream << "\033[39m"; _stream.flush(); } /* ********************************************************************************************* * * Implementation of FileLogHandler * ********************************************************************************************* */ FileLogHandler::FileLogHandler(const QString &filename, LogMessage::Level minLevel, QObject *parent) : LogHandler(parent), _file(filename), _stream(), _minLevel(minLevel) { QFileInfo info(filename); // Check if logfile exists if (! info.exists()) { // check and create path to logfile (if needed) if (! info.absoluteDir().mkpath(info.absoluteDir().absolutePath())) { logError() << "Cannot create log-file directory '" << info.absoluteDir().absolutePath() << "'."; return; } // Create logfile _file.open(QFile::WriteOnly); } else { // Open logfile _file.open(QFile::Append); } if (_file.isOpen()) { // Set stream desitnation to logfile _stream.setDevice(&_file); } } FileLogHandler::~FileLogHandler() { if (_file.isOpen()) { _file.flush(); _file.close(); } } LogMessage::Level FileLogHandler::minLevel() const { return _minLevel; } void FileLogHandler::setMinLevel(LogMessage::Level minLevel) { _minLevel = minLevel; } void FileLogHandler::handle(const LogMessage &message) { if (!_file.isOpen()) return; if (message.level() < _minLevel) return; _stream << QDateTime::currentDateTime().toString(Qt::ISODateWithMs) << ": "; switch (message.level()) { case LogMessage::DEBUG: _stream << "Debug "; break; case LogMessage::INFO: _stream << "Info "; break; case LogMessage::WARNING: _stream << "Warning "; break; case LogMessage::ERROR: _stream << "ERROR "; break; case LogMessage::FATAL: _stream << "FATAL "; break; } QFileInfo finfo(message.file()); _stream << "in " << finfo.dir().dirName() << "/" << finfo.fileName() << "@" << message.line() << ": " << message.message() << "\n"; _stream.flush(); } ================================================ FILE: lib/logger.hh ================================================ /** @defgroup log Log Message Handling * @ingroup util */ #ifndef LOGGER_HH #define LOGGER_HH #include #include #include #include /** Constructs a debug message. */ #define logDebug() LogMessage(LogMessage::DEBUG, __FILE__, __LINE__) /** Constructs an info message. */ #define logInfo() LogMessage(LogMessage::INFO, __FILE__, __LINE__) /** Constructs a warning message. */ #define logWarn() LogMessage(LogMessage::WARNING, __FILE__, __LINE__) /** Constructs an error message. */ #define logError() LogMessage(LogMessage::ERROR, __FILE__, __LINE__) /** Constructs a fatal error message. */ #ifdef __cpp_lib_stacktrace #include #define logFatal() LogMessage(LogMessage::FATAL, __FILE__, __LINE__) << \ QString::fromStdString(std::to_string(std::stacktrace::current())) #else #define logFatal() LogMessage(LogMessage::FATAL, __FILE__, __LINE__) #endif /** Implements a log-message. * Instances of this class will forward the content of this message automatically to the @c Logger * instance upon destruction. That means, you do not need to forward log messages explicitly. * @ingroup log */ class LogMessage: public QTextStream { public: /** Possible log-levels. */ typedef enum { DEBUG, ///< Level for debug messages. Will not be shown to the user unless requested. INFO, ///< Level for informative messages. Will not be shown to the user unless requested. WARNING, ///< Level for warning messages. ERROR, ///< Level for error messages. FATAL ///< Level for fatal error messages. } Level; public: /** Constructor. * @param level Specifies the level of the log message. * @param file Specifies the source file. * @param line Specifies the source line. * @param message Specifies the log message content. */ LogMessage(Level level, const QString &file, int line, const QString &message=""); /** Copy constructor. */ LogMessage(const LogMessage &other); /** Destructor. */ virtual ~LogMessage(); /** Returns the level of the log message. */ Level level() const; /** Returns the source file. */ const QString &file() const; /** Returns the source line. */ int line() const; /** Returns the log message content. */ const QString &message() const; protected: /** The log level. */ Level _level; /** The source file. */ QString _file; /** The source line. */ int _line; /** The log message content. */ QString _message; }; /** Interface for all log message handler. * @ingroup log */ class LogHandler: public QObject { Q_OBJECT public: /** Constructor. */ explicit LogHandler(QObject *parent=nullptr); /** Destructor. */ virtual ~LogHandler(); /** Callback to handle log messages. */ virtual void handle(const LogMessage &message) = 0; }; /** Singleton class to process log messages. * @ingroup log */ class Logger: public QObject { Q_OBJECT protected: /** Hidden constructor. Use @c get method to obtain an instance. */ Logger(); public: /** Destructor. */ virtual ~Logger(); /** Logs a message. */ void log(const LogMessage &msg); /** Adds a log-handler to the logger. The ownership is transferred to the logger. */ void addHandler(LogHandler *handler); /** Removes a log-handler from the logger. The ownership is transferred back to the caller. */ void remHandler(LogHandler *handler); protected slots: /** Internal callback to handle deleted handler objects. */ void onHandlerDeleted(QObject *obj); public: /** Factory method to get the singleton instance. */ static Logger &get(); protected: /** The singleton instance. */ static Logger *_instance; /** The list of registered log-handler. */ QList _handler; /** Some mutex to prevent issues with log messages from different threads. */ QMutex _lock; }; /** A log-handler that dumps log-messages into a @c QTextStream. * @ingroup log */ class StreamLogHandler: public LogHandler { Q_OBJECT public: /** Constructor. * @param stream Specifies the text stream to log into. * @param minLevel Specifies the minimum log-level to log. * @param color If @c true, the output will be colored. * @param parent Specifies the parent object. */ StreamLogHandler(QTextStream &stream, LogMessage::Level minLevel=LogMessage::DEBUG, bool color=false, QObject *parent=nullptr); /** Returns the minimum log level. */ LogMessage::Level minLevel() const; /** Resets the minimum log level. */ void setMinLevel(LogMessage::Level minLevel); void handle(const LogMessage &message); protected: /** A reference to the text stream to log into. */ QTextStream &_stream; /** The minimum log level. */ LogMessage::Level _minLevel; /** If true, write messages using console colors. */ bool _color; }; /** A log-handler that dumps log-messages into files. * @ingroup log */ class FileLogHandler: public LogHandler { Q_OBJECT public: /** Constructor. * @param file Specifies the filename to log to. * @param minLevel Specifies the minimum log-level to log. * @param parent Specifies the parent object. */ FileLogHandler(const QString &file, LogMessage::Level minLevel=LogMessage::DEBUG, QObject *parent=nullptr); /** Destructor, closes log file. */ virtual ~FileLogHandler(); /** Returns the minimum log level. */ LogMessage::Level minLevel() const; /** Resets the minimum log level. */ void setMinLevel(LogMessage::Level minLevel); void handle(const LogMessage &message); protected: /** The file to log into. */ QFile _file; /** A reference to the text stream to log into. */ QTextStream _stream; /** The minimum log level. */ LogMessage::Level _minLevel; }; #endif // LOGGER_HH ================================================ FILE: lib/md2017.cc ================================================ #include "md2017.hh" #include "md2017_limits.hh" RadioLimits *MD2017::_limits = nullptr; MD2017::MD2017(TyTInterface *device, QObject *parent) : TyTRadio(device, parent), _name("TyT DM-2017") { //_codeplug(nullptr), _callsigndb(nullptr); } MD2017::~MD2017() { // pass... } const QString & MD2017::name() const { return _name; } const RadioLimits & MD2017::limits() const { if (nullptr == _limits) _limits = new MD2017Limits(); return *_limits; } const Codeplug & MD2017::codeplug() const { return _codeplug; } Codeplug & MD2017::codeplug() { return _codeplug; } const CallsignDB * MD2017::callsignDB() const { return &_callsigndb; } CallsignDB * MD2017::callsignDB() { return &_callsigndb; } RadioInfo MD2017::defaultRadioInfo() { return RadioInfo( RadioInfo::MD2017, "md2017", "MD-2017", "TyT", {TyTInterface::interfaceInfo()}, QList{ RadioInfo(RadioInfo::RT82, "RT82", "Retevis", {TyTInterface::interfaceInfo()}) }); } ================================================ FILE: lib/md2017.hh ================================================ /** @defgroup md2017 TYT MD-2017, Retevis RT82 * Device specific classes for TYT MD-2017 and Retevis RT82. * * \image html md2017.jpg "MD-2017" width=200px * \image latex md2017.jpg "MD-2017" width=200px * * * The TYT MD-2017 and the identical Retevis RT-82 are decent VHF/UHF FM and DMR handheld radios. * Both radios are available with and without an GPS option. @c libdmrconf will support that * feature. Non-GPS variants of that radio will simply ignore any GPS system settings. * * These radios support up to 3000 channels organized in 250 zones. Each zone may hold up to 64 * channels for each VFO (64 for VFO A and 64 for VFO B). There are also up to 250 scanlists * holding up to 31(?) channels each. * * The radio can hold up to 3000 contacts (DMR contacts) and 250 RX group lists as well as up to 50 * pre-programmed messages. Depending on the firmware programmed on the radio, it may also hold a * callsign database of up to 100000 entries. This can be used to resolve amlost any DMR ID assigned * (at the time of this writing, there are about 140k IDs assigned) to name and callsign. * * @ingroup tyt */ #ifndef MD2017_HH #define MD2017_HH #include "tyt_radio.hh" #include "md2017_codeplug.hh" #include "md2017_callsigndb.hh" /** Implements an USB interface to the TYT MD-2017 & Retevis RT82 VHF/UHF 5W DMR (Tier I&II) radios. * * The TYT MD-2017 and Retevis RT82 radios use the TyT typical DFU-style communication protocol * to read and write codeplugs onto the radio (see @c TyTRadio). * * @ingroup md2017 */ class MD2017 : public TyTRadio { Q_OBJECT public: /** Constructor. * @param device Specifies the DFU device to use for communication with the device. * @param parent The QObject parent. */ MD2017(TyTInterface *device=nullptr, QObject *parent=nullptr); /** Desturctor. */ virtual ~MD2017(); const QString &name() const; const RadioLimits &limits() const; const Codeplug &codeplug() const; Codeplug &codeplug(); const CallsignDB *callsignDB() const; CallsignDB *callsignDB(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: /** Holds the name of the device. */ QString _name; /** The codeplug object. */ MD2017Codeplug _codeplug; /** The callsign DB object. */ MD2017CallsignDB _callsigndb; private: /** Singleton instance of the limits for this radio. */ static RadioLimits *_limits; }; #endif // MD2017_HH ================================================ FILE: lib/md2017_callsigndb.cc ================================================ #include "md2017_callsigndb.hh" MD2017CallsignDB::MD2017CallsignDB(QObject *parent) : TyTCallsignDB(parent) { image(0).setName("TYT MD-2017 Callsign database."); } MD2017CallsignDB::~MD2017CallsignDB() { // pass... } ================================================ FILE: lib/md2017_callsigndb.hh ================================================ #ifndef MD2017_CALLSIGNDB_HH #define MD2017_CALLSIGNDB_HH #include "tyt_callsigndb.hh" /** Device specific implementation of the call-sign DB for the TyT MD-2017. * * In fact this callsign DB is identical to the generic @c TyTCallsignDB. * * @ingroup md2017 */ class MD2017CallsignDB : public TyTCallsignDB { Q_OBJECT public: /** Constructor. */ explicit MD2017CallsignDB(QObject *parent=nullptr); /** Destructor. */ virtual ~MD2017CallsignDB(); }; #endif // MD2017_CALLSIGNDB_HH ================================================ FILE: lib/md2017_codeplug.cc ================================================ #include "md2017_codeplug.hh" #include "config.hh" #include "logger.hh" #define NUM_CHANNELS 3000 #define ADDR_CHANNELS 0x110000 #define CHANNEL_SIZE 0x000040 #define NUM_CONTACTS 10000 #define ADDR_CONTACTS 0x140000 #define CONTACT_SIZE 0x000024 #define NUM_ZONES 250 #define ADDR_ZONES 0x0149e0 #define ZONE_SIZE 0x000040 #define ADDR_ZONEEXTS 0x031000 #define ZONEEXT_SIZE 0x0000e0 #define NUM_GROUPLISTS 250 #define ADDR_GROUPLISTS 0x00ec20 #define GROUPLIST_SIZE 0x000060 #define NUM_SCANLISTS 250 #define ADDR_SCANLISTS 0x018860 #define SCANLIST_SIZE 0x000068 #define ADDR_TIMESTAMP 0x002000 #define ADDR_SETTINGS 0x002040 #define ADDR_BOOTSETTINGS 0x02f000 #define ADDR_MENUSETTINGS 0x0020f0 #define ADDR_BUTTONSETTINGS 0x002100 #define ADDR_PRIVACY_KEYS 0x0059c0 #define NUM_GPSSYSTEMS 16 #define ADDR_GPSSYSTEMS 0x03ec40 #define GPSSYSTEM_SIZE 0x000010 #define ADDR_EMERGENCY_SETTINGS 0x005a50 #define NUM_EMERGENCY_SYSTEMS 32 #define ADDR_EMERGENCY_SYSTEMS 0x005a60 #define EMERGENCY_SYSTEM_SIZE 0x000028 #define ADDR_VFO_CHANNEL_A 0x02ef00 #define ADDR_VFO_CHANNEL_B 0x02ef40 /* ********************************************************************************************* * * MD2017Codeplug::ContactElement * ********************************************************************************************* */ MD2017Codeplug::ContactElement::ContactElement(uint8_t *ptr, size_t size) : TyTCodeplug::ContactElement(ptr, size) { // pass... } MD2017Codeplug::ContactElement::ContactElement(uint8_t *ptr) : TyTCodeplug::ContactElement(ptr) { // pass... } bool MD2017Codeplug::ContactElement::isValid() const { return Codeplug::Element::isValid() && (! name().isEmpty()); } MD2017Codeplug::MD2017Codeplug(QObject *parent) : TyTCodeplug(parent) { addImage("TYT MD-2017 Codeplug"); image(0).addElement(0x002000, 0x3e000); image(0).addElement(0x110000, 0x90000); // Clear entire codeplug clear(); } MD2017Codeplug::~MD2017Codeplug() { // pass... } void MD2017Codeplug::clearTimestamp() { TimestampElement(data(ADDR_TIMESTAMP)).clear(); } bool MD2017Codeplug::encodeTimestamp() { TimestampElement ts(data(ADDR_TIMESTAMP)); ts.setTimestamp(QDateTime::currentDateTime()); return true; } void MD2017Codeplug::clearGeneralSettings() { GeneralSettingsElement(data(ADDR_SETTINGS)).clear(); } bool MD2017Codeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err) return GeneralSettingsElement(data(ADDR_SETTINGS)).fromConfig(ctx.config()); } bool MD2017Codeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return GeneralSettingsElement(data(ADDR_SETTINGS)).updateConfig(ctx.config()); } void MD2017Codeplug::clearChannels() { // Clear channels for (int i=0; i()) { chan.fromChannelObj(ctx.get(i+1), ctx); } else { chan.clear(); } } return true; } bool MD2017Codeplug::createChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); for (int i=0; ichannelList()->add(obj); ctx.add(obj, i+1); } else { logWarn() << "Cannot decode channel at index " << i << ":\n" << err.format(" "); continue; } } return true; } bool MD2017Codeplug::linkChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); for (int i=0; i(i+1))) continue; ErrorStack err; if (! chan.linkChannelObj(ctx.get(i+1), ctx, err)) { logWarn() << "Cannot link channel at index " << i << ":\n" << err.format(" "); continue; } } return true; } void MD2017Codeplug::clearContacts() { // Clear contacts for (int i=0; i()) cont.fromContactObj(ctx.get(i+1)); else cont.clear(); } return true; } bool MD2017Codeplug::createContacts(Context &ctx, const ErrorStack &err) { for (int i=0; icontacts()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid contact at index " << i << "."; return false; } } return true; } void MD2017Codeplug::clearZones() { // Clear zones & zone extensions for (int i=0; izones()->count()) { zone.fromZoneObj(ctx.config()->zones()->zone(i), ctx); if (ctx.config()->zones()->zone(i)->B()->count() || (16 < ctx.config()->zones()->zone(i)->A()->count())) ext.fromZoneObj(ctx.config()->zones()->zone(i), ctx); } } return true; } bool MD2017Codeplug::createZones(Context &ctx, const ErrorStack &err) { for (int i=0; izones()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid zone at index " << i << "."; return false; } } return true; } bool MD2017Codeplug::linkZones(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } ZoneExtElement zoneext(data(ADDR_ZONEEXTS + i*ZONEEXT_SIZE)); if (! zoneext.linkZoneObj(ctx.get(i+1), ctx)) { errMsg(err) << "Cannot link zone extension at index " << i << "."; return false; } } return true; } void MD2017Codeplug::clearGroupLists() { for (int i=0; irxGroupLists()->count()) glist.fromGroupListObj(ctx.config()->rxGroupLists()->list(i), ctx); else glist.clear(); } return true; } bool MD2017Codeplug::createGroupLists(Context &ctx, const ErrorStack &err) { for (int i=0; irxGroupLists()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid group list at index " << i << "."; return false; } } return true; } bool MD2017Codeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link group list at index " << i << "."; return false; } } return true; } void MD2017Codeplug::clearScanLists() { // Clear scan lists for (int i=0; iscanlists()->count()) scan.fromScanListObj(ctx.config()->scanlists()->scanlist(i), ctx); else scan.clear(); } return true; } bool MD2017Codeplug::createScanLists(Context &ctx, const ErrorStack &err) { for (int i=0; iscanlists()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid scanlist at index " << i << "."; return false; } } return true; } bool MD2017Codeplug::linkScanLists(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link scan list at index" << i << "."; return false; } } return true; } void MD2017Codeplug::clearPositioningSystems() { // Clear GPS systems for (int i=0; i()) gps.fromGPSSystemObj(ctx.get(i+1), ctx); else gps.clear(); } return true; } bool MD2017Codeplug::createPositioningSystems(Context &ctx, const ErrorStack &err) { for (int i=0; iposSystems()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid GPS system at index " << i << "."; return false; } } return true; } bool MD2017Codeplug::linkPositioningSystems(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link GPS system at index " << i << "."; return false; } } return true; } void MD2017Codeplug::clearBootSettings() { BootSettingsElement(data(ADDR_BOOTSETTINGS)).clear(); } void MD2017Codeplug::clearMenuSettings() { MenuSettingsElement(data(ADDR_MENUSETTINGS)).clear(); } void MD2017Codeplug::clearButtonSettings() { ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).clear(); } bool MD2017Codeplug::encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err) // Encode settings return ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).fromConfig(ctx.config()); } bool MD2017Codeplug::decodeButtonSetttings( Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).updateConfig(ctx.config()); } void MD2017Codeplug::clearPrivacyKeys() { EncryptionElement(data(ADDR_PRIVACY_KEYS)).clear(); } bool MD2017Codeplug::encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // First, reset keys clearPrivacyKeys(); // Get keys EncryptionElement keys(data(ADDR_PRIVACY_KEYS)); // If there is no encryption extension -> done. if (! ctx.config()->commercialExtension()) return true; // Otherwise encode return keys.fromCommercialExt(ctx.config()->commercialExtension(), ctx); } bool MD2017Codeplug::decodePrivacyKeys(Context &ctx, const ErrorStack &err) { // Get keys EncryptionElement keys(data(ADDR_PRIVACY_KEYS)); // Decode element if(! keys.updateCommercialExt(ctx)) { errMsg(err) << "Cannot create encryption keys."; return false; } return true; } void MD2017Codeplug::clearTextMessages() { MessageBankElement(data(Offset::messages())).clear(); } bool MD2017Codeplug::encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).encode(ctx, flags, err); } bool MD2017Codeplug::decodeTextMessages(Context &ctx, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).decode(ctx, err); } void MD2017Codeplug::clearEmergencySystems() { EmergencySettingsElement(data(ADDR_EMERGENCY_SETTINGS)).clear(); for (int i=0; i * Start End Size Content * First segment 0x002000-0x040000 * 0x002000 0x00200c 0x0000c Timestamp see @c TyTCodeplug::TimestampElement. * 0x00200c 0x002040 0x00034 Reserved, filled with 0xff. * 0x002040 0x0020f0 0x000b0 General settings see @c TyTCodeplug::GeneralSettingsElement. * 0x0020f0 0x002100 0x00010 Menu settings, see @c UV390Codeplug::menu_t * 0x002100 0x002140 0x00040 Button config, see @c UV390Codeplug::buttons_t. * 0x002140 0x002180 0x00040 Reserved, filled with 0xff. * 0x002180 0x0059c0 0x03840 50 Text messages @ 0x120 bytes each, see @c UV390Codeplug::message_t. * 0x0059c0 0x005a70 0x000b0 Privacy keys, see @c UV390Codeplug::privacy_t. * 0x005a70 0x005a80 0x00010 Emergency system settings, see @c TyTCodeplug::EmergencySettingsElement. * 0x005a80 0x005f80 0x00500 Emergency systems, see @c TyTCodeplug::EmergencySystemElement. * 0x005f80 0x00ec20 0x08ca0 Reserved, filled with 0xff. * 0x00ec20 0x0149e0 0x05dc0 250 RX Group lists @ 0x60 bytes each, see @c TyTCodeplug::GroupListElement. * 0x0149e0 0x018860 0x03e80 250 Zones @ 0x40 bytes each, see @c TyTCodeplug::ZoneElement. * 0x018860 0x01edf0 0x06590 250 Scanlists @ 0x68 bytes each, see @c TyTCodeplug::ScanListElement. * 0x01edf0 0x02ef00 0x10110 Reserved, filled with @c 0xff. * 0x02ef00 0x02ef40 0x00040 VFO A channel, see @c TyTCodeplug::VFOChannelElement. * 0x02ef40 0x02ef80 0x00040 VFO B channel, see @c TyTCodeplug::VFOChannelElement. * 0x02ef80 0x02f000 0x00080 Reserved, filled with @c 0xff. * 0x02f000 0x02f010 0x00010 Some unknown settings like current channel, see @c TyTCodeplug::BootSettingsElement. * 0x02f010 0x031000 0x01ff0 Reserved, filled with @c 0xff. * 0x031000 0x03eac0 0x0dac0 250 Zone-extensions @ 0xe0 bytes each, see @c TyTCodeplug::ZoneExtElement. * 0x03eac0 0x03ec40 0x00180 Reserved, filled with @c 0xff. * 0x03ec40 0x03ed40 0x00100 16 GPS systems @ 0x10 bytes each, see @c TyTCodeplug::GPSSystemElement. * 0x03ed40 0x040000 0x012c0 Reserved, filled with @c 0xff. * Second segment 0x110000-0x1a0000 * 0x110000 0x13ee00 0x2ee00 3000 Channels @ 0x40 bytes each, see @c TyTCodeplug::ChannelElement. * 0x13ee00 0x140000 0x01200 Reserved, filled with @c 0xff. * 0x140000 0x197e40 0x57e40 10000 Contacts @ 0x24 bytes each, see @c TyTCodeplug::ContactElement. * 0x197e40 0x1a0000 0x081c0 Reserved, filled with @c 0xff. * * * @ingroup md2017 */ class MD2017Codeplug : public TyTCodeplug { Q_OBJECT public: /** Reuse TyT MD-UV390 channel element. */ typedef UV390Codeplug::ChannelElement ChannelElement; /** Reuse TyT MD-UV390 VFO channel element. */ typedef UV390Codeplug::VFOChannelElement VFOChannelElement; /** Reuse TyT MD-UV390 zone extension element. */ typedef UV390Codeplug::ZoneExtElement ZoneExtElement; /** Reuse TyT MD-UV390 boot settings element. */ typedef UV390Codeplug::BootSettingsElement BootSettingsElement; /** Reuse TyT MD-UV390 menu settings element. */ typedef UV390Codeplug::MenuSettingsElement MenuSettingsElement; /** Contact element for MD2017 codeplugs. * * This class implements the same memory layout as the base TyTCodeplug::ContactElement. It just * overrides the @c isValid method. * * Memory layout of encoded contact: * @verbinclude tyt_contact.txt */ class ContactElement: public TyTCodeplug::ContactElement { protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactElement(uint8_t *ptr); bool isValid() const; }; public: /** Constructor. */ explicit MD2017Codeplug(QObject *parent = nullptr); /** Destructor. */ virtual ~MD2017Codeplug(); public: void clearTimestamp(); bool encodeTimestamp(); void clearGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearZones(); bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); void clearGroupLists(); bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearScanLists(); bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearPositioningSystems(); bool encodePositioningSystems(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createPositioningSystems(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkPositioningSystems(Context &ctx, const ErrorStack &err=ErrorStack()); void clearButtonSettings(); bool encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeButtonSetttings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearPrivacyKeys(); bool encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err); bool decodePrivacyKeys(Context &ctx, const ErrorStack &err=ErrorStack()); void clearTextMessages(); bool encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err); bool decodeTextMessages(Context &ctx, const ErrorStack &err); /** Resets/clears the boot settings. */ virtual void clearBootSettings(); void clearMenuSettings(); void clearEmergencySystems(); /** Resets VFO settings. */ virtual void clearVFOSettings(); protected: /** Some internal offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int messages() { return 0x002180; } /// @endcond }; }; #endif // MD2017_CODEPLUG_HH ================================================ FILE: lib/md2017_filereader.cc ================================================ #include "md2017_filereader.hh" #include #include #define SEGMENT0_FILE_ADDR 0x00002225 #define SEGMENT0_TARGET_ADDR 0x00002000 #define SEGMENT0_SIZE 0x0003e000 #define SEGMENT1_FILE_ADDR 0x00040235 #define SEGMENT1_TARGET_ADDR 0x00110000 #define SEGMENT1_SIZE 0x00090000 bool MD2017FileReader::read(const QString &filename, MD2017Codeplug *codeplug, const ErrorStack &err) { // Check file properties QFileInfo info(filename); if (! info.exists()) { errMsg(err) << "Cannot open file '" << filename << "': File does not exisist."; return false; } if (852533 != info.size()) { errMsg(err) << "Cannot read codeplug file '" << filename << "': File size is not 852533 bytes."; return false; } // Open file QFile file(filename); if (! file.open(QFile::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; return false; } // Read file content if (! file.seek(SEGMENT0_FILE_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } char *ptr = (char *)codeplug->data(SEGMENT0_TARGET_ADDR); size_t n = SEGMENT0_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } if (! file.seek(SEGMENT1_FILE_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } ptr = (char *)codeplug->data(SEGMENT1_TARGET_ADDR); n = SEGMENT1_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } return true; } ================================================ FILE: lib/md2017_filereader.hh ================================================ #ifndef MD2017_FILEREADER_HH #define MD2017_FILEREADER_HH #include "md2017_codeplug.hh" /** Methods to read manufacturer codeplug files. * * The file format of the stock CPS is still pretty simple. The first part of the file consists of * a mal-formed DFU file. This contains a single image with a single element containing the * first section of the memory written to the device. The second section is then added as-is * to the end of the file. Due to the DFU header/footer, the file and memory offsets differ. * * * * * *
File Start Memory Start Size
0x002225 0x002000 0x03e000
0x040235 0x110000 0x090000
* * @ingroup md2017 */ class MD2017FileReader { public: /** Reads manufacturer codeplug file into given codeplug object. * @param filename Specifies the file to read. * @param codeplug Specifies the codeplug object to store read codeplug. * @param err Error stack. * @returns @c true on success and @c false on error. */ static bool read(const QString &filename, MD2017Codeplug *codeplug, const ErrorStack &err=ErrorStack()); }; #endif // MD2017_FILEREADER_HH ================================================ FILE: lib/md2017_limits.cc ================================================ #include "md2017_limits.hh" #include "md2017_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingzone.hh" MD2017Limits::MD2017Limits(QObject *parent) : RadioLimits(true, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 122197; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; add("settings", new RadioLimitItem { { "introLine1", new RadioLimitString(-1, 10, RadioLimitString::Unicode) }, { "introLine2", new RadioLimitString(-1, 10, RadioLimitString::Unicode) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High) } }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(MD2017Codeplug::GeneralSettingsElement::Limit::bootPasswordLength(), RadioLimitIssue::Critical) } } } /// @todo check default radio ID. } ); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList{ { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } } ); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum { (unsigned) DMRContact::PrivateCall, (unsigned) DMRContact::GroupCall, (unsigned) DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, -1, -1, new RadioLimitIgnored() } } ); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "contacts", new RadioLimitGroupCallRefList(1, 32) } }) ); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 10000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored(nullptr, RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), }}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored()}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } ) ); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, // 16 ASCII chars in name { "A", new RadioLimitRefList(0, 64, Channel::staticMetaObject) }, { "B", new RadioLimitRefList(0, 64, Channel::staticMetaObject) }, { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions } ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } }) ); /* Define limits for positioning systems. */ add("positioning", new RadioLimitList({ { DMRAPRSSystem::staticMetaObject, 0, 16, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, -1, new RadioLimitIgnored() } } ) ); /* Check encryption keys. */ add("commercial", new RadioLimitItem { {"encryptionKeys", new RadioLimitList { {BasicEncryptionKey::staticMetaObject, 0, TyTCodeplug::EncryptionElement::Limit::basicKeys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{4}")} }}, {AESEncryptionKey::staticMetaObject, 0, TyTCodeplug::EncryptionElement::Limit::advancedKeys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{32}")} }} } } }); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); } ================================================ FILE: lib/md2017_limits.hh ================================================ #ifndef MD2017LIMITS_HH #define MD2017LIMITS_HH #include "radiolimits.hh" /** Implements the limits for the TyT MD-2017. * @ingroup md2017 */ class MD2017Limits: public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit MD2017Limits(QObject *parent=nullptr); }; #endif // MD2017LIMITS_HH ================================================ FILE: lib/md390.cc ================================================ #include "md390.hh" #include "md390_limits.hh" #include "logger.hh" #include "utils.hh" #define NUM_CHANNELS 1000 #define ADDR_CHANNELS 0x01ee00 #define CHANNEL_SIZE 0x000040 #define BLOCK_SIZE 1024 MD390::MD390(TyTInterface *device, const ErrorStack &err, QObject *parent) : TyTRadio(device, parent), _name("TyT MD-390"), _limits(nullptr) { // Read channels and identify device variance based on the channel frequencies. This may block // the GUI for some time. unsigned addr_start = align_addr(ADDR_CHANNELS, BLOCK_SIZE); unsigned channel_offset = ADDR_CHANNELS-addr_start; uint total_size = align_size(addr_start+NUM_CHANNELS*CHANNEL_SIZE, BLOCK_SIZE)-addr_start; QByteArray buffer(total_size, 0xff); if (! _dev->read_start(0, addr_start, err)) { errMsg(err) << "Cannot read channels from MD-390, cannot determine variant."; return; } for (unsigned i=0; i<(total_size/BLOCK_SIZE); i++){ if (! _dev->read(0, ADDR_CHANNELS, (uint8_t *)buffer.data()+i*BLOCK_SIZE, BLOCK_SIZE, err)) { errMsg(err) << "Cannot read channels from MD-390, cannot determine variant."; return; } } _dev->read_finish(err); // Determine frequency range of channels unsigned channelCount = 0; QPair range(std::numeric_limits::max(),0); for (unsigned i=0; i=range.second)) { _limits = new MD390Limits({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}}, this); _name += "V"; } else if ((350<=range.first) && (400>=range.second)) { _limits = new MD390Limits({{Frequency::fromMHz(350.), Frequency::fromMHz(400.)}}, this); _name += "U"; } else if ((400<=range.first) && (450>=range.second)) { _limits = new MD390Limits({{Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, this); _name += "U"; } else if ((450<=range.first) && (520>=range.second)) { _limits = new MD390Limits({{Frequency::fromMHz(450.), Frequency::fromMHz(520.)}}, this); _name += "U"; } else { // Invalid frequency range, needs "Ignore frequency limits" in settings to write any codeplug. _limits = new MD390Limits({}, this); errMsg(err) << "Cannot determine frequency range from channel frequencies between " << range.first << "MHz and " << range.second << "MHz. Will not check frequency ranges."; } } const QString & MD390::name() const { return _name; } const RadioLimits & MD390::limits() const { return *_limits; } const Codeplug & MD390::codeplug() const { return _codeplug; } Codeplug & MD390::codeplug() { return _codeplug; } const CallsignDB * MD390::callsignDB() const { return nullptr; } CallsignDB * MD390::callsignDB() { return nullptr; } RadioInfo MD390::defaultRadioInfo() { return RadioInfo(RadioInfo::MD390, "md390", "MD-390", "TyT", {TyTInterface::interfaceInfo()}, QList{ RadioInfo(RadioInfo::RT8, "rt8", "RT8", "Retevis", {TyTInterface::interfaceInfo()}) }); } ================================================ FILE: lib/md390.hh ================================================ /** @defgroup md390 TYT MD-390 (U/V) * Device specific classes for TyT MD-390, both the VHF and UHF version. * * \image html md390.jpg "MD-390" width=200px * \image latex md390.jpg "MD-390" width=200px * * The TYT MD-390 is a decent VHF or UHF FM and DMR handheld radio. * The radio is available with and without an GPS option. @c libdmrconf will support that * feature. Non-GPS variants of that radio will simply ignore any GPS system settings. * * These radios support up to 1000 channels organized in 250 zones. Each zone may hold up to 16 * channels. There are also up to 250 scanlists * holding up to 31(?) channels each. * * The radio can hold up to 1000 contacts (DMR contacts) and 250 RX group lists as well as up to 50 * pre-programmed messages. * * @ingroup tyt */ #ifndef MD390_HH #define MD390_HH #include "tyt_radio.hh" #include "md390_codeplug.hh" /** Implements an USB interface to the TYT MD-390 VHF/UHF 5W DMR (Tier I&II) radio. * * The TYT MD-390 radio use the TyT typical DFU-style communication protocol * to read and write codeplugs onto the radio (see @c TyTRadio). * * @ingroup md390 */ class MD390 : public TyTRadio { Q_OBJECT public: /** Constructor. * @param device Specifies the DFU device to use for communication with the device. * @param err Passes an error stack to put error messages on. * @param parent The QObject parent. */ MD390(TyTInterface *device=nullptr, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); const QString &name() const; const RadioLimits &limits() const; const Codeplug &codeplug() const; Codeplug &codeplug(); const CallsignDB *callsignDB() const; CallsignDB *callsignDB(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: /** Holds the name of the device. */ QString _name; /** The codeplug object. */ MD390Codeplug _codeplug; private: /** Instance of limits for this radio. Gets instantiated for every radio as it depends on the * radio variant. */ RadioLimits *_limits; }; #endif // MD2017_HH ================================================ FILE: lib/md390_codeplug.cc ================================================ #include "md390_codeplug.hh" #include "config.hh" #include "logger.hh" #include "intermediaterepresentation.hh" #define NUM_CHANNELS 1000 #define ADDR_CHANNELS 0x01ee00 #define CHANNEL_SIZE 0x000040 #define NUM_CONTACTS 1000 #define ADDR_CONTACTS 0x005f80 #define CONTACT_SIZE 0x000024 #define NUM_ZONES 250 #define ADDR_ZONES 0x0149e0 #define ZONE_SIZE 0x000040 #define NUM_GROUPLISTS 250 #define ADDR_GROUPLISTS 0x00ec20 #define GROUPLIST_SIZE 0x000060 #define NUM_SCANLISTS 250 #define ADDR_SCANLISTS 0x018860 #define SCANLIST_SIZE 0x000068 #define ADDR_TIMESTAMP 0x002000 #define ADDR_SETTINGS 0x002040 #define ADDR_BOOTSETTINGS 0x02f000 #define ADDR_MENUSETTINGS 0x0020f0 #define MENUSETTINGS_SIZE 0x000010 #define ADDR_BUTTONSETTINGS 0x002100 #define ADDR_PRIVACY_KEYS 0x0059c0 #define NUM_GPSSYSTEMS 16 #define ADDR_GPSSYSTEMS 0x03ec40 #define GPSSYSTEM_SIZE 0x000010 #define ADDR_EMERGENCY_SETTINGS 0x005a50 #define NUM_EMERGENCY_SYSTEMS 32 #define ADDR_EMERGENCY_SYSTEMS 0x005a60 #define EMERGENCY_SYSTEM_SIZE 0x000028 /* ********************************************************************************************* * * Implementation of MD390Codeplug::ChannelElement * ********************************************************************************************* */ MD390Codeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : DM1701Codeplug::ChannelElement::ChannelElement(ptr, size) { // pass... } MD390Codeplug::ChannelElement::ChannelElement(uint8_t *ptr) : DM1701Codeplug::ChannelElement(ptr, CHANNEL_SIZE) { // pass... } void MD390Codeplug::ChannelElement::clear() { DM1701Codeplug::ChannelElement::clear(); enableCompressedUDPHeader(false); } bool MD390Codeplug::ChannelElement::compressedUDPHeader() const { return !getBit(0x0003, 6); } void MD390Codeplug::ChannelElement::enableCompressedUDPHeader(bool enable) { setBit(0x0003, 6, !enable); } Channel * MD390Codeplug::ChannelElement::toChannelObj(const ErrorStack &err) const { Channel *ch = DM1701Codeplug::ChannelElement::toChannelObj(err); if (nullptr == ch) return ch; // Apply extension if (ch->tytChannelExtension()) { ch->tytChannelExtension()->enableCompressedUDPHeader(compressedUDPHeader()); } return ch; } void MD390Codeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx) { DM1701Codeplug::ChannelElement::fromChannelObj(c, ctx); // apply extensions (extension will be created in TyTCodeplug::ChannelElement::fromChannelObj) if (TyTChannelExtension *ex = c->tytChannelExtension()) { enableCompressedUDPHeader(ex->compressedUDPHeader()); } } /* ******************************************************************************************** * * Implementation of MD390Codeplug::MenuSettingsElement * ******************************************************************************************** */ MD390Codeplug::MenuSettingsElement::MenuSettingsElement(uint8_t *ptr, size_t size) : TyTCodeplug::MenuSettingsElement(ptr, size) { // pass... } MD390Codeplug::MenuSettingsElement::MenuSettingsElement(uint8_t *ptr) : TyTCodeplug::MenuSettingsElement(ptr, MENUSETTINGS_SIZE) { // pass... } void MD390Codeplug::MenuSettingsElement::clear() { TyTCodeplug::MenuSettingsElement::clear(); enableGPSInformation(true); } bool MD390Codeplug::MenuSettingsElement::gpsInformation() const { return !getBit(0x04, 4); } void MD390Codeplug::MenuSettingsElement::enableGPSInformation(bool enable) { setBit(0x04, 4, !enable); } bool MD390Codeplug::MenuSettingsElement::fromConfig(const Config *config) { if (! TyTCodeplug::MenuSettingsElement::fromConfig(config)) return false; if (TyTConfigExtension *ex = config->tytExtension()) { enableGPSInformation(ex->menuSettings()->gpsInformation()); } return true; } bool MD390Codeplug::MenuSettingsElement::updateConfig(Config *config) { if (! TyTCodeplug::MenuSettingsElement::updateConfig(config)) return false; if (TyTConfigExtension *ex = config->tytExtension()) { ex->menuSettings()->enableGPSInformation(gpsInformation()); } return true; } /* ********************************************************************************************* * * Implementation of MD390Codeplug * ********************************************************************************************* */ MD390Codeplug::MD390Codeplug(QObject *parent) : TyTCodeplug(parent) { addImage("TYT MD-390 Codeplug"); image(0).addElement(0x002000, 0x3e000); // Clear entire codeplug clear(); } Config * MD390Codeplug::preprocess(Config *config, const ErrorStack &err) const { Config *intermediate = TyTCodeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot prepare codeplug for MD-390."; return nullptr; } ZoneSplitVisitor splitter; if (! splitter.process(intermediate, err)) { errMsg(err) << "Cannot split zones in codeplug for MD-390."; delete intermediate; return nullptr; } return intermediate; } bool MD390Codeplug::postprocess(Config *config, const ErrorStack &err) const { if (! TyTCodeplug::postprocess(config, err)) { errMsg(err) << "Cannot post-process MD-390 codeplug."; return false; } ZoneMergeVisitor merger; if (! merger.process(config, err)) { errMsg(err) << "Cannot merge zones from decoded MD-390 codeplug."; return false; } return true; } bool MD390Codeplug::decodeElements(Context &ctx, const ErrorStack &err) { logDebug() << "Decode MD390 codeplug, programmed with CPS version " << TimestampElement(data(ADDR_TIMESTAMP)).cpsVersion() << "."; return TyTCodeplug::decodeElements(ctx, err); } void MD390Codeplug::clearTimestamp() { TimestampElement(data(ADDR_TIMESTAMP)).clear(); } bool MD390Codeplug::encodeTimestamp() { TimestampElement ts(data(ADDR_TIMESTAMP)); ts.setTimestamp(QDateTime::currentDateTime()); return true; } void MD390Codeplug::clearGeneralSettings() { GeneralSettingsElement(data(ADDR_SETTINGS)).clear(); } bool MD390Codeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err) return GeneralSettingsElement(data(ADDR_SETTINGS)).fromConfig(ctx.config()); } bool MD390Codeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return GeneralSettingsElement(data(ADDR_SETTINGS)).updateConfig(ctx.config()); } void MD390Codeplug::clearChannels() { // Clear channels for (int i=0; i()) { chan.fromChannelObj(ctx.get(i+1), ctx); } else { chan.clear(); } } return true; } bool MD390Codeplug::createChannels(Context &ctx, const ErrorStack &err) { for (int i=0; ichannelList()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid channel at index " << i << "."; return false; } } return true; } bool MD390Codeplug::linkChannels(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link channel at index " << i << "."; return false; } } return true; } void MD390Codeplug::clearContacts() { // Clear contacts for (int i=0; i()) cont.fromContactObj(ctx.get(i+1)); else cont.clear(); } return true; } bool MD390Codeplug::createContacts(Context &ctx, const ErrorStack &err) { for (int i=0; icontacts()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid contact at index " << i << "."; return false; } } return true; } void MD390Codeplug::clearZones() { // Clear zones & zone extensions for (int i=0; i(i+1)) continue; Zone *obj = ctx.get(i+1); zone.setName(obj->name()); // fill channels for (int c=0; c<16; c++) { if (c < obj->A()->count()) zone.setMemberIndex(c, ctx.index(obj->A()->get(c))); } } return true; } bool MD390Codeplug::createZones(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) for (int i=0; isetName(zone.name()); ctx.config()->zones()->add(obj); ctx.add(obj, i+1); } return true; } bool MD390Codeplug::linkZones(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) for (int i=0; i(i+1)) continue; Zone *obj = ctx.get(i+1); for (int j=0; ((j<16) && zone.memberIndex(j)); j++) { if (! ctx.has(zone.memberIndex(j))) { logWarn() << "Cannot link channel with index " << zone.memberIndex(j) << " channel not defined."; continue; } obj->A()->add(ctx.get(zone.memberIndex(j))); } } return true; } void MD390Codeplug::clearGroupLists() { for (int i=0; irxGroupLists()->count()) glist.fromGroupListObj(ctx.config()->rxGroupLists()->list(i), ctx); else glist.clear(); } return true; } bool MD390Codeplug::createGroupLists(Context &ctx, const ErrorStack &err) { for (int i=0; irxGroupLists()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid group list at index " << i << "."; return false; } } return true; } bool MD390Codeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link group list at index " << i << "."; return false; } } return true; } void MD390Codeplug::clearScanLists() { // Clear scan lists for (int i=0; iscanlists()->count()) scan.fromScanListObj(ctx.config()->scanlists()->scanlist(i), ctx); else scan.clear(); } return true; } bool MD390Codeplug::createScanLists(Context &ctx, const ErrorStack &err) { for (int i=0; iscanlists()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid scanlist at index " << i << "."; return false; } } return true; } bool MD390Codeplug::linkScanLists(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link scan list at index " << i << "."; return false; } } return true; } void MD390Codeplug::clearPositioningSystems() { // Clear GPS systems for (int i=0; i()) { logDebug() << "Encode GPS system #" << i << " '" << ctx.get(i+1)->name() << "'."; gps.fromGPSSystemObj(ctx.get(i+1), ctx); } else { gps.clear(); } } return true; } bool MD390Codeplug::createPositioningSystems(Context &ctx, const ErrorStack &err) { for (int i=0; iposSystems()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid GPS system at index " << i << "."; return false; } } return true; } bool MD390Codeplug::linkPositioningSystems(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link GPS system at index " << i << "."; return false; } } return true; } void MD390Codeplug::clearButtonSettings() { ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).clear(); } bool MD390Codeplug::encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(flags); Q_UNUSED(err) // Encode settings return ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).fromConfig(ctx.config()); } bool MD390Codeplug::decodeButtonSetttings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).updateConfig(ctx.config()); } void MD390Codeplug::clearPrivacyKeys() { EncryptionElement(data(ADDR_PRIVACY_KEYS)).clear(); } bool MD390Codeplug::encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // First, reset keys clearPrivacyKeys(); // Get keys EncryptionElement keys(data(ADDR_PRIVACY_KEYS)); // Encode keys return keys.fromCommercialExt(ctx.config()->commercialExtension(), ctx); } bool MD390Codeplug::decodePrivacyKeys(Context &ctx, const ErrorStack &err) { // Get keys EncryptionElement keys(data(ADDR_PRIVACY_KEYS)); // Decode element if (! keys.updateCommercialExt(ctx)) { errMsg(err) << "Cannot create encryption extension."; return false; } return true; } void MD390Codeplug::clearTextMessages() { MessageBankElement(data(Offset::messages())).clear(); } bool MD390Codeplug::encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).encode(ctx, flags, err); } bool MD390Codeplug::decodeTextMessages(Context &ctx, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).decode(ctx, err); } void MD390Codeplug::clearMenuSettings() { MenuSettingsElement(data(ADDR_MENUSETTINGS)).clear(); } void MD390Codeplug::clearEmergencySystems() { EmergencySettingsElement(data(ADDR_EMERGENCY_SETTINGS)).clear(); for (int i=0; i * Start End Size Content * Segment 0x002000-0x040000 * 0x002000 0x00200c 0x0000c Timestamp see @c TyTCodeplug::TimestampElement. * 0x00200c 0x002040 0x00034 Reserved, filled with 0xff. * 0x002100 0x002140 0x00040 Button config, see @c TyTCodeplug::ButtonSettingsElement. * 0x002140 0x002180 0x00040 Reserved, filled with 0xff. * 0x002040 0x0020f0 0x000b0 General settings see @c TyTCodeplug::GeneralSettingsElement. * 0x002180 0x0059c0 0x03840 50 Text messages @ 0x120 bytes each, see @c TyTCodeplug::MessageElement. * 0x0059c0 0x005a70 0x000b0 ??? Privacy keys, see @c TyTCodeplug::EncryptionElement. * 0x005a70 0x005a80 0x00010 Emergency system settings, see @c TyTCodeplug::EmergencySettingsElement. * 0x005a80 0x005f80 0x00500 Emergency systems, see @c TyTCodeplug::EmergencySystemElement. * 0x005f80 0x00ec20 0x008ca 1000 contacts, see @c TyTCodeplug::ContactElement. * 0x00ec20 0x0149e0 0x05dc0 250 RX Group lists @ 0x60 bytes each, see @c TyTCodeplug::GroupListElement. * 0x0149e0 0x018860 0x03e80 250 Zones @ 0x40 bytes each, see @c TyTCodeplug::ZoneElement. * 0x018860 0x01edf0 0x06590 250 Scanlists @ 0x68 bytes each, see @c TyTCodeplug::ScanListElement. * 0x01ee00 0x02e800 0x0fa00 1000 channels, see @c MD390Codeplug::ChannelElement. * 0x03ec40 0x03ed40 0x00100 16 GPS systems @ 0x10 bytes each, see @c TyTCodeplug::GPSSystemElement. * * * @ingroup md390 */ class MD390Codeplug : public TyTCodeplug { Q_OBJECT public: /** Extends the common @c TyTCodeplug::ChannelElement to implement the MD-390 specific settings. * * Memory layout of the channel (size 0x0040 bytes): * @verbinclude md390_channel.txt */ class ChannelElement: public DM1701Codeplug::ChannelElement { protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit ChannelElement(uint8_t *ptr); void clear(); /** Returns @c true if the 'compressed UDP data header' is enabled. */ virtual bool compressedUDPHeader() const; /** Enables/disables 'compressed UDP data header'. */ virtual void enableCompressedUDPHeader(bool enable); /** Constructs a generic @c Channel object from the codeplug channel. */ virtual Channel *toChannelObj(const ErrorStack &err=ErrorStack()) const; /** Initializes this codeplug channel from the given generic configuration. */ virtual void fromChannelObj(const Channel *c, Context &ctx); }; /** Extends the @c TyTCodeplug::MenuSettingsElement to implement the MD-390 specific menu settings. * * Memory layout of the settings (size 0x0010 bytes): * @verbinclude md390_menusettings.txt */ class MenuSettingsElement: public TyTCodeplug::MenuSettingsElement { protected: /** Hidden constructor. */ MenuSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit MenuSettingsElement(uint8_t *ptr); void clear(); /** Returns @c true if GPS information is enabled. */ virtual bool gpsInformation() const; /** Enables/disables GPS information menu. */ virtual void enableGPSInformation(bool enable); bool fromConfig(const Config *config); bool updateConfig(Config *config); }; public: /** Empty constructor. */ explicit MD390Codeplug(QObject *parent=nullptr); Config *preprocess(Config *config, const ErrorStack &err) const; bool postprocess(Config *config, const ErrorStack &err) const; virtual bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); void clearTimestamp(); bool encodeTimestamp(); void clearGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearZones(); bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); void clearGroupLists(); bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearScanLists(); bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearPositioningSystems(); bool encodePositioningSystems(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createPositioningSystems(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkPositioningSystems(Context &ctx, const ErrorStack &err=ErrorStack()); void clearButtonSettings(); bool encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeButtonSetttings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearPrivacyKeys(); bool encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err); bool decodePrivacyKeys(Context &ctx, const ErrorStack &err); void clearTextMessages(); bool encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err); bool decodeTextMessages(Context &ctx, const ErrorStack &err); void clearMenuSettings(); void clearEmergencySystems(); protected: /** Some internal offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int messages() { return 0x002180; } /// @endcond }; }; #endif // MD390CODEPLUG_HH ================================================ FILE: lib/md390_filereader.cc ================================================ #include "md390_filereader.hh" #include #include #define SEGMENT0_FILE_ADDR 0x00002225 #define SEGMENT0_TARGET_ADDR 0x00002000 #define SEGMENT0_SIZE 0x0003e000 bool MD390FileReader::read(const QString &filename, MD390Codeplug *codeplug, const ErrorStack &err) { // Check file properties QFileInfo info(filename); if (! info.exists()) { errMsg(err) << "Cannot open file '" << filename << "': File does not exisist."; return false; } if (262709 != info.size()) { errMsg(err) << "Cannot read codeplug file '" << filename << "': File size is not 262709 bytes."; return false; } // Open file QFile file(filename); if (! file.open(QFile::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; return false; } // Read file content if (! file.seek(SEGMENT0_FILE_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } char *ptr = (char *)codeplug->data(SEGMENT0_TARGET_ADDR); size_t n = SEGMENT0_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } return true; } ================================================ FILE: lib/md390_filereader.hh ================================================ #ifndef MD390FILEREADER_HH #define MD390FILEREADER_HH #include "md390_codeplug.hh" /** Methods to read manufacturer codeplug files. * * The file format of the stock CPS is still pretty simple. The first part of the file consists of * a mal-formed DFU file. This contains a single image with a single element containing the * first section of the memory written to the device. The second section is then added as-is * to the end of the file. Due to the DFU header/footer, the file and memory offsets differ. * * * * *
File Start Memory Start Size
0x002225 0x002000 0x03e000
* * @ingroup md390 */ class MD390FileReader { public: /** Reads manufacturer codeplug file into given codeplug object. * @param filename Specifies the file to read. * @param codeplug Specifies the codeplug object to store read codeplug. * @param err Error stack. * @returns @c true on success and @c false on error. */ static bool read(const QString &filename, MD390Codeplug *codeplug, const ErrorStack &err = ErrorStack()); }; #endif // MD390FILEREADER_HH ================================================ FILE: lib/md390_limits.cc ================================================ #include "md390_limits.hh" #include "md390_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "gpssystem.hh" #include "roamingzone.hh" MD390Limits::MD390Limits(const std::initializer_list> &freqRanges, QObject *parent) : RadioLimits(true, parent) { // Define limits for call-sign DB _hasCallSignDB = false; _callSignDBImplemented = false; _numCallSignDBEntries = 0; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; add("settings", new RadioLimitItem { { "introLine1", new RadioLimitString(-1, 10, RadioLimitString::Unicode) }, { "introLine2", new RadioLimitString(-1, 10, RadioLimitString::Unicode) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High) } }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(MD390Codeplug::GeneralSettingsElement::Limit::bootPasswordLength(), RadioLimitIssue::Critical) } } } /// @todo check default radio ID. } ); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList{ { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } } ); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum { (unsigned) DMRContact::PrivateCall, (unsigned) DMRContact::GroupCall, (unsigned) DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, -1, -1, new RadioLimitIgnored() } } ); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "contacts", new RadioLimitGroupCallRefList(1, 32) } }) ); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 1000, new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies(freqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(freqRanges)}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored(nullptr, RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies(freqRanges, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies(freqRanges)}, {"power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), }}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored()}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } ) ); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, // 16 ASCII chars in name { "A", new RadioLimitRefList(0, 64, Channel::staticMetaObject) }, { "B", new RadioLimitRefList(0, 64, Channel::staticMetaObject) }, { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions } ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } }) ); /* Define limits for positioning systems. */ add("positioning", new RadioLimitList({ { DMRAPRSSystem::staticMetaObject, 0, 16, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, -1, new RadioLimitIgnored() } } ) ); /* Check encryption keys. */ add("commercial", new RadioLimitItem { {"encryptionKeys", new RadioLimitList { {BasicEncryptionKey::staticMetaObject, 0, TyTCodeplug::EncryptionElement::Limit::basicKeys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{4}")} }}, {AESEncryptionKey::staticMetaObject, 0, TyTCodeplug::EncryptionElement::Limit::advancedKeys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{32}")} }} } } }); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); } ================================================ FILE: lib/md390_limits.hh ================================================ #ifndef MD390LIMITS_HH #define MD390LIMITS_HH #include "radiolimits.hh" /** Implements the radio limits for TyT MD-390 radios. * @ingroup md390 */ class MD390Limits: public RadioLimits { Q_OBJECT public: /** Constructor from frequency ranges. */ MD390Limits(const std::initializer_list > &freqRanges, QObject *parent=nullptr); }; #endif // MD390LIMITS_HH ================================================ FILE: lib/melody.cc ================================================ #include "melody.hh" #include "logger.hh" #include /* ********************************************************************************************* * * Implementation of Melody * ********************************************************************************************* */ Melody::Melody(unsigned int bpm, QObject *parent) : ConfigItem(parent), _bpm(bpm), _melody() { // pass... } ConfigItem * Melody::clone() const { Melody *melody = new Melody(); if (! melody->copy(*this)) { melody->deleteLater(); return nullptr; } return melody; } bool Melody::copy(const ConfigItem &other) { if (! ConfigItem::copy(other)) return false; const Melody *omelody = other.as(); _bpm = omelody->_bpm; _melody = omelody->_melody; return true; } bool Melody::isEmpty() const { return 0 == _melody.count(); } Melody::iterator Melody::begin() { return _melody.begin(); } Melody::iterator Melody::end() { return _melody.end(); } Melody::const_iterator Melody::begin() const { return _melody.begin(); } Melody::const_iterator Melody::end() const { return _melody.end(); } size_t Melody::count() const { return _melody.count(); } const Melody::Note & Melody::operator [](size_t index) const { return _melody[index]; } Melody::Note & Melody::operator [](size_t index) { return _melody[index]; } unsigned int Melody::bpm() const { return _bpm; } void Melody::setBPM(unsigned int bpm) { if (_bpm == bpm) return; _bpm = bpm; emit modified(this); } bool Melody::fromLilypond(const QString &melody) { _melody.clear(); Note::Duration currentDuration = Note::Duration::Whole; for (QString note : melody.split(" ", Qt::SkipEmptyParts)) { Note n; if (! n.fromLilypond(note, currentDuration)) return false; currentDuration = n.duration; _melody.append(n); } emit modified(this); return true; } QString Melody::toLilypond() const { QStringList res; Note::Duration currentDuration = Note::Duration::Whole; for (Note note : _melody) { res.append(note.toLilypond(currentDuration)); currentDuration = note.duration; } return res.join(" "); } QVector> Melody::toTones() const { QVector> res; for (const Note ¬e: _melody) { res.append(note.toTone(_bpm)); } return res; } bool Melody::infer(const QVector > &tones) { // Find minimum quantization error -> infer also BPM unsigned int best_bpm = 30; unsigned int best_cost = quantizationTimingError(tones, best_bpm) + std::abs(100-(int)best_bpm); for (unsigned int bpm=31; bpm<200; bpm++) { unsigned int cost = quantizationTimingError(tones, best_bpm); if (cost < best_cost) { best_bpm = bpm; best_cost = cost + std::abs(100 - (int)bpm); } } _bpm = best_bpm; _melody.clear(); for(const QPair &tone: tones) { Note note; note.infer(tone.first, tone.second, _bpm); _melody.append(note); } emit modified(this); return true; } unsigned int Melody::quantizationTimingError(const QVector > &tones, unsigned int bpm) { if (0 == tones.count()) return 0; unsigned int error=0; for (const QPair &tone: tones) { error += Note::quantizationTimingError(tone.second, bpm); } return error / tones.count(); } /* ********************************************************************************************* * * Implementation of Melody::Note * ********************************************************************************************* */ Melody::Note::Note() : tone(Tone::C), duration(Duration::Quarter), dotted(false), octave(0) { // pass... } bool Melody::Note::fromLilypond(const QString ¬e, Duration currentDuration) { QRegularExpression note_pattern("^(c|cis|des|d|dis|ees|e|f|fis|ges|g|gis|aes|a|ais|bes|b)([,]+|[']+|)(1|2|4|8|16|)(\\.|)$"); QRegularExpression rest_pattern("^r(1|2|4|8|16|)(\\.|)$"); QRegularExpressionMatch note_match = note_pattern.match(note); if (note_match.hasMatch()) { if ("c" == note_match.captured(1)) tone=Tone::C; else if (("cis" == note_match.captured(1)) || ("des" == note_match.captured(1))) tone=Tone::Cis; else if ("d" == note_match.captured(1)) tone=Tone::D; else if (("dis" == note_match.captured(1)) || ("ees" == note_match.captured(1))) tone=Tone::Dis; else if ("e" == note_match.captured(1)) tone=Tone::E; else if ("f" == note_match.captured(1)) tone=Tone::F; else if (("fis" == note_match.captured(1)) || ("ges" == note_match.captured(1))) tone=Tone::Fis; else if ("g" == note_match.captured(1)) tone=Tone::G; else if (("gis" == note_match.captured(1)) || ("aes" == note_match.captured(1))) tone=Tone::Gis; else if ("a" == note_match.captured(1)) tone=Tone::A; else if (("ais" == note_match.captured(1)) || ("bes" == note_match.captured(1))) tone=Tone::Ais; else if ("b" == note_match.captured(1)) tone=Tone::B; else return false; if (0 == note_match.capturedLength(2)) octave = 0; else if ('\'' == note_match.captured(2).at(0)) octave = note_match.capturedLength(2); else if (',' == note_match.captured(2).at(0)) octave = -note_match.capturedLength(2); else return false; if (0 == note_match.capturedLength(3)) duration = currentDuration; else if ("1" == note_match.captured(3)) duration = Duration::Whole; else if ("2" == note_match.captured(3)) duration = Duration::Half; else if ("4" == note_match.captured(3)) duration = Duration::Quarter; else if ("8" == note_match.captured(3)) duration = Duration::Eighth; else if ("16" == note_match.captured(3)) duration = Duration::Sixteenth; else return false; dotted = (1 == note_match.capturedLength(4)); return true; } QRegularExpressionMatch rest_match = rest_pattern.match(note); if (rest_match.hasMatch()) { tone = Tone::Rest; if (0 == rest_match.capturedLength(1)) duration = currentDuration; else if ("1" == rest_match.captured(1)) duration = Duration::Whole; else if ("2" == rest_match.captured(1)) duration = Duration::Half; else if ("4" == rest_match.captured(1)) duration = Duration::Quarter; else if ("8" == rest_match.captured(1)) duration = Duration::Eighth; else if ("16" == rest_match.captured(1)) duration = Duration::Sixteenth; else return false; dotted = (1 == rest_match.capturedLength(2)); return true; } return false; } QString Melody::Note::toLilypond(Duration currentDuration) const { QString res; res.reserve(10); switch (tone) { case Tone::C: res.append("c"); break; case Tone::Cis: res.append("cis"); break; case Tone::D: res.append("d"); break; case Tone::Dis: res.append("dis"); break; case Tone::E: res.append("e"); break; case Tone::F: res.append("f"); break; case Tone::Fis: res.append("fis"); break; case Tone::G: res.append("g"); break; case Tone::Gis: res.append("gis"); break; case Tone::A: res.append("a"); break; case Tone::Ais: res.append("ais"); break; case Tone::B: res.append("b"); break; case Tone::Rest: res.append("r"); break; } if (Tone::Rest != tone) { if (0 > octave) { for (int i=0; i<(-octave); i++) res.append(","); } else if (0 < octave) { for (int i=0; i Melody::Note::toTone(unsigned int bpm) const { unsigned int bar = (4*60000)/bpm; unsigned int dur = bar; if (Duration::Whole == duration) { dur = bar; if (dotted) dur += dur/2; } else if (Duration::Half == duration) { dur = bar/2; if (dotted) dur += dur/2; } else if (Duration::Quarter == duration) { dur = bar/4; if (dotted) dur += dur/2; } else if (Duration::Eighth == duration) { dur = bar/8; if (dotted) dur += dur/2; } else if (Duration::Sixteenth == duration) { dur = bar/16; if (dotted) dur += dur/2; } if (Tone::Rest == tone) { return QPair(0.0, dur); } int halfTones = 12*octave; switch (tone) { case Tone::C: halfTones -= 9; break; case Tone::Cis: halfTones -= 8; break; case Tone::D: halfTones -= 7; break; case Tone::Dis: halfTones -= 6; break; case Tone::E: halfTones -= 5; break; case Tone::F: halfTones -= 4; break; case Tone::Fis: halfTones -= 3; break; case Tone::G: halfTones -= 2; break; case Tone::Gis: halfTones -= 1; break; case Tone::A: break; case Tone::Ais: halfTones += 1; break; case Tone::B: halfTones += 2; break; case Tone::Rest: break; } double freq = 440.*std::pow(2.0,halfTones/12.0); return QPair(freq, dur); } unsigned int Melody::Note::infer(double frequency, unsigned int ms, unsigned int bpm) { if (frequency) { // Try to infer half-tones from A4 int halfTones = std::round(std::log2(frequency/440.)*12.0); int halfTonesC4 = halfTones+9; octave = halfTonesC4/12; halfTonesC4 = std::abs(halfTonesC4)%12; switch(halfTonesC4) { case 0: tone = Tone::C; break; case 1: tone = Tone::Cis; break; case 2: tone = Tone::D; break; case 3: tone = Tone::Dis; break; case 4: tone = Tone::E; break; case 5: tone = Tone::F; break; case 6: tone = Tone::Fis; break; case 7: tone = Tone::G; break; case 8: tone = Tone::Gis; break; case 9: tone = Tone::A; break; case 10: tone = Tone::Ais; break; case 11: tone = Tone::B; break; } } else if (ms) { // Obviously a rest octave = 0; tone = Tone::Rest; } else { logWarn() << "Cannot infer a note of frequency and duration 0."; octave = 0; tone = Tone::Rest; duration = Duration::Quarter; dotted=false; } // Try to infer note duration from duration and BPM static const unsigned int bar = (4*60000)/bpm; int fraction = std::round(std::log2(bar)-std::log2(ms)); int diff = 0; if (1 > fraction) { duration = Duration::Whole; diff = std::abs((int)ms-(int)bar); } else if (2 > fraction) { duration = Duration::Half; diff = std::abs((int)ms-(int)bar/2); } else if (3 > fraction) { duration = Duration::Quarter; diff = std::abs((int)ms-(int)bar/4); } else if (4 > fraction) { duration = Duration::Eighth; diff = std::abs((int)ms-(int)bar/8); } else { duration = Duration::Sixteenth; diff = std::abs((int)ms-(int)bar/16); } return diff; } unsigned int Melody::Note::quantizationTimingError(unsigned int ms, unsigned int bpm) { static const unsigned int bar = (4*60000)/bpm; int fraction = std::round(std::log2(bar)-std::log2(ms)); // Limit onto [2^0, 2^4] fractions == [1/1, 1/16] fraction = std::max(0, std::min(4, fraction)); return std::abs(ms - bar*std::pow(2, fraction)); } ================================================ FILE: lib/melody.hh ================================================ #ifndef MELODY_HH #define MELODY_HH #include "configobject.hh" #include /** A config item that encodes a melody. This can be used to configure several melodies like * ring tones and boot-up melodies. Have fun with it. In contrast to the common manufacturer CPSs, * qdmr uses a proper musical notation for that. * * This however, comes with some difficulties. While the translation from musical notation to * tone frequencies and durations is easy, the reverse direction is not. The note duration is the * most difficult. In musical notation, durations are expressed in terms of fractions of a bar, * implicitly defined by the beat frequency. This additional information must be inferred. To achieve * this, this class searches for the BPM, that minimizes the quantization error in the duration * while keeping the BPM as close as possible to 100 BPM. Yes, this is utterly over-engineered. * * @ingroup config */ class Melody : public ConfigItem { Q_OBJECT /** The BPM of the melody. */ Q_PROPERTY(unsigned int bpm READ bpm WRITE setBPM) /** The melody in LilyPond notation. */ Q_PROPERTY(QString melody READ toLilypond WRITE fromLilypond) public: /** Encodes a note, that is tone and duration. */ struct Note { /** Possible tone values. */ enum class Tone { Rest, C, Cis, D, Dis, E, F, Fis, G, Gis, A, Ais, B }; /** Note durations as fractions of a bar. */ enum class Duration { Whole, Half, Quarter, Eighth, Sixteenth }; /** The note tone. */ Tone tone; /** The note duration. */ Duration duration; /** If @c true, the note/rest is dottet. */ bool dotted; /** The octave of the note, 0 means middle. */ int octave; /** Default constructor. A middle C quarter note. */ Note(); /** Reads a note in Lilypond notation. */ bool fromLilypond(const QString ¬e, Duration currentDuration); /** Serializes the note in Lilypond notation. */ QString toLilypond(Duration currentDuration) const; /** Converts the note to a frequency in Hz and duration in seconds. */ QPair toTone(unsigned int bpm) const; /** Infers the note from the given frequency and duration in ms. * This is guesswork, consequently there will be some issues. The function updates the note and * returns the timing error in ms. */ unsigned int infer(double frequency, unsigned int ms, unsigned int bpm); /** Computes the quantization timing error for the given duration and BPM. */ static unsigned int quantizationTimingError(unsigned int ms, unsigned int bpm); }; /** Iterator over notes. */ typedef QVector::iterator iterator; /** Const iterator over notes. */ typedef QVector::const_iterator const_iterator; public: /** Empty constructor. */ Melody(unsigned int bpm=100, QObject *parent = nullptr); ConfigItem *clone() const; bool copy(const ConfigItem &other); /** Returns @c true, if the melody has no notes (invalid). */ bool isEmpty() const; /** Returns an iterator pointing at the first note. */ iterator begin(); /** Returns an iterator pointing right after the last note. */ iterator end(); /** Returns a const-iterator pointing at the first note. */ const_iterator begin() const; /** Returns a const-iterator pointing right after the last note. */ const_iterator end() const; /** Returns the number of notes (and rests) of this melody. */ size_t count() const; /** Element access. */ const Note &operator [](size_t index) const; /** Element access. */ Note &operator [](size_t index); /** Returns the BPM of the melody. */ unsigned int bpm() const; /** Sets the BPM of the melody. */ void setBPM(unsigned int bpm); /** Parses the Lilypond notation of the melody. * For example, * @code * a8 b e2 des4 d * @endcode */ bool fromLilypond(const QString &melody); /** Serializes the melody into Lilypond notation. */ QString toLilypond() const; /** Converts the melody to a series of tones in terms of frequency and duration in ms. */ QVector> toTones() const; /** Infer melody from a vector of frequeny-duration pairs. */ bool infer(const QVector> &tones); protected: /** Computes the absolute quantization timing error over the given melody for the given BPM. */ static unsigned int quantizationTimingError( const QVector> &tones, unsigned int bpm); protected: /** Holds the beats per minute. */ unsigned int _bpm; /** The actual melody. */ QVector _melody; }; #endif // MELODY_HH ================================================ FILE: lib/melody_stream.cc ================================================ #include "melody_stream.hh" #include "logger.hh" #include "melody.hh" #include MelodyStream::MelodyStream(QObject *parent) : QBuffer(parent), _melody(), _format(), _raw() { _format.setSampleRate(44100.0); _format.setSampleFormat(QAudioFormat::Int16); _format.setChannelCount(1); setBuffer(&_raw); } const QAudioFormat& MelodyStream::audioFormat() const { return _format; } void MelodyStream::setMelody(Melody *melody) { _raw.clear(); if (!_melody.isNull()) disconnect(_melody, &ConfigItem::modified, this, &MelodyStream::reload); _melody = melody; if (_melody.isNull()) return; connect(_melody, &ConfigItem::modified, this, &MelodyStream::reload); reload(); } void MelodyStream::reload() { if (_melody.isNull() || _melody->isEmpty()) return; double beat = _format.sampleRate() * _melody->bpm()/60.0; auto tones = _melody->toTones(); double duration = beat / 16; // two 1/32 silence at beginning and end for (const auto &tone : tones) duration += (tone.second * _format.sampleRate()) / 1000.; unsigned int samples = duration; // pre-allocate data _raw.fill(0, samples*2); int head = beat/64, tail=beat/32, startSample = head; for (const auto &tone : tones) { int toneDuration = tone.second * _format.sampleRate() / 1000.0; // Handle silence if (0 == tone.first) { startSample += toneDuration; continue; } // Evaluate sin and envelope for (int i = -head; i < toneDuration+tail; i++) { double envelope = 1.; if (i <= 0) envelope = double(head+i)/head; else if (i >= toneDuration) envelope = 1 - double(i-toneDuration)/tail; int16_t value = (1<<12) * envelope * std::sin(2*M_PI * tone.first * i / _format.sampleRate()); *reinterpret_cast(_raw.data() + 2 * (startSample + i)) += value; } startSample += toneDuration; } } ================================================ FILE: lib/melody_stream.hh ================================================ #ifndef MELODY_STREAM_HH #define MELODY_STREAM_HH #include #include #include class Melody; /** Represents a raw stream playing a melody. */ class MelodyStream : public QBuffer { Q_OBJECT public: /** Default constructor. */ explicit MelodyStream(QObject *parent = nullptr); /** Returns the audio format of the stream. */ const QAudioFormat &audioFormat() const; /** Sets the melody. */ void setMelody(Melody *melody); protected slots: void reload(); protected: QPointer _melody; QAudioFormat _format; QByteArray _raw; }; #endif //QDMR_MELODY_STREAM_HH ================================================ FILE: lib/opengd77.cc ================================================ #include "opengd77.hh" #include "logger.hh" #include "config.hh" OpenGD77::OpenGD77(OpenGD77Interface *device, QObject *parent) : OpenGD77Base(device, parent), _name("Open GD-77"), _codeplug(), _callsigns() { _satelliteConfig = new OpenGD77SatelliteConfig(this); } const QString & OpenGD77::name() const { return _name; } const Codeplug & OpenGD77::codeplug() const { return _codeplug; } Codeplug & OpenGD77::codeplug() { return _codeplug; } const CallsignDB * OpenGD77::callsignDB() const { return &_callsigns; } CallsignDB * OpenGD77::callsignDB() { return &_callsigns; } RadioInfo OpenGD77::defaultRadioInfo() { return RadioInfo( RadioInfo::OpenGD77, "opengd77", "OpenGD77", "OpenGD77 Project", {OpenGD77Interface::interfaceInfo()}); } ================================================ FILE: lib/opengd77.hh ================================================ #ifndef OPENGD77_HH #define OPENGD77_HH #include "opengd77base.hh" #include "opengd77_interface.hh" #include "opengd77_codeplug.hh" #include "opengd77_callsigndb.hh" /** Implements an USB interface to Open GD-77(S) VHF/UHF 5W DMR (Tier I&II) radios. * * @ingroup ogd77 */ class OpenGD77 : public OpenGD77Base { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit OpenGD77(OpenGD77Interface *device=nullptr, QObject *parent=nullptr); const QString &name() const; const Codeplug &codeplug() const; Codeplug &codeplug(); const CallsignDB *callsignDB() const; CallsignDB *callsignDB(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); protected: /** The device identifier. */ QString _name; /** The actual binary codeplug representation. */ OpenGD77Codeplug _codeplug; /** The actual binary callsign DB representation. */ OpenGD77CallsignDB _callsigns; }; #endif // OPENGD77_HH ================================================ FILE: lib/opengd77_callsigndb.cc ================================================ #include "opengd77_callsigndb.hh" #include "utils.hh" #include "userdatabase.hh" #include "logger.hh" #include /* ******************************************************************************************** * * Implementation of OpenGD77CallsignDB * ******************************************************************************************** */ OpenGD77CallsignDB::OpenGD77CallsignDB(QObject *parent) : OpenGD77BaseCallsignDB(parent) { addImage("OpenGD77 call-sign database"); } bool OpenGD77CallsignDB::encode(UserDatabase *calldb, const Flags &selection, const ErrorStack &err) { Q_UNUSED(err) // Limit entry count auto n = std::min((unsigned int)calldb->count(), Limit::entries()); if (selection.hasCountLimit()) n = std::min(n, (unsigned int)selection.countLimit()); // If there are no entries -> done. if (0 == n) return true; auto n0 = std::min(n, Limit::entries0()), n1 = std::min(n - n0, Limit::entries1()); // Select first n entries and sort them in ascending order of their IDs QVector users; for (unsigned i=0; iuser(i)); std::sort(users.begin(), users.end(), [](const UserDatabase::User &a, const UserDatabase::User &b) { return a.id < b.id; }); if (n) logDebug() << "Store " << n << " entries starting from " << users.front().id << ":" << users.front().call << ", " << users.front().name << " in " << users.front().city << " to " << users.back().id << ":" << users.back().call << ", " << users.back().name << " in " << users.back().city; // Allocate segment0 for user db if requested unsigned size = align_size(DatabaseHeaderElement::size()+n0*DatabaseEntryElement::size(), Limit::blockSize()); this->image(0).addElement(Offset::header(), size); size = align_size(n1*DatabaseEntryElement::size(), Limit::blockSize()); if (n1) this->image(0).addElement(Offset::entries1(), size); // Encode user DB DatabaseHeaderElement header(this->data(Offset::header())); header.clear(); header.setEntrySize(DatabaseEntryElement::size()); header.setEntryCount(n); for (unsigned i=0; idata(Offset::entries0() + i*DatabaseEntryElement::size())) .fromEntry(users[i]); } for (unsigned i=0; idata(Offset::entries1() + i*DatabaseEntryElement::size())) .fromEntry(users[n0+i]); } return true; } ================================================ FILE: lib/opengd77_callsigndb.hh ================================================ #ifndef OPENGD77CALLSIGNDB_HH #define OPENGD77CALLSIGNDB_HH #include "opengd77base_callsigndb.hh" #include "userdatabase.hh" /** Represents and encodes the binary format for the call-sign database within the radio. * * The memory layout of the call-sign DB is relatively simple. The DB starts at address * @c 0x00030000 with a maximum size of @c 0x00040000. The first 12bytes form the DB header * (see @c OpenGD77CallsignDB::userdb_t) followed by the DB entries (see * @c OpenGD77CallsignDB::userdb_entry_t). * * The entries can be of variable size. The size of each entry is encoded in the header. QDMR uses * a fixed size of 19bytes per entry. The entries must be sorted in ascending order to allow for an * efficient binary search. No index table is used here. * * @ingroup ogd77 */ class OpenGD77CallsignDB : public OpenGD77BaseCallsignDB { Q_OBJECT public: /** Constructor. */ explicit OpenGD77CallsignDB(QObject *parent=nullptr); static constexpr unsigned int size0() { return 0x40000; } static constexpr unsigned int size1() { return 0x48000; } /** Encodes as many entries as possible of the given user-database. */ bool encode(UserDatabase *calldb, const Flags &selection=Flags(), const ErrorStack &err=ErrorStack()); public: /** Some limits of the callsign DB. */ struct Limit: public OpenGD77BaseCallsignDB::Limit { /// Number of entries, segment 0. static constexpr unsigned int entries0() { return (size0()-DatabaseHeaderElement::size())/DatabaseEntryElement::size(); } /// Number of entries, segment 1. static constexpr unsigned int entries1() { return size1()/DatabaseEntryElement::size(); } static constexpr unsigned int entries() { return entries0() + entries1(); } }; protected: /** Some internal offsets within the callsign db. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int header() { return 0x030000; } static constexpr unsigned int entries0() { return header() + DatabaseHeaderElement::size(); } static constexpr unsigned int entries1() { return 0x0b8000; } /// @endcond }; }; #endif // OPENGD77CALLSIGNDB_HH ================================================ FILE: lib/opengd77_codeplug.cc ================================================ #include "opengd77_codeplug.hh" #include "config.hh" #include "channel.hh" #include #include /* ******************************************************************************************** * * Implementation of OpenGD77Codeplug * ******************************************************************************************** */ OpenGD77Codeplug::OpenGD77Codeplug(QObject *parent) : OpenGD77BaseCodeplug(parent) { // Delete allocated image by GD77 codeplug addImage("OpenGD77 Codeplug EEPROM"); image(EEPROM).addElement(Offset::settings(), 0x05fe0); image(EEPROM).addElement(0x07500, 0x03b00); addImage("OpenGD77 Codeplug FLASH"); image(FLASH).addElement(Offset::additionalSettings(), AdditionalSettingsElement::size()); image(FLASH).addElement(0x7b000, 0x13e60); } void OpenGD77Codeplug::clearGeneralSettings() { GeneralSettingsElement(data(Offset::settings(), ImageIndex::settings())).clear(); } bool OpenGD77Codeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { GeneralSettingsElement el(data(Offset::settings(), ImageIndex::settings())); if (! flags.updateCodeplug()) el.clear(); return el.encode(ctx, err); } bool OpenGD77Codeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings(), ImageIndex::settings())).decode(ctx, err); } void OpenGD77Codeplug::clearDTMFSettings() { //DTMFSettingsElement(data(Offset::settings(), ImageIndex::settings())).clear(); } bool OpenGD77Codeplug::encodeDTMFSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err); return true; } bool OpenGD77Codeplug::decodeDTMFSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return true; } void OpenGD77Codeplug::clearAPRSSettings() { APRSSettingsBankElement(data(Offset::aprsSettings(), ImageIndex::aprsSettings())).clear(); } bool OpenGD77Codeplug::encodeAPRSSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { APRSSettingsBankElement el(data(Offset::aprsSettings(), ImageIndex::aprsSettings())); if (! flags.updateCodeplug()) el.clear(); return el.encode(ctx, err); } bool OpenGD77Codeplug::decodeAPRSSettings(Context &ctx, const ErrorStack &err) { return APRSSettingsBankElement(data(Offset::aprsSettings(), ImageIndex::aprsSettings())) .decode(ctx, err); } bool OpenGD77Codeplug::linkAPRSSettings(Context &ctx, const ErrorStack &err) { return APRSSettingsBankElement(data(Offset::aprsSettings(), ImageIndex::aprsSettings())) .link(ctx, err); } bool OpenGD77Codeplug::encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { // Encode boot melody if set if (ctx.config()->settings()->tone()->bootToneEnabled() && !ctx.config()->settings()->tone()->bootMelody()->isEmpty()) { AdditionalSettingsElement opt(data(Offset::additionalSettings(), ImageIndex::additionalSettings())); if (! flags.updateCodeplug() && ! opt.isValid()) opt.clear(); opt.bootMelody().encode(ctx, ctx.config()->settings()->tone()->bootMelody(), err); } // Encode other boot settings return BootSettingsElement(data(Offset::bootSettings(), ImageIndex::bootSettings())) .encode(ctx, err); } bool OpenGD77Codeplug::decodeBootSettings(Context &ctx, const ErrorStack &err) { // Check if boot melody is encoded AdditionalSettingsElement opt(data(Offset::additionalSettings(), ImageIndex::additionalSettings())); if (opt.hasSettings(AdditionalSettingsElement::BootMelody)) { ctx.config()->settings()->tone()->enableBootTone( opt.bootMelody().decode(ctx, ctx.config()->settings()->tone()->bootMelody(), err)); } return BootSettingsElement(data(Offset::bootSettings(), ImageIndex::bootSettings())) .decode(ctx, err); } void OpenGD77Codeplug::clearContacts() { ContactBankElement(data(Offset::contacts(), ImageIndex::contacts())).clear(); } bool OpenGD77Codeplug::encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); return ContactBankElement(data(Offset::contacts(), ImageIndex::contacts())).encode(ctx, err); } bool OpenGD77Codeplug::createContacts(Context &ctx, const ErrorStack &err) { return ContactBankElement(data(Offset::contacts(), ImageIndex::contacts())).decode(ctx, err); } void OpenGD77Codeplug::clearDTMFContacts() { DTMFContactBankElement(data(Offset::dtmfContacts(), ImageIndex::dtmfContacts())).clear(); } bool OpenGD77Codeplug::encodeDTMFContacts(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); return DTMFContactBankElement(data(Offset::dtmfContacts(), ImageIndex::dtmfContacts())) .encode(ctx, err); } bool OpenGD77Codeplug::createDTMFContacts(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return DTMFContactBankElement(data(Offset::dtmfContacts(), ImageIndex::dtmfContacts())) .decode(ctx, err); } void OpenGD77Codeplug::clearChannels() { for (unsigned int b=0; b(c)) { if (! bank.channel(i).encode(ctx.get(c), ctx, err)) { errMsg(err) << "Cannot encode channel '" << ctx.get(c)->name() << "' at index " << i << " of bank " << b << "."; return false; } bank.enable(i, true); } else { bank.enable(i, false); } } } return true; } bool OpenGD77Codeplug::createChannels(Context &ctx, const ErrorStack &err) { for (unsigned int b=0,c=0; bchannelList()->add(obj); ctx.add(obj, c++); } } return true; } bool OpenGD77Codeplug::linkChannels(Context &ctx, const ErrorStack &err) { for (unsigned int b=0,c=0; b(c); if (! bank.channel(i).link(obj, ctx, err)) { errMsg(err) << "Cannot link channel '" << obj->name() << "' from index " << i << " in bank " << b << "."; return false; } c++; } } return true; } void OpenGD77Codeplug::clearBootSettings() { BootSettingsElement(data(Offset::bootSettings(), ImageIndex::bootSettings())).clear(); } void OpenGD77Codeplug::clearVFOSettings() { VFOChannelElement(data(Offset::vfoA(), ImageIndex::vfoA())).clear(); VFOChannelElement(data(Offset::vfoB(), ImageIndex::vfoB())).clear(); } void OpenGD77Codeplug::clearZones() { ZoneBankElement(data(Offset::zoneBank(), ImageIndex::zoneBank())).clear(); } bool OpenGD77Codeplug::encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); return ZoneBankElement(data(Offset::zoneBank(), ImageIndex::zoneBank())).encode(ctx, err); } bool OpenGD77Codeplug::createZones(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return ZoneBankElement(data(Offset::zoneBank(), ImageIndex::zoneBank())).decode(ctx, err); } bool OpenGD77Codeplug::linkZones(Context &ctx, const ErrorStack &err) { return ZoneBankElement(data(Offset::zoneBank(), ImageIndex::zoneBank())).link(ctx, err); } void OpenGD77Codeplug::clearGroupLists() { GroupListBankElement(data(Offset::groupLists(), ImageIndex::groupLists())).clear(); } bool OpenGD77Codeplug::encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); return GroupListBankElement(data(Offset::groupLists(), ImageIndex::groupLists())).encode(ctx, err); } bool OpenGD77Codeplug::createGroupLists(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return GroupListBankElement(data(Offset::groupLists(), ImageIndex::groupLists())).decode(ctx, err); } bool OpenGD77Codeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { return GroupListBankElement(data(Offset::groupLists(), ImageIndex::groupLists())).link(ctx, err); } ================================================ FILE: lib/opengd77_codeplug.hh ================================================ #ifndef OPENGD77_CODEPLUG_HH #define OPENGD77_CODEPLUG_HH #include "opengd77base_codeplug.hh" #include "opengd77_extension.hh" /** Represents, encodes and decodes the device specific codeplug for Open GD-77 firmware. * This codeplug is almost identical to the original GD77 codeplug. * * @section ogd77cpl Codeplug structure within radio * The memory representation of the codeplug within the radio is divided into two images * (EEPROM and Flash) and each image again into two sections. * The first segment of the EEPROM image starts at the address 0x000e0 and ends at 0x06000, while * the second EEPROM section starts at 0x07500 and ends at 0x0b000. * * The first segment of the Flash image starts at the address 0x00000 and ends at 0x011a0, while the * second Flash section starts at 0x7b000 and ends at 0x8ee60. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Start End Size Content
First EEPROM segment 0x000e0-0x06000
0x000e0 0x000ec 0x000c General settings, see @c RadioddityCodeplug::GeneralSettingsElement.
0x000ec 0x00128 0x003c ??? Unknown ???
0x00128 0x01370 0x1248 32 message texts, see @c RadioddityCodeplug::MessageBankElement.
0x01370 0x01790 0x0420 ??? Unknown ???
0x01790 0x02dd0 0x1640 64 scan lists, see @c GD77Codeplug::ScanListBankElement, GD77Codeplug::ScanListElement.
0x02dd0 0x02f88 0x01b8 ??? Unknown ???
0x02f88 0x03388 0x0400 DTMF contacts, see RadioddityCodeplug::DTMFContactElement.
0x03388 0x03780 0x03f8 ??? Unknown ???
0x03780 0x05390 0x1c10 First 128 channels (bank 0), see @c RadioddityCodeplug::ChannelBankElement, @c OpenGD77Codeplug::ChannelElement.
0x05390 0x06000 0x0c70 ??? Unknown ???
Second EEPROM segment 0x07500-0x13000
0x07500 0x07518 0x0018 ??? Unknown ???
0x07518 0x07538 0x0020 Boot settings, see @c RadioddityCodeplug::BootSettingsElement.
0x07538 0x07540 0x0008 Menu settings, see @c RadioddityCodeplug::MenuSettingsElement.
0x07540 0x07560 0x0020 2 intro lines, @c RadioddityCodeplug::BootTextElement.
0x07560 0x07590 0x0030 ??? Unknown ???
0x07590 0x075c8 0x0038 VFO A settings @c OpenGD77Codeplug::VFOChannelElement
0x075c8 0x07600 0x0038 VFO B settings @c OpenGD77Codeplug::VFOChannelElement
0x07600 0x08010 0x0a10 ??? Unknown ???
0x08010 0x12c10 0xac00 250 zones, see @c OpenGD77Codeplug::ZoneBankElement, @c OpenGD77Codeplug::ZoneElement.
0x12c10 0x13000 0x03f0 ??? Unknown ???
First Flash segment 0x00000-0x011a0
0x00000 0x011a0 0x11a0 ??? Unknown ???
Second Flash segment 0x7b000-0x8ee60
0x7b000 0x7b1b0 0x01b0 ??? Unknown ???
0x7b1b0 0x87620 0xc470 Remaining 896 channels (bank 1-7), see @c RadioddityCodeplug::ChannelBankElement and @c OpenGD77Codeplug::ChannelElement.
0x87620 0x8d620 0x6000 1024 contacts, see @c OpenGD77Codeplug::ContactElement.
0x8d620 0x8e2a0 0x0c80 76 RX group lists, see @c GD77Codeplug::GroupListBankElement, @c GD77Codeplug::GroupListElement.
0x8e2a0 0x8ee60 0x0bc0 ??? Unknown ???
* @ingroup ogd77 */ class OpenGD77Codeplug: public OpenGD77BaseCodeplug { Q_OBJECT public: /** Constructs an empty codeplug for the GD-77. */ explicit OpenGD77Codeplug(QObject *parent=nullptr); public: void clearGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearDTMFSettings(); bool encodeDTMFSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeDTMFSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearAPRSSettings(); bool encodeAPRSSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeAPRSSettings(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkAPRSSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearDTMFContacts(); bool encodeDTMFContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createDTMFContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void clearBootSettings(); bool encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeBootSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearVFOSettings(); void clearZones(); bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); void clearGroupLists(); bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some Limits for this codeplug. */ struct Limit: public Element::Limit { /** Number of channel banks. */ static constexpr unsigned int channelBanks() { return 8; } }; protected: /** Internal used image indices. */ struct ImageIndex { /// @cond DO_NOT_DOCUEMNT static constexpr unsigned int settings() { return EEPROM; } static constexpr unsigned int dtmfSettings() { return EEPROM; } static constexpr unsigned int aprsSettings() { return EEPROM; } static constexpr unsigned int dtmfContacts() { return EEPROM; } static constexpr unsigned int channelBank0() { return EEPROM; } static constexpr unsigned int bootSettings() { return EEPROM; } static constexpr unsigned int vfoA() { return EEPROM; } static constexpr unsigned int vfoB() { return EEPROM; } static constexpr unsigned int zoneBank() { return EEPROM; } static constexpr unsigned int additionalSettings() { return FLASH; } static constexpr unsigned int channelBank1() { return FLASH; } static constexpr unsigned int contacts() { return FLASH; } static constexpr unsigned int groupLists() { return FLASH; } /// @endcond }; /** Some offsets. */ struct Offset { /// @cond DO_NOT_DOCUEMNT static constexpr unsigned int settings() { return 0x000080; } static constexpr unsigned int dtmfSettings() { return 0x001470; } static constexpr unsigned int aprsSettings() { return 0x001588; } static constexpr unsigned int dtmfContacts() { return 0x002f88; } static constexpr unsigned int channelBank0() { return 0x003780; } // Channels 1-128 static constexpr unsigned int bootSettings() { return 0x007518; } static constexpr unsigned int vfoA() { return 0x007590; } static constexpr unsigned int vfoB() { return 0x0075c8; } static constexpr unsigned int zoneBank() { return 0x008010; } static constexpr unsigned int additionalSettings() { return 0x000000; } static constexpr unsigned int channelBank1() { return 0x07b1b0; } // Channels 129-1024 static constexpr unsigned int contacts() { return 0x087620; } static constexpr unsigned int groupLists() { return 0x08d620; } /// @endcond }; }; #endif // OPENGD77_CODEPLUG_HH ================================================ FILE: lib/opengd77_extension.cc ================================================ #include "opengd77_extension.hh" #include "utils.hh" /* ******************************************************************************************** * * Implementation of OpenGD77ChannelExtension * ******************************************************************************************** */ OpenGD77ChannelExtension::OpenGD77ChannelExtension(QObject *parent) : ConfigExtension(parent), _zoneSkip(false), _allSkip(false), _beep(true), _powerSave(true), _location(), _txTalkerAliasTS1(TalkerAlias::None), _txTalkerAliasTS2(TalkerAlias::None) { // pass... } ConfigItem * OpenGD77ChannelExtension::clone() const { OpenGD77ChannelExtension *ex = new OpenGD77ChannelExtension(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } bool OpenGD77ChannelExtension::scanZoneSkip() const { return _zoneSkip; } void OpenGD77ChannelExtension::enableScanZoneSkip(bool enable) { _zoneSkip = enable; } bool OpenGD77ChannelExtension::scanAllSkip() const { return _allSkip; } void OpenGD77ChannelExtension::enableScanAllSkip(bool enable) { _allSkip = enable; } bool OpenGD77ChannelExtension::beep() const { return _beep; } void OpenGD77ChannelExtension::enableBeep(bool enable) { _beep = enable; } bool OpenGD77ChannelExtension::powerSave() const { return _powerSave; } void OpenGD77ChannelExtension::enablePowerSave(bool enable) { _powerSave = enable; } bool OpenGD77ChannelExtension::locationEnabled() const { return _locationEnabled; } void OpenGD77ChannelExtension::enableLocation(bool enable) { if (_locationEnabled == enable) return; _locationEnabled = enable; emit modified(this); } const QGeoCoordinate & OpenGD77ChannelExtension::location() const { return _location; } void OpenGD77ChannelExtension::setLocation(const QGeoCoordinate &loc) { _location = loc; } QString OpenGD77ChannelExtension::locator() const { return deg2loc(location(), 8); } void OpenGD77ChannelExtension::setLocator(const QString &loc) { _location = loc2deg(loc); } OpenGD77ChannelExtension::TalkerAlias OpenGD77ChannelExtension::talkerAliasTS1() const { return _txTalkerAliasTS1; } void OpenGD77ChannelExtension::setTalkerAliasTS1(TalkerAlias ta) { _txTalkerAliasTS1 = ta; } OpenGD77ChannelExtension::TalkerAlias OpenGD77ChannelExtension::talkerAliasTS2() const { return _txTalkerAliasTS2; } void OpenGD77ChannelExtension::setTalkerAliasTS2(TalkerAlias ta) { _txTalkerAliasTS2 = ta; } /* ******************************************************************************************** * * Implementation of OpenGD77ContactExtension * ******************************************************************************************** */ OpenGD77ContactExtension::OpenGD77ContactExtension(QObject *parent) : ConfigExtension(parent), _timeSlotOverride(TimeSlotOverride::None) { // pass... } ConfigItem * OpenGD77ContactExtension::clone() const { OpenGD77ContactExtension *ex = new OpenGD77ContactExtension(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } OpenGD77ContactExtension::TimeSlotOverride OpenGD77ContactExtension::timeSlotOverride() const { return _timeSlotOverride; } void OpenGD77ContactExtension::setTimeSlotOverride(TimeSlotOverride ts) { _timeSlotOverride = ts; } ================================================ FILE: lib/opengd77_extension.hh ================================================ /** @defgroup ogd77ex OpenGD77 Extensions * This module collects classes that implement the firmware specific extensions to the common * codeplug configuration for radios running the OpenGD77 firmware. * * @ingroup ogd77 */ #ifndef OPENGD77EXTENSION_HH #define OPENGD77EXTENSION_HH #include #include "configobject.hh" #include "melody.hh" /** Implements the channel extensions for the OpenGD77 radios. * @since 0.9.0 * @ingroup ogd77ex */ class OpenGD77ChannelExtension: public ConfigExtension { Q_OBJECT Q_CLASSINFO("description", "Channel settings for OpenGD77 radios.") Q_CLASSINFO("longDescription", "This extension implements all channel settings specific to radios " "running the OpenGD77 firmware.") /** The zone skip flag. */ Q_PROPERTY(bool scanZoneSkip READ scanZoneSkip WRITE enableScanZoneSkip) /** The all skip flag. */ Q_PROPERTY(bool scanAllSkip READ scanAllSkip WRITE enableScanAllSkip) /** The beep enable flag. */ Q_PROPERTY(bool beep READ beep WRITE enableBeep) /** The power save enable flag. */ Q_PROPERTY(bool powerSave READ powerSave WRITE enablePowerSave) /** Sets a fixed location for the APRS report. */ Q_PROPERTY(QString location READ locator WRITE setLocator) /** Sets the talker alias for timeslot 1. */ Q_PROPERTY(TalkerAlias talkerAliasTS1 READ talkerAliasTS1 WRITE setTalkerAliasTS1) /** Sets the talker alias for timeslot 2. */ Q_PROPERTY(TalkerAlias talkerAliasTS2 READ talkerAliasTS2 WRITE setTalkerAliasTS2) public: enum class TalkerAlias { None, APRS, Text, Both }; Q_ENUM(TalkerAlias) public: /** Constructor. */ Q_INVOKABLE explicit OpenGD77ChannelExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true if the zone skip flag is set. */ bool scanZoneSkip() const; /** Enables/disables zone skip. */ void enableScanZoneSkip(bool enable); /** Returns @c true if the all-skip flag is set. */ bool scanAllSkip() const; /** Enables/disables all skip. */ void enableScanAllSkip(bool enable); /** Returns @c true if the beep tone is enabled for this channel. */ bool beep() const; /** Enable beep tone for this channel. */ void enableBeep(bool enable); /** Returns @c true, if power save is enabled for this channel (default: true). */ bool powerSave() const; /** Enables power save for this channel. */ void enablePowerSave(bool enable); /** Returns @c true if the fixed location is enabled. */ bool locationEnabled() const; /** Enables the fixed location. */ void enableLocation(bool enable); /** Returns the fixed location for this channel. */ const QGeoCoordinate &location() const; /** Returns the fixed location for this channel. */ QString locator() const; /** Sets the fixed location for this channel. */ void setLocation(const QGeoCoordinate &loc); /** Sets the fixed location for this channel. */ void setLocator(const QString &locator); /** Returns the talker alias setting for timeslot 1. */ TalkerAlias talkerAliasTS1() const; /** Sets the talker alias setting for timeslot 1. */ void setTalkerAliasTS1(TalkerAlias ta); /** Returns the talker alias setting for timeslot 2. */ TalkerAlias talkerAliasTS2() const; /** Sets the talker alias setting for timeslot 2. */ void setTalkerAliasTS2(TalkerAlias ta); protected: /** Holds the zone skip flag. */ bool _zoneSkip; /** Holds the all skip flag. */ bool _allSkip; /** Holds the beep enable flag. */ bool _beep; /** Holds the power-save flag. */ bool _powerSave; /** If @c true, fixed location is used. */ bool _locationEnabled; /** Holds the fixed location. */ QGeoCoordinate _location; /** Holds the talker alias setting for timeslot 1. */ TalkerAlias _txTalkerAliasTS1; /** Holds the talker alias setting for timeslot 2. */ TalkerAlias _txTalkerAliasTS2; }; /** Implements the contact extensions for the OpenGD77 radios. * @since 0.9.0 * @ingroup ogd77ex */ class OpenGD77ContactExtension: public ConfigExtension { Q_OBJECT Q_CLASSINFO("description", "DMR contact settings for OpenGD77 radios.") Q_CLASSINFO("longDescription", "This extension implements all contact settings specific to radios " "running the OpenGD77 firmware. As the OpenGD77 codeplug is derived from the " "Radioddity GD77 codeplug, all Radioddity extension also apply.") /** If set, overrides the channel time slot if this contact is selected as the transmit contact. */ Q_PROPERTY(TimeSlotOverride timeSlotOverride READ timeSlotOverride WRITE setTimeSlotOverride) Q_CLASSINFO("timeSlotOverrideDescription", "If set, overrides the channels timeslot.") Q_CLASSINFO("timeSlotOverrideLongDescription", "The OpenGD77 firmware allows contacts to override the channel time slot if the " "contact is selected as the current destination contact for that channel. This allows " "to assign a specific time slot to a contact, rather than creating a particular " "channel for that contact that only differs in the time slot.") public: /** Possible modes of time slot override. */ enum class TimeSlotOverride { None, ///< Do not override time slot. TS1, ///< Override with time slot 1. TS2 ///< Override with time slot 2. }; Q_ENUM(TimeSlotOverride) public: /** Constructor. */ Q_INVOKABLE explicit OpenGD77ContactExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the time slot override. */ TimeSlotOverride timeSlotOverride() const; /** Sets the time slot override. */ void setTimeSlotOverride(TimeSlotOverride ts); protected: /** Holds the time slot override. */ TimeSlotOverride _timeSlotOverride; }; #endif // OPENGD77EXTENSION_HH ================================================ FILE: lib/opengd77_interface.cc ================================================ #include "opengd77_interface.hh" #include "logger.hh" #include "radioinfo.hh" #include #include #include #define USB_VID 0x1fc9 #define USB_PID 0x0094 #define BLOCK_SIZE 32 #define SECTOR_SIZE 4096 #define ALIGN_BLOCK_SIZE(n) ((0==((n)%BLOCK_SIZE)) ? (n) : (n)+(BLOCK_SIZE-((n)%BLOCK_SIZE))) #define TIMEOUT -1 // ms /* ********************************************************************************************* * * Implementation of OpenGD77Interface::FirmwareInfo * ********************************************************************************************* */ bool OpenGD77Interface::FirmwareInfo::featureInvertedDisplay() const { return (qFromLittleEndian(features) & (1<<0)); } bool OpenGD77Interface::FirmwareInfo::featureExtendedCallsignDB() const { return (qFromLittleEndian(features) & (1<<1)); } bool OpenGD77Interface::FirmwareInfo::featureVoicePromptLoaded() const { return (qFromLittleEndian(features) & (1<<2)); } /* ********************************************************************************************* * * Implementation of OpenGD77Interface::ReadRequest * ********************************************************************************************* */ bool OpenGD77Interface::ReadRequest::initReadEEPROM(uint32_t addr, uint16_t length) { this->type = 'R'; this->command = READ_EEPROM; this->address = qToBigEndian(addr); this->length = qToBigEndian(length); return true; } bool OpenGD77Interface::ReadRequest::initReadFlash(uint32_t addr, uint16_t length) { this->type = 'R'; this->command = READ_FLASH; this->address = qToBigEndian(addr); this->length = qToBigEndian(length); return true; } bool OpenGD77Interface::ReadRequest::initReadFirmwareInfo() { this->type = 'R'; this->command = READ_FIRMWARE_INFO; this->address = 0; this->length = qToBigEndian((uint16_t)sizeof(FirmwareInfo)); return true; } /* ********************************************************************************************* * * Implementation of OpenGD77Interface::WriteRequest * ********************************************************************************************* */ bool OpenGD77Interface::WriteRequest::initWriteEEPROM(Variant variant, uint32_t addr, const uint8_t *data, uint16_t size) { if (size > 32) size = 32; this->type = (Variant::GD77 == variant) ? 'W' : 'X'; this->command = WRITE_EEPROM; this->payload.address = qToBigEndian(addr); this->payload.length = qToBigEndian(size); memcpy(this->payload.data, data, size); return true; } bool OpenGD77Interface::WriteRequest::initSetFlashSector(Variant variant, uint32_t addr) { uint32_t sec = addr/SECTOR_SIZE; this->type = (Variant::GD77 == variant) ? 'W' : 'X'; logDebug() << "Send SET_FLASH_SECTOR (" << this->type << "): " << Qt::hex << sec << "h"; this->command = SET_FLASH_SECTOR; this->sector[0] = ((sec>>16) & 0xff); this->sector[1] = ((sec>>8) & 0xff); this->sector[2] = (sec & 0xff); return true; } bool OpenGD77Interface::WriteRequest::initWriteFlash(Variant variant, uint32_t addr, const uint8_t *data, uint16_t size) { if (size > 32) size = 32; this->type = (Variant::GD77 == variant) ? 'W' : 'X'; this->command = WRITE_SECTOR_BUFFER; logDebug() << "Send WRITE_FLASH_BUFFER (" << this->type << ") @ " << Qt::hex << addr << "."; this->payload.address = qToBigEndian(addr); this->payload.length = qToBigEndian(size); memcpy(this->payload.data, data, size); return true; } bool OpenGD77Interface::WriteRequest::initFinishWriteFlash(Variant variant) { this->type = (Variant::GD77 == variant) ? 'W' : 'X'; this->command = WRITE_FLASH_SECTOR; return true; } /* ********************************************************************************************* * * Implementation of OpenGD77Interface::CommandRequest * ********************************************************************************************* */ void OpenGD77Interface::CommandRequest::initShowCPSScreen() { this->type = 'C'; this->command = SHOW_CPS_SCREEN; this->x = 0; this->y = 0; this->font = 0; this->alignment = 0; this->inverted = 0; memset(this->message, 0, sizeof(this->message)); } void OpenGD77Interface::CommandRequest::initClearScreen() { this->type = 'C'; this->command = CLEAR_SCREEN; this->x = 0; this->y = 0; this->font = 0; this->alignment = 0; this->inverted = 0; memset(this->message, 0, sizeof(this->message)); } void OpenGD77Interface::CommandRequest::initDisplay(uint8_t x, uint8_t y, const char *message, unsigned int iSize, uint8_t font, uint8_t alignment, uint8_t inverted) { this->type = 'C'; this->command = DISPLAY; this->x = x; this->y = y; this->font = font; this->alignment = alignment; this->inverted = inverted; memset(this->message, 0, 16); strncpy(this->message, message, std::min(16u, iSize)); } void OpenGD77Interface::CommandRequest::initRenderCPS() { this->type = 'C'; this->command = RENDER_CPS; this->x = 0; this->y = 0; this->font = 0; this->alignment = 0; this->inverted = 0; memset(this->message, 0, sizeof(this->message)); } void OpenGD77Interface::CommandRequest::initCloseScreen() { this->type = 'C'; this->command = CLOSE_CPS_SCREEN; this->x = 0; this->y = 0; this->font = 0; this->alignment = 0; this->inverted = 0; memset(this->message, 0, sizeof(this->message)); } void OpenGD77Interface::CommandRequest::initCommand(Option option) { this->type = 'C'; this->command = COMMAND; this->option = option; this->y = 0; this->font = 0; this->alignment = 0; this->inverted = 0; memset(this->message, 0, sizeof(this->message)); } void OpenGD77Interface::CommandRequest::initSetDateTime(const QDateTime &dt) { initCommand(SET_DATETIME); this->timestamp = qToLittleEndian(dt.toUTC().toSecsSinceEpoch()); } /* ********************************************************************************************* * * Implementation of OpenGD77Interface * ********************************************************************************************* */ OpenGD77Interface::OpenGD77Interface(const USBDeviceDescriptor &descr, const ErrorStack &err, QObject *parent) : USBSerial(descr, QSerialPort::Baud115200, err, parent), _extendedCallsignDB(false), _sector(-1) { // pass... } OpenGD77Interface::~OpenGD77Interface() { // pass... } USBDeviceInfo OpenGD77Interface::interfaceInfo() { return USBDeviceInfo(USBDeviceInfo::Class::Serial, USB_VID, USB_PID); } QList OpenGD77Interface::detect(bool saveOnly) { Q_UNUSED(saveOnly) return USBSerial::detect(USB_VID, USB_PID, true); } void OpenGD77Interface::close() { if (isOpen()) USBSerial::close(); } RadioInfo OpenGD77Interface::identifier(const ErrorStack &err) { Q_UNUSED(err); if (! isOpen()) return RadioInfo(); FirmwareInfo info; if (! readFirmwareInfo(info, err)) { errMsg(err) << "Cannot identify OpenGD77 variant."; return RadioInfo(); } logDebug() << "Got type=" << info.radioType << "."; switch ((FirmwareInfo::RadioType)info.radioType) { case FirmwareInfo::RadioType::GD77: case FirmwareInfo::RadioType::GD77S: case FirmwareInfo::RadioType::RD5R: case FirmwareInfo::RadioType::DM1801: case FirmwareInfo::RadioType::DM1801A: _protocolVariant = Variant::GD77; return RadioInfo::byID(RadioInfo::OpenGD77); case FirmwareInfo::RadioType::MDUV380: case FirmwareInfo::RadioType::MD380: case FirmwareInfo::RadioType::DM1701: case FirmwareInfo::RadioType::DM1701RGB: case FirmwareInfo::RadioType::MD9600: case FirmwareInfo::RadioType::MD2017: _protocolVariant = Variant::UV380; _extendedCallsignDB = info.featureExtendedCallsignDB() && !info.featureVoicePromptLoaded(); return RadioInfo::byID(RadioInfo::OpenUV380); } errMsg(err) << "Unknown OpenGD77 variant " << info.radioType << "."; return RadioInfo(); } bool OpenGD77Interface::extendedCallsignDB() const { return _extendedCallsignDB; } bool OpenGD77Interface::write_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { logDebug() << "Send enter prog mode ..."; if (! sendShowCPSScreen(err)) return false; //logDebug() << "Send clear screen ..."; if (! sendClearScreen(err)) return false; //logDebug() << "Send display text ..."; if (! sendDisplay(0, 0, "qDMR", 4, 1, 0, err)) return false; if (! sendDisplay(0, 16, "Writing", 7, 1, 0, err)) return false; if (! sendDisplay(0, 32, "Codeplug", 8, 1, 0, err)) return false; //logDebug() << "Send 'render CPS' ..."; if (! sendRenderCPS(err)) return false; //logDebug() << "Send 'flash red LED' ..."; if (! sendCommand(CommandRequest::FLASH_RED_LED, err)) return false; //logDebug() << "Send save settings and VFOs ..."; if (! sendCommand(CommandRequest::SAVE_SETTINGS_AND_VFOS, err)) return false; if (EEPROM == bank) { if (_sector >= 0) { if (! finishWriteFlash(err)) return false; } _sector = -1; } else if (FLASH == bank) { int32_t sector = addr/SECTOR_SIZE; if ((-1 != _sector) && (_sector != sector)) { if (! finishWriteFlash(err)) return false; } } return true; } bool OpenGD77Interface::write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err) { if (EEPROM == bank) { if ((0 <= _sector) && (! finishWriteFlash(err))) return false; for (int i=0; i _sector) { if (! setFlashSector(addr)) return false; _sector = sector; } if (sector == _sector) { for (int i=0; i _sector) return true; _sector = -1; if (! finishWriteFlash(err)) return false; if (! sendCloseScreen(err)) return false; return true; } bool OpenGD77Interface::read_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { Q_UNUSED(bank); Q_UNUSED(addr) if (! sendShowCPSScreen(err)) return false; if (! sendClearScreen(err)) return false; if (! sendDisplay(0, 0, "qDMR", 4, 1, 0, err)) return false; if (! sendDisplay(0, 16, "Reading", 7, 1, 0, err)) return false; if (! sendDisplay(0, 32, "Codeplug", 8, 1, 0, err)) return false; if (! sendRenderCPS(err)) return false; if (! sendCommand(CommandRequest::FLASH_GREEN_LED, err)) return false; if (! sendCommand(CommandRequest::SAVE_SETTINGS_AND_VFOS, err)) return false; return true; } bool OpenGD77Interface::read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err) { if (! isOpen()) { errMsg(err) << "Cannot read block: Device not open!"; return false; } for (int i=0; i retlen) { errMsg(err) << "Cannot read from serial port"; return false; } else if (0 == retlen) { errMsg(err) << "Cannot read from serial port: Device returned empty message."; return false; } if ('R' != resp.type) { errMsg(err) << "Cannot read from device: Device returned error '" << resp.type << "'."; return false; } if (qFromBigEndian(req.length) != qFromBigEndian(resp.length)) { errMsg(err) << "Cannot read from device: Device returned invalid length " << qFromBigEndian(resp.length) << "."; return false; } memcpy(data, resp.data, qFromBigEndian(resp.length)); return true; } bool OpenGD77Interface::writeEEPROM(uint32_t addr, const uint8_t *data, uint16_t len, const ErrorStack &err) { WriteRequest req; req.initWriteEEPROM(_protocolVariant, addr, data, len); WriteResponse resp; if ((8+len) != QSerialPort::write((const char *)&req, 8+len)) { errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, sizeof(WriteResponse)); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot write EEPROM: Device returned empty message."; return false; } if ((req.type != resp.type) || (req.command != resp.command)) { errMsg(err) << "Cannot write EEPROM at " << Qt::hex << addr << ": Device returned error " << resp.type << "."; return false; } return true; } bool OpenGD77Interface::readFlash(uint32_t addr, uint8_t *data, uint16_t len, const ErrorStack &err) { Q_UNUSED(len) if (! isOpen()) { errMsg(err) << "Cannot read block: Device not open!"; return false; } ReadRequest req; req.initReadFlash(addr, BLOCK_SIZE); if (sizeof(ReadRequest) != QSerialPort::write((const char *)&req, sizeof(ReadRequest))) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } ReadResponse resp; int retlen = QSerialPort::read((char *)&resp, sizeof(ReadResponse)); if (0 > retlen) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot read from serial port: Device returned empty message."; return false; } if ('R' != resp.type) { errMsg(err) << "Cannot read from device: Device returned error " << resp.type << "."; return false; } if (qFromBigEndian(req.length) != qFromBigEndian(resp.length)) { errMsg(err) << "Cannot read from device: Device returned invalid length " << qFromBigEndian(resp.length) << "."; return false; } memcpy(data, resp.data, qFromBigEndian(resp.length)); return true; } bool OpenGD77Interface::setFlashSector(uint32_t addr, const ErrorStack &err) { WriteRequest req; req.initSetFlashSector(_protocolVariant, addr); WriteResponse resp; if (5 != QSerialPort::write((const char *)&req, 5)) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, sizeof(WriteResponse)); if (0 > retlen) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot set flash sector: Device returned empty message."; return false; } if ((req.type != resp.type) || (req.command != resp.command)) { errMsg(err) << "Cannot set flash sector: Device returned error " << resp.type << "."; return false; } return true; } bool OpenGD77Interface::writeFlash(uint32_t addr, const uint8_t *data, uint16_t len, const ErrorStack &err) { WriteRequest req; req.initWriteFlash(_protocolVariant, addr, data, len); WriteResponse resp; if ((8+len) != QSerialPort::write((const char *)&req, 8+len)) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, sizeof(WriteResponse)); if (0 > retlen) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot write to buffer: Device returned empty message."; return false; } if ((req.type != resp.type) || (req.command != resp.command)) { errMsg(err) << "Cannot write to buffer at " << QString::number(addr,16) << ": Device returned error " << resp.type << "."; return false; } return true; } bool OpenGD77Interface::finishWriteFlash(const ErrorStack &err) { //logDebug() << "Send finish write flash command ..."; WriteRequest req; req.initFinishWriteFlash(_protocolVariant); logDebug() << "Send cmd WRITE_FLASH_SECTOR."; WriteResponse resp; if ((2) != QSerialPort::write((const char *)&req, 2)) { errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, sizeof(WriteResponse)); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot write to flash: Device returned empty message."; return false; } if ((req.type != resp.type) || (req.command != resp.command)) { errMsg(err) << "Cannot write to flash: Device returned error " << resp.type << "."; return false; } return true; } bool OpenGD77Interface::readFirmwareInfo(OpenGD77Interface::FirmwareInfo &radioInfo, const ErrorStack &err) { logDebug() << "Request radio info."; ReadRequest req; req.initReadFirmwareInfo(); if (sizeof(ReadRequest) != QSerialPort::write((const char *)&req, sizeof(ReadRequest))) { errMsg(err) << "Serial port error: " << QSerialPort::errorString(); errMsg(err) << "Cannot send read request."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } ReadResponse resp; int retlen = QSerialPort::read((char *)&resp, sizeof(ReadResponse)); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot read radio info: Device returned empty message."; return false; } if (req.type != resp.type) { errMsg(err) << "Cannot read radio info: Device returned error " << resp.type << ", expected 'R'."; return false; } memcpy(&radioInfo, &(resp.info), sizeof(FirmwareInfo)); return true; } bool OpenGD77Interface::sendShowCPSScreen(const ErrorStack &err) { CommandRequest req; uint8_t resp; req.initShowCPSScreen(); if (sizeof(CommandRequest) != QSerialPort::write((const char *) &req, sizeof(CommandRequest))) { errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, 1); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot send command: Device returned empty message."; return false; } else if ('-' != resp) { errMsg(err) << "Cannot send command: Device returned unexpected response '" << (char)resp << "'."; return false; } return true; } bool OpenGD77Interface::sendClearScreen(const ErrorStack &err) { CommandRequest req; req.initClearScreen(); uint8_t resp; if (sizeof(CommandRequest) != QSerialPort::write((const char *) &req, sizeof(CommandRequest))) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, 1); if (0 > retlen) { errMsg(err) << QSerialPort::errorString(); errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot send command: Device returned empty message."; return false; } else if ('-' != resp) { errMsg(err) << "Cannot send command: Device returned unexpected response '" << (char)resp << "'."; return false; } return true; } bool OpenGD77Interface::sendDisplay(uint8_t x, uint8_t y, const char *message, uint8_t iSize, uint8_t alignment, uint8_t inverted, const ErrorStack &err) { CommandRequest req; req.initDisplay(x,y, message, iSize, 3, alignment, inverted); uint8_t resp; if (sizeof(CommandRequest) != QSerialPort::write((const char *) &req, sizeof(CommandRequest))) { errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, 1); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot send command: Device returned empty message."; return false; } else if ('-' != resp) { errMsg(err) << "Cannot send command: Device returned unexpected response '" << (char)resp << "'."; return false; } return true; } bool OpenGD77Interface::sendRenderCPS(const ErrorStack &err) { CommandRequest req; req.initRenderCPS(); if (sizeof(CommandRequest) != QSerialPort::write((const char *) &req, sizeof(CommandRequest))) { errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } uint8_t resp; int retlen = QSerialPort::read((char *)&resp, 1); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot send command: Device returned empty message."; return false; } else if ('-' != resp) { errMsg(err) << "Cannot send command: Device returned unexpected response '" << (char)resp << "'."; return false; } return true; } bool OpenGD77Interface::sendCloseScreen(const ErrorStack &err) { CommandRequest req; req.initCloseScreen(); uint8_t resp; if (sizeof(CommandRequest) != QSerialPort::write((const char *) &req, sizeof(CommandRequest))) { errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, 1); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot send command: Device returned empty message."; return false; } else if ('-' != resp) { errMsg(err) << "Cannot send command: Device returned unexpected response '" << (char)resp << "'."; return false; } return true; } bool OpenGD77Interface::sendSetDateTime(const QDateTime &dt, const ErrorStack &err) { CommandRequest req; req.initSetDateTime(dt); uint8_t resp; if (sizeof(CommandRequest) != QSerialPort::write((const char *) &req, sizeof(CommandRequest))) { errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, 1); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot send command: Device returned empty message."; return false; } else if ('-' != resp) { errMsg(err) << "Cannot send command: Device returned unexpected response '" << (char)resp << "'."; return false; } return true; } bool OpenGD77Interface::sendCommand(CommandRequest::Option option, const ErrorStack &err) { CommandRequest req; req.initCommand(option); uint8_t resp; if (sizeof(CommandRequest) != QSerialPort::write((const char *) &req, sizeof(CommandRequest))) { errMsg(err) << "Cannot write to serial port."; return false; } if (! waitForReadyRead(TIMEOUT)) { errMsg(err) << "Cannot read from serial port: Timeout!"; return false; } int retlen = QSerialPort::read((char *)&resp, 1); if (0 > retlen) { errMsg(err) << "Cannot read from serial port."; return false; } else if (0 == retlen) { errMsg(err) << "Cannot send command: Device returned empty message."; return false; } else if ('-' != resp) { errMsg(err) << "Cannot send command: Device returned unexpected response '" << (char)resp << "'."; return false; } return true; } ================================================ FILE: lib/opengd77_interface.hh ================================================ #ifndef OPENGD77INTERFACE_HH #define OPENGD77INTERFACE_HH #include "usbserial.hh" #include "errorstack.hh" /** Implements the interface to a radio running the Open GD77 firmware. * * This interface uses a USB serial-port to communicate with the device. To find the corresponding * port, the device-specific VID @c 0x1fc9 and PID @c 0x0094 are used. Hence no udev rules are * needed to access these devices. The user, however, should be a member of the @c dialout group * to get access to the serial interfaces. * * * @section ogd77cmd Command requests * The overall command requests structure is * * @verbinclude opengd77_protocol_command_request.txt * * where the optional and variable length payload field is determined by the command flag. The * request starts with the command prefix 'C' (43h) followed by the command flag. Following * command flags are known. * * * * * * * * * * * *
FlagCommand
00h Show CPS screen.
01h Clear screen.
02h Display text.
03h Render screen.
05h Close CPS screen.
06h Control radio.
07h Start GPS logging.
feh Ping request.
* * @subsection ogd77cmd_cps_screen Show CPS Screen (00h) * Reserves the screen for the CPS. The content is not cleared. * * The command is quiet simple * @verbinclude opengd77_protocol_command_show_cps_screen_request.txt * as is the response * @verbinclude opengd77_protocol_command_okay_response.txt * * @subsection ogd77cmd_clear_screen Clear Screen (01h) * Once the screen has been reserved, this command clears it. * * Also this command is quiet simple * @verbinclude opengd77_protocol_command_clear_screen_request.txt * as is the response * @verbinclude opengd77_protocol_command_okay_response.txt * * @subsection ogd77cmd_set_text Set text (02h) * This command has a variable payload size. * @verbinclude opengd77_protocol_command_display_text_request.txt * * * * * * * * *
FieldMeaning
Column Address Specifies the column index.
Row Address Specifies the row index as multiple of 10h.
Size Text size (?). Actually, always observed 3.
Alignment Specifies text alignment on row. 0=left, 1=center, 2=right(?)
Inverted Inverts text, 0=off, 1=inverted (?).
Payload Variable size, up to 16bytes ASCII text.
* * If there are no errors, the radio responds with * @verbinclude opengd77_protocol_command_okay_response.txt * * @subsection ogd77cmd_render_screen Render Screen (03h) * Randers the transmitted screen. This command is quiet simple again, with no payload. * @verbinclude opengd77_protocol_command_render_screen_request.txt * * as is the response: * @verbinclude opengd77_protocol_command_okay_response.txt * * @subsection ogd77cmd_ctrl Control Radio (06h) * This command request is used to control the radio. The specific action is transmitted as a * single payload byte. * @verbinclude opengd77_protocol_command_control_request.txt * * * * * * * * * * *
Action CodeMeaning
00h Save settings and rebbot.
01h Reboot
02h Save settings and VFOs, no reboot.
03h Flash LED green.
04h Flash LED red.
05h Re-init internal buffers.
06h Re-init sound buffers.
07h Update date-time from GPS.
* * If there are no errors, the radio responds with * @verbinclude opengd77_protocol_command_okay_response.txt * * @subsection ogd77cmd_gps Start GPS Logging (07h) * A simple request without any payload. * @verbinclude opengd77_protocol_command_start_gps_request.txt * * If there is no error, the response should be. * @verbinclude opengd77_protocol_command_okay_response.txt * After that, the radio will send GPS information via the serial port until any other command * is send to the radio. * * @subsection ogd77cmd_ping Ping Request (06h) * A simple request without any payload * @verbinclude opengd77_protocol_command_ping_request.txt * and the radio should respond with * @verbinclude opengd77_protocol_command_okay_response.txt * * * @section ogd77read Read requests * The read command is used to obtain different stuff. Not only the code plug. In general, all read * share the same form * @verbinclude opengd77_protocol_read_request.txt * * * * * * * * * * * *
Code Memory region
01h Flash
02h EEPROM
05h MCU ROM
06h Display buffer
07h WAV buffer
08h AMBE buffer
09h Radio info
0ah FLASH security registers
* * Whenever the read request returns some data, it is transmitted with the read response * @verbinclude opengd77_protocol_read_response.txt * * If not, a simple ACK response is send * @verbinclude opengd77_protocol_command_okay_response.txt * * @subsection ogd77info Radio info struct * When reading the radio information, the information is returned in a binary struct: * @verbinclude opengd77_radio_info.txt * * * * * * * * * * * * * *
Code Radio Variant
00h Radioddity GD-77
01h Radioddity GD-77S
02h Baofeng DM-1801
03h Radioddity RD-5R
04h Baofeng DM-1801A
05h TyT MD-9600
06h TyT MD-UV390
07h TyT MD-380
08h Baofeng DM-1701
09h TyT MD-2017
0ah Baofeng DM-1701 RGB
* * @section ogd77write Write requests * * @ingroup ogd77 */ class OpenGD77Interface : public USBSerial { Q_OBJECT public: /** The EEPROM memory bank. */ static const uint32_t EEPROM = 0; /** The Flash memory bank. */ static const uint32_t FLASH = 1; /** Specifies the detected model variant. */ enum class Variant { GD77, UV380 }; public: /** Constructs a new interface to a specific OpenGD77 device. */ explicit OpenGD77Interface(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Destructor. */ virtual ~OpenGD77Interface(); /** Closes the interface to the device. */ void close(); /** Returns an identifier of the radio. */ RadioInfo identifier(const ErrorStack &err=ErrorStack()); /** Returns @c true if the device allows for storing part of the call-sign DB inside the voice * prompt memory. This property is valid after identifying the device, i.e., a call to * @c identifier. */ bool extendedCallsignDB() const; bool read_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool read_finish(const ErrorStack &err=ErrorStack()); bool write_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool write_finish(const ErrorStack &err=ErrorStack()); bool setDateTime(const QDateTime &datetime, const ErrorStack &err); bool saveSettingsNotVFOs(const ErrorStack &err=ErrorStack()); bool saveSettingsAndVFOs(const ErrorStack &err=ErrorStack()); bool reboot(const ErrorStack &err=ErrorStack()); public: /** Returns some information about this interface. */ static USBDeviceInfo interfaceInfo(); /** Tries to find all interfaces connected AnyTone radios. */ static QList detect(bool saveOnly=true); protected: /** Represents a read message. */ struct __attribute__((packed)) ReadRequest { /** Possible read sources. */ enum Command { READ_FLASH = 1, READ_EEPROM = 2, READ_MCU_ROM = 5, READ_DISPLAY_BUFFER = 6, READ_WAV_BUFFER = 7, READ_AMBE_BUFFER = 8, READ_FIRMWARE_INFO = 9 }; /// 'R' read block, 'W' write block, 'C' command. char type; /// Memory to read from, FLASH, EEPROM, ROM, etc. @see OpenGD77Internface::ReadReqest::Command. uint8_t command; /// Memory address to read from in big endian. uint32_t address; /// Amount of data to read in big endian. uint16_t length; /** Constructs a FLASH read message. */ bool initReadFlash(uint32_t address, uint16_t length); /** Constructs an EEPROM read message. */ bool initReadEEPROM(uint32_t address, uint16_t length); /** Constructs a firmware-info read message. */ bool initReadFirmwareInfo(); }; /** Radio information struct. */ struct __attribute__((packed)) FirmwareInfo { /** Possible radio types, returned by the radio_info struct.*/ enum class RadioType { GD77=0, GD77S=1, DM1801=2, RD5R=3, DM1801A=4, MD9600=5, MDUV380=6, MD380=7, DM1701=8, MD2017=9, DM1701RGB=10 }; uint32_t structVersion; ///< Struct version number (currently 3). uint32_t radioType; ///< Device variant (see @c RadioType). char fw_revision[16]; ///< Firmware revision ASCII, 0-padded. char build_date[16]; ///< Firmware build time, YYYYMMDDhhmmss, 0-padded. uint32_t flashChipSerial; ///< Serial number of the flash chip. uint16_t features; ///< Some flags, signaling the presence of some features. /** Returns @c true if the devices display is inverted. */ bool featureInvertedDisplay() const; /** Returns @c true if the vocie prompt memory is used for storing callsign db. */ bool featureExtendedCallsignDB() const; /** Returns @c true if the voice prompt data is loaded. */ bool featureVoicePromptLoaded() const; }; /** Represents a read response message. */ struct __attribute__((packed)) ReadResponse { /// Same code as request. That is 'R' read block, 'W' write block, 'C' command. char type; /// Length of paylod. uint16_t length; /// Payload union { uint8_t data[32]; ///< Data payload. FirmwareInfo info; ///< Firmware information struct. }; }; /** Represents a write message. */ struct __attribute__((packed)) WriteRequest { /** Possible write destinations. */ enum Command { SET_FLASH_SECTOR = 1, WRITE_SECTOR_BUFFER = 2, WRITE_FLASH_SECTOR = 3, WRITE_EEPROM = 4, WRITE_WAV_BUFFER = 7 }; /// 'R' read block, 'W'/'X' write block or 'C' command. char type; /// Command, @see OpenGD77Internface::WriteReqest::Command. uint8_t command; union { /** 24 bit sector number. */ uint8_t sector[3]; /** Payload data. */ struct __attribute__((packed)) { /** Target address. */ uint32_t address; /** Payload length. */ uint16_t length; /** Payload data. */ uint8_t data[32]; } payload; }; /** Constructs a write-to-eeprom message. */ bool initWriteEEPROM(Variant variant, uint32_t addr, const uint8_t *data, uint16_t size); /** Constructs a set-flash-sector message. */ bool initSetFlashSector(Variant variant, uint32_t addr); /** Constructs a write-to-flash message. */ bool initWriteFlash(Variant variant, uint32_t addr, const uint8_t *data, uint16_t size); /** Constructs a finish-write-to-flash message. */ bool initFinishWriteFlash(Variant variant); }; /** Represents a write-response message. */ struct __attribute__((packed)) WriteResponse { /// Same code as request. That is 'R' read block, 'W' write block, 'C' command or '-' on Error. char type; /// Same code as request if OK. uint8_t command; }; /** Represents a command message. */ struct __attribute__((packed)) CommandRequest { /** Possible commands. */ enum Command { SHOW_CPS_SCREEN = 0, CLEAR_SCREEN = 1, DISPLAY = 2, RENDER_CPS = 3, CLOSE_CPS_SCREEN = 5, COMMAND = 6 }; /** Possible options. */ enum Option { SAVE_SETTINGS_NOT_VFOS = 0, REBOOT = 1, SAVE_SETTINGS_AND_VFOS = 2, FLASH_GREEN_LED = 3, FLASH_RED_LED = 4, INIT_CODEC = 5, INIT_SOUND = 6, SET_DATETIME = 7, DELAY_10ms = 10 }; /** Message type, here 'C' for command. */ char type; /** The command. */ uint8_t command; /** Either a command option or the x position on screen. */ union { /** The x-position on the screen. */ uint8_t x; /** The command option. */ uint8_t option; }; // Either some text options or a timestamp. union { struct __attribute__((packed)) { /** The y-position on the screen. */ uint8_t y; /** The font size. */ uint8_t font; /** The text alignment. */ uint8_t alignment; /** Is text inverted? */ uint8_t inverted; }; uint32_t timestamp; }; /** Some text message. */ char message[16]; /** Construct "show CPS screen" command message. */ void initShowCPSScreen(); /** Construct a clear-screen command message. */ void initClearScreen(); /** Construct a "show text on screen" message. */ void initDisplay(uint8_t x, uint8_t y, const char *message, unsigned int iSize, uint8_t font, uint8_t alignment, uint8_t inverted); /** Construct a "render CPS" message. */ void initRenderCPS(); /** Construct a "close screen" command message. */ void initCloseScreen(); /** Construct a command message with the given option. */ void initCommand(Option option); /** Construct a SET_DATETIME message with the given date time. */ void initSetDateTime(const QDateTime &dt); }; protected: /** Read some data from EEPROM at the given address. */ bool readEEPROM(uint32_t addr, uint8_t *data, uint16_t len, const ErrorStack &err=ErrorStack()); /** Write some data to EEPROM at the given address. */ bool writeEEPROM(uint32_t addr, const uint8_t *data, uint16_t len, const ErrorStack &err=ErrorStack()); /** Read some data from Flash at the given address. */ bool readFlash(uint32_t addr, uint8_t *data, uint16_t len, const ErrorStack &err=ErrorStack()); /** Select the correct Flash sector for the given address. * This command must be sent before writing to the flash memory. */ bool setFlashSector(uint32_t addr, const ErrorStack &err=ErrorStack()); /** Write some data to the given Flash memory. */ bool writeFlash(uint32_t addr, const uint8_t *data, uint16_t len, const ErrorStack &err=ErrorStack()); /** Finalize writing to the Flash memory. If not send after writing to a sector, * the changes are lost. */ bool finishWriteFlash(const ErrorStack &err=ErrorStack()); /** Read radio info struct. */ bool readFirmwareInfo(FirmwareInfo &radioInfo, const ErrorStack &err=ErrorStack()); /** Send a "show CPS screen" message. */ bool sendShowCPSScreen(const ErrorStack &err=ErrorStack()); /** Send a "clear screen" message. */ bool sendClearScreen(const ErrorStack &err=ErrorStack()); /** Send a "display some text" message. */ bool sendDisplay(uint8_t x, uint8_t y, const char *message, uint8_t iSize, uint8_t alignment, uint8_t inverted, const ErrorStack &err=ErrorStack()); /** Send a "render CPS screen" message. */ bool sendRenderCPS(const ErrorStack &err=ErrorStack()); /** Send a "close screen" message. */ bool sendCloseScreen(const ErrorStack &err=ErrorStack()); /** Send a "set date time" message. */ bool sendSetDateTime(const QDateTime &dt, const ErrorStack &err=ErrorStack()); /** Sends some command message with the given options. */ bool sendCommand(CommandRequest::Option option, const ErrorStack &err=ErrorStack()); protected: /** The protocol variant determined by the device type obtained by the firmware info. */ Variant _protocolVariant; /** If @c true, the device allows for storing parts of the call-sign DB inside the voice prompt * memory. */ bool _extendedCallsignDB; /** The current Flash sector, set to -1 if none is currently selected. */ int32_t _sector; }; #endif // OPENGD77INTERFACE_HH ================================================ FILE: lib/opengd77_limits.cc ================================================ #include "opengd77_limits.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "scanlist.hh" #include "roamingzone.hh" #include "opengd77_satelliteconfig.hh" OpenGD77Limits::OpenGD77Limits(QObject *parent) : RadioLimits(false, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 15796; // Define limits for satellite config _hasSatelliteConfig = true; _satelliteConfigImplemented = true; _numSatellites = OpenGD77SatelliteConfig::Limit::satellites(); /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 16, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 16, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitIgnored(RadioLimitIssue::Silent) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } /// @todo check default radio ID. }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 1024, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, 0, 32, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "number", new RadioLimitStringRegEx("^[0-9A-Fa-f]+$") } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 0, 76, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // allow for any digital contact reference { "contacts", new RadioLimitRefList(1, 32, DMRContact::staticMetaObject) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 1024, // < up to 1024 channels new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRef(FMAPRSSystem::staticMetaObject)}, /// @todo handle OpenGD77 extension {"openGD77", new RadioLimitIgnored()}, {"tyt", new RadioLimitIgnored()} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject)}, {"aprs", new RadioLimitObjRef(FMAPRSSystem::staticMetaObject)}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, /// @todo handle OpenGD77 extension {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 0, 68, new RadioLimitSingleZone( 80, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Ignore scan lists. */ add("scanlists", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored()) ); /* Ignore positioning systems. */ add("positioning", new RadioLimitList( ConfigObject::staticMetaObject, 0, 8, new RadioLimitObjects { { FMAPRSSystem::staticMetaObject, new RadioLimitIgnored(RadioLimitIssue::Silent) }, { DMRAPRSSystem::staticMetaObject, new RadioLimitIgnored(RadioLimitIssue::Hint) } }) ); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored()) ); } ================================================ FILE: lib/opengd77_limits.hh ================================================ #ifndef OPENGD77LIMITS_HH #define OPENGD77LIMITS_HH #include "radiolimits.hh" /** Implements the limits for the OpenGD77 firmware. * @ingroup @ogd77 */ class OpenGD77Limits: public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit OpenGD77Limits(QObject *parent=nullptr); }; #endif // OPENGD77LIMITS_HH ================================================ FILE: lib/opengd77_satelliteconfig.cc ================================================ #include "opengd77_satelliteconfig.hh" #include "errorstack.hh" OpenGD77SatelliteConfig::OpenGD77SatelliteConfig(QObject *parent) : OpenGD77BaseSatelliteConfig(parent) { image(FLASH).addElement(Offset::satellites(), size()); // of 0x11a0 bytes } bool OpenGD77SatelliteConfig::isValid() const { return OpenGD77BaseCodeplug::AdditionalSettingsElement((uint8_t *)data(Offset::satellites(), FLASH)) .isValid(); } void OpenGD77SatelliteConfig::initialize() { OpenGD77BaseCodeplug::AdditionalSettingsElement(data(Offset::satellites(), FLASH)).clear(); } bool OpenGD77SatelliteConfig::encode(SatelliteDatabase *db, const ErrorStack &err) { OpenGD77BaseCodeplug::AdditionalSettingsElement settings(data(Offset::satellites(), FLASH)); OpenGD77BaseCodeplug::SatelliteBankElement bank = settings.satellites(); bank.clear(); if (! bank.encode(db, err)) { errMsg(err) << "Cannot encode satellite config for OpenUV380."; return false; } return true; } ================================================ FILE: lib/opengd77_satelliteconfig.hh ================================================ #ifndef OPENGD77_SATELLITECONFIG_HH #define OPENGD77_SATELLITECONFIG_HH #include "opengd77base_codeplug.hh" #include "opengd77base_satelliteconfig.hh" class OpenGD77SatelliteConfig : public OpenGD77BaseSatelliteConfig { Q_OBJECT public: /** Default constructor. */ explicit OpenGD77SatelliteConfig(QObject *parent = nullptr); bool isValid() const; void initialize(); /** Encodes the given satellite database. */ virtual bool encode(SatelliteDatabase *db, const ErrorStack &err=ErrorStack()); public: /** Some limits for the satellite config. */ struct Limit { /** The maximum number of satellites. */ static constexpr unsigned int satellites() { return OpenGD77BaseCodeplug::SatelliteBankElement::Limit::satellites(); } }; protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int satellites() { return 0x000000; } /// @endcond }; }; #endif // OPENGD77_SATELLITECONFIG_HH ================================================ FILE: lib/opengd77base.cc ================================================ #include "opengd77base.hh" #include "opengd77base_codeplug.hh" #include "opengd77_limits.hh" #include "logger.hh" #include "config.hh" #define BSIZE 32 #define SLOWDOWN 100 // us RadioLimits *OpenGD77Base::_limits = nullptr; OpenGD77Base::OpenGD77Base(OpenGD77Interface *device, QObject *parent) : Radio(parent), _dev(device), _flags(), _config(nullptr), _satelliteConfig(nullptr) { // pass... } OpenGD77Base::~OpenGD77Base() { if (_dev && _dev->isOpen()) { logDebug() << "Closing device."; _dev->reboot(); _dev->close(); } if (_dev) { logDebug() << "Deleting device."; _dev->deleteLater(); _dev = nullptr; } } const RadioLimits & OpenGD77Base::limits() const { if (nullptr == _limits) _limits = new OpenGD77Limits(); return *_limits; } bool OpenGD77Base::startDownload(const TransferFlags &flags, const ErrorStack &err) { if (StatusIdle != _task) { logError() << "Cannot download from radio, radio is not idle."; return false; } _task = StatusDownload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); // start thread for download start(); return true; } bool OpenGD77Base::startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err) { Q_UNUSED(flags) logDebug() << "Start upload to " << name() << "..."; if (StatusIdle != _task) { logError() << "Cannot upload to radio, radio is not idle."; return false; } if (_config) delete _config; if (! (_config = config)) { logError() << "Cannot upload to radio, no config given."; return false; } _config->setParent(this); _flags = flags; _task = StatusUpload; _errorStack = err; if (_flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); // start thread for upload start(); return true; } bool OpenGD77Base::startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err) { logDebug() << "Start upload to " << name() << "..."; if (StatusIdle != _task) { logError() << "Cannot upload to radio, radio is not idle."; return false; } // Assemble call-sign db from user DB logDebug() << "Encode call-signs into db."; callsignDB()->encode(db, selection); _task = StatusUploadCallsigns; _flags = selection; _errorStack = err; if (_flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); // start thread for upload start(); return true; } bool OpenGD77Base::startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err) { logDebug() << "Start upload to " << name() << "..."; if (StatusIdle != _task) { logError() << "Cannot upload to radio, radio is not idle."; return false; } _satelliteDatabase = db; _task = StatusUploadSatellites; _flags = flags; _errorStack = err; if (_flags.blocking()) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); // start thread for upload start(); return true; } void OpenGD77Base::run() { if (StatusDownload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit downloadError(this); return; } if (! download()) { _task = StatusError; _dev->read_finish(); _dev->reboot(); _dev->close(); emit downloadError(this); return; } _dev->read_finish(); _dev->close(); _task = StatusIdle; emit downloadFinished(this, &codeplug()); _config = nullptr; } else if (StatusUpload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if (! upload()) { _task = StatusError; _dev->write_finish(); _dev->reboot(); _dev->close(); emit uploadError(this); return; } _dev->write_finish(); _dev->saveSettingsNotVFOs(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadCallsigns == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if (! uploadCallsigns()) { _task = StatusError; _dev->write_finish(); _dev->reboot(); _dev->close(); emit uploadError(this); return; } _dev->saveSettingsAndVFOs(); _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadSatellites == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if (! uploadSatellites()) { _task = StatusError; _dev->write_finish(); _dev->reboot(); _dev->close(); emit uploadError(this); return; } _dev->write_finish(); _dev->saveSettingsNotVFOs(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } } bool OpenGD77Base::download() { emit downloadStarted(); if (codeplug().numImages() != 2) { errMsg(_errorStack) << "Cannot download codeplug: Codeplug does not contain two images."; return false; } // Check every segment in the codeplug if (! codeplug().isAligned(BSIZE)) { errMsg(_errorStack) << "Cannot download codeplug: Codeplug is not aligned with blocksize " << BSIZE << "."; return false; } size_t totb = codeplug().memSize(); if (! _dev->read_start(0, 0, _errorStack)) { errMsg(_errorStack) << "Cannot start codeplug download."; _dev->close(); return false; } // Then download codeplug size_t bcount = 0; for (int image=0; imageread(bank, (b0+b)*BSIZE, codeplug().data((b0+b)*BSIZE, image), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot read block " << (b0+b) << "."; return false; } QThread::usleep(SLOWDOWN); emit downloadProgress(float(bcount*100)/totb); } } // The is needed to prevent a bug in the firmware that causes the FW to crash during read. if (! _dev->read_finish(_errorStack)) return false; } return true; } bool OpenGD77Base::upload() { emit uploadStarted(); if (codeplug().numImages() != 2) { errMsg(_errorStack) << "Cannot download codeplug: Codeplug does not contain two images."; return false; } // Check every segment in the codeplug if (! codeplug().isAligned(BSIZE)) { errMsg(_errorStack) << "Cannot upload code-plug: Codeplug is not aligned with blocksize " << BSIZE << "."; return false; } size_t totb = codeplug().memSize(); if (_flags.updateDeviceClock() && (! _dev->setDateTime(QDateTime::currentDateTimeUtc(), _errorStack))) { errMsg(_errorStack) << "Cannot set device clock."; return false; } if (! _dev->read_start(0, 0, _errorStack)) { errMsg(_errorStack) << "Cannot start codeplug download."; return false; } // Then download codeplug size_t bcount = 0; for (int image=0; imageread(bank, (b0+b)*BSIZE, codeplug().data((b0+b)*BSIZE, image), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot read block " << Qt::hex << (b0+b) << "h."; return false; } QThread::usleep(SLOWDOWN); emit uploadProgress(float(bcount*50)/totb); } } // The is needed to prevent a bug in the firmware that causes the FW to crash during read. if (! _dev->read_finish(_errorStack)) return false; } // Encode config into codeplug codeplug().encode(_config); if (! _dev->write_start(0,0, _errorStack)) { errMsg(_errorStack) << "Cannot start codeplug upload."; return false; } // Then upload codeplug for (int image=0; imagewrite(bank, (b0+b)*BSIZE, codeplug().data((b0+b)*BSIZE, image), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot write block " << (b0+b) << "."; return false; } QThread::usleep(SLOWDOWN); emit uploadProgress(float(bcount*50)/totb); } } _dev->write_finish(); } return true; } bool OpenGD77Base::uploadCallsigns() { emit uploadStarted(); // Check every segment in the codeplug if (! callsignDB()->isAligned(BSIZE)) { errMsg(_errorStack) << "Cannot upload call-sign DB: Not aligned with block-size " << BSIZE << "!"; return false; } size_t totb = callsignDB()->memSize(); if (_flags.updateDeviceClock() && (! _dev->setDateTime(QDateTime::currentDateTimeUtc(), _errorStack))) { errMsg(_errorStack) << "Cannot set device clock."; return false; } if (! _dev->write_start(OpenGD77BaseCodeplug::FLASH, 0, _errorStack)) { errMsg(_errorStack) << "Cannot start callsign DB upload."; return false; } unsigned bcount = 0; // Then upload callsign DB for (int n=0; nimage(0).numElements(); n++) { unsigned addr = callsignDB()->image(0).element(n).address(); unsigned size = callsignDB()->image(0).element(n).data().size(); unsigned b0 = addr/BSIZE, nb = size/BSIZE; for (unsigned b=0; bwrite(OpenGD77BaseCodeplug::FLASH, (b0+b)*BSIZE, callsignDB()->data((b0+b)*BSIZE, 0), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot write block " << (b0+b) << "."; return false; } emit uploadProgress(float(bcount*100)/totb); } } _dev->write_finish(); return true; } bool OpenGD77Base::uploadSatellites() { if (! _satelliteDatabase) { errMsg(_errorStack) << "Cannot write satellite config. No config present."; return false; } emit uploadStarted(); // Check every segment in the codeplug if (! _satelliteConfig->isAligned(BSIZE)) { errMsg(_errorStack) << "Cannot upload satellite config: Not aligned with block-size " << BSIZE << "!"; return false; } size_t totb = _satelliteConfig->memSize(); if (! _dev->read_start(OpenGD77BaseSatelliteConfig::FLASH, 0, _errorStack)) { errMsg(_errorStack) << "Cannot start satellite config download."; return false; } // Then download satellite config size_t bcount = 0; for (int n=0; n<_satelliteConfig->image(OpenGD77BaseSatelliteConfig::FLASH).numElements(); n++) { unsigned addr = _satelliteConfig->image(OpenGD77BaseSatelliteConfig::FLASH).element(n).address(); unsigned size = _satelliteConfig->image(OpenGD77BaseSatelliteConfig::FLASH).element(n).data().size(); unsigned b0 = addr/BSIZE, nb = size/BSIZE; for (unsigned b=0; bread(OpenGD77BaseSatelliteConfig::FLASH, (b0+b)*BSIZE, _satelliteConfig->data((b0+b)*BSIZE, OpenGD77BaseSatelliteConfig::FLASH), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot read block " << (b0+b) << "."; return false; } QThread::usleep(SLOWDOWN); emit uploadProgress(float(bcount*50)/totb); } } logDebug() << "Read " << Qt::hex << bcount << "b of additional settings from device."; _dev->read_finish(); if (_flags.updateDeviceClock() && (! _dev->setDateTime(QDateTime::currentDateTimeUtc(), _errorStack))) { errMsg(_errorStack) << "Cannot set device clock."; return false; } // Encode config into codeplug if (! _satelliteConfig->encode(_satelliteDatabase, _errorStack)) { errMsg(_errorStack) << "Cannot encode satellite config."; return false; } if (! _dev->write_start(OpenGD77BaseSatelliteConfig::FLASH, 0, _errorStack)) { errMsg(_errorStack) << "Cannot start satellite config upload."; return false; } // Then upload satellite config for (int n=0; n<_satelliteConfig->image(OpenGD77BaseSatelliteConfig::FLASH).numElements(); n++) { unsigned addr = _satelliteConfig->image(OpenGD77BaseSatelliteConfig::FLASH).element(n).address(); unsigned size = _satelliteConfig->image(OpenGD77BaseSatelliteConfig::FLASH).element(n).data().size(); unsigned b0 = addr/BSIZE, nb = size/BSIZE; for (unsigned b=0; bwrite(OpenGD77BaseSatelliteConfig::FLASH, (b0+b)*BSIZE, _satelliteConfig->data((b0+b)*BSIZE, OpenGD77BaseSatelliteConfig::FLASH), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot write block " << (b0+b) << "."; return false; } QThread::usleep(SLOWDOWN); emit uploadProgress(float(bcount*50)/totb); } } _dev->write_finish(); return true; } ================================================ FILE: lib/opengd77base.hh ================================================ /** @defgroup ogd77 Open GD-77 Firmware * Implements a radio running the Open GD77 firmware. * @ingroup dsc */ #ifndef OPENGD77BASE_HH #define OPENGD77BASE_HH #include #include "radio.hh" #include "opengd77_interface.hh" #include "opengd77_satelliteconfig.hh" #include "satellitedatabase.hh" /** Implements an common USB interface to Open GD-77(S) type devices. * * @ingroup ogd77 */ class OpenGD77Base : public Radio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit OpenGD77Base(OpenGD77Interface *device=nullptr, QObject *parent=nullptr); virtual ~OpenGD77Base(); const RadioLimits &limits() const; /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); public slots: /** Starts the download of the codeplug and derives the generic configuration from it. */ bool startDownload(const TransferFlags &flags, const ErrorStack &err=ErrorStack()); /** Derives the device-specific codeplug from the generic configuration and uploads that * codeplug to the radio. */ bool startUpload(Config *config, const Codeplug::Flags &flags = Codeplug::Flags(), const ErrorStack &err=ErrorStack()); /** Encodes the given user-database and uploads it to the device. */ bool startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection=CallsignDB::Flags(), const ErrorStack &err=ErrorStack()); bool startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err); protected: /** Thread main routine, performs all blocking IO operations for codeplug up- and download. */ void run(); /** Implements the actual download process. */ bool download(); /** Implements the actual codeplug upload process. */ bool upload(); /** Implements the actual callsign DB upload process. */ bool uploadCallsigns(); /** Implements the actual satellite config upload process. */ bool uploadSatellites(); protected: /** The interface to the radio. */ OpenGD77Interface *_dev; /** Transfer flags. */ TransferFlags _flags; /** The generic configuration. */ Config *_config; /** The satellite configuration. */ QPointer _satelliteDatabase; OpenGD77BaseSatelliteConfig *_satelliteConfig; private: /** Holds the singleton instance. */ static RadioLimits *_limits; }; #endif // OPENGD77BASE_HH ================================================ FILE: lib/opengd77base_callsigndb.cc ================================================ #include "opengd77base_callsigndb.hh" #include "utils.hh" #include "userdatabase.hh" #include /* ******************************************************************************************** * * Implementation of OpenGD77BaseCallsignDB::DatabaseEntryElement * ******************************************************************************************** */ QVector OpenGD77BaseCallsignDB::DatabaseEntryElement::_lut = { ' ', '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '.' }; OpenGD77BaseCallsignDB::DatabaseEntryElement::DatabaseEntryElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } OpenGD77BaseCallsignDB::DatabaseEntryElement::DatabaseEntryElement(uint8_t *ptr) : Codeplug::Element(ptr, size()) { // pass... } void OpenGD77BaseCallsignDB::DatabaseEntryElement::clear() { memset(_data, 0x00, size()); } void OpenGD77BaseCallsignDB::DatabaseEntryElement::setId(unsigned int id) { setUInt24_le(Offset::dmrID(), id); } void OpenGD77BaseCallsignDB::DatabaseEntryElement::setText(const QString &text) { QByteArray data = pack(text); auto n = std::min(3*Limit::textLength()/4, (unsigned int)data.size()); memcpy(_data+Offset::text(), data.constData(), n); } bool OpenGD77BaseCallsignDB::DatabaseEntryElement::fromEntry(const UserDatabase::User &user) { clear(); setId(user.id); QString txt; QTextStream stream(&txt); stream << user.call << " " << user.name; if (! user.city.isEmpty()) stream << " " << user.city; if (! user.state.isEmpty()) stream << " " << user.state; if (! user.country.isEmpty()) stream << " " << user.country; setText(txt); return true; } QByteArray OpenGD77BaseCallsignDB::DatabaseEntryElement::pack(const QString &text) { // Encode chars QVector codes; foreach (QChar c, text) { if (! _lut.contains(c)) continue; codes.append(_lut.indexOf(c)); } // Fix size to multiples of 4 if (codes.size() % 4) codes.resize(codes.size() + (4-(codes.size() % 4))); // Pack QByteArray data(3*codes.size()/4, '\x00'); for (int i=0, o=0; i>16) & 0xff; data[o+1] = (encoded>> 8) & 0xff; data[o+2] = (encoded>> 0) & 0xff; } return data; } /* ******************************************************************************************** * * Implementation of OpenGD77BaseCallsignDB::DatabaseHeaderElement * ******************************************************************************************** */ OpenGD77BaseCallsignDB::DatabaseHeaderElement::DatabaseHeaderElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } OpenGD77BaseCallsignDB::DatabaseHeaderElement::DatabaseHeaderElement(uint8_t *ptr) : Codeplug::Element(ptr, size()) { // pass... } void OpenGD77BaseCallsignDB::DatabaseHeaderElement::clear() { memset(_data, 0, size()); writeASCII(Offset::magic(), "Id", 2); setUInt8(Offset::format(), (unsigned int)Format::Compressed); setEntrySize(16); // Default text length = 16 writeASCII(Offset::version(),"001", 3); setUInt32_le(Offset::entryCount(), 0); } void OpenGD77BaseCallsignDB::DatabaseHeaderElement::setEntrySize(unsigned int size) { setUInt8(Offset::entrySize(), 0x4a+size); } void OpenGD77BaseCallsignDB::DatabaseHeaderElement::setEntryCount(unsigned int count) { setUInt32_le(Offset::entryCount(), count); } /* ******************************************************************************************** * * Implementation of OpenGD77BaseCallsignDB * ******************************************************************************************** */ OpenGD77BaseCallsignDB::OpenGD77BaseCallsignDB(QObject *parent) : CallsignDB(parent) { // pass... } OpenGD77BaseCallsignDB::~OpenGD77BaseCallsignDB() { // pass... } ================================================ FILE: lib/opengd77base_callsigndb.hh ================================================ #ifndef OPENGD77BASECALLSIGNDB_HH #define OPENGD77BASECALLSIGNDB_HH #include "codeplug.hh" #include "callsigndb.hh" #include "userdatabase.hh" /** Represents and encodes the binary format for all call-sign databases within OpenGD77 radios. * @ingroup ogd77 */ class OpenGD77BaseCallsignDB : public CallsignDB { Q_OBJECT public: /** Represents a user-db entry within the binary codeplug. */ struct __attribute__((packed)) userdb_entry_t { uint32_t number; ///< DMR ID stored in BCD little-endian. char name[15]; ///< Call or name, up to 15 ASCII chars, 0x00 padded. /** Constructor. */ userdb_entry_t(); /** Resets the entry. */ void clear(); /** Returns the DMR ID number. */ uint32_t getNumber() const; /** Sets the DMR ID number. */ void setNumber(uint32_t number); /** Returns the name of the entry. */ QString getName() const; /** Sets the name of the entry, 15b max. * The name gets truncated if longer than 15b. */ void setName(const QString &name); /** Encodes the given user. */ void fromEntry(const UserDatabase::User &user); }; /** Represents a single encoded database entry. * Consists of a DMR ID and compressed text. The text size is fixed to 16 chars. Each * char is encoded using a 6bit table. Thus 16 chars are stored in 12 bytes. */ class DatabaseEntryElement: public Codeplug::Element { protected: /** Hidden constructor. */ DatabaseEntryElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DatabaseEntryElement(uint8_t *ptr); /** The size of the entry. */ static constexpr unsigned int size() { return 0x000f; } void clear() override; /** Encodes the DMR ID. */ virtual void setId(unsigned int id); /** Encodes the text. */ virtual void setText(const QString &text); /** Encodes the given user. */ virtual bool fromEntry(const UserDatabase::User &user); protected: /** Encodes and packs the given string. That is, chars are encoded into 6bit codes using _lut * and 4 encoded chars are then packed into 3bytes. */ QByteArray pack(const QString &text); public: /** Some limits. */ struct Limit: public Element::Limit { // The length of the text. static constexpr unsigned int textLength() { return 16; } }; protected: /** Internal offsets within entry. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int dmrID() { return 0x0000; } static constexpr unsigned int text() { return 0x0003; } /// @endcond }; static QVector _lut; }; /** Represents the header of the callsign database. */ class DatabaseHeaderElement: public Codeplug::Element { public: /// Possible formats. enum class Format { Uncompressed = 45, Compressed = 78 }; protected: /** Hidden constructor. */ DatabaseHeaderElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DatabaseHeaderElement(uint8_t *ptr); /** The size of the header. */ static constexpr unsigned int size() { return 0x000c; } void clear(); void setEntrySize(unsigned int size); void setEntryCount(unsigned int count); public: /** Some limits for the header. */ struct Limit: public Element::Limit { /// None.. }; protected: /** Internal offsets within the header. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int magic() { return 0x0000; } static constexpr unsigned int format() { return 0x0002; } static constexpr unsigned int entrySize() { return 0x0003; } static constexpr unsigned int version() { return 0x0004; } static constexpr unsigned int entryCount() { return 0x0008; } /// @endcond }; }; public: /** Constructor. */ explicit OpenGD77BaseCallsignDB(QObject *parent=nullptr); /** Destructor. */ virtual ~OpenGD77BaseCallsignDB(); /** Encodes as many entries as possible of the given user-database. */ virtual bool encode(UserDatabase *calldb, const Flags &selection=Flags(), const ErrorStack &err=ErrorStack()) = 0; public: /** Some limits for this callsign DB. */ struct Limit { /** Maximum block size. */ static constexpr unsigned int blockSize() { return 32; } }; }; #endif // OPENGD77BASECALLSIGNDB_HH ================================================ FILE: lib/opengd77base_codeplug.cc ================================================ #include "opengd77base_codeplug.hh" #include "opengd77_extension.hh" #include "radioid.hh" #include "config.hh" #include "logger.hh" #include "intermediaterepresentation.hh" #include "satellitedatabase.hh" #include /* ********************************************************************************************* * * Implementation of some helper functions * ********************************************************************************************* */ uint32_t OpenGD77BaseCodeplug::encodeAngle(double angle) { uint32_t sign = (angle < 0) ? 1 : 0; uint32_t decimals = std::abs(int(angle * 10000)); uint32_t deg = decimals/10000; decimals = decimals % 10000; return (sign << 23) | (deg << 15) | decimals; } double OpenGD77BaseCodeplug::decodeAngle(uint32_t code) { return (((code >> 23) & 1) ? -1 : 1) * ( ((code >> 15) & 0xff) + double(code & 0x7fff)/10000 ); } uint16_t OpenGD77BaseCodeplug::encodeSelectiveCall(const SelectiveCall &call) { if (call.isInvalid()) return 0xffff; uint16_t dcs = 0, inverted = 0, toneCode = 0; if (call.isDCS()) { dcs = 1; inverted = call.isInverted() ? 1 : 0; toneCode = call.octalCode(); } else { dcs = inverted = 0; toneCode = call.mHz()/100; } uint16_t bcd = (((toneCode/1000) % 10 ) << 12) | (((toneCode/100) % 10 ) << 8) | (((toneCode/10) % 10 ) << 4) | (((toneCode/1) % 10 ) << 0); return (dcs<<15) | (inverted << 14) | (bcd & 0x3fff); } SelectiveCall OpenGD77BaseCodeplug::decodeSelectiveCall(uint16_t code) { if (0xffff == code) return SelectiveCall(); bool dcs = ((code >> 15) & 1), inverted = ((code >> 14) & 1); uint16_t bcd = (code & 0x3fff); code = 1000 * ((bcd >> 12) & 0xf) + 100 * ((bcd >> 8) & 0xf) + 10 * ((bcd >> 4) & 0xf) + 1 * ((bcd >> 0) & 0xf); if (! dcs) return SelectiveCall(double(code)/10); return SelectiveCall(code, inverted); } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::ChannelElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } OpenGD77BaseCodeplug::ChannelElement::~ChannelElement() { // pass... } void OpenGD77BaseCodeplug::ChannelElement::clear() { setName(""); setRXFrequency(Frequency()); setTXFrequency(Frequency()); setMode(MODE_ANALOG); setPower(Channel::Power::High); enableFixedPosition(false); setRXTone(SelectiveCall()); setTXTone(SelectiveCall()); enableSimplex(false); enablePowerSave(false); enableBeep(false); clearDMRId(); clearGroupListIndex(); setColorCode(0); clearAPRSIndex(); clearTXContact(); setAliasTimeSlot1(OpenGD77ChannelExtension::TalkerAlias::None); setAliasTimeSlot2(OpenGD77ChannelExtension::TalkerAlias::None); setTimeSlot(DMRChannel::TimeSlot::TS1); setBandwidth(FMChannel::Bandwidth::Narrow); enableRXOnly(false); enableSkipScan(false); enableSkipZoneScan(false); enableVOX(false); setSquelch(SquelchMode::Global, 0); } QString OpenGD77BaseCodeplug::ChannelElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void OpenGD77BaseCodeplug::ChannelElement::setName(const QString &n) { writeASCII(Offset::name(), n, Limit::nameLength(), 0xff); } Frequency OpenGD77BaseCodeplug::ChannelElement::rxFrequency() const { return Frequency::fromHz(((unsigned long long)getBCD8_le(Offset::rxFrequency()))*10); } void OpenGD77BaseCodeplug::ChannelElement::setRXFrequency(const Frequency &freq) { setBCD8_le(Offset::rxFrequency(), freq.inHz()/10); } Frequency OpenGD77BaseCodeplug::ChannelElement::txFrequency() const { return Frequency::fromHz(((unsigned long long)getBCD8_le(Offset::txFrequency()))*10); } void OpenGD77BaseCodeplug::ChannelElement::setTXFrequency(const Frequency &freq) { setBCD8_le(Offset::txFrequency(), freq.inHz()/10); } OpenGD77BaseCodeplug::ChannelElement::Mode OpenGD77BaseCodeplug::ChannelElement::mode() const { return (Mode)getUInt8(Offset::mode()); } void OpenGD77BaseCodeplug::ChannelElement::setMode(Mode mode) { setUInt8(Offset::mode(), (unsigned)mode); } bool OpenGD77BaseCodeplug::ChannelElement::globalPower() const { return 0 == getUInt8(Offset::power()); } Channel::Power OpenGD77BaseCodeplug::ChannelElement::power() const { switch (getUInt8(Offset::power())) { case 0: case 1: return Channel::Power::Min; case 2: case 3: return Channel::Power::Low; case 4: case 5: case 6: return Channel::Power::Mid; case 7: case 8: case 9: return Channel::Power::High; case 10: return Channel::Power::Max; default: break; } return Channel::Power::Min; } void OpenGD77BaseCodeplug::ChannelElement::setPower(Channel::Power pwr) { switch (pwr) { case Channel::Power::Min: setUInt8(Offset::power(), 1); break; case Channel::Power::Low: setUInt8(Offset::power(), 3); break; case Channel::Power::Mid: setUInt8(Offset::power(), 6); break; case Channel::Power::High: setUInt8(Offset::power(), 9); break; case Channel::Power::Max: setUInt8(Offset::power(), 10); break; } } void OpenGD77BaseCodeplug::ChannelElement::clearPower() { setUInt8(Offset::power(), 0); } Interval OpenGD77BaseCodeplug::ChannelElement::transmitTimeout() const { if (0 == getUInt8(Offset::txTimeout())) return Interval::infinity(); return Interval::fromSeconds((unsigned)getUInt8(Offset::txTimeout())*15); } void OpenGD77BaseCodeplug::ChannelElement::setTransmitTimeout(const Interval &interval) { if (! interval.isFinite()) setUInt8(Offset::txTimeout(), 0); else { unsigned s = interval.seconds()/15; s = std::max(1u, std::min(33u, s)); setUInt8(Offset::txTimeout(), s); } } bool OpenGD77BaseCodeplug::ChannelElement::fixedPositionEnabled() const { return getBit(Offset::useFixedLocation()); } QGeoCoordinate OpenGD77BaseCodeplug::ChannelElement::fixedPosition() const { uint32_t latCode = (((uint32_t)getUInt8(Offset::latitude2())) << 16) + (((uint32_t)getUInt8(Offset::latitude1())) << 8) + ((uint32_t)getUInt8(Offset::latitude0())); uint32_t lonCode = (((uint32_t)getUInt8(Offset::longitude2())) << 16) + (((uint32_t)getUInt8(Offset::longitude1())) << 8) + ((uint32_t)getUInt8(Offset::longitude0())); return QGeoCoordinate(decodeAngle(latCode), decodeAngle(lonCode)); } void OpenGD77BaseCodeplug::ChannelElement::setFixedPosition(const QGeoCoordinate &coordinate) { if (! coordinate.isValid()) { enableFixedPosition(false); return; } uint32_t latCode = encodeAngle(coordinate.latitude()); uint32_t lonCode = encodeAngle(coordinate.longitude()); setUInt8(Offset::latitude0(), ((latCode >> 0) & 0xff)); setUInt8(Offset::latitude1(), ((latCode >> 8) & 0xff)); setUInt8(Offset::latitude2(), ((latCode >> 16) & 0xff)); setUInt8(Offset::longitude0(), ((lonCode >> 0) & 0xff)); setUInt8(Offset::longitude1(), ((lonCode >> 8) & 0xff)); setUInt8(Offset::longitude2(), ((lonCode >> 16) & 0xff)); } void OpenGD77BaseCodeplug::ChannelElement::enableFixedPosition(bool enable) { setBit(Offset::useFixedLocation(), enable); } SelectiveCall OpenGD77BaseCodeplug::ChannelElement::rxTone() const { return decodeSelectiveCall(getUInt16_le(Offset::rxTone())); } void OpenGD77BaseCodeplug::ChannelElement::setRXTone(const SelectiveCall &code) { setUInt16_le(Offset::rxTone(), encodeSelectiveCall(code)); } SelectiveCall OpenGD77BaseCodeplug::ChannelElement::txTone() const { return decodeSelectiveCall(getUInt16_le(Offset::txTone())); } void OpenGD77BaseCodeplug::ChannelElement::setTXTone(const SelectiveCall &code) { setUInt16_le(Offset::txTone(), encodeSelectiveCall(code)); } bool OpenGD77BaseCodeplug::ChannelElement::isSimplex() const { return getBit(Offset::simplex()); } void OpenGD77BaseCodeplug::ChannelElement::enableSimplex(bool enable) { setBit(Offset::simplex(), enable); } bool OpenGD77BaseCodeplug::ChannelElement::powerSave() const { return ! getBit(Offset::disablePowerSave()); } void OpenGD77BaseCodeplug::ChannelElement::enablePowerSave(bool enable) { setBit(Offset::disablePowerSave(), !enable); } bool OpenGD77BaseCodeplug::ChannelElement::beep() const { return ! getBit(Offset::disableBeep()); } void OpenGD77BaseCodeplug::ChannelElement::enableBeep(bool enable) { setBit(Offset::disableBeep(), !enable); } bool OpenGD77BaseCodeplug::ChannelElement::hasDMRId() const { return getBit(Offset::overrideDMRID()); } unsigned int OpenGD77BaseCodeplug::ChannelElement::dmrId() const { return getUInt24_be(Offset::dmrId()); } void OpenGD77BaseCodeplug::ChannelElement::setDMRId(unsigned int dmrId) { setBit(Offset::overrideDMRID()); setUInt24_be(Offset::dmrId(), dmrId); } void OpenGD77BaseCodeplug::ChannelElement::clearDMRId() { setUInt24_be(Offset::dmrId(), 0x001600); clearBit(Offset::overrideDMRID()); } bool OpenGD77BaseCodeplug::ChannelElement::hasGroupList() const { return 0 != getUInt8(Offset::groupList()); } unsigned OpenGD77BaseCodeplug::ChannelElement::groupListIndex() const { return getUInt8(Offset::groupList())-1; } void OpenGD77BaseCodeplug::ChannelElement::setGroupListIndex(unsigned index) { setUInt8(Offset::groupList(), index+1); } void OpenGD77BaseCodeplug::ChannelElement::clearGroupListIndex() { setUInt8(Offset::groupList(), 0); } unsigned OpenGD77BaseCodeplug::ChannelElement::colorCode() const { return getUInt8(Offset::colorCode()); } void OpenGD77BaseCodeplug::ChannelElement::setColorCode(unsigned cc) { setUInt8(Offset::colorCode(), cc); } bool OpenGD77BaseCodeplug::ChannelElement::hasAPRSIndex() const { return 0 != getUInt8(Offset::aprsIndex()); } unsigned int OpenGD77BaseCodeplug::ChannelElement::aprsIndex() const { return getUInt8(Offset::aprsIndex())-1; } void OpenGD77BaseCodeplug::ChannelElement::setAPRSIndex(unsigned int index) { setUInt8(Offset::aprsIndex(), index+1); } void OpenGD77BaseCodeplug::ChannelElement::clearAPRSIndex() { setUInt8(Offset::aprsIndex(), 0); } bool OpenGD77BaseCodeplug::ChannelElement::hasTXContact() const { return 0 != getUInt16_le(Offset::txContact()); } unsigned int OpenGD77BaseCodeplug::ChannelElement::txContactIndex() const { return getUInt16_le(Offset::txContact()) - 1; } void OpenGD77BaseCodeplug::ChannelElement::setTXContactIndex(unsigned int index) { setUInt16_le(Offset::txContact(), index+1); } void OpenGD77BaseCodeplug::ChannelElement::clearTXContact() { setUInt16_le(Offset::txContact(), 0); } OpenGD77ChannelExtension::TalkerAlias OpenGD77BaseCodeplug::ChannelElement::aliasTimeSlot1() const { switch ((Alias) getUInt2(Offset::aliasTimeSlot1())) { case Alias::None: return OpenGD77ChannelExtension::TalkerAlias::None; case Alias::APRS: return OpenGD77ChannelExtension::TalkerAlias::APRS; case Alias::Text: return OpenGD77ChannelExtension::TalkerAlias::Text; case Alias::Both: return OpenGD77ChannelExtension::TalkerAlias::Both; } return OpenGD77ChannelExtension::TalkerAlias::None; } void OpenGD77BaseCodeplug::ChannelElement::setAliasTimeSlot1(OpenGD77ChannelExtension::TalkerAlias alias) { switch (alias) { case OpenGD77ChannelExtension::TalkerAlias::None: setUInt2(Offset::aliasTimeSlot1(), (unsigned int)Alias::None); break; case OpenGD77ChannelExtension::TalkerAlias::APRS: setUInt2(Offset::aliasTimeSlot1(), (unsigned int)Alias::APRS); break; case OpenGD77ChannelExtension::TalkerAlias::Text: setUInt2(Offset::aliasTimeSlot1(), (unsigned int)Alias::Text); break; case OpenGD77ChannelExtension::TalkerAlias::Both: setUInt2(Offset::aliasTimeSlot1(), (unsigned int)Alias::Both); break; } } OpenGD77ChannelExtension::TalkerAlias OpenGD77BaseCodeplug::ChannelElement::aliasTimeSlot2() const { switch ((Alias) getUInt2(Offset::aliasTimeSlot2())) { case Alias::None: return OpenGD77ChannelExtension::TalkerAlias::None; case Alias::APRS: return OpenGD77ChannelExtension::TalkerAlias::APRS; case Alias::Text: return OpenGD77ChannelExtension::TalkerAlias::Text; case Alias::Both: return OpenGD77ChannelExtension::TalkerAlias::Both; } return OpenGD77ChannelExtension::TalkerAlias::None; } void OpenGD77BaseCodeplug::ChannelElement::setAliasTimeSlot2(OpenGD77ChannelExtension::TalkerAlias alias) { switch (alias) { case OpenGD77ChannelExtension::TalkerAlias::None: setUInt2(Offset::aliasTimeSlot2(), (unsigned int)Alias::None); break; case OpenGD77ChannelExtension::TalkerAlias::APRS: setUInt2(Offset::aliasTimeSlot2(), (unsigned int)Alias::APRS); break; case OpenGD77ChannelExtension::TalkerAlias::Text: setUInt2(Offset::aliasTimeSlot2(), (unsigned int)Alias::Text); break; case OpenGD77ChannelExtension::TalkerAlias::Both: setUInt2(Offset::aliasTimeSlot2(), (unsigned int)Alias::Both); break; } } DMRChannel::TimeSlot OpenGD77BaseCodeplug::ChannelElement::timeSlot() const { return (getBit(Offset::timeSlot()) ? DMRChannel::TimeSlot::TS2 : DMRChannel::TimeSlot::TS1); } void OpenGD77BaseCodeplug::ChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { setBit(Offset::timeSlot(), DMRChannel::TimeSlot::TS2 == ts); } FMChannel::Bandwidth OpenGD77BaseCodeplug::ChannelElement::bandwidth() const { return (getBit(Offset::bandwidth()) ? FMChannel::Bandwidth::Wide : FMChannel::Bandwidth::Narrow); } void OpenGD77BaseCodeplug::ChannelElement::setBandwidth(FMChannel::Bandwidth bw) { setBit(Offset::bandwidth(), FMChannel::Bandwidth::Wide == bw); } bool OpenGD77BaseCodeplug::ChannelElement::rxOnly() const { return getBit(Offset::rxOnly()); } void OpenGD77BaseCodeplug::ChannelElement::enableRXOnly(bool enable) { setBit(Offset::rxOnly(), enable); } bool OpenGD77BaseCodeplug::ChannelElement::skipScan() const { return getBit(Offset::skipScan()); } void OpenGD77BaseCodeplug::ChannelElement::enableSkipScan(bool enable) { setBit(Offset::skipScan(), enable); } bool OpenGD77BaseCodeplug::ChannelElement::skipZoneScan() const { return getBit(Offset::skipZoneScan()); } void OpenGD77BaseCodeplug::ChannelElement::enableSkipZoneScan(bool enable) { setBit(Offset::skipZoneScan(), enable); } bool OpenGD77BaseCodeplug::ChannelElement::vox() const { return getBit(Offset::vox()); } void OpenGD77BaseCodeplug::ChannelElement::enableVOX(bool enable) { setBit(Offset::vox(), enable); } OpenGD77BaseCodeplug::ChannelElement::SquelchMode OpenGD77BaseCodeplug::ChannelElement::squelchMode() const { switch (getUInt8(Offset::squelch())) { case 0: return SquelchMode::Global; case 1: return SquelchMode::Open; case 15: return SquelchMode::Closed; default: return SquelchMode::Normal; } } unsigned int OpenGD77BaseCodeplug::ChannelElement::squelchLevel() const { int level = (10*(getUInt8(Offset::squelch())-1))/14; return std::min(level, 10); } void OpenGD77BaseCodeplug::ChannelElement::setSquelch(SquelchMode mode, unsigned int level) { level = std::min(level, 10U); switch (mode) { case SquelchMode::Global: level = 0; break; case SquelchMode::Open: level = 1; break; case SquelchMode::Closed: level = 15; break; case SquelchMode::Normal: level = 1 + (14*level)/10; break; } setUInt8(Offset::squelch(), level); } Channel * OpenGD77BaseCodeplug::ChannelElement::decode(Codeplug::Context &ctx, const ErrorStack& err) const { Q_UNUSED(err); Q_UNUSED(ctx); Channel *ch = nullptr; if (MODE_ANALOG == mode()) { FMChannel *ach = new FMChannel(); ch = ach; ach->setBandwidth(bandwidth()); ach->setRXTone(rxTone()); ach->setTXTone(txTone()); ach->setSquelchDefault(); // There is no per-channel squelch setting } else { DMRChannel *dch = new DMRChannel(); ch = dch; dch->setTimeSlot(timeSlot()); dch->setColorCode(colorCode()); } // Apply common settings ch->setName(name()); ch->setRXFrequency(rxFrequency()); if (isSimplex()) ch->setTXFrequency(rxFrequency()); else ch->setTXFrequency(txFrequency()); if (globalPower()) ch->setDefaultPower(); else ch->setPower(power()); ch->setRXOnly(rxOnly()); if (vox()) ch->setVOXDefault(); else ch->disableVOX(); if (transmitTimeout().isInfinite()) ch->disableTimeout(); else ch->setTimeout(transmitTimeout()); ch->setOpenGD77ChannelExtension(new OpenGD77ChannelExtension()); ch->openGD77ChannelExtension()->enableScanZoneSkip(skipZoneScan()); ch->openGD77ChannelExtension()->enableScanAllSkip(skipScan()); ch->openGD77ChannelExtension()->enableBeep(beep()); ch->openGD77ChannelExtension()->enablePowerSave(powerSave()); ch->openGD77ChannelExtension()->enableLocation(fixedPositionEnabled()); ch->openGD77ChannelExtension()->setLocation(fixedPosition()); ch->openGD77ChannelExtension()->setTalkerAliasTS1(aliasTimeSlot1()); ch->openGD77ChannelExtension()->setTalkerAliasTS2(aliasTimeSlot2()); // done. return ch; } bool OpenGD77BaseCodeplug::ChannelElement::link(Channel *c, Context &ctx, const ErrorStack& err) const { Q_UNUSED(err) // Link common if (c->is()) { // Link DMR channel DMRChannel *dc = c->as(); if (hasGroupList() && ctx.has(groupListIndex())) dc->setGroupList(ctx.get(groupListIndex())); if (hasTXContact() && ctx.has(txContactIndex())) dc->setContact(ctx.get(txContactIndex())); // Testing dmrId() == 0 fixes a bug in the OpenGD77 firmware. May change in future. if (hasDMRId() && (0 != dmrId())) { logDebug() << "Channel '" << c->name() << "' overrides default DMR id with " << dmrId() << "."; auto id = ctx.config()->radioIDs()->find(dmrId()); if (nullptr == id) { logDebug() << "DMR Id " << dmrId() << " is not defined yet, create one as 'Unknown ID'."; id = new DMRRadioID(QString("Unknown ID"), dmrId()); ctx.config()->radioIDs()->add(id); } dc->setRadioId(id); } } else if (c->is()) { // Link FM channel auto fm = c->as(); if (hasAPRSIndex()) { if (! ctx.has(aprsIndex())) { logWarn() << "Cannot link APRS system index " << aprsIndex() << ": Unknown index. (ignored)"; } else { fm->setAPRS(ctx.get(aprsIndex())); } } } return true; } bool OpenGD77BaseCodeplug::ChannelElement::encode(const Channel *c, Context &ctx, const ErrorStack& err) { clear(); setName(c->name()); setRXFrequency(c->rxFrequency()); setTXFrequency(c->txFrequency()); enableSimplex(false); clearPower(); if (! c->defaultPower()) setPower(c->power()); enableRXOnly(c->rxOnly()); setTransmitTimeout(c->timeout()); // Enable vox bool defaultVOXEnabled = (c->defaultVOX() && ctx.config()->settings()->audio()->voxEnabled()); bool channelVOXEnabled = (! (c->voxDisabled()||c->defaultVOX())); enableVOX(defaultVOXEnabled || channelVOXEnabled); if (c->is()) { const FMChannel *ac = c->as(); setMode(MODE_ANALOG); setBandwidth(ac->bandwidth()); setRXTone(ac->rxTone()); setTXTone(ac->txTone()); // no per channel squelch setting if (ac->aprs() && (0<=ctx.index(ac->aprs()))) setAPRSIndex(ctx.index(ac->aprs())); } else if (c->is()) { const DMRChannel *dc = c->as(); setMode(MODE_DIGITAL); setTimeSlot(dc->timeSlot()); setColorCode(dc->colorCode()); // OpenGD77 does not allow for both TX contact and group list, select one, prefer group list if (dc->groupList()) setGroupListIndex(ctx.index(dc->groupList())); else if (dc->contact()) setTXContactIndex(ctx.index(dc->contact())); if (dc->radioId() != ctx.config()->settings()->defaultId()) setDMRId(dc->radioId()->number()); } else { errMsg(err) << "Cannot encode channel of type '" << c->metaObject()->className() << "': Not supported by the radio."; return false; } if (nullptr == c->openGD77ChannelExtension()) return true; // apply extension enableSkipZoneScan(c->openGD77ChannelExtension()->scanZoneSkip()); enableSkipScan(c->openGD77ChannelExtension()->scanAllSkip()); enableBeep(c->openGD77ChannelExtension()->beep()); enablePowerSave(c->openGD77ChannelExtension()->powerSave()); setFixedPosition(c->openGD77ChannelExtension()->location()); enableFixedPosition(c->openGD77ChannelExtension()->locationEnabled()); setAliasTimeSlot1(c->openGD77ChannelExtension()->talkerAliasTS1()); setAliasTimeSlot2(c->openGD77ChannelExtension()->talkerAliasTS2()); return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::ChannelBankElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } OpenGD77BaseCodeplug::ChannelBankElement::~ChannelBankElement() { // pass... } void OpenGD77BaseCodeplug::ChannelBankElement::clear() { memset(_data, 0, size()); } bool OpenGD77BaseCodeplug::ChannelBankElement::isEnabled(unsigned idx) const { unsigned byte = Offset::bitmask() + idx/8, bit = idx%8; return getBit(byte, bit); } void OpenGD77BaseCodeplug::ChannelBankElement::enable(unsigned idx, bool enabled) { unsigned byte = Offset::bitmask() + idx/8, bit = idx%8; return setBit(byte, bit, enabled); } uint8_t * OpenGD77BaseCodeplug::ChannelBankElement::get(unsigned idx) const { return (_data+Offset::channels())+idx*ChannelElement::size(); } OpenGD77BaseCodeplug::ChannelElement OpenGD77BaseCodeplug::ChannelBankElement::channel(unsigned int n) { return ChannelElement((_data+Offset::channels())+n*ChannelElement::size()); } /* ******************************************************************************************** * * Implementation of OpenGD77BaseCodeplug::VFOChannelElement * ******************************************************************************************** */ OpenGD77BaseCodeplug::VFOChannelElement::VFOChannelElement(uint8_t *ptr, unsigned size) : ChannelElement(ptr, size) { // pass... } OpenGD77BaseCodeplug::VFOChannelElement::VFOChannelElement(uint8_t *ptr) : ChannelElement(ptr) { // pass... } void OpenGD77BaseCodeplug::VFOChannelElement::clear() { ChannelElement::clear(); setStepSize(12.5); setOffsetMode(OffsetMode::Off); setTXOffset(10.0); } QString OpenGD77BaseCodeplug::VFOChannelElement::name() const { return QString(); } void OpenGD77BaseCodeplug::VFOChannelElement::setName(const QString &name) { Q_UNUSED(name); ChannelElement::setName(""); } double OpenGD77BaseCodeplug::VFOChannelElement::stepSize() const { switch (StepSize(getUInt4(Offset::stepSize()))) { case StepSize::SS2_5kHz: return 2.5; case StepSize::SS5kHz: return 5; case StepSize::SS6_25kHz: return 6.25; case StepSize::SS10kHz: return 10.0; case StepSize::SS12_5kHz: return 12.5; case StepSize::SS20kHz: return 20; case StepSize::SS30kHz: return 30; case StepSize::SS50kHz: return 50; } return 12.5; } void OpenGD77BaseCodeplug::VFOChannelElement::setStepSize(double kHz) { if (2.5 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS2_5kHz); else if (5.0 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS5kHz); else if (6.25 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS6_25kHz); else if (10.0 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS10kHz); else if (12.5 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS12_5kHz); else if (20.0 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS20kHz); else if (30.0 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS30kHz); else setUInt4(Offset::stepSize(), (unsigned)StepSize::SS50kHz); } OpenGD77BaseCodeplug::VFOChannelElement::OffsetMode OpenGD77BaseCodeplug::VFOChannelElement::offsetMode() const { return (OffsetMode)getUInt2(Offset::offsetMode()); } void OpenGD77BaseCodeplug::VFOChannelElement::setOffsetMode(OffsetMode mode) { setUInt2(Offset::offsetMode(), (unsigned)mode); } double OpenGD77BaseCodeplug::VFOChannelElement::txOffset() const { return ((double)getBCD4_le(Offset::txOffset()))/100; } void OpenGD77BaseCodeplug::VFOChannelElement::setTXOffset(double f) { setBCD4_le(Offset::txOffset(), (f*100)); } /* ******************************************************************************************** * * Implementation of OpenGD77BaseCodeplug::GeneralSettingsElement * ******************************************************************************************** */ OpenGD77BaseCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::GeneralSettingsElement::clear() { setCall(""); setRadioId(0); } Frequency OpenGD77BaseCodeplug::GeneralSettingsElement::uhfMinFrequency() const { return Frequency::fromMHz(getBCD4_le(Offset::uhfMinFrequency())); } void OpenGD77BaseCodeplug::GeneralSettingsElement::setUHFMinFrequency(const Frequency &f) { setBCD4_le(Offset::uhfMinFrequency(), f.inMHz()); } Frequency OpenGD77BaseCodeplug::GeneralSettingsElement::uhfMaxFrequency() const { return Frequency::fromMHz(getBCD4_le(Offset::uhfMaxFrequency())); } void OpenGD77BaseCodeplug::GeneralSettingsElement::setUHFMaxFrequency(const Frequency &f) { setBCD4_le(Offset::uhfMaxFrequency(), f.inMHz()); } Frequency OpenGD77BaseCodeplug::GeneralSettingsElement::vhfMinFrequency() const { return Frequency::fromMHz(getBCD4_le(Offset::vhfMinFrequency())); } void OpenGD77BaseCodeplug::GeneralSettingsElement::setVHFMinFrequency(const Frequency &f) { setBCD4_le(Offset::vhfMinFrequency(), f.inMHz()); } Frequency OpenGD77BaseCodeplug::GeneralSettingsElement::vhfMaxFrequency() const { return Frequency::fromMHz(getBCD4_le(Offset::vhfMaxFrequency())); } void OpenGD77BaseCodeplug::GeneralSettingsElement::setVHFMaxFrequency(const Frequency &f) { setBCD4_le(Offset::vhfMaxFrequency(), f.inMHz()); } QString OpenGD77BaseCodeplug::GeneralSettingsElement::call() const { return readASCII(Offset::call(), Limit::callLength(), 0xff); } void OpenGD77BaseCodeplug::GeneralSettingsElement::setCall(const QString &call) { writeASCII(Offset::call(), call, Limit::callLength(), 0xff); } unsigned int OpenGD77BaseCodeplug::GeneralSettingsElement::radioId() const { return getBCD8_be(Offset::dmrId()); } void OpenGD77BaseCodeplug::GeneralSettingsElement::setRadioId(unsigned int id) { setBCD8_be(Offset::dmrId(), id); } bool OpenGD77BaseCodeplug::GeneralSettingsElement::encode(const Context &ctx, const ErrorStack &err) { DMRRadioID *id = ctx.config()->settings()->defaultId(); if (nullptr == id) { errMsg(err) << "Cannot encode DMR ID. No default ID defined."; return false; } setCall(id->name()); setRadioId(id->number()); return true; } bool OpenGD77BaseCodeplug::GeneralSettingsElement::decode(const Context &ctx, const ErrorStack &err) { Q_UNUSED(err) DMRRadioID *id = new DMRRadioID(call(), radioId()); ctx.config()->radioIDs()->add(id); ctx.config()->settings()->setDefaultId(id); return true; } /* ******************************************************************************************** * * Implementation of OpenGD77BaseCodeplug::APRSSettingsElement * ******************************************************************************************** */ OpenGD77BaseCodeplug::APRSSettingsElement::APRSSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::APRSSettingsElement::APRSSettingsElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::APRSSettingsElement::clear() { Element::clear(); setName(""); enableFixedPosition(false); setUInt32_le(Offset::fmFrequency(), 0); // Some random data, appears to be important writeASCII(Offset::unknownBytes(), "RA", 2); } bool OpenGD77BaseCodeplug::APRSSettingsElement::isValid() const { return ! name().isEmpty(); } QString OpenGD77BaseCodeplug::APRSSettingsElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void OpenGD77BaseCodeplug::APRSSettingsElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } unsigned int OpenGD77BaseCodeplug::APRSSettingsElement::sourceSSID() const { return getUInt8(Offset::sourceSSID()); } void OpenGD77BaseCodeplug::APRSSettingsElement::setSourceSSID(unsigned int ssid) { setUInt8(Offset::sourceSSID(), ssid); } bool OpenGD77BaseCodeplug::APRSSettingsElement::fixedPositionEnabled() const { return getBit(Offset::useFixedPosition()); } QGeoCoordinate OpenGD77BaseCodeplug::APRSSettingsElement::fixedPosition() const { uint32_t latCode = getUInt24_le(Offset::latitude()); uint32_t lonCode = getUInt24_le(Offset::longitude()); return QGeoCoordinate(decodeAngle(latCode), decodeAngle(lonCode)); } void OpenGD77BaseCodeplug::APRSSettingsElement::setFixedPosition(const QGeoCoordinate &coor) { setUInt24_le(Offset::latitude(), encodeAngle(coor.latitude())); setUInt24_le(Offset::longitude(), encodeAngle(coor.longitude())); setBit(Offset::useFixedPosition()); } void OpenGD77BaseCodeplug::APRSSettingsElement::enableFixedPosition(bool enable) { setBit(Offset::useFixedPosition(), enable); } OpenGD77BaseCodeplug::APRSSettingsElement::PositionPrecision OpenGD77BaseCodeplug::APRSSettingsElement::positionPrecision() const { return (PositionPrecision)getUInt4(Offset::positionPrecision()); } void OpenGD77BaseCodeplug::APRSSettingsElement::setPositionPrecision(PositionPrecision prec) { setUInt4(Offset::positionPrecision(), (unsigned int) prec); } bool OpenGD77BaseCodeplug::APRSSettingsElement::hasVia1() const { return ! via1Call().isEmpty(); } QString OpenGD77BaseCodeplug::APRSSettingsElement::via1Call() const { return readASCII(Offset::via1Call(), 6, 0x00); } unsigned int OpenGD77BaseCodeplug::APRSSettingsElement::via1SSID() const { return getUInt8(Offset::via1SSID()); } void OpenGD77BaseCodeplug::APRSSettingsElement::setVia1(const QString &call, unsigned int ssid) { writeASCII(Offset::via1Call(), call, 6, 0x00); setUInt8(Offset::via1SSID(), ssid); } void OpenGD77BaseCodeplug::APRSSettingsElement::clearVia1() { setVia1("", 0); } bool OpenGD77BaseCodeplug::APRSSettingsElement::hasVia2() const { return ! via2Call().isEmpty(); } QString OpenGD77BaseCodeplug::APRSSettingsElement::via2Call() const { return readASCII(Offset::via2Call(), 6, 0x00); } unsigned int OpenGD77BaseCodeplug::APRSSettingsElement::via2SSID() const { return getUInt8(Offset::via2SSID()); } void OpenGD77BaseCodeplug::APRSSettingsElement::setVia2(const QString &call, unsigned int ssid) { writeASCII(Offset::via2Call(), call, 6, 0x00); setUInt8(Offset::via2SSID(), ssid); } void OpenGD77BaseCodeplug::APRSSettingsElement::clearVia2() { setVia2("", 0); } FMAPRSSystem::Icon OpenGD77BaseCodeplug::APRSSettingsElement::icon() const { return (FMAPRSSystem::Icon)getUInt8(Offset::iconIndex()); } void OpenGD77BaseCodeplug::APRSSettingsElement::setIcon(FMAPRSSystem::Icon icon) { setUInt8(Offset::iconTable(), (FMAPRSSystem::SECONDARY_TABLE & (unsigned int)icon) ? 1 : 0); setUInt8(Offset::iconIndex(), FMAPRSSystem::ICON_MASK & (unsigned int)icon); } QString OpenGD77BaseCodeplug::APRSSettingsElement::comment() const { return readASCII(Offset::comment(), Limit::commentLength(), 0x00); } void OpenGD77BaseCodeplug::APRSSettingsElement::setComment(const QString &comment) { writeASCII(Offset::comment(), comment, Limit::commentLength(), 0x00); } OpenGD77BaseCodeplug::APRSSettingsElement::BaudRate OpenGD77BaseCodeplug::APRSSettingsElement::baudRate() const { return getBit(Offset::baudRate()) ? BaudRate::Baud300 : BaudRate::Baud1200; } void OpenGD77BaseCodeplug::APRSSettingsElement::setBaudRate(BaudRate rate) { setBit(Offset::baudRate(), BaudRate::Baud300 == rate); } Frequency OpenGD77BaseCodeplug::APRSSettingsElement::fmFrequency() const { return Frequency::fromHz(getUInt32_le(Offset::fmFrequency())*10); } void OpenGD77BaseCodeplug::APRSSettingsElement::setFMFrequency(Frequency f) { setUInt32_le(Offset::fmFrequency(), f.inHz()/10); } bool OpenGD77BaseCodeplug::APRSSettingsElement::encode(const FMAPRSSystem *sys, const Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); clear(); setName(sys->name()); setSourceSSID(sys->srcSSID()); QStringList vias = sys->path().split(","); unsigned int viaCount = 0; for (auto via: vias) { QRegularExpression pattern("^([A-Z0-9]+)-(1?[0-9])$"); auto match = pattern.match(via); if (! match.hasMatch()) continue; if (0 == viaCount) setVia1(match.captured(1), match.captured(2).toUInt()); else if (1 == viaCount) setVia2(match.captured(1), match.captured(2).toUInt()); else break; viaCount++; } setIcon(sys->icon()); setComment(sys->message()); enableFixedPosition(false); setBaudRate(BaudRate::Baud1200); setPositionPrecision(PositionPrecision::Max); if(sys->hasRevertChannel()) { setFMFrequency(sys->revertChannel()->txFrequency()); } return true; } FMAPRSSystem * OpenGD77BaseCodeplug::APRSSettingsElement::decode(const Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); if (! isValid()) { errMsg(err) << "Cannot decode invalid APRS settings."; return nullptr; } FMAPRSSystem *sys = new FMAPRSSystem(); sys->setName(name()); sys->setDestination("APN000", 0); sys->setSrcSSID(sourceSSID()); QStringList path; if (hasVia1()) path.append(QString("%1-%2").arg(via1Call()).arg(via1SSID())); if (hasVia2()) path.append(QString("%1-%2").arg(via2Call()).arg(via2SSID())); sys->setPath(path.join(",")); sys->setIcon(icon()); sys->setMessage(comment()); return sys; } bool OpenGD77BaseCodeplug::APRSSettingsElement::link(FMAPRSSystem *sys, const Context &ctx, const ErrorStack &err) { Q_UNUSED(err); if(fmFrequency().inHz() == 0) { sys->resetRevertChannel(); } else { // First, try to find a matching analog channel in list FMChannel *ch = ctx.config()->channelList()->findFMChannelByTxFreq(fmFrequency()); if (! ch) { // If no channel is found, create one with the settings from APRS channel: ch = new FMChannel(); ch->setName("APRS Channel"); ch->setRXFrequency(fmFrequency()); ch->setTXFrequency(fmFrequency()); ch->setBandwidth(FMChannel::Bandwidth::Narrow); logInfo() << "No matching APRS channel found for TX frequency " << double(fmFrequency().inHz())/1e6 << "MHz, create one as 'APRS Channel'"; ctx.config()->channelList()->add(ch); } sys->setRevertChannel(ch); } if (ctx.config()->settings()->defaultId()) sys->setSource(ctx.config()->settings()->defaultId()->name()); return true; } /* ******************************************************************************************** * * Implementation of OpenGD77BaseCodeplug::APRSSettingsBank * ******************************************************************************************** */ OpenGD77BaseCodeplug::APRSSettingsBankElement::APRSSettingsBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::APRSSettingsBankElement::APRSSettingsBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::APRSSettingsBankElement::clear() { for (unsigned int i=0; i(i)) { if (! system(i).encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode APRS system '" << ctx.get(i)->name() << " at index " << i << "."; return false; } } else { system(i).clear(); } } return true; } bool OpenGD77BaseCodeplug::APRSSettingsBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; iposSystems()->add(sys); ctx.add(sys, i); } } return true; } bool OpenGD77BaseCodeplug::APRSSettingsBankElement::link(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(i)) { errMsg(err) << "Cannot link APRS system at index " << i << ": Not found in context."; return false; } if (! system(i).link(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot link APRS system '" << ctx.get(i)->name() << "' at index " << i << "."; return false; } } } return true; } /* ******************************************************************************************** * * Implementation of OpenGD77BaseCodeplug::DTMFContactElement * ******************************************************************************************** */ OpenGD77BaseCodeplug::DTMFContactElement::DTMFContactElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::DTMFContactElement::DTMFContactElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } bool OpenGD77BaseCodeplug::DTMFContactElement::isValid() const { return !name().isEmpty(); } void OpenGD77BaseCodeplug::DTMFContactElement::clear() { setName(""); setNumber(""); } QString OpenGD77BaseCodeplug::DTMFContactElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void OpenGD77BaseCodeplug::DTMFContactElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } QString OpenGD77BaseCodeplug::DTMFContactElement::number() const { QString number; uint8_t *ptr = _data + Offset::number(); const QVector lut = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','*','#'}; for (unsigned int i=0; (i *ptr); i++, ptr++) number.append(lut[*ptr]); return number; } void OpenGD77BaseCodeplug::DTMFContactElement::setNumber(const QString &number) { uint8_t *ptr = _data + Offset::number(); memset(ptr, 0xff, Limit::numberLength()); unsigned int n = std::min(Limit::numberLength(), (unsigned int)number.length()); const QVector lut = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','*','#'}; for (unsigned int i=0; (iname()); setNumber(contact->number()); return true; } DTMFContact * OpenGD77BaseCodeplug::DTMFContactElement::decode(const Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); if (! isValid()) { errMsg(err) << "Cannot decode invalid DTMF contact."; return nullptr; } return new DTMFContact(name(), number()); } /* ******************************************************************************************** * * Implementation of OpenGD77BaseCodeplug::DTMFContactBankElement * ******************************************************************************************** */ OpenGD77BaseCodeplug::DTMFContactBankElement::DTMFContactBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::DTMFContactBankElement::DTMFContactBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::DTMFContactBankElement::clear() { for (unsigned int i=0; i(i)) { if (! contact(i).encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode DTMF contact " << ctx.get(i)->name() << " at index " << i << "."; return false; } } else { contact(i).clear(); } } return true; } bool OpenGD77BaseCodeplug::DTMFContactBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; icontacts()->add(cnt); ctx.add(cnt, i); } return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::BootSettingsElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::BootSettingsElement::BootSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::BootSettingsElement::BootSettingsElement(uint8_t *ptr) : Element(ptr, 0x20) { // pass... } OpenGD77BaseCodeplug::BootSettingsElement::~BootSettingsElement() { // pass... } void OpenGD77BaseCodeplug::BootSettingsElement::clear() { enableBootText(true); clearBootPassword(); setLine1(""); setLine2(""); } bool OpenGD77BaseCodeplug::BootSettingsElement::bootText() const { return (1 == getUInt8(Offset::bootText())); } void OpenGD77BaseCodeplug::BootSettingsElement::enableBootText(bool enable) { setUInt8(Offset::bootText(), (enable ? 1 :0)); } bool OpenGD77BaseCodeplug::BootSettingsElement::bootPasswordEnabled() const { return (1 == getUInt8(Offset::bootPasswdEnable())); } unsigned OpenGD77BaseCodeplug::BootSettingsElement::bootPassword() const { return getBCD8_be(Offset::bootPasswd()); } void OpenGD77BaseCodeplug::BootSettingsElement::setBootPassword(unsigned passwd) { setBCD8_be(Offset::bootPasswd(), passwd); setUInt8(Offset::bootPasswdEnable(), 1); } void OpenGD77BaseCodeplug::BootSettingsElement::clearBootPassword() { setUInt8(Offset::bootPasswdEnable(), 0); } QString OpenGD77BaseCodeplug::BootSettingsElement::line1() const { return readASCII(Offset::line1(), Limit::lineLength(), 0xff); } void OpenGD77BaseCodeplug::BootSettingsElement::setLine1(const QString &text) { writeASCII(Offset::line1(), text, Limit::lineLength(), 0xff); } QString OpenGD77BaseCodeplug::BootSettingsElement::line2() const { return readASCII(Offset::line2(), Limit::lineLength(), 0xff); } void OpenGD77BaseCodeplug::BootSettingsElement::setLine2(const QString &text) { writeASCII(Offset::line2(), text, Limit::lineLength(), 0xff); } bool OpenGD77BaseCodeplug::BootSettingsElement::encode(const Context &ctx, const ErrorStack &err) { Q_UNUSED(err) setLine1(ctx.config()->settings()->boot()->message1()); setLine2(ctx.config()->settings()->boot()->message2()); return true; } bool OpenGD77BaseCodeplug::BootSettingsElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ctx.config()->settings()->boot()->setMessage1(line1()); ctx.config()->settings()->boot()->setMessage2(line2()); return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::ZoneElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::ZoneElement::ZoneElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::ZoneElement::ZoneElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } OpenGD77BaseCodeplug::ZoneElement::~ZoneElement() { // pass... } void OpenGD77BaseCodeplug::ZoneElement::clear() { memset(_data+Offset::name(), 0xff, Limit::nameLength()); memset(_data+Offset::channels(), 0x00, Offset::betweenChannels()*Limit::memberCount()); } bool OpenGD77BaseCodeplug::ZoneElement::isValid() const { return (! name().isEmpty()); } QString OpenGD77BaseCodeplug::ZoneElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void OpenGD77BaseCodeplug::ZoneElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } bool OpenGD77BaseCodeplug::ZoneElement::hasMember(unsigned n) const { if (n >= Limit::memberCount()) return false; return 0 != getUInt16_le(Offset::channels() + Offset::betweenChannels()*n); } unsigned OpenGD77BaseCodeplug::ZoneElement::member(unsigned n) const { if (n >= Limit::memberCount()) return 0; return getUInt16_le(Offset::channels() + Offset::betweenChannels()*n)-1; } void OpenGD77BaseCodeplug::ZoneElement::setMember(unsigned n, unsigned idx) { if (n >= Limit::memberCount()) return; setUInt16_le(Offset::channels() + Offset::betweenChannels()*n, idx+1); } void OpenGD77BaseCodeplug::ZoneElement::clearMember(unsigned n) { setUInt16_le(Offset::channels() + Offset::betweenChannels()*n, 0); } Zone * OpenGD77BaseCodeplug::ZoneElement::decode(const Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx) if (! isValid()) { errMsg(err) << "Cannot decode an invalid zone."; return nullptr; } return new Zone(name()); } bool OpenGD77BaseCodeplug::ZoneElement::link(Zone *zone, Context &ctx, const ErrorStack &err) const { if (! isValid()) { errMsg(err) << "Cannot link invalid zone."; return false; } for (unsigned int i=0; (i(member(i))) { zone->A()->add(ctx.get(member(i))); } else { logWarn() << "While linking zone '" << zone->name() << "': " << i <<"-th channel index " << member(i) << " out of bounds."; } } return true; } bool OpenGD77BaseCodeplug::ZoneElement::encode(const Zone *zone, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) if (zone->A()->count() && zone->B()->count()) setName(zone->name() + " A"); else setName(zone->name()); for (unsigned int i=0; iA()->count()) setMember(i, ctx.index(zone->A()->get(i))); else clearMember(i); } return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::ZoneBankElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } OpenGD77BaseCodeplug::ZoneBankElement::~ZoneBankElement() { // pass... } void OpenGD77BaseCodeplug::ZoneBankElement::clear() { memset(_data, 0, size()); } bool OpenGD77BaseCodeplug::ZoneBankElement::isEnabled(unsigned idx) const { unsigned byte= Offset::bitmap() + idx/8, bit = idx%8; return getBit(byte, bit); } void OpenGD77BaseCodeplug::ZoneBankElement::enable(unsigned idx, bool enabled) { unsigned byte=Offset::bitmap() + idx/8, bit = idx%8; setBit(byte, bit, enabled); } OpenGD77BaseCodeplug::ZoneElement OpenGD77BaseCodeplug::ZoneBankElement::zone(unsigned int idx) { return ZoneElement(_data + Offset::zones() + idx*Offset::betweenZones()); } bool OpenGD77BaseCodeplug::ZoneBankElement::encode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(i)) { if (! zone(i).encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode zone '" << ctx.get(i)->name() << "' at index " << i << "."; return false; } enable(i, true); } else { zone(i).clear(); enable(i, false); } } return true; } bool OpenGD77BaseCodeplug::ZoneBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; izones()->add(obj); ctx.add(obj, i); } return true; } bool OpenGD77BaseCodeplug::ZoneBankElement::link(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(i); if (! zone(i).link(obj, ctx, err)) { errMsg(err) << "Cannot link zone '" << obj->name() << "' at index " << i << "."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::ContactElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::ContactElement::ContactElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::ContactElement::ContactElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } OpenGD77BaseCodeplug::ContactElement::~ContactElement() { // pass... } void OpenGD77BaseCodeplug::ContactElement::clear() { setName(""); setNumber(0); setType(DMRContact::GroupCall); setTimeSlotOverride(TimeSlotOverride::None); } bool OpenGD77BaseCodeplug::ContactElement::isValid() const { return (! name().isEmpty()); } QString OpenGD77BaseCodeplug::ContactElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void OpenGD77BaseCodeplug::ContactElement::setName(const QString name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } unsigned OpenGD77BaseCodeplug::ContactElement::number() const { return getBCD8_be(Offset::number()); } void OpenGD77BaseCodeplug::ContactElement::setNumber(unsigned id) { setBCD8_be(Offset::number(), id); } DMRContact::Type OpenGD77BaseCodeplug::ContactElement::type() const { switch (getUInt8(Offset::type())) { case 0: return DMRContact::GroupCall; case 1: return DMRContact::PrivateCall; case 2: return DMRContact::AllCall; default: break; } return DMRContact::PrivateCall; } void OpenGD77BaseCodeplug::ContactElement::setType(DMRContact::Type type) { switch (type) { case DMRContact::GroupCall: setUInt8(Offset::type(), 0); break; case DMRContact::PrivateCall: setUInt8(Offset::type(), 1); break; case DMRContact::AllCall: setUInt8(Offset::type(), 2); break; } } OpenGD77BaseCodeplug::ContactElement::TimeSlotOverride OpenGD77BaseCodeplug::ContactElement::timeSlotOverride() const { return (TimeSlotOverride)getUInt8(Offset::timeSlotOverride()); } void OpenGD77BaseCodeplug::ContactElement::setTimeSlotOverride(TimeSlotOverride ts) { setUInt8(Offset::timeSlotOverride(), (unsigned int) ts); } DMRContact * OpenGD77BaseCodeplug::ContactElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx) if (! isValid()) { errMsg(err) << "Cannot create contact from an invalid element."; return nullptr; } auto contact = new DMRContact(type(), name(), number(), false); contact->setOpenGD77ContactExtension(new OpenGD77ContactExtension()); switch (timeSlotOverride()) { case TimeSlotOverride::None: contact->openGD77ContactExtension()->setTimeSlotOverride( OpenGD77ContactExtension::TimeSlotOverride::None); break; case TimeSlotOverride::TS1: contact->openGD77ContactExtension()->setTimeSlotOverride( OpenGD77ContactExtension::TimeSlotOverride::TS1); break; case TimeSlotOverride::TS2: contact->openGD77ContactExtension()->setTimeSlotOverride( OpenGD77ContactExtension::TimeSlotOverride::TS2); break; } return contact; } bool OpenGD77BaseCodeplug::ContactElement::encode(const DMRContact *cont, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) setName(cont->name()); setNumber(cont->number()); setType(cont->type()); if (nullptr == cont->openGD77ContactExtension()) return true; switch (cont->openGD77ContactExtension()->timeSlotOverride()) { case OpenGD77ContactExtension::TimeSlotOverride::None: setTimeSlotOverride(TimeSlotOverride::None); break; case OpenGD77ContactExtension::TimeSlotOverride::TS1: setTimeSlotOverride(TimeSlotOverride::TS1); break; case OpenGD77ContactExtension::TimeSlotOverride::TS2: setTimeSlotOverride(TimeSlotOverride::TS2); break; } return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::ContactBankElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::ContactBankElement::ContactBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::ContactBankElement::ContactBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::ContactBankElement::clear() { for (unsigned int i=0; i(i)) { if (! contact(i).encode(ctx.get(i), ctx, err)) { errMsg(err) << "Cannot encode DMR contact " << ctx.get(i)->name() << " at index " << i << "."; return false; } } else { contact(i).clear(); } } return true; } bool OpenGD77BaseCodeplug::ContactBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; icontacts()->add(cnt); ctx.add(cnt, i); } return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::GroupListElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::GroupListElement::GroupListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::GroupListElement::GroupListElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::GroupListElement::clear() { memset(_data + Offset::name(), 0xff, Limit::nameLength()+1); memset(_data + Offset::contacts(), 0, Limit::contactCount()*2); } QString OpenGD77BaseCodeplug::GroupListElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void OpenGD77BaseCodeplug::GroupListElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } bool OpenGD77BaseCodeplug::GroupListElement::hasContactIndex(unsigned int i) const { return 0 != getUInt16_le(Offset::contacts() + i*Offset::betweenContacts()); } unsigned int OpenGD77BaseCodeplug::GroupListElement::contactIndex(unsigned int i) const { return getUInt16_le(Offset::contacts() + i*Offset::betweenContacts()) - 1; } void OpenGD77BaseCodeplug::GroupListElement::setContactIndex(unsigned int i, unsigned int contactIdx) { setUInt16_le(Offset::contacts() + i*Offset::betweenContacts(), contactIdx + 1); } void OpenGD77BaseCodeplug::GroupListElement::clearContactIndex(unsigned int i) { setUInt16_le(Offset::contacts() + i*Offset::betweenContacts(), 0); } bool OpenGD77BaseCodeplug::GroupListElement::encode(RXGroupList *lst, Context &ctx, const ErrorStack &err) { setName(lst->name()); for (unsigned int i=0; icount()) { int idx = ctx.index(lst->contact(i)); if (0 > idx) { errMsg(err) << "Cannot encode group list '" << lst->name() << "', contact '" << lst->contact(i)->name() << "' not indexed."; return false; } setContactIndex(i, idx); } else { clearContactIndex(i); } } return true; } RXGroupList * OpenGD77BaseCodeplug::GroupListElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err); return new RXGroupList(name()); } bool OpenGD77BaseCodeplug::GroupListElement::link(RXGroupList *lst, Context &ctx, const ErrorStack &err) const { for (unsigned int i=0; i(contactIndex(i))) { errMsg(err) << "Cannot resolve contact index " << contactIndex(i) << "."; return false; } lst->addContact(ctx.get(contactIndex(i))); } return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::GroupListBankElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::GroupListBankElement::clear() { for (unsigned int i=0; i(i)) { auto obj = ctx.get(i); setGroupListContactCount(i, obj->count()); if (! groupList(i).encode(obj, ctx, err)) { clearGroupList(i); errMsg(err) << "Cannot encode group list '" << obj->name() << "' at index " << i << "."; return false; } } else { clearGroupList(i); } } return true; } bool OpenGD77BaseCodeplug::GroupListBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; irxGroupLists()->add(obj); ctx.add(obj, i); } return true; } bool OpenGD77BaseCodeplug::GroupListBankElement::link(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(i); if (! groupList(i).link(obj, ctx, err)) { errMsg(err) << "Cannot link group list '" << obj->name() << "' at index " << i << "."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::SatelliteElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::SatelliteElement::SatelliteElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::SatelliteElement::SatelliteElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::SatelliteElement::clear() { memset(_data, 0, size()); } void OpenGD77BaseCodeplug::SatelliteElement::writeDigit(const Offset::Bit &offset, uint8_t digit) { // Must be bit 0 or 3 (BCD) if (offset.bit % 4) return; uint8_t val = getUInt8(offset.byte); val &= ~(0xf << offset.bit); val |= ((digit & 0xf) << offset.bit); setUInt8(offset.byte, val); } void OpenGD77BaseCodeplug::SatelliteElement::writeInteger(const Offset::Bit &offset, int value, bool sign, unsigned int dec) { unsigned int o = 0; // Must be bit 0 or 4 (BCD) if (offset.bit % 4) return; if (0 == dec) return; if (sign && 0 > value) writeDigit(offset + o, 0xc); // '-' ?!? else writeDigit(offset + o, 0xb); // blank o += 4*(dec-1); for (int i=dec; i>0; i--, o = o - 4) { writeDigit(offset + o, value % 10); value /= 10; } } void OpenGD77BaseCodeplug::SatelliteElement::writeFractional(const Offset::Bit &offset, double value, bool sign, unsigned int frac) { unsigned int o = 0; if (offset.bit % 4) return; if (0 == frac) return; if (sign) { if (0 > value) writeDigit(offset + o, 0xc); else writeDigit(offset + o, 0xb); o += 4; } value -= int(value); for (unsigned int i=0; icount() <= i) continue; if (! el.encode(db->getAt(i), err)) { errMsg(err) << "Cannot encode satellite '" << db->getAt(i).name() << "' at index " << i << "."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::NoteElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::NoteElement::NoteElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::NoteElement::clear() { memset(_data, 0, size()); } bool OpenGD77BaseCodeplug::NoteElement::isValid() const { return (0 != getUInt8(Offset::pitch()) || 0 != getUInt8(Offset::duration())) && 0xff != getUInt8(Offset::pitch()) && 0xff != getUInt8(Offset::duration()); } bool OpenGD77BaseCodeplug::NoteElement::isPause() const { return 0 == getUInt8(Offset::pitch()); } double OpenGD77BaseCodeplug::NoteElement::frequency() const { if (isPause()) return 0; return _lut[std::min(44, getUInt8(Offset::pitch())-1)]; } void OpenGD77BaseCodeplug::NoteElement::setFrequency(double pitch) { auto it = std::lower_bound(std::begin(_lut), std::end(_lut), pitch); if (it != std::begin(_lut)) { // check if it-1 is closer if ((pitch-*(it-1)) < (*it-pitch)) --it; } setUInt8(Offset::pitch(), (it == std::end(_lut)) ? 44 : (1+it-std::begin(_lut))); } void OpenGD77BaseCodeplug::NoteElement::setPause() { setUInt8(Offset::pitch(), 0); } unsigned int OpenGD77BaseCodeplug::NoteElement::duration() const { return 100 * (unsigned int)getUInt8(Offset::duration()); } void OpenGD77BaseCodeplug::NoteElement::setDuration(unsigned int ms) { ms = Limit::Range{100,25500}.limit(ms); setUInt8(Offset::duration(), ms/100); } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::BootMelodyElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::BootMelodyElement::BootMelodyElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } void OpenGD77BaseCodeplug::BootMelodyElement::clear() { memset(_data, 0, size()); setUInt32_le(Offset::blockId(), magic()); setUInt32_le(Offset::segmentSize(), size()-8); } bool OpenGD77BaseCodeplug::BootMelodyElement::encode(Context &ctx, const Melody *melody, const ErrorStack &err) { Q_UNUSED(err); Q_UNUSED(ctx); clear(); auto notes = melody->toTones(); unsigned int numNotes = std::min(Limit::notes(), (unsigned int)notes.size()); for (unsigned int i=0; i> notes; for (unsigned int i=0; iinfer(notes); return true; } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug::AdditionalSettingsElement * ********************************************************************************************* */ OpenGD77BaseCodeplug::AdditionalSettingsElement::AdditionalSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenGD77BaseCodeplug::AdditionalSettingsElement::AdditionalSettingsElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } bool OpenGD77BaseCodeplug::AdditionalSettingsElement::isValid() const { return Element::isValid() && ("OpenGD77" == magic()) && (1 == version()); } void OpenGD77BaseCodeplug::AdditionalSettingsElement::clear() { memset(_data, 0xff, size()); writeASCII(Offset::magicString(), "OpenGD77", Limit::magicStringLength(), 0xff); setUInt32_le(Offset::versionNumber(), 1); } QString OpenGD77BaseCodeplug::AdditionalSettingsElement::magic() const { return readASCII(Offset::magicString(), Limit::magicStringLength(), 0xff); } unsigned int OpenGD77BaseCodeplug::AdditionalSettingsElement::version() const { return getUInt32_le(Offset::versionNumber()); } bool OpenGD77BaseCodeplug::AdditionalSettingsElement::hasSettings(Settings set) const { if (! isValid()) return false; for (unsigned int offset = Offset::blocks(); offset < size(); ) { uint32_t magic = getUInt32_le(offset), content_size = getUInt32_le(offset+4); if (magic == (uint32_t)set) return true; if (0xffffffff == magic) return false; offset += 8 + content_size; } return false; } OpenGD77BaseCodeplug::SatelliteBankElement OpenGD77BaseCodeplug::AdditionalSettingsElement::satellites() const { if (! isValid()) return SatelliteBankElement(nullptr); for (unsigned int offset = Offset::blocks(); offset < size(); ) { uint32_t magic = getUInt32_le(offset), content_size = getUInt32_le(offset+4); if (0xffffffff == magic) { SatelliteBankElement(_data + offset).clear(); return SatelliteBankElement(_data + offset); } else if (magic == (uint32_t)Settings::SatelliteOrbitals) { return SatelliteBankElement(_data + offset); } offset += 8 + content_size; } return SatelliteBankElement(nullptr); } OpenGD77BaseCodeplug::BootMelodyElement OpenGD77BaseCodeplug::AdditionalSettingsElement::bootMelody() const { if (! isValid()) return BootMelodyElement(nullptr); for (unsigned int offset = Offset::blocks(); offset < size(); ) { uint32_t magic = getUInt32_le(offset), content_size = getUInt32_le(offset+4); if (0xffffffff == magic) { BootMelodyElement(_data + offset).clear(); return BootMelodyElement(_data + offset); } if (magic == (uint32_t)Settings::BootMelody) { return BootMelodyElement(_data + offset); } offset += 8 + content_size; } return BootMelodyElement(nullptr); } /* ********************************************************************************************* * * Implementation of OpenGD77BaseCodeplug * ********************************************************************************************* */ OpenGD77BaseCodeplug::OpenGD77BaseCodeplug(QObject *parent) : Codeplug{parent} { // pass... } void OpenGD77BaseCodeplug::clear() { // Clear general config clearGeneralSettings(); clearDTMFSettings(); clearAPRSSettings(); // clear DTMF contacts clearDTMFContacts(); // clear channel clearChannels(); // clear boot settings clearBootSettings(); // clear VFO settings clearVFOSettings(); // clear zones clearZones(); // Clear contacts clearContacts(); // clear group lists clearGroupLists(); } bool OpenGD77BaseCodeplug::index(Config *config, Context &ctx, const ErrorStack &err) const { Q_UNUSED(err) // Map radio IDs for (int i=0; iradioIDs()->count(); i++) { if (config->radioIDs()->get(i)->is()) ctx.add(config->radioIDs()->get(i)->as(), i+1); } // Map digital and DTMF contacts for (int i=0, d=0, a=0; icontacts()->count(); i++) { if (config->contacts()->contact(i)->is()) { ctx.add(config->contacts()->contact(i)->as(), d); d++; } else if (config->contacts()->contact(i)->is()) { ctx.add(config->contacts()->contact(i)->as(), a); a++; } } // Map rx group lists for (int i=0; irxGroupLists()->count(); i++) ctx.add(config->rxGroupLists()->list(i), i); // Map channels for (int i=0; ichannelList()->count(); i++) ctx.add(config->channelList()->channel(i), i); // Map zones for (int i=0; izones()->count(); i++) ctx.add(config->zones()->zone(i), i); // Map FM APRS systems for (int i=0,a=0; iposSystems()->count(); i++) { if (config->posSystems()->system(i)->is()) { ctx.add(config->posSystems()->system(i)->as(), a); a++; } } return true; } Config * OpenGD77BaseCodeplug::preprocess(Config *config, const ErrorStack &err) const { Config *intermediate = Codeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot pre-process OpenGD77 codeplug."; return nullptr; } // Remove all AM & M17 channels ObjectFilterVisitor amFilter{AMChannel::staticMetaObject, M17Channel::staticMetaObject}; if (! amFilter.process(intermediate, err)) { errMsg(err) << "Remove AM & M17 channels."; delete intermediate; return nullptr; } ZoneSplitVisitor splitter; if (! splitter.process(intermediate, err)) { errMsg(err) << "Cannot split zone for OpenGD77 codeplug."; delete intermediate; return nullptr; } return intermediate; } bool OpenGD77BaseCodeplug::encode(Config *config, const Flags &flags, const ErrorStack &err) { // Check if default DMR id is set. if (config->settings()->defaultIdRef()->isNull()) { errMsg(err) << "No default radio ID specified."; return false; } // Create index<->object table. Context ctx(config); if (! index(config, ctx, err)) { errMsg(err) << "Cannot index configuration objects."; return false; } return this->encodeElements(flags, ctx); } bool OpenGD77BaseCodeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { // General config if (! this->encodeGeneralSettings(flags, ctx, err)) { errMsg(err) << "Cannot encode general settings."; return false; } if (! this->encodeDTMFSettings(flags, ctx, err)) { errMsg(err) << "Cannot encode DTMF settings."; return false; } if (! this->encodeAPRSSettings(flags, ctx, err)) { errMsg(err) << "Cannot encode APRS settings."; return false; } if (! this->encodeDTMFContacts(flags, ctx, err)) { errMsg(err) << "Cannot encode DTMF contacts."; return false; } if (! this->encodeChannels(flags, ctx, err)) { errMsg(err) << "Cannot encode channels"; return false; } if (! this->encodeBootSettings(flags, ctx, err)) { errMsg(err) << "Cannot encode boot text."; return false; } if (! this->encodeZones(flags, ctx, err)) { errMsg(err) << "Cannot encode zones."; return false; } // Define Contacts if (! this->encodeContacts(flags, ctx, err)) { errMsg(err) << "Cannot encode contacts."; return false; } if (! this->encodeGroupLists(flags, ctx, err)) { errMsg(err) << "Cannot encode group lists."; return false; } return true; } bool OpenGD77BaseCodeplug::decode(Config *config, const ErrorStack &err) { // Clear config object config->clear(); // Create index<->object table. Context ctx(config); return this->decodeElements(ctx, err); } bool OpenGD77BaseCodeplug::postprocess(Config *config, const ErrorStack &err) const { if (! Codeplug::postprocess(config, err)) { errMsg(err) << "Cannot post-process Radioddy codeplug."; return false; } ZoneMergeVisitor merger; if (! merger.process(config, err)) { errMsg(err) << "Cannot merg zones in decoded Radioddity codeplug."; return false; } return true; } bool OpenGD77BaseCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { if (! this->decodeGeneralSettings(ctx, err)) { errMsg(err) << "Cannot decode general settings."; return false; } if (! this->decodeDTMFSettings(ctx, err)) { errMsg(err) << "Cannot decode DTMF settings."; return false; } if (! this->decodeAPRSSettings(ctx, err)) { errMsg(err) << "Cannot decode APRS settings."; return false; } if (! this->decodeBootSettings(ctx, err)) { errMsg(err) << "Cannot decode boot settings."; return false; } if (! this->createContacts(ctx, err)) { errMsg(err) << "Cannot create contacts."; return false; } if (! this->createDTMFContacts(ctx, err)) { errMsg(err) << "Cannot create DTMF contacts"; return false; } if (! this->createChannels(ctx, err)) { errMsg(err) << "Cannot create channels."; return false; } if (! this->createZones(ctx, err)) { errMsg(err) << "Cannot create zones."; return false; } if (! this->createGroupLists(ctx, err)) { errMsg(err) << "Cannot create group lists."; return false; } if (! this->linkChannels(ctx, err)) { errMsg(err) << "Cannot link channels."; return false; } if (! this->linkZones(ctx, err)) { errMsg(err) << "Cannot link zones."; return false; } if (! this->linkGroupLists(ctx, err)) { errMsg(err) << "Cannot link group lists."; return false; } if (! this->linkAPRSSettings(ctx, err)) { errMsg(err) << "Cannot decode APRS settings."; return false; } return true; } ================================================ FILE: lib/opengd77base_codeplug.hh ================================================ #ifndef OPENGD77BASE_CODEPLUG_HH #define OPENGD77BASE_CODEPLUG_HH #include "channel.hh" #include "codeplug.hh" #include "gpssystem.hh" #include "contact.hh" #include "zone.hh" #include "satellitedatabase.hh" #include "melody.hh" #include /** Base codeplug for all OpenGD77 based firmware variants. * @ingroup ogd77 */ class OpenGD77BaseCodeplug : public Codeplug { Q_OBJECT public: /** Possible image types. */ enum ImageType { EEPROM = 0, FLASH = 1 }; public: /** Encodes an angle used to store locations. */ static uint32_t encodeAngle(double degee); /** Decodes an angle used to store locations. */ static double decodeAngle(uint32_t code); /** Encodes a selective call (tx/rx tone). */ static uint16_t encodeSelectiveCall(const SelectiveCall &call); /** Decodes a selective call (tx/rx tone). */ static SelectiveCall decodeSelectiveCall(uint16_t code); public: /** Implements the base for all OpenGD77 channel encodings. */ class ChannelElement: public Codeplug::Element { public: /** Possible channel types. */ enum Mode { MODE_ANALOG = 0, ///< Analog channel, aka FM. MODE_DIGITAL = 1 ///< Digital channel, aka DMR. }; /** Alias to transmit. */ enum class Alias { None = 0, APRS = 1, Text = 2, Both = 3 }; /** Possible squelch modes. */ enum class SquelchMode { Global, Open, Normal, Closed }; protected: /** Constructs a channel from the given memory. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructs a channel from the given memory. */ explicit ChannelElement(uint8_t *ptr); /** Destructor. */ virtual ~ChannelElement(); /** The size of the channel. */ static constexpr unsigned int size() { return 0x0038; } /** Resets the channel. */ virtual void clear(); /** Returns the name of the channel. */ virtual QString name() const; /** Sets the name of the channel. */ virtual void setName(const QString &n); /** Returns the RX frequency of the channel. */ virtual Frequency rxFrequency() const; /** Sets the RX frequency of the channel. */ virtual void setRXFrequency(const Frequency &freq); /** Returns the TX frequency of the channel. */ virtual Frequency txFrequency() const; /** Sets the TX frequency of the channel. */ virtual void setTXFrequency(const Frequency &freq); /** Returns the channel mode. */ virtual Mode mode() const; /** Sets the channel mode. */ virtual void setMode(Mode mode); /** Returns @c true if the power-setting is global. */ virtual bool globalPower() const; /** Returns the power setting of the channel. */ virtual Channel::Power power() const; /** Sets the power setting of the channel. */ virtual void setPower(Channel::Power pwr); /** Clears the power setting. The global power setting is used. */ virtual void clearPower(); /** Returns the transmit timeout. * An invalid interval indicates an infinite timeout. */ virtual Interval transmitTimeout() const; /** Sets a transmit timeout. * To disable the timeout, set an invalid interval. */ virtual void setTransmitTimeout(const Interval &interval); /** Returns @c true, if a fixed position is set for the channel. */ virtual bool fixedPositionEnabled() const; /** Returns the fixed position. */ virtual QGeoCoordinate fixedPosition() const; /** Sets the fixed position for this channel. */ virtual void setFixedPosition(const QGeoCoordinate &coordinate); /** Resets the fixed position. */ virtual void enableFixedPosition(bool enable); /** Returns the RX subtone. */ virtual SelectiveCall rxTone() const; /** Sets the RX subtone. */ virtual void setRXTone(const SelectiveCall &code); /** Returns the TX subtone. */ virtual SelectiveCall txTone() const; /** Sets the TX subtone. */ virtual void setTXTone(const SelectiveCall &code); /** Returns @c true if the channel is set to simplex. */ virtual bool isSimplex() const; /** Sets the channel to simplex. */ virtual void enableSimplex(bool enable); /** Returns @c true, if the power-save feature is enabled. */ virtual bool powerSave() const; /** Enables/disables power-save. */ virtual void enablePowerSave(bool enable); /** Returns @c true, if the "beep" is enabled. */ virtual bool beep() const; /** Enables/disables "the beep". */ virtual void enableBeep(bool enable); /** Returns @c true if the global DMR ID is overridden. */ virtual bool hasDMRId() const; /** Returns the DMR ID for this channel. */ virtual unsigned int dmrId() const; /** Sets the DMR ID for this channel. */ virtual void setDMRId(unsigned int id); /** Resets the DMR ID for this channel to the global one. */ virtual void clearDMRId(); /** Returns @c true if a group list is set. */ virtual bool hasGroupList() const; /** Returns the group-list index. */ virtual unsigned groupListIndex() const; /** Sets the group-list index. */ virtual void setGroupListIndex(unsigned index); /** Clears the group list index. */ virtual void clearGroupListIndex(); /** Returns the color code. */ virtual unsigned colorCode() const; /** Sets thecolor code. */ virtual void setColorCode(unsigned cc); /** Returns @c true, if the APRS system index is set. */ virtual bool hasAPRSIndex() const; /** Returns the APRS system index. */ virtual unsigned int aprsIndex() const; /** Sets the APRS system index. */ virtual void setAPRSIndex(unsigned int index); /** Resets the APRS system index. */ virtual void clearAPRSIndex(); /** Returns @c true, if the TX contact is set. */ virtual bool hasTXContact() const; /** Returns the TX contact index. */ virtual unsigned int txContactIndex() const; /** Sets the TX contact index. */ virtual void setTXContactIndex(unsigned int index); /** Clears the TX contact index. */ virtual void clearTXContact(); /** Returns the alias transmitted on time slot 1. */ virtual OpenGD77ChannelExtension::TalkerAlias aliasTimeSlot1() const; /** Sets the alias transmitted on time slot 1. */ virtual void setAliasTimeSlot1(OpenGD77ChannelExtension::TalkerAlias alias); /** Returns the alias transmitted on time slot 2. */ virtual OpenGD77ChannelExtension::TalkerAlias aliasTimeSlot2() const; /** Sets the alias transmitted on time slot 2. */ virtual void setAliasTimeSlot2(OpenGD77ChannelExtension::TalkerAlias alias); /** Returns the time slot of the channel. */ virtual DMRChannel::TimeSlot timeSlot() const; /** Sets the time slot of the channel. */ virtual void setTimeSlot(DMRChannel::TimeSlot ts); /** Returns the bandwidth. */ virtual FMChannel::Bandwidth bandwidth() const; /** Sets the bandwidth. */ virtual void setBandwidth(FMChannel::Bandwidth bw); /** Returns @c true if RX only is enabled. */ virtual bool rxOnly() const; /** Enables/disables RX only. */ virtual void enableRXOnly(bool enable); /** Returns @c true if channel is skipped in a scan. */ virtual bool skipScan() const; /** Enables/disables skipping in scan. */ virtual void enableSkipScan(bool enable); /** Returns @c true if channel is skipped in zone scan. */ virtual bool skipZoneScan() const; /** Enables/disables skipping in zone scan. */ virtual void enableSkipZoneScan(bool enable); /** Returns @c true if VOX is enabled. */ virtual bool vox() const; /** Enables/disables VOX. */ virtual void enableVOX(bool enable); /** Returns the squelch mode*/ virtual SquelchMode squelchMode() const; /** Returns the squelch level. */ virtual unsigned int squelchLevel() const; /** Set the squelch level and mode. Level is ignored, if mode is not normal.*/ virtual void setSquelch(SquelchMode mode, unsigned int level); /** Constructs a generic @c Channel object from the codeplug channel. */ virtual Channel *decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links a previously constructed channel to the rest of the configuration. */ virtual bool link(Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Initializes this codeplug channel from the given generic configuration. */ virtual bool encode(const Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for this element. */ struct Limit { /** The maximum length of the name. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets within the channel element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int rxFrequency() { return 0x0010; } static constexpr unsigned int txFrequency() { return 0x0014; } static constexpr unsigned int mode() { return 0x0018; } static constexpr unsigned int power() { return 0x0019; } static constexpr unsigned int latitude0() { return 0x001a; } static constexpr unsigned int txTimeout() { return 0x001b; } static constexpr unsigned int latitude1() { return 0x001c; } static constexpr unsigned int latitude2() { return 0x001d; } static constexpr unsigned int longitude0() { return 0x001e; } static constexpr unsigned int longitude1() { return 0x001f; } static constexpr unsigned int rxTone() { return 0x0020; } static constexpr unsigned int txTone() { return 0x0022; } static constexpr unsigned int longitude2() { return 0x0024; } static constexpr Bit simplex() { return {0x0026, 2}; } static constexpr Bit useFixedLocation() { return {0x0026, 3}; } static constexpr Bit disablePowerSave() { return {0x0026, 5}; } static constexpr Bit disableBeep() { return {0x0026, 6}; } static constexpr Bit overrideDMRID() { return {0x0026, 7}; } static constexpr unsigned int dmrId() { return 0x0027; } static constexpr unsigned int groupList() { return 0x002b; } static constexpr unsigned int colorCode() { return 0x002c; } static constexpr unsigned int aprsIndex() { return 0x002d; } static constexpr unsigned int txContact() { return 0x002e; } static constexpr Bit aliasTimeSlot2() { return { 0x030, 2}; } static constexpr Bit aliasTimeSlot1() { return { 0x030, 0}; } static constexpr Bit timeSlot() { return {0x0031, 6}; } static constexpr Bit bandwidth() { return {0x0033, 1}; } static constexpr Bit enableMonitor() { return {0x0033, 3}; } static constexpr Bit rxOnly() { return {0x0033, 2}; } static constexpr Bit skipScan() { return {0x0033, 4}; } static constexpr Bit skipZoneScan() { return {0x0033, 5}; } static constexpr Bit vox() { return {0x0033, 6}; } static constexpr unsigned int squelch() { return 0x0037; } /// @endcond }; }; /** Implements the base for channel banks in Radioddity codeplugs. * * Memory layout of a channel bank: * @verbinclude radioddity_channelbank.txt */ class ChannelBankElement: public Element { protected: /** Hidden constructor. */ ChannelBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ChannelBankElement(uint8_t *ptr); /** Destructor. */ virtual ~ChannelBankElement(); /** The size of the channel bank. */ static constexpr unsigned int size() { return 0x1c10; } /** Clears the bank. */ void clear(); /** Returns @c true if the channel is enabled. */ virtual bool isEnabled(unsigned idx) const ; /** Enable/disable a channel in the bank. */ virtual void enable(unsigned idx, bool enabled); /** Returns a pointer to the channel at the given index. */ virtual uint8_t *get(unsigned idx) const; /** Returns the n-th channel. */ ChannelElement channel(unsigned int n); public: /** Some limits for the channel bank. */ struct Limit { /** The maximum number of channels. */ static constexpr unsigned int channelCount() { return 128; } }; protected: /** Some internal offset within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bitmask() { return 0x0000; } static constexpr unsigned int channels() { return 0x0010; } /// @endcond }; }; /** VFO Channel representation within the binary codeplug. * * Each channel requires 0x38b: * @verbinclude radioddity_vfochannel.txt */ class VFOChannelElement: public ChannelElement { public: /** Possible offset frequency modes. */ enum class OffsetMode { Off = 0, ///< Disables transmit frequency offset. Positive = 1, ///< Transmit offset frequency is positive (TX above RX). Negative = 2 ///< Transmit offset frequency is negative (TX below RX). }; /** Possible tuning step sizes. */ enum class StepSize { SS2_5kHz = 0, ///< 2.5kHz SS5kHz = 1, ///< 5kHz SS6_25kHz = 2, ///< 6.25kHz SS10kHz = 3, ///< 10kHz SS12_5kHz = 4, ///< 12.5kHz SS20kHz = 5, ///< 20kHz SS30kHz = 6, ///< 30kHz SS50kHz = 7 ///< 50kHz }; protected: /** Hidden constructor. */ VFOChannelElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit VFOChannelElement(uint8_t *ptr); void clear(); /** The VFO channel has no name. */ QString name() const; /** The VFO channel has no name. */ void setName(const QString &name); /** Returns the tuning step-size in kHz. */ virtual double stepSize() const; /** Sets the tuning step-size in kHz. */ virtual void setStepSize(double kHz); /** Returns the transmit frequency offset mode. */ virtual OffsetMode offsetMode() const; /** Returns the transmit frequency offset. */ virtual double txOffset() const; /** Sets the transmit frequency offset in MHz. */ virtual void setTXOffset(double f); /** Sets the transmit frequency offset mode. */ virtual void setOffsetMode(OffsetMode mode); protected: /// @cond DO_NOT_DOCUMENT struct Offset: public ChannelElement::Offset { static constexpr Bit stepSize() { return {0x0036, 4} ; } static constexpr Bit offsetMode() { return {0x0036, 2} ; } static constexpr unsigned int txOffset() { return 0x0034; } }; /// @endcond }; /** Encodes the settings element for all OpenGD77 codeplugs. */ class GeneralSettingsElement: public Element { protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GeneralSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0090; } void clear(); /** Returns the UHF minimum frequency. */ virtual Frequency uhfMinFrequency() const; /** Sets the UHF minimum frequency. */ virtual void setUHFMinFrequency(const Frequency &f); /** Returns the UHF maximum frequency. */ virtual Frequency uhfMaxFrequency() const; /** Sets the UHF maximum frequency. */ virtual void setUHFMaxFrequency(const Frequency &f); /** Returns the VHF minimum frequency. */ virtual Frequency vhfMinFrequency() const; /** Sets the VHF minimum frequency. */ virtual void setVHFMinFrequency(const Frequency &f); /** Returns the VHF maximum frequency. */ virtual Frequency vhfMaxFrequency() const; /** Sets the VHF maximum frequency. */ virtual void setVHFMaxFrequency(const Frequency &f); /** Returns the radio callsign. */ virtual QString call() const; /** Sets the radio callsign. */ virtual void setCall(const QString &call); /** Returns the DMR ID. */ virtual unsigned int radioId() const; /** Sets the DMR ID. */ virtual void setRadioId(unsigned int id); /** Encodes the settings. */ virtual bool encode(const Context &ctx, const ErrorStack &err = ErrorStack()); /** Decodes the settings. */ virtual bool decode(const Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits. */ struct Limit: public Element::Limit { /** The maximum call length. */ static constexpr unsigned int callLength() { return 8; } }; protected: /** Some internal offset within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int uhfMinFrequency() { return 0x0000; } static constexpr unsigned int uhfMaxFrequency() { return 0x0002; } static constexpr unsigned int vhfMinFrequency() { return 0x0004; } static constexpr unsigned int vhfMaxFrequency() { return 0x0006; } static constexpr unsigned int call() { return 0x0060; } static constexpr unsigned int dmrId() { return 0x0068; } /// @endcond }; }; /** APRS system for OpenGD77 devices. */ class APRSSettingsElement: public Element { public: /** Possible APRS baud rates. */ enum class BaudRate { Baud300 = 1, Baud1200 = 0 }; /** Possible position precisions. */ enum class PositionPrecision { Max = 0, Mask1_8sec = 1, Mask3_6sec = 2, Mask18sec = 3, Mask36sec = 4, Mask3min = 5, Mask6min = 6, Mask30min = 7 }; public: /** Constructor from pointer. */ explicit APRSSettingsElement(uint8_t *ptr); protected: /** Hidden constructor. */ APRSSettingsElement(uint8_t *ptr, size_t size); public: /** The size of the channel bank. */ static constexpr unsigned int size() { return 0x40; } /** Clears the bank. */ void clear(); /** Returns @c true, if the system is valid. */ virtual bool isValid() const; /** Returns the name of the system. */ virtual QString name() const; /** Sets the name of the system. */ virtual void setName(const QString &name); /** Returns the source SSID. */ virtual unsigned int sourceSSID() const; /** Sets the source SSID. */ virtual void setSourceSSID(unsigned int ssid); /** Returns @c true, if a fixed position is send. */ virtual bool fixedPositionEnabled() const; /** Returns the fixed position. */ virtual QGeoCoordinate fixedPosition() const; /** Sets the fixed position. */ virtual void setFixedPosition(const QGeoCoordinate &coor); /** Resets the fixed position. */ virtual void enableFixedPosition(bool enable); /** Returns the posiiton reporting precision. */ virtual PositionPrecision positionPrecision() const; /** Sets the position reporting precision in degrees. */ virtual void setPositionPrecision(PositionPrecision prec); /** Returns @c true, if the first via node is set. */ virtual bool hasVia1() const; /** Returns the first via node call. */ virtual QString via1Call() const; /** Returns the first via node ssid. */ virtual unsigned int via1SSID() const; /** Sets the first via node. */ virtual void setVia1(const QString &call, unsigned int ssid); /** Clears the first via node. */ virtual void clearVia1(); /** Returns @c true, if the second via node is set. */ virtual bool hasVia2() const; /** Returns the second via node call. */ virtual QString via2Call() const; /** Returns the second via node ssid. */ virtual unsigned int via2SSID() const; /** Sets the second via node. */ virtual void setVia2(const QString &call, unsigned int ssid); /** Clears the second via node. */ virtual void clearVia2(); /** Returns the icon. */ virtual FMAPRSSystem::Icon icon() const; /** Sets the icon. */ virtual void setIcon(FMAPRSSystem::Icon icon); /** Returns the comment text. */ virtual QString comment() const; /** Sets the comment text. */ virtual void setComment(const QString &text); /** Returns the baud-rate. */ virtual BaudRate baudRate() const; /** Sets the baud rate. */ virtual void setBaudRate(BaudRate rate); Frequency fmFrequency() const; void setFMFrequency(Frequency f); /** Encodes the APRS settings. */ virtual bool encode(const FMAPRSSystem *system, const Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes some APRS settings. */ virtual FMAPRSSystem *decode(const Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links the ARPS settings. */ virtual bool link(FMAPRSSystem *system, const Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit: public Element::Limit { /** The maximum name length in chars. */ static constexpr unsigned int nameLength() { return 8; } /** The maximum comment length in chars. */ static constexpr unsigned int commentLength() { return 23; } }; protected: /** Some internal offsets within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int sourceSSID() { return 0x0008; } static constexpr unsigned int latitude() { return 0x0009; } static constexpr unsigned int longitude() { return 0x000c; } static constexpr unsigned int via1Call() { return 0x000f; } static constexpr unsigned int via1SSID() { return 0x0015; } static constexpr unsigned int via2Call() { return 0x0016; } static constexpr unsigned int via2SSID() { return 0x001c; } static constexpr unsigned int iconTable() { return 0x001d; } static constexpr unsigned int iconIndex() { return 0x001e; } static constexpr unsigned int comment() { return 0x001f; } static constexpr unsigned int fmFrequency() { return 0x0037; } static constexpr Bit positionPrecision() { return { 0x003d, 4}; } static constexpr Bit transmitQSY() { return { 0x003d, 2}; } static constexpr Bit useFixedPosition() { return { 0x003d, 1}; } static constexpr Bit baudRate() { return { 0x003d, 0}; } static constexpr unsigned int unknownBytes() { return 0x003e; } /// @endcond }; }; /** APRS System bank. */ class APRSSettingsBankElement: public Element { public: /** Constructor from pointer. */ explicit APRSSettingsBankElement(uint8_t *ptr); protected: /** Hidden constructor. */ APRSSettingsBankElement(uint8_t *ptr, size_t size); public: /** The size of the channel bank. */ static constexpr unsigned int size() { return 0x40; } /** Clears the bank. */ void clear(); /** Returns the n-th APRS system. */ APRSSettingsElement system(unsigned int idx) const; /** Encodes all FM APRS systems. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes all FM APRS systems. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links all FM APRS systems. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the bank. */ struct Limit: public Element::Limit { /** The total number of APRS systems. */ static constexpr unsigned int systems() { return 8; } }; public: /// @cond DO_NOT_DOCUMENT struct Offset: public Element::Offset { static constexpr unsigned int systems() { return 0x0000; } static constexpr unsigned int betweenSystems() { return APRSSettingsElement::size(); } }; /// @endcond }; /** DTMF contact element. * Just a name and DTMF number. */ class DTMFContactElement: public Element { protected: /** Hidden constructor. */ DTMFContactElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit DTMFContactElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0020; } void clear(); bool isValid() const; /** Returns the name. */ virtual QString name() const; /** Sets the name. */ virtual void setName(const QString &name); /** Returns the DTMF number. */ virtual QString number() const; /** Sets the DTMF number. */ virtual void setNumber(const QString &number); /** Encodes a number. */ virtual bool encode(const DTMFContact *contact, const Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes a number. */ virtual DTMFContact *decode(const Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. Ü*/ struct Limit: public Element::Limit { /** The maximum name length. */ static constexpr unsigned int nameLength() { return 16; } /** The maximum length of the number. */ static constexpr unsigned int numberLength() { return 16; } }; protected: /// @cond DO_NOT_DOCUMENT struct Offset: public Element::Offset { static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int number() { return 0x0010; } }; /// @endcond }; /** DTMF contact bank. */ class DTMFContactBankElement: public Element { protected: /** Hidden constructor. */ DTMFContactBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ DTMFContactBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return Limit::contacts()*DTMFContactElement::size(); } void clear(); /** Returns the n-th DTMF contact. */ DTMFContactElement contact(unsigned int n) const; /** Encodes all DTMF contacts. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes all DTMF contacts. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the bank. */ struct Limit: public Element::Limit { /** The total number of contacts. */ static constexpr unsigned int contacts() { return 64; } }; public: /// @cond DO_NOT_DOCUMENT struct Offset: public Element::Offset { static constexpr unsigned int contacts() { return 0x0000; } static constexpr unsigned int betweenContacts() { return DTMFContactElement::size(); } }; /// @endcond }; /** Implements the base class of boot settings for all OpenGD77 codeplugs. */ class BootSettingsElement: public Element { protected: /** Hidden constructor. */ BootSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit BootSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~BootSettingsElement(); /** Resets the settings. */ void clear(); /** Returns @c true if the text is shown on boot, other wise an image is shown. */ virtual bool bootText() const; /** Enables/disables boot text. */ virtual void enableBootText(bool enable); /** Returns @c true if the boot password is enabled. */ virtual bool bootPasswordEnabled() const; /** Returns the boot password (6 digit). */ virtual unsigned bootPassword() const; /** Sets the boot password (6 digit). */ virtual void setBootPassword(unsigned passwd); /** Clear boot password. */ virtual void clearBootPassword(); /** Returns the first line. */ virtual QString line1() const; /** Sets the first line. */ virtual void setLine1(const QString &text); /** Returns the Second line. */ virtual QString line2() const; /** Sets the second line. */ virtual void setLine2(const QString &text); /** Encodes boot text settings from configuration. */ virtual bool encode(const Context &ctx, const ErrorStack &err = ErrorStack()); /** Updates the configuration with the boot text settings. */ virtual bool decode(Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits for the settings. */ struct Limit: public Element::Limit { /** The total number of contacts. */ static constexpr unsigned int lineLength() { return 16; } }; public: /// @cond DO_NOT_DOCUMENT struct Offset: public Element::Offset { static constexpr unsigned int bootText() { return 0x0000; } static constexpr unsigned int bootPasswdEnable() { return 0x0001; } static constexpr unsigned int bootPasswd() { return 0x000c; } static constexpr unsigned int line1() { return 0x0028; } static constexpr unsigned int line2() { return 0x0038; } }; /// @endcond }; /** Represents a zone within OpenGD77 codeplugs. */ class ZoneElement: public Element { protected: /** Hidden constructor. */ ZoneElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ZoneElement(uint8_t *ptr); virtual ~ZoneElement(); /** The size of the zone element. */ static constexpr unsigned int size() { return 0x00b0; } /** Resets the zone. */ void clear(); /** Returns @c true if the zone is valid. */ bool isValid() const; /** Returns the name of the zone. */ virtual QString name() const; /** Sets the name of the zone. */ virtual void setName(const QString &name); /** Returns @c true if a member is stored at the given index. */ virtual bool hasMember(unsigned n) const; /** Returns the n-th member index. */ virtual unsigned member(unsigned n) const; /** Sets the n-th member index. */ virtual void setMember(unsigned n, unsigned idx); /** Clears the n-th member index. */ virtual void clearMember(unsigned n); /** Resets this codeplug zone representation from the given generic @c Zone object. */ virtual bool encode(const Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); /** Constructs a generic @c Zone object from this codeplug zone. */ virtual Zone *decode(const Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links a previously constructed @c Zone object to the rest of the configuration. That is * linking to the referred channels. */ virtual bool link(Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()) const; public: /** Some limits for zone elements. */ struct Limit: public Element::Limit { /** The maximum length of the zone name. */ static constexpr unsigned int nameLength() { return 16; } /** The maximum number of members. */ static constexpr unsigned int memberCount() { return 80; } }; protected: /** Some internal offsets within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int channels() { return 0x0010; } static constexpr unsigned int betweenChannels() { return 0x0002; } /// @endcond }; }; /** Implements the base class for all zone banks of OpenGD77 codeplugs. */ class ZoneBankElement: public Element { protected: /** Hidden constructor. */ ZoneBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ZoneBankElement(uint8_t *ptr); /** Destructor. */ ~ZoneBankElement(); /** The size of the zone element. */ static constexpr unsigned int size() { return 0x0020 + Limit::zoneCount()*ZoneElement::size(); } /** Resets the bank. */ void clear(); /** Returns @c true if the zone is enabled. */ virtual bool isEnabled(unsigned idx) const ; /** Enable/disable a zone in the bank. */ virtual void enable(unsigned idx, bool enabled); /** Returns the n-th zone. */ ZoneElement zone(unsigned int n); /** Encodes all zones. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes all zones. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links all zones. */ virtual bool link(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the zone bank. */ struct Limit: public Element::Limit { /** The maximum number of zones in this bank. */ static constexpr unsigned int zoneCount() { return 68; } }; protected: /** Some internal offsets within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bitmap() { return 0x0000; } static constexpr unsigned int zones() { return 0x0020; } static constexpr unsigned int betweenZones() { return ZoneElement::size(); } /// @endcond }; }; /** Implements digital contacts in OpenGD77 codeplugs. */ class ContactElement: public Element { public: /** Possible values for the time-slot override option. * Encoded values are correct for firmware 2022-02-28 (0118581D) to 2025-03-23 (1bd23ea). */ enum class TimeSlotOverride { None = 0x01, ///< Do not override time-slot of channel. TS1 = 0x00, ///< Force time-slot to TS1. TS2 = 0x02 ///< Force time-slot to TS2. }; protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ContactElement(uint8_t *ptr); /** Destructor. */ virtual ~ContactElement(); /** The size of the contact element. */ static constexpr unsigned int size() { return 0x0018; } /** Resets the contact. */ void clear(); /** Returns @c true if the contact is valid. */ bool isValid() const; /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString name); /** Returns the DMR number of the contact. */ virtual unsigned number() const; /** Sets the DMR number of the contact. */ virtual void setNumber(unsigned id); /** Returns the call type. */ virtual DMRContact::Type type() const; /** Sets the call type. */ virtual void setType(DMRContact::Type type); /** Returns the time slot override of the contact. */ virtual TimeSlotOverride timeSlotOverride() const; /** Sets the time slot override. */ virtual void setTimeSlotOverride(TimeSlotOverride ts); /** Constructs a @c DigitalContact instance from this codeplug contact. */ virtual DMRContact *decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Resets this codeplug contact from the given @c DigitalContact. */ virtual bool encode(const DMRContact *obj, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the contact. */ struct Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int number() { return 0x0010; } static constexpr unsigned int type() { return 0x0014; } static constexpr unsigned int timeSlotOverride() { return 0x0017; } /// @endcond }; }; /** Encodes the contact bank. */ class ContactBankElement: public Element { protected: /** Hidden constructor. */ ContactBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactBankElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return Limit::contactCount() * ContactElement::size(); } /** Resets the contact. */ void clear(); /** Returns the i-th contact element. */ ContactElement contact(unsigned int idx) const; /** Encodes all DMR contacts. */ virtual bool encode(Context &ctx, const ErrorStack &err = ErrorStack()); /** Decodes all DMR contacts. */ virtual bool decode(Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits for the element. */ struct Limit { /** Maximum number of contacts. */ static constexpr unsigned int contactCount() { return 1024; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int contacts() { return 0x0000; } static constexpr unsigned int betweenContacts() { return ContactElement::size(); } // @endcond }; }; /** Encodes a group list for all OpenGD77 codeplugs. */ class GroupListElement: public Element { protected: /** Hidden constructor. */ GroupListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0050; } /** Clears the group list. */ void clear(); /** Returns the name of the group list. */ virtual QString name() const; /** Sets the name of the group list. */ virtual void setName(const QString &name); /** Returns @c true, if the i-th contact is set. */ virtual bool hasContactIndex(unsigned int i) const; /** Returns the i-th contact index. */ virtual unsigned int contactIndex(unsigned int i) const; /** Sets the i-th contact index. */ virtual void setContactIndex(unsigned int i, unsigned int contactIdx); /** Clears the i-th contact index. */ virtual void clearContactIndex(unsigned int i); /** Encodes group list element. */ virtual bool encode(RXGroupList *lst, Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes group list element. */ virtual RXGroupList *decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links the group list element. */ virtual bool link(RXGroupList *lst, Context &ctx, const ErrorStack &err=ErrorStack()) const; public: /** Some limits for the element. */ struct Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 15; } /** Maximum number of contacts. */ static constexpr unsigned int contactCount() { return 32; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int contacts() { return 0x0010; } static constexpr unsigned int betweenContacts() { return 0x0002; } // @endcond }; }; /** Encodes a group list bank for all OpenGD77 codeplugs. */ class GroupListBankElement: public Element { protected: /** Hidden constructor. */ GroupListBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListBankElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x1840; } /** Clears the group list bank. */ void clear(); /** Returns @c true, if the i-th group list is encoded. */ virtual bool hasGroupList(unsigned int i) const; /** Returns the number of contacts in the given group list. */ virtual unsigned int groupListContactCount(unsigned int i) const; /** Sets the number of contacts in the given group list. */ virtual void setGroupListContactCount(unsigned int i, unsigned int count); /** Returns the i-th group list. */ virtual GroupListElement groupList(unsigned int i) const; /** Clears the i-th group list. */ virtual void clearGroupList(unsigned int i); /** Encodes all group lists. */ virtual bool encode(Context &ctx, const ErrorStack &err = ErrorStack()); /** Decodes all group lists. */ virtual bool decode(Context &ctx, const ErrorStack &err = ErrorStack()); /** Links all group lists. */ virtual bool link(Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits for the element. */ struct Limit { /** Maximum number of group lists. */ static constexpr unsigned int groupListCount() { return 76; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int length() { return 0x0000; } static constexpr unsigned int groupLists() { return 0x0080; } static constexpr unsigned int betweenGroupLists() { return GroupListElement::size(); } // @endcond }; }; /** Encodes a satellite for the OpenGD77 devices. * That is a set of orbital elements and transponder information. */ class SatelliteElement: public Codeplug::Element { protected: /** Hidden constructor. */ SatelliteElement(uint8_t *ptr, size_t size); public: /** Constructor. */ SatelliteElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0064; } void clear(); /** Sets the name of the element. */ virtual void setName(const QString &name); /** Sets the epoch. */ virtual void setEpoch(const ::OrbitalElement::Epoch &epoch); /** Sets the first derivative of mean motion. */ virtual void setMeanMotion(double mm); /** Sets the first derivative of mean motion. */ virtual void setMeanMotionDerivative(double dmm); /** Sets the inclination. */ virtual void setInclination(double incl); /** Right ascension of the ascending node. */ virtual void setAscension(double asc); /** Sets eccentricity. */ virtual void setEccentricity(double ecc); /** Sets argument of perigee. */ virtual void setPerigee(double arg); /** Set the mean anomaly. */ virtual void setMeanAnomaly(double ma); /** Sets the revolution number at epoch. */ virtual void setRevolutionNumber(unsigned int num); /** Sets the downlink frequency. */ void setFMDownlink(const Frequency &f); /** Sets the uplink frequency. */ void setFMUplink(const Frequency &f); /** Sets the CTCSS tone. */ void setCTCSS(const SelectiveCall &call); /** Sets the APRS downlink frequency. */ void setAPRSDownlink(const Frequency &f); /** Sets the APRS uplink frequency. */ void setAPRSUplink(const Frequency &f); /** Sets the beacon frequency. */ void setBeacon(const Frequency &f); /** Sets the APRS path. */ void setAPRSPath(const QString &path); /** Encodes a satellite. */ virtual bool encode(const Satellite &sat, const ErrorStack &err = ErrorStack()); protected: /** Writes a fixed point value as a BCD number. Using 0-9 as digits, ah as decimal dot and bh * as blank. * @param offset Specifies, where to write the fixed point value. * @param value The value to write. * @param sign If @c true, a sign is written. * @param dec The number of digits in the integer part. * @param frac The number of digits in the fractional part. */ void writeFixedPoint(const Offset::Bit &offset, double value, bool sign, unsigned int dec, unsigned int frac); /** Writes a fixed point value as a BCD number. Using 0-9 as digits and bh as blank. In contrast * to @c writeFixedPoint, this function expects no integer part. * @param offset Specifies, where to write the fixed point value. * @param value The value to write. * @param sign If @c true, a sign is written. * @param frac The number of digits in the fractional part. */ void writeFractional(const Offset::Bit &offset, double value, bool sign, unsigned int frac); /** Write a fixed digit integer value. */ void writeInteger(const Offset::Bit &offset, int value, bool sign, unsigned dec); /** Writes a single digit at the given offset. */ void writeDigit(const Offset::Bit &offset, uint8_t digit); public: /** Some limits for the zone bank. */ struct Limit: public Element::Limit { /** The maximum name length. */ static constexpr unsigned int nameLength() { return 8; } /** Maximum length of the APRS path. */ static constexpr unsigned int pathLength() { return 24; } }; protected: /** Some internal offsets within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr Bit epochYear() { return {0x0008, 4}; } static constexpr Bit epochJulienDay() { return {0x0009, 4}; } static constexpr Bit meanMotionDerivative() { return {0x000f, 4}; } static constexpr Bit inclination() { return {0x0014, 4}; } static constexpr Bit ascension() { return {0x0018, 4}; } static constexpr Bit eccentricity() { return {0x001c, 4}; } static constexpr Bit perigee() { return {0x001f, 0}; } static constexpr Bit meanAnomaly() { return {0x0023, 0}; } static constexpr Bit meanMotion() { return {0x0027, 0}; } static constexpr Bit revolutionNumber() { return {0x002d, 4}; } static constexpr unsigned int fmDownlink() { return 0x0030; } static constexpr unsigned int fmUplink() { return 0x0034; } static constexpr unsigned int ctcss() { return 0x0038; } static constexpr unsigned int aprsDownlink() { return 0x003c; } static constexpr unsigned int aprsUplink() { return 0x0040; } static constexpr unsigned int beacon() { return 0x0044; } static constexpr unsigned int aprsPath() { return 0x004c; } /// @endcond }; }; /** Implements the satellite config bank. Holding all satellites to track. */ class SatelliteBankElement: Codeplug::Element { protected: /** Hidden constructor. */ SatelliteBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ SatelliteBankElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x09e0; } /** The magic number of the element. */ static constexpr unsigned int magic() { return 0x0003; } void clear(); /** Returns the i-th satellite. */ SatelliteElement satellite(unsigned int idx); /** Encodes the given satellite database. */ bool encode(SatelliteDatabase *db, const ErrorStack &err=ErrorStack()); public: /** Some limits for the satellite config. */ struct Limit { /** The maximum number of satellites. */ static constexpr unsigned int satellites() { return 25; } }; protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int blockId() { return 0x0000; } static constexpr unsigned int segmentSize() { return 0x0004; } static constexpr unsigned int satellites() { return 0x0008; } static constexpr unsigned int betweenSatellites() { return SatelliteElement::size(); } /// @endcond }; }; class NoteElement: public Element { public: explicit NoteElement(uint8_t *ptr); /** Size of the element. */ static constexpr unsigned int size() { return 0x0002; } void clear() override; bool isValid() const override; /** Retunrs @c true if the note is a pause. */ bool isPause() const; /** Returns the pitch in Hz. */ double frequency() const; /** Sets the pitch in Hz. */ void setFrequency(double pitch); /** Set note as pause. */ void setPause(); /** Returns the duration in ms. */ unsigned int duration() const; /** Sets the interval. */ void setDuration(unsigned int ms); protected: /** Internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int pitch() { return 0x0000; } static constexpr unsigned int duration() { return 0x0001; } /// @endcond }; static constexpr double _lut[] = { 0110.00, 0116.54, 0123.47, 0130.81, 0138.59, 0146.83, 0155.56, 0164.81, 0174.61, 0185.00, 0196.00, 0207.65, 0220.00, 0233.08, 0246.94, 0261.62, 0277.18, 0293.66, 0311.13, 0329.63, 0349.23, 0369.99, 0392.00, 0415.30, 0440.00, 0466.16, 0493.88, 0523.25, 0554.37, 0587.33, 0622.25, 0659.25, 0698.46, 0739.99, 0783.99, 0830.61, 0880.00, 0932.33, 0987.77, 1046.50, 1108.73, 1174.66, 1244.51, 1318.51, 1396.91 }; }; /** Encodes the boot melody. */ class BootMelodyElement: public Element { public: /** Constructor. */ BootMelodyElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x0208; } /** The magic number of the element. */ static constexpr unsigned int magic() { return 0x0002; } void clear(); /** Encodes the given melody. */ virtual bool encode(Context &ctx, const Melody *melody, const ErrorStack &err=ErrorStack()); /** Decodes the boot melody. */ virtual bool decode(Context &ctx, Melody *melody, const ErrorStack &err=ErrorStack()) const; public: /** Some limits for the melody config. */ struct Limit { /** The maximum number of notes/pauses. */ static constexpr unsigned int notes() { return 256; } }; protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int blockId() { return 0x0000; } static constexpr unsigned int segmentSize() { return 0x0004; } static constexpr unsigned int notes() { return 0x0008; } static constexpr unsigned int betweenNotes() { return NoteElement::size(); } /// @endcond }; }; /** Encodes some additional settings for OpenGD77 based radios. These * settings include boot image, boot melody, satellite orbital elements and themes, if the radio * supports it. */ class AdditionalSettingsElement: public Element { public: enum Settings { BootImage = 1, BootMelody = 2, SatelliteOrbitals = 3, LightTheme = 4, DarkTheme = 5 }; protected: /** Hidden constructor. */ AdditionalSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ AdditionalSettingsElement(uint8_t *ptr); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x11a0; } bool isValid() const; void clear(); /** Returns the magic string. */ virtual QString magic() const; /** Returns the version number. */ virtual unsigned int version() const; /** Returns @c true, if the given settings is stored. */ virtual bool hasSettings(Settings set) const; /** Returns statellite settings bank, if present. If not, a new empty setting is returned. */ virtual SatelliteBankElement satellites() const; /** Returns boot melody settings, if present. If not, a new empty setting is returned. */ virtual BootMelodyElement bootMelody() const; public: /** Some limits for the element. */ struct Limit { /** Size of magic string. */ static constexpr unsigned int magicStringLength() { return 8; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int magicString() { return 0x0000; } static constexpr unsigned int versionNumber() { return 0x0008; } static constexpr unsigned int blocks() { return 0x000c; } // @endcond }; }; protected: /** Default hidden constructor. */ explicit OpenGD77BaseCodeplug(QObject *parent = nullptr); public: /** Clears and resets the complete codeplug to some default values. */ virtual void clear(); bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const; bool decode(Config *config, const ErrorStack &err=ErrorStack()); bool postprocess(Config *config, const ErrorStack &err=ErrorStack()) const; Config *preprocess(Config *config, const ErrorStack &err=ErrorStack()) const; bool encode(Config *config, const Flags &flags = Flags(), const ErrorStack &err=ErrorStack()); public: /** Decodes the binary codeplug and stores its content in the given generic configuration using * the given context. */ virtual bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given generic configuration as a binary codeplug using the given context. */ virtual bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Clears the general settings in the codeplug. */ virtual void clearGeneralSettings() = 0; /** Updates the general settings from the given configuration. */ virtual bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Updates the given configuration from the general settings. */ virtual bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears the DTMF settings. */ virtual void clearDTMFSettings() = 0; /** Encodes DTMF settings. */ virtual bool encodeDTMFSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Decodes the DTMF settings. */ virtual bool decodeDTMFSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears the APRS settings. */ virtual void clearAPRSSettings() = 0; /** Encodes APRS settings. */ virtual bool encodeAPRSSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Decodes the APRS settings. */ virtual bool decodeAPRSSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links the APRS settings. */ virtual bool linkAPRSSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all DTMF contacts in the codeplug. */ virtual void clearDTMFContacts() = 0; /** Encodes all DTMF contacts. */ virtual bool encodeDTMFContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds all DTMF contacts to the configuration. */ virtual bool createDTMFContacts(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clear all channels. */ virtual void clearChannels() = 0; /** Encode all channels. */ virtual bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds all defined channels to the configuration. */ virtual bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all channels. */ virtual bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clear boot settings. */ virtual void clearBootSettings() = 0; /** Encodes boot settings. */ virtual bool encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Decodes the boot settings. */ virtual bool decodeBootSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears the VFO settings. */ virtual void clearVFOSettings() = 0; /** Clears all zones. */ virtual void clearZones() = 0; /** Encodes zones. */ virtual bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds zones to the configuration. */ virtual bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all zones within the configuration. */ virtual bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all contacts in the codeplug. */ virtual void clearContacts() = 0; /** Encodes all digital contacts in the configuration into the codeplug. */ virtual bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds a digital contact to the configuration for each one in the codeplug. */ virtual bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all group lists. */ virtual void clearGroupLists() = 0; /** Encodes all group lists. */ virtual bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Creates all group lists. */ virtual bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all group lists. */ virtual bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; }; #endif // OPENGD77BASE_CODEPLUG_HH ================================================ FILE: lib/opengd77base_satelliteconfig.cc ================================================ #include "opengd77base_satelliteconfig.hh" #include "satellitedatabase.hh" /* ********************************************************************************************* * * Implementation of OpenGD77BaseSatelliteConfig * ********************************************************************************************* */ OpenGD77BaseSatelliteConfig::OpenGD77BaseSatelliteConfig(QObject *parent) : SatelliteConfig{parent} { addImage("OpenGD77 satellite configuration EEPROM"); addImage("OpenGD77 satellite configuration FLASH"); } ================================================ FILE: lib/opengd77base_satelliteconfig.hh ================================================ #ifndef OPENGD77BASE_SATELLITECONFIG_HH #define OPENGD77BASE_SATELLITECONFIG_HH #include #include #include #include #include #include /** Implements the satellite tracking configuration for the OpenGD77 type radios. * @ingroup ogd77 */ class OpenGD77BaseSatelliteConfig : public SatelliteConfig { Q_OBJECT public: /** Possible image types. */ enum ImageType { EEPROM = 0, FLASH = 1 }; public: /** Default constructor. */ explicit OpenGD77BaseSatelliteConfig(QObject *parent = nullptr); /** Size of the image to write. */ static constexpr unsigned int size() { return 0x11a0; } /** Returns @c true, if the additional settings element is valid, that should contain the * satellite settings. */ virtual bool isValid() const = 0; /** Initializes and clears the additional settings element. */ virtual void initialize() = 0; /** Encodes the given satellite database. */ virtual bool encode(SatelliteDatabase *db, const ErrorStack &err=ErrorStack()) = 0; }; #endif // OPENGD77BASE_SATELLITECONFIG_HH ================================================ FILE: lib/openrtx.cc ================================================ #include "openrtx.hh" #include "openrtx_interface.hh" #include "logger.hh" #include "config.hh" #define BSIZE 32 OpenRTX::OpenRTX(OpenRTXInterface *device, QObject *parent) : Radio(parent), _name("Open RTX"), _dev(device), _config(nullptr), _codeplug() { if (! connect()) return; logDebug() << "Connected to radio '" << _name << "'."; } OpenRTX::~OpenRTX() { if (_dev && _dev->isOpen()) { logDebug() << "Closing device."; _dev->reboot(); _dev->close(); } if (_dev) { logDebug() << "Deleting device."; _dev->deleteLater(); _dev = nullptr; } } const QString & OpenRTX::name() const { return _name; } const Codeplug & OpenRTX::codeplug() const { return _codeplug; } Codeplug & OpenRTX::codeplug() { return _codeplug; } RadioInfo OpenRTX::defaultRadioInfo() { return RadioInfo( RadioInfo::OpenRTX, "openrtx", "OpenRTX", {OpenRTXInterface::interfaceInfo()}); } bool OpenRTX::startDownload(bool blocking, const ErrorStack &err) { if (StatusIdle != _task) { errMsg(err) << "Cannot download from radio, radio is not idle."; return false; } _task = StatusDownload; if (blocking) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); // start thread for download start(); return true; } bool OpenRTX::startUpload(Config *config, bool blocking, const Codeplug::Flags &flags, const ErrorStack &err) { Q_UNUSED(flags) logDebug() << "Start upload to " << name() << "..."; if (StatusIdle != _task) { errMsg(err) << "Cannot upload to radio, radio is not idle."; return false; } if (_config) delete _config; if (! (_config = config)) { errMsg(err) << "Cannot upload to radio, no config given."; return false; } _config->setParent(this); _task = StatusUpload; if (blocking) { run(); return (StatusIdle == _task); } // If non-blocking -> move device to this thread if (_dev && _dev->isOpen()) _dev->moveToThread(this); // start thread for upload start(); return true; } bool OpenRTX::startUploadCallsignDB(UserDatabase *db, bool blocking, const CallsignDB::Flags &selection,const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(blocking); Q_UNUSED(selection) errMsg(err) << "OpenRTX has no call-sign DB implemented."; return false; } void OpenRTX::run() { if (StatusDownload == _task) { if (! connect()) { emit downloadError(this); return; } if (! download()) { _task = StatusError; _dev->read_finish(); _dev->reboot(); _dev->close(); emit downloadError(this); return; } _dev->read_finish(); _dev->reboot(); _dev->close(); _task = StatusIdle; emit downloadFinished(this, &_codeplug); _config = nullptr; } else if (StatusUpload == _task) { if (! connect()) { emit uploadError(this); return; } if (! upload()) { _task = StatusError; _dev->write_finish(); _dev->reboot(); _dev->close(); emit uploadError(this); return; } _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadCallsigns == _task) { emit uploadError(this); return; } } bool OpenRTX::connect(const ErrorStack &err) { if (_dev && _dev->isOpen()) return true; if (_dev) _dev->deleteLater(); _dev = new OpenRTXInterface(USBDeviceDescriptor()); if (! _dev->isOpen()) { _task = StatusError; errMsg(err) << "Cannot connect to radio."; _dev->deleteLater(); _dev = nullptr; return false; } return true; } bool OpenRTX::download(const ErrorStack &err) { emit downloadStarted(); if (_codeplug.numImages() != 2) { errMsg(err) << "Cannot download codeplug: Codeplug does not contain two images."; return false; } // Check every segment in the codeplug if (! _codeplug.isAligned(BSIZE)) { errMsg(err) << "Cannot download codeplug: Codeplug is not aligned with blocksize " << BSIZE << "."; return false; } size_t totb = _codeplug.memSize(); if (! _dev->read_start(0, 0, err)) { errMsg(err) << "Cannot start codeplug download."; _dev->close(); return false; } // Then download codeplug size_t bcount = 0; for (int image=0; image<_codeplug.numImages(); image++) { uint32_t bank = 0; for (int n=0; n<_codeplug.image(image).numElements(); n++) { unsigned addr = _codeplug.image(image).element(n).address(); unsigned size = _codeplug.image(image).element(n).data().size(); unsigned b0 = addr/BSIZE, nb = size/BSIZE; for (unsigned b=0; bread(bank, (b0+b)*BSIZE, _codeplug.data((b0+b)*BSIZE, image), BSIZE, err)) { errMsg(err) << "Cannot read block "<< (b0+b) <<"."; return false; } QThread::usleep(100); emit downloadProgress(float(bcount*100)/totb); } } _dev->read_finish(err); } return true; } bool OpenRTX::upload(const ErrorStack &err) { emit uploadStarted(); if (_codeplug.numImages() != 2) { errMsg(err) << "Cannot download codeplug: Codeplug does not contain two images."; return false; } // Check every segment in the codeplug if (! _codeplug.isAligned(BSIZE)) { errMsg(err) << "Cannot upload code-plug: Codeplug is not aligned with blocksize " << BSIZE << "."; return false; } size_t totb = _codeplug.memSize(); if (! _dev->read_start(0, 0, err)) { errMsg(err) << "Cannot start codeplug download."; return false; } // Then download codeplug size_t bcount = 0; for (int image=0; image<_codeplug.numImages(); image++) { uint32_t bank = 0; for (int n=0; n<_codeplug.image(image).numElements(); n++) { unsigned addr = _codeplug.image(image).element(n).address(); unsigned size = _codeplug.image(image).element(n).data().size(); unsigned b0 = addr/BSIZE, nb = size/BSIZE; for (unsigned b=0; bread(bank, (b0+b)*BSIZE, _codeplug.data((b0+b)*BSIZE, image), BSIZE, err)) { errMsg(err) << "Cannot read block " << (b0+b) << "."; return false; } QThread::usleep(100); emit uploadProgress(float(bcount*50)/totb); } } _dev->read_finish(err); } // Encode config into codeplug _codeplug.encode(_config, Codeplug::Flags(), err); if (! _dev->write_start(0,0, err)) { errMsg(err) << "Cannot start codeplug upload."; return false; } // Then upload codeplug for (int image=0; image<_codeplug.numImages(); image++) { uint32_t bank = 0; for (int n=0; n<_codeplug.image(image).numElements(); n++) { unsigned addr = _codeplug.image(image).element(n).address(); unsigned size = _codeplug.image(image).element(n).data().size(); unsigned b0 = addr/BSIZE, nb = size/BSIZE; for (unsigned b=0; bwrite(bank, (b0+b)*BSIZE, _codeplug.data((b0+b)*BSIZE, image), BSIZE, err)) { errMsg(err) << "Cannot write block " << (b0+b) << "."; return false; } QThread::usleep(100); emit uploadProgress(float(bcount*50)/totb); } } _dev->write_finish(err); } return true; } ================================================ FILE: lib/openrtx.hh ================================================ /** @defgroup ortx Open RTX Firmware * Implements a radio running the Open RTX firmware. * @ingroup dsc */ #ifndef OPENGRTX_HH #define OPENGRTX_HH #include "radio.hh" //#include "openrtx_interface.hh" #include "openrtx_codeplug.hh" class OpenRTXInterface; /** Implements an USB interface to radios running the Open RTX firmware. * * @ingroup ortx */ class OpenRTX: public Radio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit OpenRTX(OpenRTXInterface *device=nullptr, QObject *parent=nullptr); virtual ~OpenRTX(); const QString &name() const; const Codeplug &codeplug() const; Codeplug &codeplug(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); public slots: /** Starts the download of the codeplug and derives the generic configuration from it. */ bool startDownload(bool blocking=false, const ErrorStack &err=ErrorStack()); /** Derives the device-specific codeplug from the generic configuration and uploads that * codeplug to the radio. */ bool startUpload(Config *config, bool blocking=false, const Codeplug::Flags &flags = Codeplug::Flags(), const ErrorStack &err=ErrorStack()); /** Encodes the given user-database and uploads it to the device. */ bool startUploadCallsignDB(UserDatabase *db, bool blocking=false, const CallsignDB::Flags &selection=CallsignDB::Flags(), const ErrorStack &err=ErrorStack()); protected: /** Thread main routine, performs all blocking IO operations for codeplug up- and download. */ void run(); /** Connects to the radio, if a radio interface is passed to the constructor, this interface * instance is used. */ bool connect(const ErrorStack &err=ErrorStack()); /** Implements the actual download process. */ bool download(const ErrorStack &err=ErrorStack()); /** Implements the actual codeplug upload process. */ bool upload(const ErrorStack &err=ErrorStack()); protected: /** The device identifier. */ QString _name; /** The interface to the radio. */ OpenRTXInterface *_dev; /** The generic configuration. */ Config *_config; /** The actual binary codeplug representation. */ OpenRTXCodeplug _codeplug; }; #endif // OPENGD77_HH ================================================ FILE: lib/openrtx_codeplug.cc ================================================ #include "openrtx_codeplug.hh" #include "logger.hh" #include "scanlist.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "config.hh" #include "config.h" #include "intermediaterepresentation.hh" #include QVector _openrtx_ctcss_tone_table{ 670, 693, 719, 744, 770, 797, 825, 854, 885, 915, 948, 974, 1000, 1034, 1072, 1109, 1148, 1188, 1230, 1273, 1318, 1365, 1413, 1462, 1514, 1567, 1598, 1622, 1655, 1679, 1713, 1738, 1773, 1799, 1835, 1862, 1899, 1928, 1966, 1995, 2035, 2065, 2107, 2181, 2257, 2291, 2336, 2418, 2503, 2541 }; /* ********************************************************************************************* * * Implementation of OpenRTXCodeplug::HeaderElement * ********************************************************************************************* */ OpenRTXCodeplug::HeaderElement::HeaderElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } OpenRTXCodeplug::HeaderElement::HeaderElement(uint8_t *ptr) : Codeplug::Element(ptr, 0x0058) { // pass... } void OpenRTXCodeplug::HeaderElement::clear() { memset(_data, 0, _size); setUInt64_le(OffsetMagic, MagicNumber); setVersion(); setTimestamp(); } bool OpenRTXCodeplug::HeaderElement::isValid() const { return Codeplug::Element::isValid() && (MagicNumber == getUInt64_le(OffsetMagic)); } uint16_t OpenRTXCodeplug::HeaderElement::version() const { return getUInt16_le(OffsetVersion); } void OpenRTXCodeplug::HeaderElement::setVersion() { setUInt16_le(OffsetVersion, SupportedVersion); } QString OpenRTXCodeplug::HeaderElement::author() const { return readASCII(OffsetAuthor, StringLength, 0); } void OpenRTXCodeplug::HeaderElement::setAuthor(const QString &name) { writeASCII(OffsetAuthor, name, StringLength, 0); } QString OpenRTXCodeplug::HeaderElement::description() const { return readASCII(OffsetDescription, StringLength, 0); } void OpenRTXCodeplug::HeaderElement::setDescription(const QString description) { writeASCII(OffsetDescription, description, StringLength, 0); } QDateTime OpenRTXCodeplug::HeaderElement::timestamp() const { return QDateTime::fromSecsSinceEpoch(getUInt64_le(OffsetTimestamp), QTimeZone::utc()); } void OpenRTXCodeplug::HeaderElement::setTimestamp(const QDateTime timestamp) { setUInt64_le(OffsetTimestamp, timestamp.toUTC().toSecsSinceEpoch()); } unsigned int OpenRTXCodeplug::HeaderElement::contactCount() const { return getUInt16_le(OffsetContactCount); } void OpenRTXCodeplug::HeaderElement::setContactCount(unsigned int n) { setUInt16_le(OffsetContactCount, n); } unsigned int OpenRTXCodeplug::HeaderElement::channelCount() const { return getUInt16_le(OffsetChannelCount); } void OpenRTXCodeplug::HeaderElement::setChannelCount(unsigned int n) { setUInt16_le(OffsetChannelCount, n); } unsigned int OpenRTXCodeplug::HeaderElement::zoneCount() const { return getUInt16_le(OffsetZoneCount); } void OpenRTXCodeplug::HeaderElement::setZoneCount(unsigned int n) { return setUInt16_le(OffsetZoneCount, n); } /* ********************************************************************************************* * * Implementation of OpenRTXCodeplug::ChannelElement * ********************************************************************************************* */ OpenRTXCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } OpenRTXCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : Element(ptr, 0x5a) { // pass... } OpenRTXCodeplug::ChannelElement::~ChannelElement() { // pass... } bool OpenRTXCodeplug::ChannelElement::isValid() const { return Codeplug::Element::isValid() && (Mode_None != mode()); } void OpenRTXCodeplug::ChannelElement::clear() { memset(_data, 0, _size); } OpenRTXCodeplug::Mode OpenRTXCodeplug::ChannelElement::mode() const { return (OpenRTXCodeplug::Mode)getUInt8(OffsetMode); } void OpenRTXCodeplug::ChannelElement::setMode(Mode mode) { setUInt8(Offsets::OffsetMode, (uint8_t)mode); } bool OpenRTXCodeplug::ChannelElement::rxOnly() const { return getBit(OffsetRXOnly, BitRXOnly); } void OpenRTXCodeplug::ChannelElement::setRXOnly(bool enable) { setBit(OffsetRXOnly, BitRXOnly, enable); } OpenRTXCodeplug::ChannelElement::Bandwidth OpenRTXCodeplug::ChannelElement::bandwidth() const { return (Bandwidth)getUInt2(OffsetBandwidth, BitBandwidth); } void OpenRTXCodeplug::ChannelElement::setBandwidth(Bandwidth bw) { setUInt2(OffsetBandwidth, BitBandwidth, bw); } float OpenRTXCodeplug::ChannelElement::power() const { return 10.+0.2*getUInt8(OffsetPower); } void OpenRTXCodeplug::ChannelElement::setPower(float dBm) { if (dBm < 10) dBm = 10; setUInt8(OffsetPower, (uint8_t)((dBm-10)*5)); } Frequency OpenRTXCodeplug::ChannelElement::rxFrequency() const { return Frequency::fromHz(getUInt32_le(OffsetRXFrequency)); } void OpenRTXCodeplug::ChannelElement::setRXFrequency(Frequency MHz) { setUInt32_le(OffsetRXFrequency, MHz.inHz()); } Frequency OpenRTXCodeplug::ChannelElement::txFrequency() const { return Frequency::fromHz(getUInt32_le(OffsetTXFrequency)); } void OpenRTXCodeplug::ChannelElement::setTXFrequency(Frequency MHz) { setUInt32_le(OffsetTXFrequency, MHz.inHz()); } bool OpenRTXCodeplug::ChannelElement::hasScanListIndex() const { return 0 != scanListIndex(); } unsigned int OpenRTXCodeplug::ChannelElement::scanListIndex() const { return getUInt8(OffsetScanList); } void OpenRTXCodeplug::ChannelElement::setScanListIndex(unsigned int index) { setUInt8(OffsetScanList, index); } void OpenRTXCodeplug::ChannelElement::clearScanListIndex() { setScanListIndex(0); } bool OpenRTXCodeplug::ChannelElement::hasGroupListIndex() const { return 0 != groupListIndex(); } unsigned int OpenRTXCodeplug::ChannelElement::groupListIndex() const { return getUInt8(OffsetGroupList); } void OpenRTXCodeplug::ChannelElement::setGroupListIndex(unsigned int index) { setUInt8(OffsetGroupList, index); } void OpenRTXCodeplug::ChannelElement::clearGroupListIndex() { setGroupListIndex(0); } QString OpenRTXCodeplug::ChannelElement::name() const { return readASCII(OffsetName, StringLength, 0x00); } void OpenRTXCodeplug::ChannelElement::setName(const QString &name) { writeASCII(OffsetName, name, StringLength, 0x00); } QString OpenRTXCodeplug::ChannelElement::description() const { return readASCII(OffsetDescription, StringLength, 0x00); } void OpenRTXCodeplug::ChannelElement::setDescription(const QString &description) { writeASCII(OffsetDescription, description, StringLength, 0x00); } float OpenRTXCodeplug::ChannelElement::latitude() const { return getInt8(OffsetChLatInt) + ((float)getUInt16_le(OffsetChLatDec))/65536; } void OpenRTXCodeplug::ChannelElement::setLatitude(float lat) { setInt8(OffsetChLatInt, (int)lat); setUInt16_le(OffsetChLatDec, std::abs(lat-((int)lat))*65536); } float OpenRTXCodeplug::ChannelElement::longitude() const { return getInt8(OffsetChLonInt) + ((float)getUInt16_le(OffsetChLonDec))/65536; } void OpenRTXCodeplug::ChannelElement::setLongitude(float lon) { setInt8(OffsetChLonInt, (int)lon); setUInt16_le(OffsetChLonDec, std::abs(lon-((int)lon))*65536); } unsigned int OpenRTXCodeplug::ChannelElement::altitude() const { return getUInt16_le(OffsetChAltitude); } void OpenRTXCodeplug::ChannelElement::setAltitude(unsigned int alt) { setUInt16_le(OffsetChAltitude, alt); } SelectiveCall OpenRTXCodeplug::ChannelElement::rxTone() const { if (! getBit(OffsetRXTone, 0)) return SelectiveCall(); int idx = getUInt8(OffsetRXTone)>>1; if (idx >= _openrtx_ctcss_tone_table.size()) return SelectiveCall(); return SelectiveCall(double(_openrtx_ctcss_tone_table[idx])/10); } void OpenRTXCodeplug::ChannelElement::setRXTone(const SelectiveCall &code, const ErrorStack &err) { if (code.isInvalid()) { setBit(OffsetRXTone, 0, false); return; } if (! code.isCTCSS()) { errMsg(err) << "Can only encode CTCSS tones."; setBit(OffsetRXTone, 0, false); return; } if (! _openrtx_ctcss_tone_table.contains((unsigned int)(code.Hz()*10))) { errMsg(err) << "Cannot encode CTCSS frequency " << code.Hz() << "Hz: " << "Not supported."; setBit(OffsetRXTone, 0, false); return; } uint8_t index = _openrtx_ctcss_tone_table.indexOf( (unsigned int)(code.Hz()*10)); setUInt8(OffsetRXTone, (index<<1)|1); } SelectiveCall OpenRTXCodeplug::ChannelElement::txTone() const { if (! getBit(OffsetTXTone, 0)) return SelectiveCall(); int idx = getUInt8(OffsetTXTone)>>1; if (idx >= _openrtx_ctcss_tone_table.size()) return SelectiveCall(); return SelectiveCall(float(_openrtx_ctcss_tone_table[idx])/10); } void OpenRTXCodeplug::ChannelElement::setTXTone(const SelectiveCall &code, const ErrorStack &err) { if (code.isInvalid()) { setBit(OffsetRXTone, 0, false); return; } if (! code.isCTCSS()) { errMsg(err) << "Can only encode CTCSS tones."; setBit(OffsetRXTone, 0, false); return; } if (! _openrtx_ctcss_tone_table.contains((unsigned int)(code.Hz()*10))) { errMsg(err) << "Cannot encode CTCSS frequency " << code.Hz() << "Hz: " << "Not supported."; setBit(OffsetRXTone, 0, false); return; } uint8_t index = _openrtx_ctcss_tone_table.indexOf( (unsigned int)(code.Hz()*10)); setUInt8(OffsetTXTone, (index<<1)|1); } unsigned int OpenRTXCodeplug::ChannelElement::rxColorCode() const { return getUInt4(OffsetRXColorCode, BitRXColorCode); } void OpenRTXCodeplug::ChannelElement::setRXColorCode(unsigned int cc) { setUInt4(OffsetRXColorCode, BitRXColorCode, cc); } unsigned int OpenRTXCodeplug::ChannelElement::txColorCode() const { return getUInt4(OffsetTXColorCode, BitTXColorCode); } void OpenRTXCodeplug::ChannelElement::setTXColorCode(unsigned int cc) { setUInt4(OffsetTXColorCode, BitTXColorCode, cc); } DMRChannel::TimeSlot OpenRTXCodeplug::ChannelElement::timeslot() const { if (1 == getUInt8(OffsetTimeSlot)) return DMRChannel::TimeSlot::TS1; return DMRChannel::TimeSlot::TS2; } void OpenRTXCodeplug::ChannelElement::setTimeslot(DMRChannel::TimeSlot ts) { if (DMRChannel::TimeSlot::TS1 == ts) setUInt8(OffsetTimeSlot, 1); else setUInt8(OffsetTimeSlot, 2); } bool OpenRTXCodeplug::ChannelElement::hasDMRContactIndex() const { return 0 != dmrContactIndex(); } unsigned int OpenRTXCodeplug::ChannelElement::dmrContactIndex() const { return getUInt16_le(OffsetDMRContact); } void OpenRTXCodeplug::ChannelElement::setDMRContactIndex(unsigned int idx) { setUInt16_le(OffsetDMRContact, idx); } void OpenRTXCodeplug::ChannelElement::clearDMRContactIndex() { setDMRContactIndex(0); } unsigned int OpenRTXCodeplug::ChannelElement::rxChannelAccessNumber() const { return getUInt4(OffsetRXCAN, BitRXCAN); } void OpenRTXCodeplug::ChannelElement::setRXChannelAccessNumber(unsigned int cc) { setUInt4(OffsetRXCAN, BitRXCAN, cc); } unsigned int OpenRTXCodeplug::ChannelElement::txChannelAccessNumber() const { return getUInt4(OffsetTXCAN, BitTXCAN); } void OpenRTXCodeplug::ChannelElement::setTXChannelAccessNumber(unsigned int cc) { setUInt4(OffsetTXCAN, BitTXCAN, cc); } OpenRTXCodeplug::ChannelElement::EncryptionMode OpenRTXCodeplug::ChannelElement::encryptionMode() const { return (EncryptionMode)getUInt4(OffsetEncrMode, BitEncrMode); } void OpenRTXCodeplug::ChannelElement::setEncryptionMode(EncryptionMode mode) { return setUInt4(OffsetEncrMode, BitEncrMode, mode); } OpenRTXCodeplug::ChannelElement::ChannelMode OpenRTXCodeplug::ChannelElement::channelMode() const { return (ChannelMode) getUInt4(OffsetM17ChMode, BitM17ChMode); } void OpenRTXCodeplug::ChannelElement::setChannelMode(ChannelMode mode) { setUInt4(OffsetM17ChMode, BitM17ChMode, mode); } bool OpenRTXCodeplug::ChannelElement::gpsDataEnabled() const { return 1 == getUInt8(OffsetM17GPSMode); } void OpenRTXCodeplug::ChannelElement::enableGPSData(bool enable) { if (enable) setUInt8(OffsetM17GPSMode, 1); else setUInt8(OffsetM17GPSMode, 0); } bool OpenRTXCodeplug::ChannelElement::hasM17ContactIndex() const { return 0 != m17ContactIndex(); } unsigned int OpenRTXCodeplug::ChannelElement::m17ContactIndex() const { return getUInt16_le(OffsetM17Contact); } void OpenRTXCodeplug::ChannelElement::setM17ContactIndex(unsigned int idx) { setUInt16_le(OffsetM17Contact, idx); } void OpenRTXCodeplug::ChannelElement::clearM17ContactIndex() { setM17ContactIndex(0); } Channel * OpenRTXCodeplug::ChannelElement::toChannelObj(Codeplug::Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx) if (! isValid()) { errMsg(err) << "Cannot decode invalid channel."; return nullptr; } if (Mode_M17 == mode()) { errMsg(err) << "Cannot decode M17 channel. Not implemented yet."; return nullptr; } Channel *ch = nullptr; if (Mode_FM == mode()) { FMChannel *an = new FMChannel(); ch = an; switch(bandwidth()) { case BW_12_5kHz: an->setBandwidth(FMChannel::Bandwidth::Narrow); break; case BW_20kHz: case BW_25kHz: an->setBandwidth(FMChannel::Bandwidth::Wide); break; } an->setRXTone(rxTone()); an->setTXTone(txTone()); } else if (Mode_DMR == mode()) { DMRChannel *dmr = new DMRChannel(); ch = dmr; dmr->setAdmit(DMRChannel::Admit::ColorCode); dmr->setColorCode(rxColorCode()); dmr->setTimeSlot(timeslot()); } // Common settings ch->setName(name()); ch->setRXOnly(rxOnly()); if (30 > power()) { // less than 30dBm (1W) min ch->setPower(Channel::Power::Min); } else if (34 > power()) { ch->setPower(Channel::Power::Low); } else if (37 > power()) { ch->setPower(Channel::Power::Mid); } else if (38 > power()) { ch->setPower(Channel::Power::High); } else { ch->setPower(Channel::Power::Max); } ch->setRXFrequency(rxFrequency()); ch->setTXFrequency(txFrequency()); return ch; } bool OpenRTXCodeplug::ChannelElement::linkChannelObj(Channel *c, Context &ctx, const ErrorStack &err) const { if (! isValid()) { errMsg(err) << "Cannot link invalid channel '" << c->name() << "'."; return false; } if (Mode_M17 == mode()) { errMsg(err) << "Cannot link M17 channel '" << c->name() << "', not implemented yet."; return false; } else if (Mode_DMR == mode()) { DMRChannel *dmr = c->as(); // Link group list, if set /*if (hasGroupListIndex()) { if (! ctx.has(groupListIndex())) { errMsg(err) << "Cannot link group list index " << groupListIndex() << " for channel '" << c->name() << "': Index not found."; return false; } dmr->setGroupListObj(ctx.get(groupListIndex())); }*/ // Link contact, if set if (hasDMRContactIndex()) { if (! ctx.has(dmrContactIndex())) { errMsg(err) << "Cannot link DMR contact index " << dmrContactIndex() << " for channel '" << c->name() << "': Index not found."; return false; } dmr->setContact(ctx.get(dmrContactIndex())); } } // Link scan list, if set /*if (hasScanListIndex()) { if (! ctx.has(scanListIndex())) { errMsg(err) << "Cannot link scan list index " << scanListIndex() << " for channel '" << c->name() << "': Index not found."; return false; } c->setScanList(ctx.get(scanListIndex())); }*/ return true; } bool OpenRTXCodeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx, const ErrorStack &err) { clear(); setName(c->name()); setRXOnly(c->rxOnly()); switch (c->power()) { case Channel::Power::Min: setPower(27); break; case Channel::Power::Low: setPower(30); break; case Channel::Power::Mid: setPower(34); break; case Channel::Power::High: setPower(37); break; case Channel::Power::Max: setPower(38.5); break; } setRXFrequency(c->rxFrequency()); setTXFrequency(c->txFrequency()); if (c->scanListRef()->isNull()) clearScanListIndex(); else setScanListIndex(ctx.index(c->scanList())); clearGroupListIndex(); if (c->is()) { const FMChannel *fm = c->as(); setMode(Mode_FM); setRXTone(fm->rxTone(), err); setTXTone(fm->txTone(), err); } else if (c->is()) { const DMRChannel *dmr = c->as(); setMode(Mode_DMR); if (! dmr->groupListRef()->isNull()) setGroupListIndex(ctx.index(dmr->groupList())); setTXColorCode(dmr->colorCode()); setRXColorCode(dmr->colorCode()); setTimeslot(dmr->timeSlot()); if (! dmr->contactRef()->isNull()) setDMRContactIndex(ctx.index(dmr->contact())); } return true; } /* ********************************************************************************************* * * Implementation of OpenRTXCodeplug::ContactElement * ********************************************************************************************* */ OpenRTXCodeplug::ContactElement::ContactElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } OpenRTXCodeplug::ContactElement::ContactElement(uint8_t *ptr) : Element(ptr, 0x0027) { // pass... } OpenRTXCodeplug::ContactElement::~ContactElement() { // pass... } void OpenRTXCodeplug::ContactElement::clear() { memset(_data, 0, _size); } bool OpenRTXCodeplug::ContactElement::isValid() const { return Codeplug::Element::isValid() && ( (Mode_M17 == mode()) || (Mode_DMR == mode()) ); } QString OpenRTXCodeplug::ContactElement::name() const { return readASCII(OffsetName, StringLength, 0); } void OpenRTXCodeplug::ContactElement::setName(const QString &name) { writeASCII(OffsetName, name, StringLength, 0); } OpenRTXCodeplug::Mode OpenRTXCodeplug::ContactElement::mode() const { return (Mode) getUInt8(OffsetMode); } void OpenRTXCodeplug::ContactElement::setMode(Mode mode) { setUInt8(OffsetMode, mode); } unsigned int OpenRTXCodeplug::ContactElement::dmrId() const { return getUInt32_le(OffsetDMRId); } void OpenRTXCodeplug::ContactElement::setDMRId(unsigned int id) { setUInt32_le(OffsetDMRId, id); } bool OpenRTXCodeplug::ContactElement::dmrRing() const { return getBit(OffsetDMRRing, BitDMRRing); } void OpenRTXCodeplug::ContactElement::enableDMRRing(bool enable) { return setBit(OffsetDMRRing, BitDMRRing, enable); } DMRContact::Type OpenRTXCodeplug::ContactElement::dmrContactType() const { // This is not specified yet?!? return (DMRContact::Type)getUInt2(OffsetDMRCallType, BitDMRCallType); } void OpenRTXCodeplug::ContactElement::setDMRContactType(DMRContact::Type type) { setUInt2(OffsetDMRCallType, BitDMRCallType, type); } QString OpenRTXCodeplug::ContactElement::m17Call() const { static const char charMap[] = "xABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-/."; uint64_t encoded; memcpy(((uint8_t *)&encoded)+2, _data+OffsetM17Address, 6); QString result; while (encoded) { result.append(charMap[encoded%40]); encoded /= 40; } return result; } bool OpenRTXCodeplug::ContactElement::setM17Call(const QString &call, const ErrorStack &err) { if (call.size() > 9) { errMsg(err) << "Cannot encode calls longer than 9 chars."; return false; } QString C = call.toUpper(); uint64_t encoded = 0; for (QString::const_reverse_iterator it=C.rbegin(); it!=C.rend(); it++) { encoded *= 40; char c = QChar(*it).toLatin1(); if (('A' <= c) && ('Z' >= c)) encoded += (c-'A')+1; else if (('0' <= c) && ('9' >= c)) encoded += (c-'0')+27; else if ('-' == c) encoded += 37; else if ('/' == c) encoded += 38; else if ('.' == c) encoded += 39; else { errMsg(err) << "Invalid char '" << *it << "' for an M17 call [A-Z,0-9,-,/,.]."; return false; } } memcpy(_data+OffsetM17Address, ((uint8_t*)&encoded)+2, 6); return true; } DigitalContact *OpenRTXCodeplug::ContactElement::toContactObj(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx) if (! isValid()) { errMsg(err) << "Cannot decode invalid contacts."; return nullptr; } if (Mode_DMR == mode()) { DMRContact *contact = new DMRContact(); contact->setName(name()); contact->setRing(dmrRing()); contact->setNumber(dmrId()); contact->setType(dmrContactType()); return contact; } else if (Mode_M17 == mode()) { M17Contact *contact = new M17Contact(); contact->setName(name()); contact->setRing(dmrRing()); contact->setCall(m17Call()); return contact; } return nullptr; } bool OpenRTXCodeplug::ContactElement::fromContactObj(const DigitalContact *cont, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); setName(cont->name()); enableDMRRing(cont->ring()); if (cont->is()) { const DMRContact *dmr = cont->as(); setMode(Mode_DMR); setDMRId(dmr->number()); setDMRContactType(dmr->type()); } else if (cont->is()) { const M17Contact *m17 = cont->as(); setM17Call(m17->call()); } else { errMsg(err) << "Cannot encode contact '" << cont->name() << "': Contact type '" << cont->metaObject()->className() << "' not supported by OpenRTX firmware."; return false; } return true; } /* ********************************************************************************************* * * Implementation of OpenRTXCodeplug::ZoneElement * ********************************************************************************************* */ OpenRTXCodeplug::ZoneElement::ZoneElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } OpenRTXCodeplug::ZoneElement::ZoneElement(uint8_t *ptr) : Element(ptr, 0x0022) { _size = 0x22 + channelCount()*4; } OpenRTXCodeplug::ZoneElement::~ZoneElement() { // pass... } void OpenRTXCodeplug::ZoneElement::clear() { memset(_data, 0, 0x22); setChannelCount(0); } bool OpenRTXCodeplug::ZoneElement::isValid() const { return Codeplug::Element::isValid(); } QString OpenRTXCodeplug::ZoneElement::name() const { return readASCII(OffsetName, StringLength, 0); } void OpenRTXCodeplug::ZoneElement::setName(const QString &name) { writeASCII(OffsetName, name, StringLength, 0); } unsigned int OpenRTXCodeplug::ZoneElement::channelCount() const { return getUInt16_le(OffsetCount); } void OpenRTXCodeplug::ZoneElement::setChannelCount(unsigned int n) { setUInt16_le(OffsetCount, n); _size = 0x22 + sizeof(uint32_t)*n; } unsigned int OpenRTXCodeplug::ZoneElement::channelIndex(unsigned int n) const { return getUInt32_le(OffsetChannel + sizeof(uint32_t)*n); } void OpenRTXCodeplug::ZoneElement::setChannelIndex(unsigned int n, unsigned int idx) { setUInt32_le(OffsetChannel + sizeof(uint32_t)*n, idx); } Zone * OpenRTXCodeplug::ZoneElement::toZoneObj(Context &ctx) const { Q_UNUSED(ctx) Zone *zone = new Zone(); zone->setName(name()); return zone; } bool OpenRTXCodeplug::ZoneElement::linkZoneObj(Zone *zone, Context &ctx, bool putInB, const ErrorStack &err) const { for (unsigned int i=0; i(channelIndex(i))) { errMsg(err) << "Cannot link zone '" << zone->name() << "': Channel index " << channelIndex(i) << " not known."; return false; } if (putInB) zone->B()->add(ctx.get(channelIndex(i))); else zone->A()->add(ctx.get(channelIndex(i))); } return true; } void OpenRTXCodeplug::ZoneElement::fromZoneObjA(const Zone *zone, Context &ctx) { if (zone->B()->count()) setName(zone->name()+" A"); else setName(zone->name()); setChannelCount(zone->A()->count()); for (int i=0; iA()->count(); i++) { setChannelIndex(i, ctx.index(zone->A()->get(i))); } } void OpenRTXCodeplug::ZoneElement::fromZoneObjB(const Zone *zone, Context &ctx) { setName(zone->name() + " B"); setChannelCount(zone->B()->count()); for (int i=0; iB()->count(); i++) { setChannelIndex(i, ctx.index(zone->B()->get(i))); } } /* ********************************************************************************************* * * Implementation of OpenRTXCodeplug * ********************************************************************************************* */ OpenRTXCodeplug::OpenRTXCodeplug(QObject *parent) : Codeplug(parent) { addImage("OpenRTX codeplug v0.1"); image(0).addElement(0x0000, HeaderSize); } OpenRTXCodeplug::~OpenRTXCodeplug() { // pass... } void OpenRTXCodeplug::clear() { remImage(0); addImage("OpenRTX codeplug v0.1"); image(0).addElement(0x0000, HeaderSize); } bool OpenRTXCodeplug::index(Config *config, Context &ctx, const ErrorStack &err) const { Q_UNUSED(err) // All indices as 1-based. That is, the first channel gets index 1 etc. // Map DMR contacts for (int i=0, c=0; icontacts()->count(); i++) { Contact *contact = config->contacts()->contact(i); if (contact->is() || contact->is()) { ctx.add(contact, c+1); c++; } else { logInfo() << "Cannot index contact '" << contact->name() << "'. Contact type '" << contact->metaObject()->className() << "' not supported by or implemented for OpenRTX devices."; } } // Map channels for (int i=0; ichannelList()->count(); i++) { Channel *channel = config->channelList()->channel(i); ctx.add(channel, i+1); } // Map zones for (int i=0; izones()->count(); i++) ctx.add(config->zones()->zone(i), i+1); return true; } Config * OpenRTXCodeplug::preprocess(Config *config, const ErrorStack &err) const { Config *intermediate = Codeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot pre-process OpenRTX codeplug."; return nullptr; } // Remove all AM channels ObjectFilterVisitor amFilter{AMChannel::staticMetaObject}; if (! amFilter.process(intermediate, err)) { errMsg(err) << "Remove AM channels."; delete intermediate; return nullptr; } ZoneSplitVisitor splitter; if (! splitter.process(intermediate, err)) { errMsg(err) << "Cannot split zone for OpenRTX codeplug."; delete intermediate; return nullptr; } return intermediate; } bool OpenRTXCodeplug::encode(Config *config, const Flags &flags, const ErrorStack &err) { // Check if default DMR id is set. if (nullptr == config->settings()->defaultId()) { errMsg(err) << "Cannot encode OpenRTX codeplug: No default radio ID specified."; return false; } // Create index<->object table. Context ctx(config); if (! index(config, ctx, err)) return false; return this->encodeElements(flags, ctx, err); } bool OpenRTXCodeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { HeaderElement header(data(0)); header.clear(); header.setAuthor(ctx.config()->settings()->defaultId()->name()); header.setDescription("Encoded by qdmr v" VERSION_STRING); // Define Contacts if (! this->encodeContacts(ctx.config(), flags, ctx, err)) { errMsg(err) << "Cannot encode contacts."; return false; } if (! this->encodeChannels(ctx.config(), flags, ctx, err)) { errMsg(err) << "Cannot encode channels."; return false; } if (! this->encodeZones(ctx.config(), flags, ctx, err)) { errMsg(err) << "Cannot encode zones."; return false; } return true; } bool OpenRTXCodeplug::decode(Config *config, const ErrorStack &err) { // Clear config object config->clear(); // Create index<->object table. Context ctx(config); return this->decodeElements(ctx, err); } bool OpenRTXCodeplug::postprocess(Config *config, const ErrorStack &err) const { if (! Codeplug::postprocess(config, err)) { errMsg(err) << "Cannot post-process Radioddy codeplug."; return false; } ZoneMergeVisitor merger; if (! merger.process(config, err)) { errMsg(err) << "Cannot merg zones in decoded Radioddity codeplug."; return false; } return true; } bool OpenRTXCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { if (! this->createContacts(ctx.config(), ctx, err)) { errMsg(err) << "Cannot create contacts."; return false; } if (! this->createChannels(ctx.config(), ctx, err)) { errMsg(err) << "Cannot create channels."; return false; } if (! this->createZones(ctx.config(), ctx, err)) { errMsg(err) << "Cannot create zones."; return false; } if (! this->linkChannels(ctx.config(), ctx, err)) { errMsg(err) << "Cannot link channels."; return false; } if (! this->linkZones(ctx.config(), ctx, err)) { errMsg(err) << "Cannot link zones."; return false; } return true; } unsigned int OpenRTXCodeplug::numContacts() { return HeaderElement(data(0x0000)).contactCount(); } unsigned int OpenRTXCodeplug::offsetContact(unsigned int n) { return HeaderSize + n*ContactSize; } bool OpenRTXCodeplug::encodeContacts(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(config); Q_UNUSED(flags) /// @todo Limit number of contacts. unsigned int numContacts = ctx.count(); HeaderElement(data(0x0000)).setContactCount(numContacts); image(0).addElement(offsetContact(0), numContacts*ContactSize); for (unsigned int i=0; i(); i++) { ContactElement contact(data(offsetContact(i))); if (! contact.fromContactObj(ctx.get(i+1), ctx, err)) { errMsg(err) << "Cannot encode contact '" << ctx.get(i+1)->name() <<"'."; return false; } } return true; } bool OpenRTXCodeplug::createContacts(Config *config, Context &ctx, const ErrorStack &err) { unsigned int numContacts = HeaderElement(data(0x0000)).contactCount(); for (unsigned int i=0; icontacts()->add(contact); ctx.add(contact, i+1); } return true; } unsigned int OpenRTXCodeplug::numChannels() { return HeaderElement(data(0x0000)).channelCount(); } unsigned int OpenRTXCodeplug::offsetChannel(unsigned int n) { unsigned int numContacts = HeaderElement(data(0x0000)).contactCount(); return HeaderSize + numContacts*ContactSize + n*ChannelSize; } bool OpenRTXCodeplug::encodeChannels(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags) /// @todo Limit number of channels. unsigned int numChannels = config->channelList()->count(); HeaderElement(data(0x0000)).setChannelCount(numChannels); image(0).addElement(offsetChannel(0), numChannels*ChannelSize); for (int i=0; ichannelList()->count(); i++) { ChannelElement ch(data(offsetChannel(i))); if (! ch.fromChannelObj(config->channelList()->channel(i), ctx, err)) { errMsg(err) << "Cannot encode " << (i+1) << "-th channel '" << config->channelList()->channel(i)->name() << "'."; return false; } ctx.add(config->channelList()->channel(i), i+1); } return true; } bool OpenRTXCodeplug::createChannels(Config *config, Context &ctx, const ErrorStack &err) { unsigned int numChannels = HeaderElement(data(0x0000)).channelCount(); unsigned int offsetChannels = offsetChannel(0); for (unsigned int i=0; ichannelList()->add(chObj); ctx.add(chObj, i+1); } return true; } bool OpenRTXCodeplug::linkChannels(Config *config, Context &ctx, const ErrorStack &err) { unsigned int numChannels = HeaderElement(data(0x0000)).channelCount(); unsigned int offsetChannels = offsetChannel(0); for (unsigned int i=0; ichannelList()->channel(i); if (! ch.linkChannelObj(chObj, ctx, err)) { errMsg(err) << "Cannot link " << (i+1) << "-th channel " << chObj->name() << "."; return false; } } return true; } unsigned int OpenRTXCodeplug::numZones() { return HeaderElement(data(0x0000)).zoneCount(); } unsigned int OpenRTXCodeplug::offsetZoneOffsets() { HeaderElement header(data(0x0000)); unsigned int numContacts = header.contactCount(); unsigned int numChannels = header.channelCount(); return HeaderSize + numContacts*ContactSize + numChannels*ChannelSize; } unsigned int OpenRTXCodeplug::offsetZone(unsigned int n) { HeaderElement header(data(0x0000)); unsigned int numContacts = header.contactCount(); unsigned int numChannels = header.channelCount(); uint32_t *ptr = (uint32_t *)data(HeaderSize + numContacts*ContactSize + numChannels*ChannelSize + n*sizeof(uint32_t)); return qFromLittleEndian(*ptr); } bool OpenRTXCodeplug::encodeZones(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) // Allocate zone offsets HeaderElement(data(0x0000)).setZoneCount(config->zones()->count()); image(0).addElement(offsetZoneOffsets(), (config->zones()->count())*sizeof(uint32_t)); uint32_t *offsets = (uint32_t *)data(offsetZoneOffsets()); // Allocate and encode zones uint32_t currentOffset = offsetZoneOffsets() + (config->zones()->count())*sizeof(uint32_t); for (int z=0, i=0; izones()->count(); i++,z++) { // Allocate & encode zone A unsigned int zoneSize = ZoneHeaderSize+config->zones()->zone(z)->A()->count()*sizeof(uint32_t); image(0).addElement(currentOffset, zoneSize); offsets[i] = qToLittleEndian(currentOffset); ZoneElement(data(currentOffset)).fromZoneObjA(config->zones()->zone(z), ctx); currentOffset += zoneSize; } return true; } bool OpenRTXCodeplug::createZones(Config *config, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) unsigned int zoneCount = numZones(); uint32_t *zoneOffsets = (uint32_t *) data(offsetZoneOffsets()); for (unsigned int i=0; izones()->add(obj); ctx.add(obj, i+1); } return true; } bool OpenRTXCodeplug::linkZones(Config *config, Context &ctx, const ErrorStack &err) { Q_UNUSED(config); Q_UNUSED(err) uint32_t *zoneOffsets = (uint32_t *) data(offsetZoneOffsets()); for (unsigned int i=0, z=0; i(i+1)) continue; Zone *obj = ctx.get(i+1); for (unsigned int i=0; i(zone.channelIndex(i))) { logWarn() << "Cannot link channel with index " << zone.channelIndex(i) << " channel not defined."; continue; } obj->A()->add(ctx.get(zone.channelIndex(i))); } } return true; } ================================================ FILE: lib/openrtx_codeplug.hh ================================================ #ifndef OPENRTX_CODEPLUG_HH #define OPENRTX_CODEPLUG_HH #include "codeplug.hh" #include "signaling.hh" #include "channel.hh" #include "contact.hh" class DMRContact; class Zone; class RXGroupList; class ScanList; /** Implements the binary encoding and decoding of the OpenRTX codeplug. * * @section ortxcpl Codeplug structure within radio * The binary codeplug does not use fixed offsets. It is just a concatenation arrays of codeplug * elements. This codeplug implements the revision 0.1. * * * * * * * *
Content
Header see @c HeaderElement.
Max. 65536 contacts as specified in the header. See @c ContactElement.
Max. 65536 channels as specified in the header. See @c ChannelElement.
Max. 65536 bank (zone) offsets as specified in the header. Each offset is stored as a * ??? and specifies the offset to the bank w.r.t. ??? in ???.
Max. 65536 banks (zones). See @c ZoneElement.
* * @ingroup ortx */ class OpenRTXCodeplug : public Codeplug { Q_OBJECT public: /** Possible modes for a channel or contact. */ enum Mode { Mode_None = 0, ///< Disabled? Mode_FM = 1, ///< FM Channel. Mode_DMR = 2, ///< DMR Channel. Mode_M17 = 3 ///< M17 Channel. }; public: /** Implements the codeplug header element. * * Binary representation of the header (size 0058h bytes): * @verbinclude openrtx_header.txt */ class HeaderElement: public Codeplug::Element { protected: /** Hidden constructor. */ HeaderElement(uint8_t *ptr, size_t size); public: /** Constructor. */ HeaderElement(uint8_t *ptr); void clear(); bool isValid() const; /** Returns the version number (MAJOR<<8)|MINOR. */ virtual uint16_t version() const; /** Sets the version number. This number is fixed to 0.1, the supported version. */ virtual void setVersion(); /** Returns the author name. */ virtual QString author() const; /** Sets the author name. */ virtual void setAuthor(const QString &name); /** Returns the description. */ virtual QString description() const; /** Sets the description. */ virtual void setDescription(const QString description); /** Returns the timestamp. */ virtual QDateTime timestamp() const; /** Sets the timestamp. */ virtual void setTimestamp(const QDateTime timestamp=QDateTime::currentDateTime()); /** Returns the contact count. */ virtual unsigned int contactCount() const; /** Sets the contact count. */ virtual void setContactCount(unsigned int n); /** Returns the channel count. */ virtual unsigned int channelCount() const; /** Sets the channel count. */ virtual void setChannelCount(unsigned int n); /** Returns the zone count. */ virtual unsigned int zoneCount() const; /** Sets the zone count. */ virtual void setZoneCount(unsigned int n); protected: /** Just holds the offsets within the header and other constants. */ enum Offsets { OffsetMagic = 0x00, MagicNumber = 0x43585452, OffsetVersion = 0x08, OffsetAuthor = 0x0a, OffsetDescription = 0x2a, OffsetTimestamp = 0x4a, OffsetContactCount = 0x52, OffsetChannelCount = 0x54, OffsetZoneCount = 0x56, StringLength = 0x20, SupportedVersion = ((0<<8)|1) }; }; /** Implements the binary representation of a channel. * * Binary representation (size 005ah bytes): * @verbinclude openrtx_channel.txt */ class ChannelElement: public Codeplug::Element { public: /** Specifies the possible bandwidth settings. */ enum Bandwidth { BW_12_5kHz = 0, BW_20kHz = 1, BW_25kHz = 2 }; /** Specifies the DMR time slot settings. */ enum Timeslot { Timeslot1 = 1, Timeslot2 = 2 }; /** Specifies the M17 channel mode. */ enum ChannelMode { M17Voice = 1, M17Data = 2, M17VoiceData = 3 }; /** Specifies the M17 encryption modes. */ enum EncryptionMode { EncrNone = 0, EncrAES256 = 1, EncrScrambler = 2 }; protected: /** Constructs a channel from the given memory. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructs a channel from the given memory. */ explicit ChannelElement(uint8_t *ptr); /** Destructor. */ virtual ~ChannelElement(); bool isValid() const; /** Resets the channel. */ virtual void clear(); /** Returns the channel mode. */ virtual Mode mode() const; /** Sets the channel mode. */ virtual void setMode(Mode mode); /** Returns @c true, if the channel is RX only. */ virtual bool rxOnly() const; /** Enables/disables RX only for the channel. */ virtual void setRXOnly(bool enable); /** Returns the bandwidth of the channel. */ virtual Bandwidth bandwidth() const; /** Sets the bandwidth of the channel. */ virtual void setBandwidth(Bandwidth bw); /** Power in dBm. */ virtual float power() const; /** Set power in dBm. */ virtual void setPower(float dBm); /** Returns the RX frequency in MHz. */ virtual Frequency rxFrequency() const; /** Sets the RX frequency in MHz. */ virtual void setRXFrequency(Frequency MHz); /** Returns the TX frequency in MHz. */ virtual Frequency txFrequency() const; /** Sets the TX frequency in MHz. */ virtual void setTXFrequency(Frequency MHz); /** Retrusn @c true if the scan list is set. */ virtual bool hasScanListIndex() const; /** Returns the scan list index. */ virtual unsigned int scanListIndex() const; /** Sets the scan list index. */ virtual void setScanListIndex(unsigned int index); /** Clears the scan list index. */ virtual void clearScanListIndex(); /** Retrusn @c true if the group list is set. */ virtual bool hasGroupListIndex() const; /** Returns the group list index. */ virtual unsigned int groupListIndex() const; /** Sets the group list index. */ virtual void setGroupListIndex(unsigned int index); /** Clears the group list index. */ virtual void clearGroupListIndex(); /** Returns the channel name. */ virtual QString name() const; /** Sets the channel name. */ virtual void setName(const QString &name); /** Returns the channel description. */ virtual QString description() const; /** Sets the channel description. */ virtual void setDescription(const QString &description); /** Returns the channel latitude. */ virtual float latitude() const; /** Sets the latitude. */ virtual void setLatitude(float lat); /** Returns the channel longitude. */ virtual float longitude() const; /** Sets the longitude. */ virtual void setLongitude(float lat); /** Returns the height in meters. */ virtual unsigned int altitude() const; /** Sets the height in meters. */ virtual void setAltitude(unsigned int alt); /** Returns the CTCSS RX sub-tone. Only valid for FM channels. */ virtual SelectiveCall rxTone() const; /** Sets the CTCSS RX sub-tone. Only valid for FM channels. */ virtual void setRXTone(const SelectiveCall &code, const ErrorStack &err=ErrorStack()); /** Returns the CTCSS TX sub-tone. Only valid for FM channels. */ virtual SelectiveCall txTone() const; /** Sets the CTCSS TX sub-tone. Only valid for FM channels. */ virtual void setTXTone(const SelectiveCall &code, const ErrorStack &err=ErrorStack()); /** Returns the RX color code. Only valid for DMR channels. */ virtual unsigned int rxColorCode() const; /** Sets the RX color code. Only valid for DMR channels. */ virtual void setRXColorCode(unsigned int cc); /** Returns the TX color code. Only valid for DMR channels. */ virtual unsigned int txColorCode() const; /** Sets the TX color code. Only valid for DMR channels. */ virtual void setTXColorCode(unsigned int cc); /** Returns the times slot for the channel. Only valid for DMR channels. */ virtual DMRChannel::TimeSlot timeslot() const; /** Sets the timeslot for the channel. Only valid for DMR channels. */ virtual void setTimeslot(DMRChannel::TimeSlot ts); /** Returns @c true if the DMR contact index is set. */ virtual bool hasDMRContactIndex() const; /** Returns the contact index for the DMR contact. Only valid for DMR channels. */ virtual unsigned int dmrContactIndex() const; /** Sets the DMR contact index. Only valid for DMR channels. */ virtual void setDMRContactIndex(unsigned int idx); /** Clears the DMR contact index. */ virtual void clearDMRContactIndex(); /** Returns the RX channel access number. Only valid for M17 channels. */ virtual unsigned int rxChannelAccessNumber() const; /** Sets the RX channel access number. Only valid for M17 channels. */ virtual void setRXChannelAccessNumber(unsigned int cc); /** Returns the TX color code. Only valid for M17 channels. */ virtual unsigned int txChannelAccessNumber() const; /** Sets the TX color code. Only valid for M17 channels. */ virtual void setTXChannelAccessNumber(unsigned int cc); /** Returns the encryption mode of the channel. Only valid for M17 channels. */ virtual EncryptionMode encryptionMode() const; /** Sets the encryption mode for the channel. Only valid for M17 channels. */ virtual void setEncryptionMode(EncryptionMode mode); /** Returns the channel mode. Only valid for M17 channels. */ virtual ChannelMode channelMode() const; /** Sets the channel mode. Only valid for M17 channels. */ virtual void setChannelMode(ChannelMode mode); /** Returns @c true if GPS position is sent as meta-data. Only valid for M17 channels. */ virtual bool gpsDataEnabled() const; /** Enables/disables sending of GPS position as meta-data. Only valid for M17 channels. */ virtual void enableGPSData(bool enable); /** Returns @c true if the M17 contact index is set. */ virtual bool hasM17ContactIndex() const; /** Returns the M17 contact index. Only valid for M17 channels. */ virtual unsigned int m17ContactIndex() const; /** Sets the M17 contact index. Only valid for M17 channels. */ virtual void setM17ContactIndex(unsigned int idx); /** Clears the M17 contact index. */ virtual void clearM17ContactIndex(); /** Constructs a generic @c Channel object from the codeplug channel. */ virtual Channel *toChannelObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links a previously constructed channel to the rest of the configuration. */ virtual bool linkChannelObj(Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Initializes this codeplug channel from the given generic configuration. */ virtual bool fromChannelObj(const Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Just contains the offsets within the channel element. */ enum Offsets { OffsetMode = 0x00, OffsetBandwidth = 0x01, BitBandwidth = 0x00, OffsetRXOnly = 0x01, BitRXOnly = 0x02, OffsetPower = 0x02, OffsetRXFrequency = 0x03, OffsetTXFrequency = 0x07, OffsetScanList = 0x0b, OffsetGroupList = 0x0c, OffsetName = 0x0d, OffsetDescription = 0x2d, OffsetChLatInt = 0x4d, OffsetChLatDec = 0x4e, OffsetChLonInt = 0x50, OffsetChLonDec = 0x51, OffsetChAltitude = 0x53, OffsetRXTone = 0x55, OffsetTXTone = 0x56, OffsetRXColorCode = 0x55, BitRXColorCode = 0x00, OffsetTXColorCode = 0x55, BitTXColorCode = 0x04, OffsetTimeSlot = 0x56, OffsetDMRContact = 0x57, OffsetRXCAN = 0x55, BitRXCAN = 0x00, OffsetTXCAN = 0x55, BitTXCAN = 0x04, OffsetEncrMode = 0x56, BitEncrMode = 0x04, OffsetM17ChMode = 0x56, BitM17ChMode = 0x00, OffsetM17GPSMode = 0x57, OffsetM17Contact = 0x58, StringLength = 0x20 }; }; /** Implements the digital contact for the OpenRTX firmware. * * Binary representation (size 0027h bytes): * @verbinclude openrtx_contact.txt */ class ContactElement: public Element { protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ContactElement(uint8_t *ptr); /** Destructor. */ virtual ~ContactElement(); /** Resets the contact. */ void clear(); /** Returns @c true if the contact is valid. */ bool isValid() const; /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString &name); /** Returns the mode of the contact (either DMR or M17). */ virtual Mode mode() const; /** Sets the mode of the contact. */ virtual void setMode(Mode mode); /** Returns the DMR ID. Only valid for DMR contacts. */ virtual unsigned int dmrId() const; /** Sets the DMR ID. Only valid for DMR contacts. */ virtual void setDMRId(unsigned int id); /** Returns @c true if the RX tone is enabled (ring). Only valid for DMR contacts. */ virtual bool dmrRing() const; /** Enables/disables RX tone (ring). Only valid for DMR contacts. */ virtual void enableDMRRing(bool enable); /** Returns the contact type. Only valid for DMR contacts. */ virtual DMRContact::Type dmrContactType() const; /** Sets the contact type. */ virtual void setDMRContactType(DMRContact::Type type); /** Returns the contact call. */ virtual QString m17Call() const; /** Sets the M17 call. */ virtual bool setM17Call(const QString &call, const ErrorStack &err=ErrorStack()); /** Constructs a @c DigitalContact instance from this codeplug contact. */ virtual DigitalContact *toContactObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Resets this codeplug contact from the given @c DigitalContact. */ virtual bool fromContactObj(const DigitalContact *obj, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Just holds the offsets within the codeplug. */ enum Offsets { OffsetName = 0x00, OffsetMode = 0x20, OffsetDMRId = 0x21, OffsetDMRCallType = 0x25, BitDMRCallType = 0, OffsetDMRRing = 0x25, BitDMRRing = 0x02, OffsetM17Address = 0x21, StringLength = 0x20 }; }; /** The binary encoding of a zone. * * Binary representation (variable size): * @verbinclude openrtx_zone.txt */ class ZoneElement: Element { protected: /** Hidden constructor. */ ZoneElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ZoneElement(uint8_t *ptr); virtual ~ZoneElement(); /** Resets the zone. */ void clear(); /** Returns @c true if the zone is valid. */ bool isValid() const; /** Returns the zone name. */ virtual QString name() const; /** Sets the name of the zone. */ virtual void setName(const QString &name); /** Returns the number of channels in zone. */ virtual unsigned int channelCount() const; /** Sets the number of channels in zone. */ virtual void setChannelCount(unsigned int n); /** Returns the n-th channel index. */ virtual unsigned int channelIndex(unsigned int n) const; /** Sets the n-th channel index. */ virtual void setChannelIndex(unsigned int n, unsigned int idx); /** Constructs a generic @c Zone object from this codeplug zone. */ virtual Zone *toZoneObj(Context &ctx) const; /** Links a previously constructed @c Zone object to the rest of the configuration. That is * linking to the referred channels. */ virtual bool linkZoneObj(Zone *zone, Context &ctx, bool putInB, const ErrorStack &err=ErrorStack()) const; /** Resets this codeplug zone representation from the given generic @c Zone object. */ virtual void fromZoneObjA(const Zone *zone, Context &ctx); /** Resets this codeplug zone representation from the given generic @c Zone object. */ virtual void fromZoneObjB(const Zone *zone, Context &ctx); protected: /** Just defines the offsets with the element. */ enum Offsets { OffsetName = 0x00, OffsetCount = 0x20, OffsetChannel = 0x22, StringLength = 0x20 }; }; public: /** Hidden constructor, use a device specific class to instantiate. */ explicit OpenRTXCodeplug(QObject *parent=nullptr); /** Destructor. */ virtual ~OpenRTXCodeplug(); /** Clears and resets the complete codeplug to some default values. */ virtual void clear(); bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const; bool decode(Config *config, const ErrorStack &err=ErrorStack()); bool postprocess(Config *config, const ErrorStack &err=ErrorStack()) const; Config *preprocess(Config *config, const ErrorStack &err=ErrorStack()) const; bool encode(Config *config, const Flags &flags = Flags(), const ErrorStack &err=ErrorStack()); public: /** Decodes the binary codeplug and stores its content in the given generic configuration using * the given context. */ virtual bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given generic configuration as a binary codeplug using the given context. */ virtual bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Returns the number of stored contacts. */ virtual unsigned int numContacts(); /** Returns the offset to the n-th contact element. */ virtual unsigned int offsetContact(unsigned int n); /** Encodes all digital contacts in the configuration into the codeplug. */ virtual bool encodeContacts(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Adds a digital contact to the configuration for each one in the codeplug. */ virtual bool createContacts(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()); /** Returns the number of stored channels. */ virtual unsigned int numChannels(); /** Returns the offset to the n-th channel element. */ virtual unsigned int offsetChannel(unsigned int n); /** Encode all channels. */ virtual bool encodeChannels(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Adds all defined channels to the configuration. */ virtual bool createChannels(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()); /** Links all channels. */ virtual bool linkChannels(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()); /** Returns the number of stored zones. */ virtual unsigned int numZones(); /** Returns the offset to the zone offset array. */ virtual unsigned int offsetZoneOffsets(); /** Returns the offset to the n-th zone element. */ virtual unsigned int offsetZone(unsigned int n); /** Encodes zones. */ virtual bool encodeZones(Config *config, const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Adds zones to the configuration. */ virtual bool createZones(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()); /** Links all zones within the configuration. */ virtual bool linkZones(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Just stores some sizes. */ enum Offsets { HeaderSize = 0x58, ChannelSize = 0x5a, ContactSize = 0x27, ZoneHeaderSize=0x22 }; }; #endif // OPENRTX_CODEPLUG_HH ================================================ FILE: lib/openrtx_interface.cc ================================================ #include "openrtx_interface.hh" #include "usbserial.hh" #include "logger.hh" #include #include #define USB_VID 0x0483 #define USB_PID 0x5740 OpenRTXInterface::OpenRTXInterface(const USBDeviceDescriptor &descriptor, const ErrorStack &err, QObject *parent) : QObject(parent), RadioInterface(), _rtxLink(nullptr) { if (USBDeviceInfo::Class::Serial != descriptor.interfaceClass()) { errMsg(err) << "Cannot open serial port for a non-serial descriptor: " << descriptor.description(); return; } auto serial = new QSerialPort(this); logDebug() << "Try to open " << descriptor.description() << "."; QSerialPortInfo port(descriptor.device().toString()); serial->setPort(port); if (! serial->open(QIODevice::ReadWrite)) { errMsg(err) << "Cannot open port: " << serial->errorString(); return; } auto slip = new SlipStream(serial, this); _rtxLink = new OpenRTXLink(slip, this); } bool OpenRTXInterface::isOpen() const { return _rtxLink && _rtxLink->isOpen(); } void OpenRTXInterface::close() { if (_rtxLink) _rtxLink->close(); } RadioInfo OpenRTXInterface::identifier(const ErrorStack &err) { QByteArray id = _rtxLink->cat()->info(err); if (id.isEmpty()) return RadioInfo(); logInfo() << "Detected '" << QString::fromLatin1(id) << "'."; return RadioInfo::byID(RadioInfo::OpenRTX); } bool OpenRTXInterface::read_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { return false; } bool OpenRTXInterface::read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err) { return false; } bool OpenRTXInterface::read_finish(const ErrorStack &err) { return false; } bool OpenRTXInterface::write_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { return false; } bool OpenRTXInterface::write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err) { return false; } bool OpenRTXInterface::write_finish(const ErrorStack &err) { return false; } bool OpenRTXInterface::reboot(const ErrorStack &err) { return false; } USBDeviceInfo OpenRTXInterface::interfaceInfo() { return USBDeviceInfo(USBDeviceInfo::Class::Serial, USB_VID, USB_PID, false); } QList OpenRTXInterface::detect(bool saveOnly) { QList interfaces; // Find matching serial port by VID/PID. logDebug() << "Search for serial port with matching VID:PID " << QString::number(USB_VID, 16) << ":" << QString::number(USB_PID, 16) << "."; QList ports = QSerialPortInfo::availablePorts(); foreach (QSerialPortInfo port, ports) { if (port.hasProductIdentifier() && (USB_PID == port.productIdentifier()) && port.hasVendorIdentifier() && (USB_VID == port.vendorIdentifier())) { interfaces.append(USBSerial::Descriptor(USB_VID, USB_PID, port.portName(), saveOnly)); logDebug() << "Found " << port.portName() << " (USB " << QString::number(USB_VID, 16) << ":" << QString::number(USB_PID, 16) << ")."; } } return interfaces; } ================================================ FILE: lib/openrtx_interface.hh ================================================ #ifndef OPENRTXINTERFACE_HH #define OPENRTXINTERFACE_HH #include "radiointerface.hh" #include "packetstream.hh" #include "usbserial.hh" #include "xmodem.hh" /** Implements the communication interface to radios running the OpenRTX firmware. * * The protocol is called rtxlink and is documented at https://openrtx.org/#/rtxlink. The protocol * has several layers. The lowest is a serial interface either as a VCOM (UBS CDC-ACM) or a proper * hardware UART. Ontop of that, there is SLIP. Followed by a simple framing layer, that determines * the higher-level protocol. * @ingroup ortx */ class OpenRTXInterface : public QObject, public RadioInterface { Q_OBJECT public: /** Constructor. * @param descr The USB device descriptor. Used to identify a specific USB device. * @param err The stack of error messages. * @param parent The QObject parent. */ explicit OpenRTXInterface(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent = nullptr); bool isOpen() const; void close(); RadioInfo identifier(const ErrorStack &err=ErrorStack()); bool read_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool read_finish(const ErrorStack &err=ErrorStack()); bool write_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool write_finish(const ErrorStack &err=ErrorStack()); bool reboot(const ErrorStack &err=ErrorStack()); public: /** Returns some information about this interface. */ static USBDeviceInfo interfaceInfo(); /** Tries to find all interfaces connected AnyTone radios. */ static QList detect(bool saveOnly=true); protected: /** OpenRTX link interface. This is the protocol dispatcher for all implemented data transfer * protocols. */ OpenRTXLink *_rtxLink; }; #endif // OPENRTXINTERFACE_HH ================================================ FILE: lib/openrtx_link.cc ================================================ #include "openrtx_link.hh" /* ******************************************************************************************** * * Implements the OpenRTX link stream socket * ******************************************************************************************** */ OpenRTXLinkStream::OpenRTXLinkStream(OpenRTXLink::Protocol proto, OpenRTXLink *link) : QIODevice(link), _proto(proto), _inBuffer(), _link(link) { // pass... } qint64 OpenRTXLinkStream::writeData(const char *data, qint64 len) { QByteArray buffer(data, len); ErrorStack err; if (! _link->send(OpenRTXLink::Protocol::Stdio, buffer, -1, err)) { setErrorString(err.format()); return -1; } return len; } qint64 OpenRTXLinkStream::readData(char *data, qint64 maxlen) { ErrorStack err; qint64 n_read = 0, n_toread=maxlen; while (0 < n_toread) { if (_inBuffer.size()) { qint64 n = std::min((qint64)_inBuffer.size(), n_toread); memcpy(data, _inBuffer.data(), n); _inBuffer.remove(0, n); n_toread -= n; data+= n; n_read += n; } if (0 == n_toread) return n_read; if (! _link->receive(OpenRTXLink::Protocol::Stdio, _inBuffer, -1, err)) { setErrorString(err.format()); return -1; } } return n_read; } /* ******************************************************************************************** * * Implements the OpenRTX link datagram socket * ******************************************************************************************** */ OpenRTXLinkDatagram::OpenRTXLinkDatagram(OpenRTXLink::Protocol proto, OpenRTXLink *link) : PacketStream(link), _proto(proto), _link(link) { // pass... } bool OpenRTXLinkDatagram::isOpen() const { return _link->isOpen(); } void OpenRTXLinkDatagram::close() { // Cannot close data-gram socket by its own. } bool OpenRTXLinkDatagram::receive(QByteArray &buffer, int timeout, const ErrorStack &err) { return _link->receive(_proto, buffer, timeout, err); } bool OpenRTXLinkDatagram::send(const QByteArray &buffer, int timeout, const ErrorStack &err) { return _link->send(_proto, buffer, timeout, err); } /* ******************************************************************************************** * * Implements the OpenRTX CAT interface * ******************************************************************************************** */ OpenRTXCAT::OpenRTXCAT(OpenRTXLink *link) : OpenRTXLinkDatagram(OpenRTXLink::Protocol::CAT, link) { // pass... } QByteArray OpenRTXCAT::info(const ErrorStack &err) { if (! send(QByteArray("GIN",3), _link->timeout(), err)) { errMsg(err) << "Cannot request device info."; return QByteArray(); } QByteArray res; if (! receive(res, _link->timeout(), err)) { errMsg(err) << "Cannot receive device info."; return QByteArray(); } if ('A' == res[0]) { errMsg(err) << "Cannot obtain device info. Received error code " << (int8_t)res[1]; return QByteArray(); } else if ('D' == res[0]) { return res.right(16); } errMsg(err) << "Obtained unexpected response " << res.toHex(' '); return QByteArray(); } /* ******************************************************************************************** * * Implements the OpenRTX link protocol dispatcher * ******************************************************************************************** */ OpenRTXLink::OpenRTXLink(PacketStream *link, QObject *parent) : QObject{parent}, _link(link), _stdio(new OpenRTXLinkStream(Protocol::Stdio, this)), _cat(new OpenRTXCAT(this)), _fmp(new OpenRTXLinkDatagram(Protocol::FMP, this)), _xmodem(new OpenRTXLinkStream(Protocol::XMODEM, this)), _timeout(2000) { if (_link) _link->setParent(this); } OpenRTXLink::~OpenRTXLink() { if (_link) _link->close(); } bool OpenRTXLink::isOpen() const { return _link && _link->isOpen(); } void OpenRTXLink::close() { if (_link) _link->close(); } OpenRTXLinkStream * OpenRTXLink::stdio() const { return _stdio; } OpenRTXCAT *OpenRTXLink::cat() const { return _cat; } OpenRTXLinkDatagram * OpenRTXLink::fmp() const { return _fmp; } OpenRTXLinkStream * OpenRTXLink::xmodem() const { return _xmodem; } unsigned int OpenRTXLink::timeout() const { return _timeout; } uint16_t OpenRTXLink::crc16(const QByteArray &data) { uint16_t x = 0, crc = 0; for (qsizetype i=0; i> 8) ^ data[i]; x ^= x >> 4; crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x; } return crc; } bool OpenRTXLink::send(Protocol proto, const QByteArray &data, int timeout, const ErrorStack &err) { QByteArray packet; packet.reserve(data.size()+3); packet.append((char)proto); packet.append(data); uint16_t crc = crc16(data); packet.append(crc&0xff); packet.append(crc>>8); return this->_link->send(packet, timeout, err); } bool OpenRTXLink::receive(Protocol proto, QByteArray &data, int timeout, const ErrorStack &err) { QByteArray buffer; while (true) { // Receive a datagram if (! this->_link->receive(buffer, timeout, err)) return false; // Check CRC if (0 != crc16(buffer)) { errMsg(err) << "Invalid CRC in RTXLink packet."; return false; } // Dispatch by type Protocol rxProto = (Protocol)buffer.at(0); // If requested type matches -> done if (proto == rxProto) { data = buffer.mid(1, buffer.size()-2); return true; } // Otherwise, store and receive next if (! _inBuffers.contains((unsigned int)rxProto)) _inBuffers.insert((unsigned int)rxProto, QList()); _inBuffers[(unsigned int)rxProto].append(buffer.mid(1,buffer.size()-2)); } } ================================================ FILE: lib/openrtx_link.hh ================================================ #ifndef OPENRTXLINK_HH #define OPENRTXLINK_HH #include #include #include #include "packetstream.hh" // Forward declaration class OpenRTXLink; class OpenRTXLinkStream; class OpenRTXLinkDatagram; class OpenRTXCAT; /** Implements the OpenRTX link protocol. This is a datagram-oriented protocol, that dispatches * several different protocols to talk to the radio. It provides a stream, to the stdio of the * radio, a CAT interface, a file-system management protocol (FMP) as well as XMODEM to transfer * files. The file transfer must be initialized via the FMP. * * All these protocols are exposed through specialized interface objects, accessible through this * classs. * * @code * +------------------+-------------------+-------------------+------------------+ * | stdio | CAT | FMP | XMODEM | * +------------------+-------------------+-------------------+------------------+ * | OpenRTXLink | * +-----------------------------------------------------------------------------+ * | SLIP (SlipStream) | * +-----------------------------------------------------------------------------+ * | USB CDC-ACM (VCOM/Serial-over-USB, USBSerial) | * +-----------------------------------------------------------------------------+ * @endcode * @ingroup ortx */ class OpenRTXLink: public QObject { Q_OBJECT protected: /** The possible protocols, encapsulated in OpenRTX link. */ enum class Protocol { Stdio = 0, CAT = 1, FMP = 2, XMODEM = 3 }; public: /** Constructor. * @param link Specifies the datagram socket to talk to the radio (SLIP). * @param parent Specifies the QObject parent. */ explicit OpenRTXLink(PacketStream *link, QObject *parent = nullptr); virtual ~OpenRTXLink(); virtual bool isOpen() const; virtual void close(); /** Returns a stream to the stdio of the radio. Implements the @c QIODevice interface. */ OpenRTXLinkStream *stdio() const; /** The CAT interface to the radio. */ OpenRTXCAT *cat() const; /** The file-system management protocol interface to the radio. */ OpenRTXLinkDatagram *fmp() const; /** An XMODEM channel to transfer files. */ OpenRTXLinkStream *xmodem() const; /** Returns the timeout in milliseconds. */ unsigned int timeout() const; protected: /** Dispatcher to receive datagrams over OpenRTXLink. */ bool receive(Protocol proto, QByteArray &data, int timeout=-1, const ErrorStack &err=ErrorStack()); /** Dispatcher to send datagrams over OpenRTXLink. */ bool send(Protocol proto, const QByteArray &data, int timeout=-1, const ErrorStack &err=ErrorStack()); static uint16_t crc16(const QByteArray &data); protected: /** Owns the packet stream to the device. */ PacketStream *_link; QHash> _inBuffers; OpenRTXLinkStream *_stdio; OpenRTXCAT *_cat; OpenRTXLinkDatagram *_fmp; OpenRTXLinkStream *_xmodem; unsigned int _timeout; friend class OpenRTXLinkStream; friend class OpenRTXLinkDatagram; friend class OpenRTXCAT; }; /** An abstract stream encapsulated within OpenRTXLink. */ class OpenRTXLinkStream: public QIODevice { Q_OBJECT protected: OpenRTXLinkStream(OpenRTXLink::Protocol proto, OpenRTXLink *link); protected: qint64 writeData(const char *data, qint64 len); qint64 readData(char *data, qint64 maxlen); protected: OpenRTXLink::Protocol _proto; QByteArray _inBuffer; OpenRTXLink *_link; friend class OpenRTXLink; }; class OpenRTXLinkDatagram: public PacketStream { Q_OBJECT protected: OpenRTXLinkDatagram(OpenRTXLink::Protocol proto, OpenRTXLink *link); public: bool isOpen() const override; void close() override; bool receive(QByteArray &buffer, int timeout, const ErrorStack &err) override; bool send(const QByteArray &buffer, int timeout, const ErrorStack &err) override; protected: OpenRTXLink::Protocol _proto; OpenRTXLink *_link; friend class OpenRTXLink; }; /** Implements the CAT interface to OpenRTX devices. */ class OpenRTXCAT: public OpenRTXLinkDatagram { Q_OBJECT protected: /** Hidden constrcutor. An instance of this class can be obtained from @c OpenRTXLink. * @param link Specifies the unerlying RTX-link to the the device. */ explicit OpenRTXCAT(OpenRTXLink *link); public: QByteArray info(const ErrorStack &err=ErrorStack()); bool enterFMPMode(const ErrorStack &err=ErrorStack()); protected: friend class OpenRTXLink; }; #endif // OPENRTXLINK_HH ================================================ FILE: lib/openuv380.cc ================================================ #include "openuv380.hh" #include "openuv380_satelliteconfig.hh" OpenUV380::OpenUV380(OpenGD77Interface *device, QObject *parent) : OpenGD77Base(device, parent), _name("Open MD-UV380"), _codeplug(), _callsigns(device->extendedCallsignDB()) { _satelliteConfig = new OpenUV380SatelliteConfig(this); } const QString & OpenUV380::name() const { return _name; } const Codeplug & OpenUV380::codeplug() const { return _codeplug; } Codeplug & OpenUV380::codeplug() { return _codeplug; } const CallsignDB * OpenUV380::callsignDB() const { return &_callsigns; } CallsignDB * OpenUV380::callsignDB() { return &_callsigns; } RadioInfo OpenUV380::defaultRadioInfo() { return RadioInfo( RadioInfo::OpenUV380, "openuv380", "OpenMDUV380", "OpenGD77 Project", {OpenGD77Interface::interfaceInfo()}); } ================================================ FILE: lib/openuv380.hh ================================================ #ifndef OPENUV380_HH #define OPENUV380_HH #include "opengd77base.hh" #include "openuv380_codeplug.hh" #include "openuv380_callsigndb.hh" /** Implements an USB interface to Open UV380 VHF/UHF 5W DMR (Tier I&II) radios. * * @ingroup ogd77 */ class OpenUV380 : public OpenGD77Base { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit OpenUV380(OpenGD77Interface *device=nullptr, QObject *parent=nullptr); const QString &name() const; const Codeplug &codeplug() const; Codeplug &codeplug(); const CallsignDB *callsignDB() const; CallsignDB *callsignDB(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); protected: /** The device identifier. */ QString _name; /** The actual binary codeplug representation. */ OpenUV380Codeplug _codeplug; /** The actual binary callsign DB representation. */ OpenUV380CallsignDB _callsigns; }; #endif // OPENGD77_HH ================================================ FILE: lib/openuv380_callsigndb.cc ================================================ #include "openuv380_callsigndb.hh" #include "utils.hh" #include "userdatabase.hh" #include "logger.hh" #include /* ******************************************************************************************** * * Implementation of OpenUV380CallsignDB::DatabaseEntryElement * ******************************************************************************************** */ OpenUV380CallsignDB::DatabaseEntryElement::DatabaseEntryElement(uint8_t *ptr) : OpenGD77BaseCallsignDB::DatabaseEntryElement(ptr, size()) { // pass... } void OpenUV380CallsignDB::DatabaseEntryElement::clear() { memset(_data, 0x00, size()); } void OpenUV380CallsignDB::DatabaseEntryElement::setText(const QString &text) { QByteArray data = pack(text); auto n = std::min(3*Limit::textLength()/4, (unsigned int)data.size()); memset(_data+Offset::text(), 0, 3*Limit::textLength()/4); memcpy(_data+Offset::text(), data.constData(), n); } /* ******************************************************************************************** * * Implementation of OpenUV380CallsignDB * ******************************************************************************************** */ OpenUV380CallsignDB::OpenUV380CallsignDB(bool extended, QObject *parent) : OpenGD77BaseCallsignDB(parent) { addImage("OpenUV380 call-sign database"); if (extended) logDebug() << "Used extended call-sign DB memory."; } bool OpenUV380CallsignDB::encode(UserDatabase *calldb, const Flags &selection, const ErrorStack &err) { Q_UNUSED(err) // Limit entry count auto n = std::min((unsigned int)calldb->count(), Limit::entries()); if (selection.hasCountLimit()) n = std::min(n, (unsigned int)selection.countLimit()); // If there are no entries -> done. if (0 == n) return true; auto n0 = std::min(n, Limit::entries0()), n1 = std::min(n - n0, Limit::entries1()); // Select first n entries and sort them in ascending order of their IDs QVector users; for (unsigned i=0; iuser(i)); std::sort(users.begin(), users.end(), [](const UserDatabase::User &a, const UserDatabase::User &b) { return a.id < b.id; }); if (n) logDebug() << "Store " << n << " entries for OpenUV380 starting from " << users.front().id << ":" << users.front().call << ", " << users.front().name << " in " << users.front().city << " to " << users.back().id << ":" << users.back().call << ", " << users.back().name << " in " << users.back().city; // Allocate segment0 for user db if requested unsigned size = align_size(DatabaseHeaderElement::size()+n0*DatabaseEntryElement::size(), Limit::blockSize()); this->image(0).addElement(Offset::header(), size); size = align_size(n1*DatabaseEntryElement::size(), Limit::blockSize()); if (n1) this->image(0).addElement(Offset::entries1(), size); // Encode user DB DatabaseHeaderElement header(this->data(Offset::header())); header.clear(); header.setEntrySize(DatabaseEntryElement::size()); header.setEntryCount(n); for (unsigned i=0; idata(Offset::entries0() + i*DatabaseEntryElement::size())) .fromEntry(users[i]); } for (unsigned i=0; idata(Offset::entries1() + i*DatabaseEntryElement::size())) .fromEntry(users[n0+i]); } return true; } ================================================ FILE: lib/openuv380_callsigndb.hh ================================================ #ifndef OPENUV380CALLSIGNDB_HH #define OPENUV380CALLSIGNDB_HH #include "opengd77base_callsigndb.hh" #include "userdatabase.hh" /** Represents and encodes the binary format for the call-sign database within the radio. * * The memory layout of the call-sign DB is relatively simple. The DB starts at address * @c 0x00030000 with a maximum size of @c 0x00040000. The first 12bytes form the DB header * (see @c OpenGD77CallsignDB::userdb_t) followed by the DB entries (see * @c OpenGD77CallsignDB::userdb_entry_t). * * The entries can be of variable size. The size of each entry is encoded in the header. QDMR uses * a fixed size of 19bytes per entry. The entries must be sorted in ascending order to allow for an * efficient binary search. No index table is used here. * * @ingroup ogd77 */ class OpenUV380CallsignDB : public OpenGD77BaseCallsignDB { Q_OBJECT public: class DatabaseEntryElement: public OpenGD77BaseCallsignDB::DatabaseEntryElement { public: /** Constructor. */ DatabaseEntryElement(uint8_t *ptr); /** The size of the entry. */ static constexpr unsigned int size() { return 0x001b; } void clear() override; /** Encodes the text. */ void setText(const QString &text) override; public: /** Some limits. */ struct Limit: public Element::Limit { // The length of the text. static constexpr unsigned int textLength() { return 32; } }; }; public: /** Constructor. * @param extended If @c true, some extended callsign db memory is used. */ explicit OpenUV380CallsignDB(bool extended, QObject *parent=nullptr); static constexpr unsigned int size0() { return 0x040000; } static constexpr unsigned int size1() { return 0xd28000; } /** Encodes as many entries as possible of the given user-database. */ bool encode(UserDatabase *calldb, const Flags &selection=Flags(), const ErrorStack &err=ErrorStack()); public: /** Some limits of the callsign DB. */ struct Limit: public OpenGD77BaseCallsignDB::Limit { /// Number of entries, segment 0. static constexpr unsigned int entries0() { return (size0()-DatabaseHeaderElement::size())/DatabaseEntryElement::size(); } /// Number of entries, segment 1. static constexpr unsigned int entries1() { return size1()/DatabaseEntryElement::size(); } static constexpr unsigned int entries() { return entries0() + entries1(); } }; protected: /** Some internal offsets within the callsign db. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int header() { return 0x050000; } static constexpr unsigned int entries0() { return header() + DatabaseHeaderElement::size(); } static constexpr unsigned int entries1() { return 0x0d8000; } /// @endcond }; }; #endif // OPENGD77CALLSIGNDB_HH ================================================ FILE: lib/openuv380_codeplug.cc ================================================ #include "openuv380_codeplug.hh" #include "config.hh" #include "channel.hh" #include "utils.hh" #include "logger.hh" #include #include #include "opengd77_extension.hh" /* ******************************************************************************************** * * Implementation of OpenUV380Codeplug * ******************************************************************************************** */ OpenUV380Codeplug::OpenUV380Codeplug(QObject *parent) : OpenGD77BaseCodeplug(parent) { addImage("OpenGD77 Codeplug EEPROM"); addImage("OpenGD77 Codeplug FLASH"); image(FLASH).addElement(0x00000080, 0x00005fe0); image(FLASH).addElement(0x00007500, 0x00003b00); image(FLASH).addElement(0x00020000, AdditionalSettingsElement::size()); image(FLASH).addElement(0x0009b000, 0x00013e60); } void OpenUV380Codeplug::clearGeneralSettings() { GeneralSettingsElement(data(Offset::settings(), ImageIndex::settings())).clear(); } bool OpenUV380Codeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { GeneralSettingsElement el(data(Offset::settings(), ImageIndex::settings())); if (! flags.updateCodeplug()) el.clear(); return el.encode(ctx, err); } bool OpenUV380Codeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings(), ImageIndex::settings())).decode(ctx, err); } void OpenUV380Codeplug::clearDTMFSettings() { //DTMFSettingsElement(data(Offset::settings(), ImageIndex::settings())).clear(); } bool OpenUV380Codeplug::encodeDTMFSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err); return true; } bool OpenUV380Codeplug::decodeDTMFSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return true; } void OpenUV380Codeplug::clearAPRSSettings() { APRSSettingsBankElement(data(Offset::aprsSettings(), ImageIndex::aprsSettings())).clear(); } bool OpenUV380Codeplug::encodeAPRSSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { APRSSettingsBankElement el(data(Offset::aprsSettings(), ImageIndex::aprsSettings())); if (! flags.updateCodeplug()) el.clear(); return el.encode(ctx, err); } bool OpenUV380Codeplug::decodeAPRSSettings(Context &ctx, const ErrorStack &err) { return APRSSettingsBankElement(data(Offset::aprsSettings(), ImageIndex::aprsSettings())) .decode(ctx, err); } bool OpenUV380Codeplug::linkAPRSSettings(Context &ctx, const ErrorStack &err) { return APRSSettingsBankElement(data(Offset::aprsSettings(), ImageIndex::aprsSettings())) .link(ctx, err); } bool OpenUV380Codeplug::encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); // Encode boot melody if set if (ctx.config()->settings()->tone()->bootToneEnabled() && !ctx.config()->settings()->tone()->bootMelody()->isEmpty()) { AdditionalSettingsElement opt(data(Offset::additionalSettings(), ImageIndex::additionalSettings())); if (! flags.updateCodeplug() && ! opt.isValid()) opt.clear(); opt.bootMelody().encode(ctx, ctx.config()->settings()->tone()->bootMelody(), err); } // Encode other boot settings return BootSettingsElement(data(Offset::bootSettings(), ImageIndex::bootSettings())) .encode(ctx, err); } bool OpenUV380Codeplug::decodeBootSettings(Context &ctx, const ErrorStack &err) { // Check if boot melody is encoded AdditionalSettingsElement opt(data(Offset::additionalSettings(), ImageIndex::additionalSettings())); if (opt.hasSettings(AdditionalSettingsElement::BootMelody)) { ctx.config()->settings()->tone()->enableBootTone( opt.bootMelody().decode(ctx, ctx.config()->settings()->tone()->bootMelody(), err)); } return BootSettingsElement(data(Offset::bootSettings(), ImageIndex::bootSettings())) .decode(ctx, err); } void OpenUV380Codeplug::clearContacts() { ContactBankElement(data(Offset::contacts(), ImageIndex::contacts())).clear(); } bool OpenUV380Codeplug::encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); return ContactBankElement(data(Offset::contacts(), ImageIndex::contacts())).encode(ctx, err); } bool OpenUV380Codeplug::createContacts(Context &ctx, const ErrorStack &err) { return ContactBankElement(data(Offset::contacts(), ImageIndex::contacts())).decode(ctx, err); } void OpenUV380Codeplug::clearDTMFContacts() { DTMFContactBankElement(data(Offset::dtmfContacts(), ImageIndex::dtmfContacts())).clear(); } bool OpenUV380Codeplug::encodeDTMFContacts(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); return DTMFContactBankElement(data(Offset::dtmfContacts(), ImageIndex::dtmfContacts())) .encode(ctx, err); } bool OpenUV380Codeplug::createDTMFContacts(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return DTMFContactBankElement(data(Offset::dtmfContacts(), ImageIndex::dtmfContacts())) .decode(ctx, err); } void OpenUV380Codeplug::clearChannels() { for (unsigned int b=0; b(c)) { if (! bank.channel(i).encode(ctx.get(c), ctx, err)) { errMsg(err) << "Cannot encode channel '" << ctx.get(c)->name() << "' at index " << i << " of bank " << b << "."; return false; } bank.enable(i, true); } else { bank.enable(i, false); } } } return true; } bool OpenUV380Codeplug::createChannels(Context &ctx, const ErrorStack &err) { for (unsigned int b=0,c=0; bchannelList()->add(obj); ctx.add(obj, c++); } } return true; } bool OpenUV380Codeplug::linkChannels(Context &ctx, const ErrorStack &err) { for (unsigned int b=0,c=0; b(c); ChannelElement element = bank.channel(i); assert(obj->name() == element.name()); if (! element.link(obj, ctx, err)) { errMsg(err) << "Cannot link channel '" << obj->name() << "' from index " << i << " in bank " << b << "."; return false; } c++; } } return true; } void OpenUV380Codeplug::clearBootSettings() { BootSettingsElement(data(Offset::bootSettings(), ImageIndex::bootSettings())).clear(); } void OpenUV380Codeplug::clearVFOSettings() { VFOChannelElement(data(Offset::vfoA(), ImageIndex::vfoA())).clear(); VFOChannelElement(data(Offset::vfoB(), ImageIndex::vfoB())).clear(); } void OpenUV380Codeplug::clearZones() { ZoneBankElement(data(Offset::zoneBank(), ImageIndex::zoneBank())).clear(); } bool OpenUV380Codeplug::encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); return ZoneBankElement(data(Offset::zoneBank(), ImageIndex::zoneBank())).encode(ctx, err); } bool OpenUV380Codeplug::createZones(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return ZoneBankElement(data(Offset::zoneBank(), ImageIndex::zoneBank())).decode(ctx, err); } bool OpenUV380Codeplug::linkZones(Context &ctx, const ErrorStack &err) { return ZoneBankElement(data(Offset::zoneBank(), ImageIndex::zoneBank())).link(ctx, err); } void OpenUV380Codeplug::clearGroupLists() { GroupListBankElement(data(Offset::groupLists(), ImageIndex::groupLists())).clear(); } bool OpenUV380Codeplug::encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); return GroupListBankElement(data(Offset::groupLists(), ImageIndex::groupLists())).encode(ctx, err); } bool OpenUV380Codeplug::createGroupLists(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return GroupListBankElement(data(Offset::groupLists(), ImageIndex::groupLists())).decode(ctx, err); } bool OpenUV380Codeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { return GroupListBankElement(data(Offset::groupLists(), ImageIndex::groupLists())).link(ctx, err); } ================================================ FILE: lib/openuv380_codeplug.hh ================================================ #ifndef OPENUV380_CODEPLUG_HH #define OPENUV380_CODEPLUG_HH #include "opengd77base_codeplug.hh" #include "opengd77_extension.hh" /** Represents, encodes and decodes the device specific codeplug for Open MD-UV380 firmware. * * This codeplug is almost identical to the original GD77 codeplug. * * @ingroup ogd77 */ class OpenUV380Codeplug: public OpenGD77BaseCodeplug { Q_OBJECT public: /** Constructs an empty codeplug for the Open MD-UV380. */ explicit OpenUV380Codeplug(QObject *parent=nullptr); public: void clearGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearDTMFSettings(); bool encodeDTMFSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeDTMFSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearAPRSSettings(); bool encodeAPRSSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeAPRSSettings(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkAPRSSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearDTMFContacts(); bool encodeDTMFContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createDTMFContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void clearBootSettings(); bool encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeBootSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearVFOSettings(); void clearZones(); bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); void clearGroupLists(); bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some Limits for this codeplug. */ struct Limit: public Element::Limit { /** Number of channel banks. */ static constexpr unsigned int channelBanks() { return 8; } }; protected: /** Internal used image indices. */ struct ImageIndex { /// @cond DO_NOT_DOCUEMNT static constexpr unsigned int settings() { return FLASH; } static constexpr unsigned int dtmfSettings() { return FLASH; } static constexpr unsigned int aprsSettings() { return FLASH; } static constexpr unsigned int dtmfContacts() { return FLASH; } static constexpr unsigned int channelBank0() { return FLASH; } static constexpr unsigned int bootSettings() { return FLASH; } static constexpr unsigned int vfoA() { return FLASH; } static constexpr unsigned int vfoB() { return FLASH; } static constexpr unsigned int zoneBank() { return FLASH; } static constexpr unsigned int additionalSettings() { return FLASH; } static constexpr unsigned int channelBank1() { return FLASH; } static constexpr unsigned int contacts() { return FLASH; } static constexpr unsigned int groupLists() { return FLASH; } /// @endcond }; /** Some offsets. */ struct Offset { /// @cond DO_NOT_DOCUEMNT static constexpr unsigned int settings() { return 0x00000080; } static constexpr unsigned int dtmfSettings() { return 0x00001470; } static constexpr unsigned int aprsSettings() { return 0x00001588; } static constexpr unsigned int dtmfContacts() { return 0x00002f88; } static constexpr unsigned int channelBank0() { return 0x00003780; } // Channels 1-128 static constexpr unsigned int bootSettings() { return 0x00007518; } static constexpr unsigned int vfoA() { return 0x00007590; } static constexpr unsigned int vfoB() { return 0x000075c8; } static constexpr unsigned int zoneBank() { return 0x00008010; } static constexpr unsigned int additionalSettings() { return 0x00020000; } static constexpr unsigned int channelBank1() { return 0x0009b1b0; } // Channels 129-1024 static constexpr unsigned int contacts() { return 0x000a7620; } static constexpr unsigned int groupLists() { return 0x000ad620; } /// @endcond }; }; #endif // OPENUV380_CODEPLUG_HH ================================================ FILE: lib/openuv380_satelliteconfig.cc ================================================ #include "openuv380_satelliteconfig.hh" #include "errorstack.hh" OpenUV380SatelliteConfig::OpenUV380SatelliteConfig(QObject *parent) : OpenGD77BaseSatelliteConfig(parent) { image(FLASH).addElement(Offset::satellites(), size()); // of 0x11a0 bytes } bool OpenUV380SatelliteConfig::isValid() const { return OpenGD77BaseCodeplug::AdditionalSettingsElement((uint8_t *)data(Offset::satellites(), FLASH)) .isValid(); } void OpenUV380SatelliteConfig::initialize() { OpenGD77BaseCodeplug::AdditionalSettingsElement(data(Offset::satellites(), FLASH)).clear(); } bool OpenUV380SatelliteConfig::encode(SatelliteDatabase *db, const ErrorStack &err) { OpenGD77BaseCodeplug::AdditionalSettingsElement settings(data(Offset::satellites(), FLASH)); if (! settings.isValid()) { errMsg(err) << "Cannot encode satellite config for OpenUV380: Invalid settings element."; return false; } OpenGD77BaseCodeplug::SatelliteBankElement bank = settings.satellites(); bank.clear(); if (! bank.encode(db, err)) { errMsg(err) << "Cannot encode satellite config for OpenUV380."; return false; } return true; } ================================================ FILE: lib/openuv380_satelliteconfig.hh ================================================ #ifndef OPENUV380_SATELLITECONFIG_HH #define OPENUV380_SATELLITECONFIG_HH #include "opengd77base_codeplug.hh" #include "opengd77base_satelliteconfig.hh" class OpenUV380SatelliteConfig : public OpenGD77BaseSatelliteConfig { Q_OBJECT public: /** Default constructor. */ explicit OpenUV380SatelliteConfig(QObject *parent = nullptr); bool isValid() const; void initialize(); bool encode(SatelliteDatabase *db, const ErrorStack &err=ErrorStack()); public: /** Some limits for the satellite config. */ struct Limit { /** The maximum number of satellites. */ static constexpr unsigned int satellites() { return OpenGD77BaseCodeplug::SatelliteBankElement::Limit::satellites(); } }; protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int satellites() { return 0x020000; } /// @endcond }; }; #endif // OPENUV380_SATELLITECONFIG_HH ================================================ FILE: lib/orbitalelementsdatabase.cc ================================================ #include "orbitalelementsdatabase.hh" #include #include #include #include #include #include #include #include "logger.hh" /* ********************************************************************************************* * * Implementation of OrbitalElement::Epoch * ********************************************************************************************* */ OrbitalElement::Epoch::Epoch() : year(0), month(0), day(0), hour(0), minute(0), second(0), microsecond(0) { // pass... } OrbitalElement::Epoch::Epoch(unsigned int pyear, unsigned int pmonth, unsigned int pday, unsigned int phour, unsigned int pminute, unsigned int psecond, unsigned int pmicrosecond) : year(pyear), month(pmonth), day(pday), hour(phour), minute(pminute), second(psecond), microsecond(pmicrosecond) { // pass... } OrbitalElement::Epoch OrbitalElement::Epoch::parse(const QString &datetime) { QRegularExpression pattern("([0-9]{4})-([0-9]{2})-([0-9]{2})T([0-9]{2}):([0-9]{2}):([0-9]{2}).([0-9]{6})"); QRegularExpressionMatch match = pattern.match(datetime); if (! match.hasMatch()) return Epoch(); return Epoch{ match.captured(1).toUInt(), match.captured(2).toUInt(), match.captured(3).toUInt(), match.captured(4).toUInt(), match.captured(5).toUInt(), match.captured(6).toUInt(), match.captured(7).toUInt() }; } double OrbitalElement::Epoch::toEpoch() const { QDate date(year, month, day); double res = microsecond; res = (res / 1e6) + second; res = (res / 60) + minute; res = (res / 60) + hour; res = (res / 24) + date.dayOfYear(); return res; } QString OrbitalElement::Epoch::toString() const { return QString("%1-%2-%3T%4:%5:%6.%7") .arg(year, 4, 10, QChar('0')) .arg(month, 2, 10, QChar('0')) .arg(day, 2, 10, QChar('0')) .arg(hour, 2, 10, QChar('0')) .arg(minute, 2, 10, QChar('0')) .arg(second, 2, 10, QChar('0')) .arg(microsecond, 6, 10, QChar('0')); } /* ********************************************************************************************* * * Implementation of OrbitalElement * ********************************************************************************************* */ OrbitalElement::OrbitalElement() : _id(0), _name(), _epoch(), _meanMotion(0.0), _meanMotionDerivative(0.0), _inclination(0.0), _ascension(0.0), _eccentricity(0.0), _perigee(0.0), _meanAnomaly(0.0), _revolutionNumber(0) { // pass... } OrbitalElement::OrbitalElement(unsigned int id) : _id(id), _name(), _epoch(), _meanMotion(0.0), _meanMotionDerivative(0.0), _inclination(0.0), _ascension(0.0), _eccentricity(0.0), _perigee(0.0), _meanAnomaly(0.0), _revolutionNumber(0) { // pass... } bool OrbitalElement::isValid() const { return 0 != _id; } unsigned int OrbitalElement::id() const { return _id; } const QString & OrbitalElement::name() const { return _name; } const OrbitalElement::Epoch & OrbitalElement::epoch() const { return _epoch; } double OrbitalElement::meanMotion() const { return _meanMotion; } double OrbitalElement::meanMotionDerivative() const { return _meanMotionDerivative; } double OrbitalElement::inclination() const { return _inclination; } double OrbitalElement::ascension() const { return _ascension; } double OrbitalElement::eccentricity() const { return _eccentricity; } double OrbitalElement::perigee() const { return _perigee; } double OrbitalElement::meanAnomaly() const { return _meanAnomaly; } unsigned int OrbitalElement::revolutionNumber() const { return _revolutionNumber; } OrbitalElement OrbitalElement::fromCelesTrak(const QJsonObject &obj) { OrbitalElement el; el._name = obj.value("OBJECT_NAME").toString(); el._epoch = Epoch::parse(obj.value("EPOCH").toString()); el._meanMotion = obj.value("MEAN_MOTION").toDouble(); el._meanMotionDerivative = obj.value("MEAN_MOTION_DOT").toDouble(); el._inclination = obj.value("INCLINATION").toDouble(); el._ascension = obj.value("RA_OF_ASC_NODE").toDouble(); el._eccentricity = obj.value("ECCENTRICITY").toDouble(); el._perigee = obj.value("ARG_OF_PERICENTER").toDouble(); el._meanAnomaly = obj.value("MEAN_ANOMALY").toDouble(); el._revolutionNumber = obj.value("REV_AT_EPOCH").toInt(); el._id = obj.value("NORAD_CAT_ID").toInt(); return el; } /* ********************************************************************************************* * * Implementation of OrbitalElementsDatabase * ********************************************************************************************* */ OrbitalElementsDatabase::OrbitalElementsDatabase(bool autoLoad, unsigned int updatePeriod, QObject *parent) : QAbstractTableModel{parent}, _updatePeriod(updatePeriod), _elements(), _network() { connect(&_network, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*))); if (autoLoad) load(); } bool OrbitalElementsDatabase::contains(unsigned int id) const { return _idIndexMap.contains(id); } OrbitalElement OrbitalElementsDatabase::getById(unsigned int id) const { return _elements.at(_idIndexMap.value(id)); } const OrbitalElement & OrbitalElementsDatabase::getAt(unsigned int idx) const { return _elements[idx]; } OrbitalElement & OrbitalElementsDatabase::getAt(unsigned int idx) { return _elements[idx]; } int OrbitalElementsDatabase::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return _elements.size(); } int OrbitalElementsDatabase::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 3; } QVariant OrbitalElementsDatabase::data(const QModelIndex &index, int role) const { if (index.row() >= _elements.size()) return QVariant(); if (Qt::DisplayRole == role) { if (0 == index.column()) return _elements.at(index.row()).id(); if (1 == index.column()) return _elements.at(index.row()).name(); if (2 == index.column()) return _elements.at(index.row()).epoch().toString(); } return QVariant(); } QVariant OrbitalElementsDatabase::headerData(int section, Qt::Orientation orientation, int role) const { if (role != Qt::DisplayRole || orientation != Qt::Horizontal) return QVariant(); switch (section) { case 0: return QStringLiteral("NORAD"); case 1: return QStringLiteral("Name"); case 2: return QStringLiteral("Epoch"); } return QVariant(); } unsigned OrbitalElementsDatabase::dbAge() const { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/elements.json"; QFileInfo info(path); if (! info.exists()) return -1; return info.lastModified().daysTo(QDateTime::currentDateTime()); } void OrbitalElementsDatabase::load() { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/elements.json"; if ((! load(path)) || (_updatePeriod < dbAge())) download(); } bool OrbitalElementsDatabase::load(const QString &filename) { QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { QString msg = QString("Cannot open orbital elements '%1': %2").arg(filename).arg(file.errorString()); logError() << msg; emit error(msg); return false; } QByteArray data = file.readAll(); file.close(); QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (doc.isEmpty()) { QString msg = "Failed to load orbital elements: " + err.errorString(); logError() << msg; emit error(msg); return false; } if (! doc.isArray()) { QString msg = "Failed to load orbital elements: JSON document is not an array!"; logError() << msg; emit error(msg); return false; } beginResetModel(); QJsonArray array = doc.array(); _elements.clear(); _elements.reserve(array.size()); for (int i=0; ierror()) { QString msg = QString("Cannot download orbital elements: %1").arg(reply->errorString()); logError() << msg; emit error(msg); return; } QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QFile file(path+"/elements.json"); QDir directory; if ((! directory.exists(path)) && (!directory.mkpath(path))) { QString msg = QString("Cannot create path '%1'.").arg(path); logError() << msg; emit error(msg); return; } if (! file.open(QIODevice::WriteOnly)) { QString msg = QString("Cannot save orbital elements at '%1'.").arg(file.fileName()); logError() << msg; emit error(msg); return; } file.write(reply->readAll()); file.flush(); file.close(); load(); reply->deleteLater(); } ================================================ FILE: lib/orbitalelementsdatabase.hh ================================================ /** @defgroup sat Satellite tracking settings. * @ingroup conf */ #ifndef ORBITALELEMENTSDATABASE_HH #define ORBITALELEMENTSDATABASE_HH #include #include /** Defines a single orbital element, enabling the tracking of a single satellite. This dataset * does not contain any transponder information. * @ingroup sat */ class OrbitalElement { public: /** Represents a Julien day epoch since a specified year. */ struct Epoch { /** The year of the epoch. */ unsigned int year; /** The month. */ unsigned int month; /** The day. */ unsigned int day; /** The hour. */ unsigned int hour; /** The minute. */ unsigned int minute; /** The second. */ unsigned int second; /** The microsecond. */ unsigned int microsecond; /** Default constructor. */ Epoch(); /** Constructor */ Epoch(unsigned int year, unsigned int month, unsigned int day, unsigned int hour, unsigned int minute, unsigned int second, unsigned int microsecond); /** Copy constructor. */ Epoch(const Epoch &other) = default; /** Copy assignment. */ Epoch &operator =(const Epoch &other) = default; /** Parses a date-time string into the epoch. */ static Epoch parse(const QString &datetime); /** Computes the decimal epoch as the day of year a */ double toEpoch() const; /** Encodes the Epoch as YYYY-MM-DDThh:mm:ss.uuuuuu. */ QString toString() const; }; public: /** Default constructor. */ OrbitalElement(); /** Constructor from ID. */ OrbitalElement(unsigned int id); /** Copy constructor. */ OrbitalElement(const OrbitalElement &other) = default; /** Copy assignment. */ OrbitalElement &operator=(const OrbitalElement &other) = default; /** Returns @c true, if this represents a valid satellite information. */ bool isValid() const; /** Returns the NORAD catalog id. */ unsigned int id() const; /** Returns the name of the satellite. */ const QString &name() const; /** Epoch of the orbital elements. */ const Epoch &epoch() const; /** Returns the mean motion. */ double meanMotion() const; /** Returns the first derivative of the mean motion. */ double meanMotionDerivative() const; /** Returns the inclination. */ double inclination() const; /** Returns the right ascension of the ascending node. */ double ascension() const; /** Returns the eccentricity. */ double eccentricity() const; /** Returns the argument of perigee. */ double perigee() const; /** Returns the mean anomaly. */ double meanAnomaly() const; /** Returns the revolution number. */ unsigned int revolutionNumber() const; public: /** Constructs a orbital element from a CelesTrak JSON object. */ static OrbitalElement fromCelesTrak(const QJsonObject &obj); protected: /** NORAD id of the satellite. */ unsigned int _id; /** Descriptive name of the satellite. */ QString _name; /** The epoch. */ Epoch _epoch; /** Mean motion. */ double _meanMotion; /** First derivative of the mean motion. */ double _meanMotionDerivative; /** Inclination. */ double _inclination; /** Right ascension of the ascending node. */ double _ascension; /** Eccentricity. */ double _eccentricity; /** Argument of perigee. */ double _perigee; /** Mean anomaly. */ double _meanAnomaly; /** The revolution number. */ unsigned int _revolutionNumber; }; /** Downloads and updates a database of orbital elements from CelesTrak. * @ingroup sat */ class OrbitalElementsDatabase: public QAbstractTableModel { Q_OBJECT public: /** Constructs a orbital element database. * @param autoLoad [in] If @c true, the database gets downloaded and loaded automatically. * @param updatePeriodDays [in] Specifies the max age of the local database cache in days. * @param parent [in] Specifies the QObject parent. */ explicit OrbitalElementsDatabase(bool autoLoad, unsigned int updatePeriodDays=7, QObject *parent=nullptr); /** @c returns @c true if the database contains a satellite with the given NORAD id. */ bool contains(unsigned int id) const; /** Returns the orbital elements for the satellite with the given NORAD id. */ OrbitalElement getById(unsigned int id) const; /** Returns the i-th orbital element. */ const OrbitalElement &getAt(unsigned int idx) const; /** Returns the i-th orbital element. */ OrbitalElement &getAt(unsigned int idx); /** Returns the current age of the cache. */ unsigned int dbAge() const; /** If needed, downloads the database and loads all received orbital elements. */ void load(); /** Returns the number of elements in the database. */ int rowCount(const QModelIndex &parent = QModelIndex()) const; /** Returns the number of columns of the database table. */ int columnCount(const QModelIndex &parent = QModelIndex()) const; /** Returns a single cell of the database table. */ QVariant data(const QModelIndex &index, int role) const; /** Returns a single header of the database table. */ QVariant headerData(int section, Qt::Orientation orientation, int role) const; signals: /** Gets emitted once the satellite orbitals has been loaded. */ void loaded(); /** Gets emitted if the loading one of the sources fails. */ void error(const QString &msg); public slots: /** Starts the download of the orbital elements. */ void download(); private slots: /** Gets called whenever the orbital elements download is complete. */ void downloadFinished(QNetworkReply *reply); protected: /** Loads a database from the given filename. */ bool load(const QString &filename); private: /** Update period in days. */ unsigned int _updatePeriod; /** Holds all satellites sorted by their catalog number. */ QVector _elements; /** Maps NORAD id to element. */ QHash _idIndexMap; /** The network access used for downloading. */ QNetworkAccessManager _network; }; #endif // ORBITALELEMENTSDATABASE_HH ================================================ FILE: lib/packetstream.cc ================================================ #include "packetstream.hh" #include /* ******************************************************************************************** * * Implementation of PacketStream interface * ******************************************************************************************** */ PacketStream::PacketStream(QObject *parent) : QObject{parent} { // pass... } PacketStream::~PacketStream() { // pass... } /* ******************************************************************************************** * * Implementation of SlipStream * ******************************************************************************************** */ SlipStream::SlipStream(QIODevice *device, QObject *parent) : PacketStream(parent), _device(device), _buffer() { if (_device) _device->setParent(this); } bool SlipStream::isOpen() const { return _device && _device->isOpen(); } void SlipStream::close() { if (_device) _device->close(); } bool SlipStream::receive(QByteArray &buffer, int timeout, const ErrorStack &err) { while (! _buffer.contains(END_OF_PACKET)) { // If there is some stuff to read -> append to buffer and continue if (_device->bytesAvailable()) { _buffer.append(_device->readAll()); continue; } if (! _device->waitForReadyRead(timeout)) { errMsg(err) << _device->errorString(); errMsg(err) << "Cannot read from device."; return false; } _buffer.append(_device->readAll()); } // Unpack packet int endIndex = _buffer.indexOf(END_OF_PACKET); // Clear output buffer buffer.clear(); buffer.reserve(endIndex); for (int i=0; iwrite(outBuffer); if (! _device->waitForBytesWritten(timeout)) { errMsg(err) << "Cannot write to the device."; return false; } return true; } ================================================ FILE: lib/packetstream.hh ================================================ #ifndef PACKETSTREAM_HH #define PACKETSTREAM_HH #include #include "errorstack.hh" class QIODevice; /** Defines an interface for a datagram socket. * That is, just some methods to send and receive packets. * @ingroup rif */ class PacketStream : public QObject { Q_OBJECT protected: /** Hidden constructor. */ explicit PacketStream(QObject *parent = nullptr); public: /** Destructor. */ virtual ~PacketStream(); /** Returns @c true if the stream is open. */ virtual bool isOpen() const = 0; /** Closes the stream. */ virtual void close() = 0; /** Receives a datagram. Blocks for up to @c timeout milliseconds. */ virtual bool receive(QByteArray &buffer, int timeout=-1, const ErrorStack &err=ErrorStack()) = 0; /** Receives a datagram. Blocks for up to @c timeout milliseconds. */ virtual bool send(const QByteArray& buffer, int timeout=-1, const ErrorStack &err=ErrorStack()) = 0; }; /** Implement SLIP (serial line internet protocol). * @ingroup rif */ class SlipStream: public PacketStream { Q_OBJECT public: /** Constructor, takes ownership of the device. */ SlipStream(QIODevice *device, QObject *parent=nullptr); public: bool isOpen() const override; void close() override; bool receive(QByteArray &buffer, int timeout=-1, const ErrorStack &err=ErrorStack()) override; bool send(const QByteArray& buffer, int timeout=-1, const ErrorStack &err=ErrorStack()) override; protected: QIODevice *_device; QByteArray _buffer; private: static constexpr char END_OF_PACKET = '\xC0'; static constexpr char ESCAPE = '\xDB'; static constexpr char ESCAPED_C0 = '\xDC'; static constexpr char ESCAPED_DB = '\xDD'; }; #endif // PACKETSTREAM_HH ================================================ FILE: lib/radio.cc ================================================ #include "radio.hh" #include "anytone_interface.hh" #include "radioddity_interface.hh" #include "tyt_interface.hh" #include "dr1801uv_interface.hh" #include "gd73_interface.hh" #include "dm32uv_interface.hh" #include "openrtx_interface.hh" #include "rd5r.hh" #include "gd73.hh" #include "gd77.hh" #include "md390.hh" #include "uv390.hh" #include "md2017.hh" #include "dm1701.hh" #include "dm32uv.hh" #include "dr1801uv.hh" #include "opengd77.hh" #include "openuv380.hh" #include "openrtx.hh" #include "d868uv.hh" #include "d878uv.hh" #include "d878uv2.hh" #include "d578uv.hh" #include "d168uv.hh" #include "dmr6x2uv.hh" #include "logger.hh" #include /* ******************************************************************************************** * * Implementation of Radio * ******************************************************************************************** */ Radio::Radio(QObject *parent) : QThread(parent), _task(StatusIdle) { // pass... } Radio::~Radio() { // pass... } const CallsignDB * Radio::callsignDB() const { return nullptr; } CallsignDB * Radio::callsignDB() { return nullptr; } Radio * Radio::detect(const USBDeviceDescriptor &descr, const RadioInfo &force, const ErrorStack &err) { if (! descr.isValid()) { errMsg(err) << "Cannot detect radio: Invalid interface descriptor."; return nullptr; } logDebug() << "Try to detect radio at " << descr.description() << "."; if (AnytoneGD32Interface::interfaceInfo() == descr) { auto anytone = new AnytoneGD32Interface(descr, err); if (anytone->isOpen()) { RadioInfo id = anytone->identifier(err); if ((id.isValid() && (RadioInfo::D868UVE == id.id())) || (force.isValid() && (RadioInfo::D868UVE == force.id()))) { return new D868UV(anytone); } else if ((id.isValid() && (RadioInfo::D878UV == id.id())) || (force.isValid() && (RadioInfo::D878UV == force.id()))) { return new D878UV(anytone); } else if ((id.isValid() && (RadioInfo::D878UVII == id.id())) || (force.isValid() && (RadioInfo::D878UVII == force.id()))) { return new D878UV2(anytone); } else if ((id.isValid() && (RadioInfo::D578UV == id.id())) || (force.isValid() && (RadioInfo::D578UV == force.id()))) { return new D578UV(anytone); } else if ((id.isValid() && (RadioInfo::DMR6X2UV == id.id())) || (force.isValid() && (RadioInfo::DMR6X2UV == force.id()))) { return new DMR6X2UV(anytone); } else if ((id.isValid() && (RadioInfo::D578UV == id.id())) || (force.isValid() && (RadioInfo::D578UV == force.id()))) { return new D578UV(anytone); } else if (id.isValid()) { errMsg(err) << tr("Unhandled device %1 '%2'. Device known but not implemented yet.") .arg(id.manufacturer()) .arg(id.name()); } else { errMsg(err) << tr("Unknown AnyTone (or similar) device."); } anytone->close(); anytone->deleteLater(); return nullptr; } anytone->deleteLater(); } else if (AnytoneSTM32Interface::interfaceInfo() == descr) { auto anytone = new AnytoneSTM32Interface(descr, err); if (anytone->isOpen()) { RadioInfo id = anytone->identifier(err); if ((id.isValid() && (RadioInfo::D578UV == id.id())) || (force.isValid() && (RadioInfo::D578UV == force.id()))) { return new D578UV(anytone); } else if ((id.isValid() && (RadioInfo::D168UV == id.id())) || (force.isValid() && (RadioInfo::D168UV == force.id()))) { return new D168UV(anytone); } else if (id.isValid()) { errMsg(err) << tr("Unhandled device %1 '%2'. Device known but not implemented yet.") .arg(id.manufacturer()) .arg(id.name()); } else { errMsg(err) << tr("Unknown AnyTone (or similar) device."); } anytone->close(); anytone->deleteLater(); return nullptr; } anytone->deleteLater(); } else if (OpenGD77Interface::interfaceInfo() == descr) { OpenGD77Interface *ogd77 = new OpenGD77Interface(descr, err); if (ogd77->isOpen()) { RadioInfo id = ogd77->identifier(); if ((id.isValid() && (RadioInfo::OpenGD77 == id.id())) || (force.isValid() && (RadioInfo::OpenGD77 == force.id()))) { return new OpenGD77(ogd77); } else if ((id.isValid() && (RadioInfo::OpenUV380 == id.id())) || (force.isValid() && (RadioInfo::OpenUV380 == force.id()))) { return new OpenUV380(ogd77); } else { errMsg(err) << "Unhandled device " << id.manufacturer() << " " << id.name() << ". Device known but not implemented yet."; } ogd77->close(); ogd77->deleteLater(); return nullptr; } ogd77->deleteLater(); } else if (OpenRTXInterface::interfaceInfo() == descr) { auto rtxIf = new OpenRTXInterface(descr, err); if (rtxIf->isOpen()) { RadioInfo id = rtxIf->identifier(); if ((id.isValid() && (RadioInfo::OpenRTX == id.id())) || (force.isValid() && (RadioInfo::OpenRTX == force.id()))) { logInfo() << "Yah!"; return nullptr; //new OpenRTX(rtxIf); } else { errMsg(err) << "Unhandled device " << id.manufacturer() << " " << id.name() << ". Device known but not implemented yet."; } rtxIf->close(); rtxIf->deleteLater(); return nullptr; } rtxIf->deleteLater(); } else if (TyTInterface::interfaceInfo() == descr) { TyTInterface *dfu = new TyTInterface(descr, err); if (dfu->isOpen()) { RadioInfo id = dfu->identifier(); if ((id.isValid() && (RadioInfo::MD390 == id.id())) || (force.isValid() && (RadioInfo::MD390 == force.id()))) { return new MD390(dfu); } else if ((id.isValid() && (RadioInfo::UV390 == id.id())) || (force.isValid() && (RadioInfo::UV390 == force.id()))) { return new UV390(dfu); } else if ((id.isValid() && (RadioInfo::MD2017 == id.id())) || (force.isValid() && (RadioInfo::MD2017 == force.id()))) { return new MD2017(dfu); } else if ((id.isValid() && (RadioInfo::DM1701 == id.id())) || (force.isValid() && (RadioInfo::DM1701 == force.id()))) { logDebug() << "Create DM-1701 radio object."; return new DM1701(dfu); } else { errMsg(err) << "Unhandled device " << id.manufacturer() << " " << id.name() << ". Device known but not implemented yet."; } dfu->close(); dfu->deleteLater(); return nullptr; } dfu->deleteLater(); } else if (RadioddityInterface::interfaceInfo() == descr) { RadioddityInterface *hid = new RadioddityInterface(descr, err); if (hid->isOpen()) { RadioInfo id = hid->identifier(); if ((id.isValid() && (RadioInfo::RD5R == id.id())) || (force.isValid() && (RadioInfo::RD5R == force.id()))) { return new RD5R(hid); } else if ((id.isValid() && (RadioInfo::GD77 == id.id())) || (force.isValid() && (RadioInfo::GD77 == force.id()))) { return new GD77(hid); } else if (id.isValid()) { errMsg(err) << "Unhandled device " << id.manufacturer() << " " << id.name() << ". Device known but not implemented yet."; } else { errMsg(err) << "Unhandled device " << id.manufacturer() << " " << id.name() << ". Device not known."; } hid->close(); hid->deleteLater(); return nullptr; } hid->deleteLater(); } else if ((DR1801UVInterface::interfaceInfo() == descr) && force.isValid() && (RadioInfo::DR1801UV==force.id())){ DR1801UVInterface *dif = new DR1801UVInterface(descr, err); if (dif->isOpen()) { RadioInfo id = dif->identifier(err); if (id.isValid() && (RadioInfo::DR1801UV == id.id())) { return new DR1801UV(dif); } else if (id.isValid()) { errMsg(err) << "Unhandled device " << id.manufacturer() << " " << id.name() << ". Device known but not implemented yet."; } else { errMsg(err) << "Unknown device or failed connection to the device."; } dif->close(); dif->deleteLater(); return nullptr; } dif->deleteLater(); } else if (C7000Device::interfaceInfo() == descr) { GD73Interface *gdif = new GD73Interface(descr, err); if (gdif->isOpen()) { RadioInfo id = gdif->identifier(); if ((id.isValid() && (RadioInfo::GD73 == id.id())) || (force.isValid() && (RadioInfo::GD73 == force.id()))) { return new GD73(gdif); } else { errMsg(err) << "Unhandled device " << id.manufacturer() << " " << id.name() << ". Device known but not implemented yet."; } gdif->close(); gdif->deleteLater(); return nullptr; } gdif->deleteLater(); } else if ((DM32UVInterface::interfaceInfo() == descr) && force.isValid() && (RadioInfo::DM32UV==force.id())) { DM32UVInterface *dm32if = new DM32UVInterface(descr, err); if (dm32if->isOpen()) { RadioInfo id = dm32if->identifier(err); if (! id.isValid()) { errMsg(err) << "Cannot identify device."; } else if (RadioInfo::DM32UV == id.id()) { return new DM32UV(dm32if); } else { errMsg(err) << "Unhandled device " << id.manufacturer() << " " << id.name() << ". Device known but not implemented yet."; } dm32if->close(); dm32if->deleteLater(); return nullptr; } dm32if->deleteLater(); return nullptr; } return nullptr; } Radio::Status Radio::status() const { return _task; } const ErrorStack & Radio::errorStack() const { return _errorStack; } ================================================ FILE: lib/radio.hh ================================================ /** @defgroup dsc Supported devices * This module collects all classes are device specific. * * That is, implementing device specific configurations, aka codeplugs as well as the specific * communication with these radios. */ #ifndef RADIO_HH #define RADIO_HH #include #include "radioinfo.hh" #include "radiointerface.hh" #include "codeplug.hh" #include "userdatabase.hh" #include "callsigndb.hh" #include "errorstack.hh" #include "config.hh" class RadioLimits; /** Base class for all Radio objects. * * The radio objects represents a connected radio. This class controlles the communication * with the device as well as the conversion between device specific code-plugs and generic * configurations. * * @ingroup rif */ class Radio : public QThread { Q_OBJECT public: /** Possible states of the radio object. */ typedef enum { StatusIdle, ///< Idle, nothing to do. StatusDownload, ///< Downloading codeplug. StatusUpload, ///< Uploading codeplug. StatusUploadCallsigns, ///< Uploading codeplug. StatusUploadSatellites, ///< Uploading satellite config. StatusError ///< An error occurred. } Status; public: /** Default constructor. */ explicit Radio(QObject *parent = nullptr); virtual ~Radio(); /** Returns the name of the radio (e.g., device identifier). */ virtual const QString &name() const = 0; /** Returns the limits for this radio. * * Call @c RadioLimits::verifyConfig to verify a codeplug with respect to a radio. * * @since Version 0.10.2 */ virtual const RadioLimits &limits() const = 0; /** Returns the codeplug instance. */ virtual const Codeplug &codeplug() const = 0; /** Returns the codeplug instance. */ virtual Codeplug &codeplug() = 0; /** Returns the call-sign DB instance. */ virtual const CallsignDB *callsignDB() const; /** Returns the call-sign DB instance. */ virtual CallsignDB *callsignDB(); /** Returns the current status. */ Status status() const; /** Returns the error stack, passed to @c startDownload, @c startUpload or * @c startUploadCallsignDB. It contains the error messages from the upload/download process. */ const ErrorStack &errorStack() const; public: /** Tries to detect the radio connected to the specified interface or constructs the specified * radio using the @c RadioInfo passed by @c force. */ static Radio *detect(const USBDeviceDescriptor &descr, const RadioInfo &force=RadioInfo(), const ErrorStack &err=ErrorStack()); public slots: /** Starts the download of the codeplug. * Once the download finished, the codeplug can be accessed and decoded using * the @c codeplug() method. */ virtual bool startDownload(const TransferFlags &flags, const ErrorStack &err=ErrorStack()) = 0; /** Derives the device-specific codeplug from the generic configuration and uploads that * codeplug to the radio. */ virtual bool startUpload( Config *config, const Codeplug::Flags &flags = Codeplug::Flags(), const ErrorStack &err=ErrorStack()) = 0; /** Assembles the callsign DB from the given one and uploads it to the device. */ virtual bool startUploadCallsignDB( UserDatabase *db, const CallsignDB::Flags &selection=CallsignDB::Flags(), const ErrorStack &err=ErrorStack()) = 0; /** Assembles the satellite config and writes it to the device. */ virtual bool startUploadSatelliteConfig( SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err=ErrorStack()) = 0; signals: /** Gets emitted once the codeplug download has been started. */ void downloadStarted(); /** Gets emitted on download progress (e.g., for progress bars). */ void downloadProgress(int percent); /** Gets emitted once the codeplug download has been finished. */ void downloadFinished(Radio *radio, Codeplug *codeplug); /** Gets emitted if there was an error during the codeplug download. */ void downloadError(Radio *radio); /** Gets emitted once the codeplug upload has been started. */ void uploadStarted(); /** Gets emitted on upload progress (e.g., for progress bars). */ void uploadProgress(int percent); /** Gets emitted if there was an error during the upload. */ void uploadError(Radio *radio); /** Gets emitted once the codeplug upload has been completed successfully. */ void uploadComplete(Radio *radio); protected: /** The current state/task. */ Status _task; /** The error stack. */ ErrorStack _errorStack; }; #endif // RADIO_HH ================================================ FILE: lib/radioddity_codeplug.cc ================================================ #include "radioddity_codeplug.hh" #include #include "utils.hh" #include "logger.hh" #include "scanlist.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "zone.hh" #include "config.hh" #include "commercial_extension.hh" #include "intermediaterepresentation.hh" /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::ChannelElement * ********************************************************************************************* */ RadioddityCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } RadioddityCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::ChannelElement::~ChannelElement() { // pass... } void RadioddityCodeplug::ChannelElement::clear() { setName(""); setRXFrequency(0); setTXFrequency(0); setMode(MODE_ANALOG); setUInt8(0x0019, 0x00); setUInt8(0x001a, 0x00); setTXTimeOut(Interval::infinity()); setTXTimeOutRekeyDelay(0); setAdmitCriterion(ADMIT_ALWAYS); setUInt8(0x001e, 0x50); setScanListIndex(0x00); setRXTone(SelectiveCall()); setTXTone(SelectiveCall()); setUInt8(0x0024, 0x00); setTXSignalingIndex(0); setUInt8(0x0026, 0x00); setRXSignalingIndex(0); setUInt8(0x0028, 0x16); setPrivacyGroup(PRIVGR_NONE); setTXColorCode(0); setGroupListIndex(0); setRXColorCode(0); setEmergencySystemIndex(0); setContactIndex(0); setUInt32_be(0x0030, 0); // clear all bitfields at once. setUInt8(0x0034, 0); setUInt8(0x0035, 0); setUInt8(0x0036, 0); } QString RadioddityCodeplug::ChannelElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void RadioddityCodeplug::ChannelElement::setName(const QString &n) { writeASCII(Offset::name(), n, Limit::nameLength(), 0xff); } uint32_t RadioddityCodeplug::ChannelElement::rxFrequency() const { return getBCD8_le(Offset::rxFrequency())*10; } void RadioddityCodeplug::ChannelElement::setRXFrequency(uint32_t freq) { setBCD8_le(Offset::rxFrequency(), freq/10); } uint32_t RadioddityCodeplug::ChannelElement::txFrequency() const { return getBCD8_le(Offset::txFrequency())*10; } void RadioddityCodeplug::ChannelElement::setTXFrequency(uint32_t freq) { setBCD8_le(Offset::txFrequency(), freq/10); } RadioddityCodeplug::ChannelElement::Mode RadioddityCodeplug::ChannelElement::mode() const { return (Mode)getUInt8(Offset::mode()); } void RadioddityCodeplug::ChannelElement::setMode(Mode mode) { setUInt8(Offset::mode(), (unsigned)mode); } Interval RadioddityCodeplug::ChannelElement::txTimeOut() const { if (0 == getUInt8(Offset::txTimeout())) return Interval::infinity(); return Interval::fromSeconds(getUInt8(Offset::txTimeout())*15); } void RadioddityCodeplug::ChannelElement::setTXTimeOut(const Interval& tot) { if (tot.isInfinite()) setUInt8(Offset::txTimeout(), 0); else setUInt8(Offset::txTimeout(), tot.seconds()/15); } unsigned RadioddityCodeplug::ChannelElement::txTimeOutRekeyDelay() const { return getUInt8(Offset::txTimeoutRekeyDelay()); } void RadioddityCodeplug::ChannelElement::setTXTimeOutRekeyDelay(unsigned delay) { setUInt8(Offset::txTimeoutRekeyDelay(), delay); } RadioddityCodeplug::ChannelElement::Admit RadioddityCodeplug::ChannelElement::admitCriterion() const { return (Admit) getUInt8(Offset::admitCriterion()); } void RadioddityCodeplug::ChannelElement::setAdmitCriterion(Admit admit) { setUInt8(Offset::admitCriterion(), (unsigned)admit); } bool RadioddityCodeplug::ChannelElement::hasScanList() const { return 0 != scanListIndex(); } unsigned RadioddityCodeplug::ChannelElement::scanListIndex() const { return getUInt8(Offset::scanList()); } void RadioddityCodeplug::ChannelElement::setScanListIndex(unsigned index) { setUInt8(Offset::scanList(), index); } SelectiveCall RadioddityCodeplug::ChannelElement::rxTone() const { return decode_ctcss_tone_table(getUInt16_le(Offset::rxTone())); } void RadioddityCodeplug::ChannelElement::setRXTone(const SelectiveCall &code) { setUInt16_le(Offset::rxTone(), encode_ctcss_tone_table(code)); } SelectiveCall RadioddityCodeplug::ChannelElement::txTone() const { return decode_ctcss_tone_table(getUInt16_le(Offset::txTone())); } void RadioddityCodeplug::ChannelElement::setTXTone(const SelectiveCall &code) { setUInt16_le(Offset::txTone(), encode_ctcss_tone_table(code)); } unsigned RadioddityCodeplug::ChannelElement::txSignalingIndex() const { return getUInt8(Offset::txSignaling()); } void RadioddityCodeplug::ChannelElement::setTXSignalingIndex(unsigned index) { setUInt8(Offset::txSignaling(), index); } unsigned RadioddityCodeplug::ChannelElement::rxSignalingIndex() const { return getUInt8(Offset::rxSignaling()); } void RadioddityCodeplug::ChannelElement::setRXSignalingIndex(unsigned index) { setUInt8(Offset::rxSignaling(), index); } RadioddityCodeplug::ChannelElement::PrivacyGroup RadioddityCodeplug::ChannelElement::privacyGroup() const { return (PrivacyGroup) getUInt8(Offset::privacyGroup()); } void RadioddityCodeplug::ChannelElement::setPrivacyGroup(PrivacyGroup grp) { setUInt8(Offset::privacyGroup(), (unsigned)grp); } unsigned RadioddityCodeplug::ChannelElement::txColorCode() const { return getUInt8(Offset::txColorCode()); } void RadioddityCodeplug::ChannelElement::setTXColorCode(unsigned cc) { setUInt8(Offset::txColorCode(), cc); } bool RadioddityCodeplug::ChannelElement::hasGroupList() const { return 0 != groupListIndex(); } unsigned RadioddityCodeplug::ChannelElement::groupListIndex() const { return getUInt8(Offset::groupList()); } void RadioddityCodeplug::ChannelElement::setGroupListIndex(unsigned index) { setUInt8(Offset::groupList(), index); } unsigned RadioddityCodeplug::ChannelElement::rxColorCode() const { return getUInt8(Offset::rxColorCode()); } void RadioddityCodeplug::ChannelElement::setRXColorCode(unsigned cc) { setUInt8(Offset::rxColorCode(), cc); } bool RadioddityCodeplug::ChannelElement::hasEmergencySystem() const { return 0 != emergencySystemIndex(); } unsigned RadioddityCodeplug::ChannelElement::emergencySystemIndex() const { return getUInt8(Offset::emergencySystem()); } void RadioddityCodeplug::ChannelElement::setEmergencySystemIndex(unsigned index) { setUInt8(Offset::emergencySystem(), index); } bool RadioddityCodeplug::ChannelElement::hasContact() const { return 0!=contactIndex(); } unsigned RadioddityCodeplug::ChannelElement::contactIndex() const { return getUInt16_le(Offset::transmitContact()); } void RadioddityCodeplug::ChannelElement::setContactIndex(unsigned index) { setUInt16_le(Offset::transmitContact(), index); } bool RadioddityCodeplug::ChannelElement::dataCallConfirm() const { return getBit(Offset::dataCallConfirm()); } void RadioddityCodeplug::ChannelElement::enableDataCallConfirm(bool enable) { setBit(Offset::dataCallConfirm(), enable); } bool RadioddityCodeplug::ChannelElement::emergencyAlarmACK() const { return getBit(Offset::emergencyAlarmACK()); } void RadioddityCodeplug::ChannelElement::enableEmergencyAlarmACK(bool enable) { setBit(Offset::emergencyAlarmACK(), enable); } bool RadioddityCodeplug::ChannelElement::privateCallConfirm() const { return getBit(Offset::privateCallConfirm()); } void RadioddityCodeplug::ChannelElement::enablePrivateCallConfirm(bool enable) { setBit(Offset::privateCallConfirm(), enable); } bool RadioddityCodeplug::ChannelElement::privacyEnabled() const { return getBit(Offset::privacyEnabled()); } void RadioddityCodeplug::ChannelElement::enablePrivacy(bool enable) { setBit(Offset::privacyEnabled(), enable); } DMRChannel::TimeSlot RadioddityCodeplug::ChannelElement::timeSlot() const { return (getBit(Offset::timeSlot()) ? DMRChannel::TimeSlot::TS2 : DMRChannel::TimeSlot::TS1); } void RadioddityCodeplug::ChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { setBit(Offset::timeSlot(), DMRChannel::TimeSlot::TS2 == ts); } bool RadioddityCodeplug::ChannelElement::dualCapacityDirectMode() const { return getBit(Offset::dualCapacityDirectMode()); } void RadioddityCodeplug::ChannelElement::enableDualCapacityDirectMode(bool enable) { setBit(Offset::dualCapacityDirectMode(), enable); } bool RadioddityCodeplug::ChannelElement::nonSTEFrequency() const { return getBit(Offset::nonSTEFrequency()); } void RadioddityCodeplug::ChannelElement::enableNonSTEFrequency(bool enable) { setBit(Offset::nonSTEFrequency(), enable); } FMChannel::Bandwidth RadioddityCodeplug::ChannelElement::bandwidth() const { return (getBit(Offset::bandwidth()) ? FMChannel::Bandwidth::Wide : FMChannel::Bandwidth::Narrow); } void RadioddityCodeplug::ChannelElement::setBandwidth(FMChannel::Bandwidth bw) { setBit(Offset::bandwidth(), FMChannel::Bandwidth::Wide == bw); } bool RadioddityCodeplug::ChannelElement::rxOnly() const { return getBit(Offset::rxOnly()); } void RadioddityCodeplug::ChannelElement::enableRXOnly(bool enable) { setBit(Offset::rxOnly(), enable); } bool RadioddityCodeplug::ChannelElement::talkaround() const { return getBit(Offset::talkaround()); } void RadioddityCodeplug::ChannelElement::enableTalkaround(bool enable) { setBit(Offset::talkaround(), enable); } bool RadioddityCodeplug::ChannelElement::vox() const { return getBit(Offset::vox()); } void RadioddityCodeplug::ChannelElement::enableVOX(bool enable) { setBit(Offset::vox(), enable); } Channel::Power RadioddityCodeplug::ChannelElement::power() const { return (getBit(Offset::power()) ? Channel::Power::High : Channel::Power::Low); } void RadioddityCodeplug::ChannelElement::setPower(Channel::Power pwr) { switch (pwr) { case Channel::Power::Min: case Channel::Power::Low: clearBit(Offset::power()); break; case Channel::Power::Mid: case Channel::Power::High: case Channel::Power::Max: setBit(Offset::power()); break; } } Channel * RadioddityCodeplug::ChannelElement::toChannelObj(Codeplug::Context &ctx, const ErrorStack& err) const { Q_UNUSED(ctx); Q_UNUSED(err) Channel *ch = nullptr; if (MODE_ANALOG == mode()) { FMChannel *ach = new FMChannel(); ch = ach; switch (admitCriterion()) { case ADMIT_ALWAYS: ach->setAdmit(FMChannel::Admit::Always); break; case ADMIT_CH_FREE: ach->setAdmit(FMChannel::Admit::Free); break; default: ach->setAdmit(FMChannel::Admit::Always); break; } ach->setBandwidth(bandwidth()); ach->setRXTone(rxTone()); ach->setTXTone(txTone()); ach->setSquelchDefault(); // There is no per-channel squelch setting ach->extended()->enableTalkaround(talkaround()); } else { DMRChannel *dch = new DMRChannel(); ch = dch; switch (admitCriterion()) { case ADMIT_ALWAYS: dch->setAdmit(DMRChannel::Admit::Always); break; case ADMIT_CH_FREE: dch->setAdmit(DMRChannel::Admit::Free); break; case ADMIT_COLOR: dch->setAdmit(DMRChannel::Admit::ColorCode); break; default: dch->setAdmit(DMRChannel::Admit::Always); break; } dch->setTimeSlot(timeSlot()); dch->setColorCode(txColorCode()); dch->extended()->enableDataConfirm(dataCallConfirm()); dch->extended()->enablePrivateCallConfirm(privateCallConfirm()); dch->extended()->enableDCDM(dualCapacityDirectMode()); dch->extended()->enableTalkaround(talkaround()); } // Apply common settings ch->setName(name()); ch->setRXFrequency(Frequency::fromHz(rxFrequency())); ch->setTXFrequency(Frequency::fromHz(txFrequency())); ch->setPower(power()); ch->setTimeout(txTimeOut()); ch->setRXOnly(rxOnly()); if (vox()) ch->setVOXDefault(); else ch->disableVOX(); // done. return ch; } bool RadioddityCodeplug::ChannelElement::linkChannelObj(Channel *c, Context &ctx, const ErrorStack& err) const { Q_UNUSED(err) // Link common if (hasScanList() && ctx.has(scanListIndex())) c->setScanList(ctx.get(scanListIndex())); // Link digital channel if (c->is()) { DMRChannel *dc = c->as(); if (hasGroupList() && ctx.has(groupListIndex())) dc->setGroupList(ctx.get(groupListIndex())); if (hasContact() && ctx.has(contactIndex())) dc->setContact(ctx.get(contactIndex())); } return true; } bool RadioddityCodeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx, const ErrorStack& err) { clear(); setName(c->name()); setRXFrequency(c->rxFrequency().inHz()); setTXFrequency(c->txFrequency().inHz()); if (c->defaultPower()) setPower(ctx.config()->settings()->power()); else setPower(c->power()); if (c->defaultTimeout()) setTXTimeOut(ctx.config()->settings()->tot()); else setTXTimeOut(c->timeout()); enableRXOnly(c->rxOnly()); // Enable vox bool defaultVOXEnabled = (c->defaultVOX() && ctx.config()->settings()->audio()->voxEnabled()); bool channelVOXEnabled = (! (c->voxDisabled()||c->defaultVOX())); enableVOX(defaultVOXEnabled || channelVOXEnabled); if (c->scanList()) setScanListIndex(ctx.index(c->scanList())); if (c->is()) { const FMChannel *ac = c->as(); setMode(MODE_ANALOG); switch (ac->admit()) { case FMChannel::Admit::Always: setAdmitCriterion(ADMIT_ALWAYS); break; case FMChannel::Admit::Free: setAdmitCriterion(ADMIT_CH_FREE); break; default: setAdmitCriterion(ADMIT_ALWAYS); } setBandwidth(ac->bandwidth()); setRXTone(ac->rxTone()); setTXTone(ac->txTone()); // no per channel squelch setting enableTalkaround(ac->extended()->talkaround()); } else if (c->is()) { const DMRChannel *dc = c->as(); setMode(MODE_DIGITAL); switch (dc->admit()) { case DMRChannel::Admit::Always: setAdmitCriterion(ADMIT_ALWAYS); break; case DMRChannel::Admit::Free: setAdmitCriterion(ADMIT_CH_FREE); break; case DMRChannel::Admit::ColorCode: setAdmitCriterion(ADMIT_COLOR); break; } setTimeSlot(dc->timeSlot()); setRXColorCode(dc->colorCode()); setTXColorCode(dc->colorCode()); if (dc->groupList()) setGroupListIndex(ctx.index(dc->groupList())); if (dc->contact()) setContactIndex(ctx.index(dc->contact())); enableTalkaround(dc->extended()->talkaround()); enableDataCallConfirm(dc->extended()->dataConfirm()); enablePrivateCallConfirm(dc->extended()->privateCallConfirm()); enableDualCapacityDirectMode(dc->extended()->dcdm()); } else { errMsg(err) << "Cannot encode channel of type '" << c->metaObject()->className() << "': Not supported by the radio."; return false; } return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::ChannelBankElement * ********************************************************************************************* */ RadioddityCodeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::ChannelBankElement::ChannelBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::ChannelBankElement::~ChannelBankElement() { // pass... } void RadioddityCodeplug::ChannelBankElement::clear() { memset(_data, 0, size()); } bool RadioddityCodeplug::ChannelBankElement::isEnabled(unsigned idx) const { unsigned byte = Offset::bitmask() + idx/8, bit = idx%8; return getBit(byte, bit); } void RadioddityCodeplug::ChannelBankElement::enable(unsigned idx, bool enabled) { unsigned byte = Offset::bitmask() + idx/8, bit = idx%8; return setBit(byte, bit, enabled); } uint8_t * RadioddityCodeplug::ChannelBankElement::get(unsigned idx) const { return (_data+Offset::channels())+idx*ChannelElement::size(); } RadioddityCodeplug::ChannelElement RadioddityCodeplug::ChannelBankElement::channel(unsigned int n) { return ChannelElement((_data+Offset::channels())+n*ChannelElement::size()); } /* ******************************************************************************************** * * Implementation of RadioddityCodeplug::VFOChannelElement * ******************************************************************************************** */ RadioddityCodeplug::VFOChannelElement::VFOChannelElement(uint8_t *ptr, unsigned size) : ChannelElement(ptr, size) { // pass... } RadioddityCodeplug::VFOChannelElement::VFOChannelElement(uint8_t *ptr) : ChannelElement(ptr) { // pass... } void RadioddityCodeplug::VFOChannelElement::clear() { ChannelElement::clear(); setStepSize(12.5); setOffsetMode(OffsetMode::Off); setTXOffset(10.0); } QString RadioddityCodeplug::VFOChannelElement::name() const { return QString(); } void RadioddityCodeplug::VFOChannelElement::setName(const QString &name) { Q_UNUSED(name); ChannelElement::setName(""); } double RadioddityCodeplug::VFOChannelElement::stepSize() const { switch (StepSize(getUInt4(Offset::stepSize()))) { case StepSize::SS2_5kHz: return 2.5; case StepSize::SS5kHz: return 5; case StepSize::SS6_25kHz: return 6.25; case StepSize::SS10kHz: return 10.0; case StepSize::SS12_5kHz: return 12.5; case StepSize::SS20kHz: return 20; case StepSize::SS30kHz: return 30; case StepSize::SS50kHz: return 50; } return 12.5; } void RadioddityCodeplug::VFOChannelElement::setStepSize(double kHz) { if (2.5 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS2_5kHz); else if (5.0 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS5kHz); else if (6.25 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS6_25kHz); else if (10.0 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS10kHz); else if (12.5 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS12_5kHz); else if (20.0 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS20kHz); else if (30.0 >= kHz) setUInt4(Offset::stepSize(), (unsigned)StepSize::SS30kHz); else setUInt4(Offset::stepSize(), (unsigned)StepSize::SS50kHz); } RadioddityCodeplug::VFOChannelElement::OffsetMode RadioddityCodeplug::VFOChannelElement::offsetMode() const { return (OffsetMode)getUInt2(Offset::offsetMode()); } void RadioddityCodeplug::VFOChannelElement::setOffsetMode(OffsetMode mode) { setUInt2(Offset::offsetMode(), (unsigned)mode); } double RadioddityCodeplug::VFOChannelElement::txOffset() const { return ((double)getBCD4_le(Offset::txOffset()))/100; } void RadioddityCodeplug::VFOChannelElement::setTXOffset(double f) { setBCD4_le(Offset::txOffset(), (f*100)); } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::ContactElement * ********************************************************************************************* */ RadioddityCodeplug::ContactElement::ContactElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::ContactElement::ContactElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::ContactElement::~ContactElement() { // pass... } void RadioddityCodeplug::ContactElement::clear() { setName(""); setNumber(0); setType(DMRContact::GroupCall); enableRing(0); setRingStyle(0); setUInt8(0x017, 0x00); } bool RadioddityCodeplug::ContactElement::isValid() const { return (! name().isEmpty()); } QString RadioddityCodeplug::ContactElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void RadioddityCodeplug::ContactElement::setName(const QString name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } unsigned RadioddityCodeplug::ContactElement::number() const { return getBCD8_be(Offset::number()); } void RadioddityCodeplug::ContactElement::setNumber(unsigned id) { setBCD8_be(Offset::number(), id); } DMRContact::Type RadioddityCodeplug::ContactElement::type() const { switch (getUInt8(Offset::type())) { case 0: return DMRContact::GroupCall; case 1: return DMRContact::PrivateCall; case 2: return DMRContact::AllCall; default: break; } return DMRContact::PrivateCall; } void RadioddityCodeplug::ContactElement::setType(DMRContact::Type type) { switch (type) { case DMRContact::GroupCall: setUInt8(Offset::type(), 0); break; case DMRContact::PrivateCall: setUInt8(Offset::type(), 1); break; case DMRContact::AllCall: setUInt8(Offset::type(), 2); break; } } bool RadioddityCodeplug::ContactElement::ring() const { return 0x00 != getUInt8(Offset::ring()); } void RadioddityCodeplug::ContactElement::enableRing(bool enable) { setUInt8(Offset::ring(), enable ? 0x01 : 0x00); } unsigned RadioddityCodeplug::ContactElement::ringStyle() const { return getUInt8(Offset::ringStyle()); } void RadioddityCodeplug::ContactElement::setRingStyle(unsigned style) { style = std::min(style, Limit::ringStyle()); setUInt8(Offset::ringStyle(), style); } DMRContact * RadioddityCodeplug::ContactElement::toContactObj(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx) if (! isValid()) { errMsg(err) << "Cannot create contact from an invalid element."; return nullptr; } return new DMRContact(type(), name(), number(), ring()); } bool RadioddityCodeplug::ContactElement::fromContactObj(const DMRContact *cont, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) setName(cont->name()); setNumber(cont->number()); setType(cont->type()); if (cont->ring()) { enableRing(true); setRingStyle(1); } else { enableRing(false); } return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::DTMFContactElement * ********************************************************************************************* */ RadioddityCodeplug::DTMFContactElement::DTMFContactElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::DTMFContactElement::DTMFContactElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::DTMFContactElement::~DTMFContactElement() { // pass... } void RadioddityCodeplug::DTMFContactElement::clear() { memset(_data, 0xff, Limit::nameLength()); } bool RadioddityCodeplug::DTMFContactElement::isValid() const { return (! name().isEmpty()); } QString RadioddityCodeplug::DTMFContactElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void RadioddityCodeplug::DTMFContactElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } QString RadioddityCodeplug::DTMFContactElement::number() const { return readASCII(Offset::number(), Limit::numberLength(), 0xff); } void RadioddityCodeplug::DTMFContactElement::setNumber(const QString &number) { writeASCII(Offset::number(), number, Limit::numberLength(), 0xff); } DTMFContact * RadioddityCodeplug::DTMFContactElement::toContactObj(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx) if (! isValid()) { errMsg(err) << "Cannot create a DTMF contact from an invalid element."; return nullptr; } return new DTMFContact(name(), number()); } bool RadioddityCodeplug::DTMFContactElement::fromContactObj(const DTMFContact *cont, Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) setName(cont->name()); setNumber(cont->number()); return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::ZoneElement * ********************************************************************************************* */ RadioddityCodeplug::ZoneElement::ZoneElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::ZoneElement::ZoneElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::ZoneElement::~ZoneElement() { // pass... } void RadioddityCodeplug::ZoneElement::clear() { memset(_data+Offset::name(), 0xff, Limit::nameLength()); memset(_data+Offset::channels(), 0x00, sizeof(uint16_t)*Limit::memberCount()); } bool RadioddityCodeplug::ZoneElement::isValid() const { return (! name().isEmpty()); } QString RadioddityCodeplug::ZoneElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void RadioddityCodeplug::ZoneElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } bool RadioddityCodeplug::ZoneElement::hasMember(unsigned n) const { if (n >= Limit::memberCount()) return false; return (0 != member(n)); } unsigned RadioddityCodeplug::ZoneElement::member(unsigned n) const { if (n >= Limit::memberCount()) return 0; return getUInt16_le(Offset::channels()+Offset::betweenChannels()*n); } void RadioddityCodeplug::ZoneElement::setMember(unsigned n, unsigned idx) { if (n >= Limit::memberCount()) return; setUInt16_le(Offset::channels()+Offset::betweenChannels()*n, idx); } void RadioddityCodeplug::ZoneElement::clearMember(unsigned n) { setMember(n, 0); } Zone * RadioddityCodeplug::ZoneElement::toZoneObj(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx) if (! isValid()) { errMsg(err) << "Cannot decode an invalid zone."; return nullptr; } return new Zone(name()); } bool RadioddityCodeplug::ZoneElement::linkZoneObj(Zone *zone, Context &ctx, const ErrorStack &err) const { if (! isValid()) { errMsg(err) << "Cannot link invalid zone."; return false; } for (unsigned int i=0; (i(member(i))) { zone->A()->add(ctx.get(member(i))); } else { logWarn() << "While linking zone '" << zone->name() << "': " << i <<"-th channel index " << member(i) << " out of bounds."; } } return true; } bool RadioddityCodeplug::ZoneElement::fromZoneObjA(const Zone *zone, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) if (zone->A()->count() && zone->B()->count()) setName(zone->name() + " A"); else setName(zone->name()); for (unsigned int i=0; iA()->count()) setMember(i, ctx.index(zone->A()->get(i))); else clearMember(i); } return true; } bool RadioddityCodeplug::ZoneElement::fromZoneObjB(const Zone *zone, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) if (zone->A()->count() && zone->B()->count()) setName(zone->name() + " B"); else setName(zone->name()); for (unsigned int i=0; iB()->count()) setMember(i, ctx.index(zone->B()->get(i))); else clearMember(i); } return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::ZoneBankElement * ********************************************************************************************* */ RadioddityCodeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::ZoneBankElement::ZoneBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::ZoneBankElement::~ZoneBankElement() { // pass... } void RadioddityCodeplug::ZoneBankElement::clear() { memset(_data, 0, size()); } bool RadioddityCodeplug::ZoneBankElement::isEnabled(unsigned idx) const { unsigned byte=Offset::bitmap() + idx/8, bit = idx%8; return getBit(byte, bit); } void RadioddityCodeplug::ZoneBankElement::enable(unsigned idx, bool enabled) { unsigned byte=Offset::bitmap() + idx/8, bit = idx%8; setBit(byte, bit, enabled); } uint8_t * RadioddityCodeplug::ZoneBankElement::get(unsigned idx) const { return _data + Offset::zones() + idx*ZoneElement::size(); } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::GroupListElement * ********************************************************************************************* */ RadioddityCodeplug::GroupListElement::GroupListElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::GroupListElement::GroupListElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::GroupListElement::~GroupListElement() { // pass... } void RadioddityCodeplug::GroupListElement::clear() { setName(""); if ((Offset::members() + Offset::betweenMembers()*Limit::memberCount()) > _size) { logFatal() << "Cannot clear group list: Overflow."; return; } memset(_data+Offset::members(), 0, Offset::betweenMembers()*Limit::memberCount()); } QString RadioddityCodeplug::GroupListElement::name() const { return readASCII(Offset::name(), Limit::nameLength(), 0xff); } void RadioddityCodeplug::GroupListElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::nameLength(), 0xff); } bool RadioddityCodeplug::GroupListElement::hasMember(unsigned n) const { if (n >= Limit::memberCount()) return false; return 0 != member(n); } unsigned RadioddityCodeplug::GroupListElement::member(unsigned n) const { return getUInt16_le(Offset::members() + sizeof(uint16_t)*n); } void RadioddityCodeplug::GroupListElement::setMember(unsigned n, unsigned idx) { return setUInt16_le(Offset::members() + sizeof(uint16_t)*n, idx); } void RadioddityCodeplug::GroupListElement::clearMember(unsigned n) { setMember(n,0); } RXGroupList * RadioddityCodeplug::GroupListElement::toRXGroupListObj(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err) return new RXGroupList(name()); } bool RadioddityCodeplug::GroupListElement::linkRXGroupListObj(unsigned int ncnt, RXGroupList *lst, Context &ctx, const ErrorStack &err) const { for (unsigned int i=0; (i(member(i))) { lst->addContact(ctx.get(member(i))); } else { errMsg(err) << "Cannot link group list '" << lst->name() << "': Member index " << member(i) << " does not refer to a digital contact."; return false; } } return true; } bool RadioddityCodeplug::GroupListElement::fromRXGroupListObj(const RXGroupList *lst, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) setName(lst->name()); int j = 0; // Iterate over all entries in the codeplug for (unsigned int i=0; icount() > j) { // Skip non-group-call entries while((lst->count() > j) && (DMRContact::GroupCall != lst->contact(j)->type())) { logWarn() << "Contact '" << lst->contact(i)->name() << "' in group list '" << lst->name() << "' is not a group call. Skip entry."; j++; } setMember(i, ctx.index(lst->contact(j))); j++; } else { // Clear entry. clearMember(i); } } return false; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::GroupListBankElement * ********************************************************************************************* */ RadioddityCodeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::GroupListBankElement::GroupListBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::GroupListBankElement::~GroupListBankElement() { // pass... } void RadioddityCodeplug::GroupListBankElement::clear() { memset(_data, 0, Limit::groupListCount()); } bool RadioddityCodeplug::GroupListBankElement::isEnabled(unsigned n) const { return 0 != getUInt8(Offset::contactCounts() + n); } unsigned RadioddityCodeplug::GroupListBankElement::contactCount(unsigned n) const { return getUInt8(Offset::contactCounts() + n) - 1; } void RadioddityCodeplug::GroupListBankElement::setContactCount(unsigned n, unsigned size) { setUInt8(Offset::contactCounts() + n, size+1); } void RadioddityCodeplug::GroupListBankElement::disable(unsigned n) { setUInt8(Offset::contactCounts() + n, 0); } uint8_t * RadioddityCodeplug::GroupListBankElement::get(unsigned n) const { if ((Offset::groupLists() + (n+1)*GroupListElement::size())>_size) { logFatal() << "Cannot resolve group list at index " << n << ": Overflow."; return nullptr; } return _data + Offset::groupLists() + n*GroupListElement::size(); } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::ScanListElement * ********************************************************************************************* */ RadioddityCodeplug::ScanListElement::ScanListElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::ScanListElement::ScanListElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::ScanListElement::~ScanListElement() { // pass... } void RadioddityCodeplug::ScanListElement::clear() { setName(""); setUInt8(0x000f, 0xff); // Clear member memset(_data+0x0010, 0x00, 2*32); clearPrimary(); clearSecondary(); clearRevert(); setHoldTime(1000); setPrioritySampleTime(2000); } QString RadioddityCodeplug::ScanListElement::name() const { return readASCII(Offset::name(), Limit::name(), 0xff); } void RadioddityCodeplug::ScanListElement::setName(const QString &name) { writeASCII(Offset::name(), name, Limit::name(), 0xff); } bool RadioddityCodeplug::ScanListElement::channelMark() const { return getBit(Offset::channelMark()); } void RadioddityCodeplug::ScanListElement::enableChannelMark(bool enable) { setBit(Offset::channelMark(), enable); } RadioddityCodeplug::ScanListElement::Mode RadioddityCodeplug::ScanListElement::mode() const { return (Mode) getUInt2(Offset::mode()); } void RadioddityCodeplug::ScanListElement::setMode(Mode mode) { setUInt2(Offset::mode(), (unsigned)mode); } bool RadioddityCodeplug::ScanListElement::talkback() const { return getBit(Offset::talkback()); } void RadioddityCodeplug::ScanListElement::enableTalkback(bool enable) { setBit(Offset::talkback(), enable); } bool RadioddityCodeplug::ScanListElement::hasMember(unsigned n) const { return 0 != getUInt16_le(Offset::members()+Offset::betweenMembers()*n); } bool RadioddityCodeplug::ScanListElement::isSelected(unsigned n) const { return 1 == getUInt16_le(Offset::members()+Offset::betweenMembers()*n); } unsigned RadioddityCodeplug::ScanListElement::member(unsigned n) const { return getUInt16_le(Offset::members() + Offset::betweenMembers()*n)-1; } void RadioddityCodeplug::ScanListElement::setMember(unsigned n, unsigned idx) { setUInt16_le(Offset::members()+Offset::betweenMembers()*n, idx+1); } void RadioddityCodeplug::ScanListElement::setSelected(unsigned n) { setUInt16_le(Offset::members() + Offset::betweenMembers()*n, 1); } void RadioddityCodeplug::ScanListElement::clearMember(unsigned n) { setUInt16_le(Offset::members() + Offset::betweenMembers()*n, 0); } bool RadioddityCodeplug::ScanListElement::hasPrimary() const { return 0 != getUInt16_le(Offset::primary()); } bool RadioddityCodeplug::ScanListElement::primaryIsSelected() const { return 1 == getUInt16_le(Offset::primary()); } unsigned RadioddityCodeplug::ScanListElement::primary() const { return getUInt16_le(Offset::primary())-1; } void RadioddityCodeplug::ScanListElement::setPrimary(unsigned idx) { setUInt16_le(Offset::primary(), idx+1); } void RadioddityCodeplug::ScanListElement::setPrimarySelected() { setUInt16_le(Offset::primary(), 1); } void RadioddityCodeplug::ScanListElement::clearPrimary() { setUInt16_le(Offset::primary(), 0); } bool RadioddityCodeplug::ScanListElement::hasSecondary() const { return 0 != getUInt16_le(Offset::secondary()); } bool RadioddityCodeplug::ScanListElement::secondaryIsSelected() const { return 1 == getUInt16_le(Offset::secondary()); } unsigned RadioddityCodeplug::ScanListElement::secondary() const { return getUInt16_le(Offset::secondary())-1; } void RadioddityCodeplug::ScanListElement::setSecondary(unsigned idx) { setUInt16_le(Offset::secondary(), idx+1); } void RadioddityCodeplug::ScanListElement::setSecondarySelected() { setUInt16_le(Offset::secondary(), 1); } void RadioddityCodeplug::ScanListElement::clearSecondary() { setUInt16_le(Offset::secondary(), 0); } bool RadioddityCodeplug::ScanListElement::hasRevert() const { return 0 != getUInt16_le(Offset::revert()); } bool RadioddityCodeplug::ScanListElement::revertIsSelected() const { return 1 == getUInt16_le(Offset::revert()); } unsigned RadioddityCodeplug::ScanListElement::revert() const { return getUInt16_le(Offset::revert())-1; } void RadioddityCodeplug::ScanListElement::setRevert(unsigned idx) { setUInt16_le(Offset::revert(), idx+1); } void RadioddityCodeplug::ScanListElement::setRevertSelected() { setUInt16_le(Offset::revert(), 1); } void RadioddityCodeplug::ScanListElement::clearRevert() { setUInt16_le(Offset::revert(), 0); } unsigned RadioddityCodeplug::ScanListElement::holdTime() const { return unsigned(getUInt8(Offset::holdTime()))*25; } void RadioddityCodeplug::ScanListElement::setHoldTime(unsigned ms) { setUInt8(Offset::holdTime(), ms/25); } unsigned RadioddityCodeplug::ScanListElement::prioritySampleTime() const { return unsigned(getUInt8(Offset::primaryHoldTime()))*250; } void RadioddityCodeplug::ScanListElement::setPrioritySampleTime(unsigned ms) { setUInt8(Offset::primaryHoldTime(), ms/250); } ScanList * RadioddityCodeplug::ScanListElement::toScanListObj(Context &ctx, const ErrorStack &err) const { Q_UNUSED(ctx); Q_UNUSED(err) return new ScanList(name()); } bool RadioddityCodeplug::ScanListElement::linkScanListObj(ScanList *lst, Context &ctx, const ErrorStack &err) const { if (primaryIsSelected()) { lst->setPrimaryChannel(SelectedChannel::get()); } else if (hasPrimary()) { if (! ctx.has(primary())) { errMsg(err) << "Cannot link scan list '" << lst->name() << "', primary priority channel index " << primary() << " not defined."; return false; } lst->setPrimaryChannel(ctx.get(primary())); } if (secondaryIsSelected()) { lst->setSecondaryChannel(SelectedChannel::get()); } else if (hasSecondary()) { if (! ctx.has(secondary())) { errMsg(err) << "Cannot link scan list '" << lst->name() << "', secondary priority channel index " << secondary() << " not defined."; return false; } lst->setSecondaryChannel(ctx.get(secondary())); } if (revertIsSelected()) { lst->setRevertChannel(SelectedChannel::get()); } else if (hasRevert()) { if (! ctx.has(revert())) { errMsg(err) << "Cannot link scan list '" << lst->name() << "', revert channel index " << revert() << " not defined."; return false; } lst->setRevertChannel(ctx.get(revert())); } for (unsigned int i=0; (iaddChannel(SelectedChannel::get()); else if (hasMember(i)) { if (! ctx.has(member(i))) { errMsg(err) << "Cannot link scan list '" << lst->name() << "', " << (i+1) << "-th member index " << member(i) << " not defined."; return false; } lst->addChannel(ctx.get(member(i))); } } return true; } bool RadioddityCodeplug::ScanListElement::fromScanListObj(const ScanList *lst, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) clear(); setName(lst->name()); if (lst->primaryChannel() && (SelectedChannel::get() == lst->primaryChannel())) setPrimarySelected(); else if (lst->primaryChannel()) setPrimary(ctx.index(lst->primaryChannel())); if (lst->secondaryChannel() && (SelectedChannel::get() == lst->secondaryChannel())) setSecondarySelected(); else if (lst->secondaryChannel()) setSecondary(ctx.index(lst->secondaryChannel())); if (lst->revertChannel() && (SelectedChannel::get() == lst->revertChannel())) setRevertSelected(); else if (lst->revertChannel()) setRevert(ctx.index(lst->revertChannel())); for (unsigned int i=0; i= (unsigned int)lst->count()) clearMember(i); else if (SelectedChannel::get() == lst->channel(i)) setSelected(i); else setMember(i, ctx.index(lst->channel(i))); } return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::ScanListBankElement * ********************************************************************************************* */ RadioddityCodeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::ScanListBankElement::ScanListBankElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::ScanListBankElement::~ScanListBankElement() { // pass... } void RadioddityCodeplug::ScanListBankElement::clear() { memset(_data, 0, Limit::scanListCount()); } bool RadioddityCodeplug::ScanListBankElement::isEnabled(unsigned n) const { return 0x00 != getUInt8(Offset::bytemap() + n); } void RadioddityCodeplug::ScanListBankElement::enable(unsigned n, bool enabled) { if (enabled) setUInt8(Offset::bytemap() + n, 0x01); else setUInt8(Offset::bytemap() + n, 0x00); } uint8_t * RadioddityCodeplug::ScanListBankElement::get(unsigned n) const { return _data+Offset::scanLists() + n*ScanListElement::size(); } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::GeneralSettingsElement * ********************************************************************************************* */ RadioddityCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pas... } RadioddityCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::GeneralSettingsElement::~GeneralSettingsElement() { // pass... } void RadioddityCodeplug::GeneralSettingsElement::clear() { memset(_data+0x0000, 0xff, 8); memset(_data+0x0008, 0x00, 4); setUInt32_be(0x000c, 0); setUInt8(0x0010, 0); setPreambleDuration(Interval::fromMilliseconds(360)); setMonitorType(MonitorType::Silent); setVOXSensitivity(Level::fromValue(3)); setLowBatteryWarnInterval(30); setCallAlertDuration(120); setLoneWorkerResponsePeriod(1); setLoneWorkerReminderPeriod(10); setGroupCallHangTime(Interval::fromMilliseconds(3000)); setPrivateCallHangTime(Interval::fromMilliseconds(3000)); enableDownChannelModeVFO(false); enableUpChannelModeVFO(false); enableResetTone(false); enableUnknownNumberTone(false); setARTSToneMode(ARTSTone::Once); enableDigitalTalkPermitTone(false); enableAnalogTalkPermitTone(false); enableSelftestTone(true); enableChannelFreeIndicationTone(false); setBit(0x001b, 4, false); disableAllTones(false); enableBatsaveRX(true); enableBatsavePreamble(true); setUInt5(0x001c, 0, 0); disableAllLEDs(false); inhibitQuickKeyOverride(false); setBit(0x001c, 7, true); setUInt3(0x001d, 0, 0); enableTXExitTone(false); enableTXOnActiveChannel(true); enableAnimation(false); setScanMode(ScanMode::Time); setRepeaterEndDelay(0); setRepeaterSTE(0); setUInt8(0x001f, 0); clearProgPassword(); } QString RadioddityCodeplug::GeneralSettingsElement::name() const { return readASCII(0x0000, 8, 0xff); } void RadioddityCodeplug::GeneralSettingsElement::setName(const QString &name) { writeASCII(0x0000, name, 8, 0xff); } unsigned RadioddityCodeplug::GeneralSettingsElement::radioID() const { return getBCD8_be(0x0008); } void RadioddityCodeplug::GeneralSettingsElement::setRadioID(unsigned id) { setBCD8_be(0x0008, id); } Interval RadioddityCodeplug::GeneralSettingsElement::preambleDuration() const { return Interval::fromMilliseconds(unsigned(getUInt8(0x0011)*60)); } void RadioddityCodeplug::GeneralSettingsElement::setPreambleDuration(const Interval &dur) { setUInt8(0x0011, dur.milliseconds()/60); } RadioddityCodeplug::GeneralSettingsElement::MonitorType RadioddityCodeplug::GeneralSettingsElement::monitorType() const { return MonitorType(getUInt8(0x0012)); } void RadioddityCodeplug::GeneralSettingsElement::setMonitorType(MonitorType type) { setUInt8(0x0012, (unsigned)type); } Level RadioddityCodeplug::GeneralSettingsElement::voxSensitivity() const { return Level::fromValue(getUInt8(0x0013)); } void RadioddityCodeplug::GeneralSettingsElement::setVOXSensitivity(Level value) { setUInt8(0x0013, value.mapTo(Limit::vox())); } unsigned RadioddityCodeplug::GeneralSettingsElement::lowBatteryWarnInterval() const { return unsigned(getUInt8(0x0014))*5; } void RadioddityCodeplug::GeneralSettingsElement::setLowBatteryWarnInterval(unsigned sec) { setUInt8(0x0014, sec/5); } unsigned RadioddityCodeplug::GeneralSettingsElement::callAlertDuration() const { return unsigned(getUInt8(0x0015))*5; } void RadioddityCodeplug::GeneralSettingsElement::setCallAlertDuration(unsigned sec) { setUInt8(0x0015, sec/5); } unsigned RadioddityCodeplug::GeneralSettingsElement::loneWorkerResponsePeriod() const { return getUInt8(0x0016); } void RadioddityCodeplug::GeneralSettingsElement::setLoneWorkerResponsePeriod(unsigned min) { setUInt8(0x0016, min); } unsigned RadioddityCodeplug::GeneralSettingsElement::loneWorkerReminderPeriod() const { return getUInt8(0x0017); } void RadioddityCodeplug::GeneralSettingsElement::setLoneWorkerReminderPeriod(unsigned sec) { setUInt8(0x0017, sec); } Interval RadioddityCodeplug::GeneralSettingsElement::groupCallHangTime() const { return Interval::fromMilliseconds(unsigned(getUInt8(0x0018))*500); } void RadioddityCodeplug::GeneralSettingsElement::setGroupCallHangTime(const Interval &dur) { setUInt8(0x0018, dur.milliseconds()/500); } Interval RadioddityCodeplug::GeneralSettingsElement::privateCallHangTime() const { return Interval::fromMilliseconds(unsigned(getUInt8(0x0019))*500); } void RadioddityCodeplug::GeneralSettingsElement::setPrivateCallHangTime(const Interval &dur) { setUInt8(0x0019, dur.milliseconds()/500); } bool RadioddityCodeplug::GeneralSettingsElement::downChannelModeVFO() const { return getBit(0x001a, 0); } void RadioddityCodeplug::GeneralSettingsElement::enableDownChannelModeVFO(bool enable) { setBit(0x001a, 0, enable); } bool RadioddityCodeplug::GeneralSettingsElement::upChannelModeVFO() const { return getBit(0x001a, 1); } void RadioddityCodeplug::GeneralSettingsElement::enableUpChannelModeVFO(bool enable) { setBit(0x001a, 1, enable); } bool RadioddityCodeplug::GeneralSettingsElement::resetTone() const { return getBit(0x001a, 2); } void RadioddityCodeplug::GeneralSettingsElement::enableResetTone(bool enable) { setBit(0x001a, 2, enable); } bool RadioddityCodeplug::GeneralSettingsElement::unknownNumberTone() const { return getBit(0x001a, 3); } void RadioddityCodeplug::GeneralSettingsElement::enableUnknownNumberTone(bool enable) { setBit(0x001a, 3, enable); } RadioddityCodeplug::GeneralSettingsElement::ARTSTone RadioddityCodeplug::GeneralSettingsElement::artsToneMode() const { return ARTSTone(getUInt4(0x001a, 4)); } void RadioddityCodeplug::GeneralSettingsElement::setARTSToneMode(ARTSTone mode) { setUInt4(0x001a, 4, (unsigned) mode); } bool RadioddityCodeplug::GeneralSettingsElement::digitalTalkPermitTone() const { return getBit(0x001b, 0); } void RadioddityCodeplug::GeneralSettingsElement::enableDigitalTalkPermitTone(bool enable) { setBit(0x001b, 0, enable); } bool RadioddityCodeplug::GeneralSettingsElement::analogTalkPermitTone() const { return getBit(0x001b, 1); } void RadioddityCodeplug::GeneralSettingsElement::enableAnalogTalkPermitTone(bool enable) { setBit(0x001b, 1, enable); } bool RadioddityCodeplug::GeneralSettingsElement::selftestTone() const { return getBit(0x001b, 2); } void RadioddityCodeplug::GeneralSettingsElement::enableSelftestTone(bool enable) { setBit(0x001b, 2, enable); } bool RadioddityCodeplug::GeneralSettingsElement::channelFreeIndicationTone() const { return getBit(0x001b, 3); } void RadioddityCodeplug::GeneralSettingsElement::enableChannelFreeIndicationTone(bool enable) { setBit(0x001b, 3, enable); } bool RadioddityCodeplug::GeneralSettingsElement::allTonesDisabled() const { return getBit(0x001b, 5); } void RadioddityCodeplug::GeneralSettingsElement::disableAllTones(bool disable) { setBit(0x001b, 5, disable); } bool RadioddityCodeplug::GeneralSettingsElement::batsaveRX() const { return getBit(0x001b, 6); } void RadioddityCodeplug::GeneralSettingsElement::enableBatsaveRX(bool enable) { setBit(0x001b, 6, enable); } bool RadioddityCodeplug::GeneralSettingsElement::batsavePreamble() const { return getBit(0x001b, 7); } void RadioddityCodeplug::GeneralSettingsElement::enableBatsavePreamble(bool enable) { setBit(0x001b, 7, enable); } bool RadioddityCodeplug::GeneralSettingsElement::allLEDsDisabled() const { return getBit(0x001c, 5); } void RadioddityCodeplug::GeneralSettingsElement::disableAllLEDs(bool disable) { setBit(0x001c, 5, disable); } bool RadioddityCodeplug::GeneralSettingsElement::quickKeyOverrideInhibited() const { return getBit(0x001c, 6); } void RadioddityCodeplug::GeneralSettingsElement::inhibitQuickKeyOverride(bool inhibit) { setBit(0x001c, 6, inhibit); } bool RadioddityCodeplug::GeneralSettingsElement::txExitTone() const { return getBit(0x001d, 3); } void RadioddityCodeplug::GeneralSettingsElement::enableTXExitTone(bool enable) { setBit(0x001d, 3, enable); } bool RadioddityCodeplug::GeneralSettingsElement::txOnActiveChannel() const { return getBit(0x001d, 4); } void RadioddityCodeplug::GeneralSettingsElement::enableTXOnActiveChannel(bool enable) { setBit(0x001d, 4, enable); } bool RadioddityCodeplug::GeneralSettingsElement::animation() const { return getBit(0x001d, 5); } void RadioddityCodeplug::GeneralSettingsElement::enableAnimation(bool enable) { setBit(0x001d, 5, enable); } RadioddityCodeplug::GeneralSettingsElement::ScanMode RadioddityCodeplug::GeneralSettingsElement::scanMode() const { return ScanMode(getUInt2(0x001d, 6)); } void RadioddityCodeplug::GeneralSettingsElement::setScanMode(ScanMode mode) { setUInt2(0x001d, 6, unsigned(mode)); } unsigned RadioddityCodeplug::GeneralSettingsElement::repeaterEndDelay() const { return getUInt4(0x001e, 0); } void RadioddityCodeplug::GeneralSettingsElement::setRepeaterEndDelay(unsigned delay) { setUInt4(0x001e, 0, delay); } unsigned RadioddityCodeplug::GeneralSettingsElement::repeaterSTE() const { return getUInt4(0x001e, 4); } void RadioddityCodeplug::GeneralSettingsElement::setRepeaterSTE(unsigned ste) { setUInt4(0x001e, 4, ste); } bool RadioddityCodeplug::GeneralSettingsElement::hasProgPassword() const { return (0xff != _data[0x0020]) && (0x00 != _data[0x0020]); } QString RadioddityCodeplug::GeneralSettingsElement::progPassword() const { return readASCII(0x0020, 8, 0xff); } void RadioddityCodeplug::GeneralSettingsElement::setProgPassword(const QString &pwd) { writeASCII(0x0020, pwd, 8, 0xff); } void RadioddityCodeplug::GeneralSettingsElement::clearProgPassword() { memset(_data+0x0020, 0xff, 8); } bool RadioddityCodeplug::GeneralSettingsElement::fromConfig(Context &ctx, const ErrorStack &err) { if (! ctx.config()->settings()->defaultIdRef()->isNull()) { setName(ctx.config()->settings()->defaultIdRef()->as()->name()); setRadioID(ctx.config()->settings()->defaultIdRef()->as()->number()); } else if (ctx.count()) { setName(ctx.get(0)->name()); setRadioID(ctx.get(0)->number()); } else { errMsg(err) << "Cannot encode radioddity codeplug: No radio ID defined."; return false; } setVOXSensitivity(ctx.config()->settings()->audio()->vox()); enableAnimation(BootSettings::BootDisplay::Logo == ctx.config()->settings()->boot()->bootDisplay()); setPreambleDuration(ctx.config()->settings()->dmr()->preamble()); setGroupCallHangTime(ctx.config()->settings()->dmr()->groupCallHangTime()); setPrivateCallHangTime(ctx.config()->settings()->dmr()->privateCallHangTime()); // There is no global squelch settings either // Apply tone settings disableAllTones(ctx.config()->settings()->tone()->silent()); enableDigitalTalkPermitTone(ctx.config()->settings()->tone()->talkPermit().testFlag(Channel::Type::DMR)); enableAnalogTalkPermitTone(ctx.config()->settings()->tone()->talkPermit().testFlag(Channel::Type::FM)); enableTXExitTone( ctx.config()->settings()->tone()->callEnd().testFlags( Channel::Type::DMR | Channel::Type::FM)); enableChannelFreeIndicationTone( ctx.config()->settings()->tone()->channelIdle().testAnyFlags( Channel::Type::DMR|Channel::Type::FM)); enableResetTone(ctx.config()->settings()->tone()->callResetEnabled()); // Handle Radioddity extension if (RadiodditySettingsExtension *ext = ctx.config()->settings()->radioddityExtension()) { setMonitorType(ext->monitorType()); setLowBatteryWarnInterval(ext->tone()->lowBatteryWarnInterval().seconds()); setCallAlertDuration(ext->tone()->callAlertDuration().seconds()); setLoneWorkerResponsePeriod(ext->loneWorkerResponseTime().minutes()); setLoneWorkerReminderPeriod(ext->loneWorkerReminderPeriod().seconds()); enableDownChannelModeVFO(ext->downChannelModeVFO()); enableUpChannelModeVFO(ext->upChannelModeVFO()); enableUnknownNumberTone(ext->tone()->unknownNumberTone()); setARTSToneMode(ext->tone()->artsToneMode()); enableSelftestTone(ext->tone()->selftestTone()); enableBatsaveRX(ext->powerSaveMode()); enableBatsavePreamble(ext->wakeupPreamble()); disableAllLEDs(ext->allLEDsDisabled()); inhibitQuickKeyOverride(ext->quickKeyOverrideInhibited()); enableTXOnActiveChannel(ext->txOnActiveChannel()); setScanMode(ext->scanMode()); setRepeaterEndDelay(ext->repeaterEndDelay().seconds()); setRepeaterSTE(ext->repeaterSTE().seconds()); if (ext->boot()->progPassword().isEmpty()) clearProgPassword(); else setProgPassword(ext->boot()->progPassword()); } return true; } bool RadioddityCodeplug::GeneralSettingsElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) if (ctx.config()->settings()->defaultIdRef()->isNull()) { int idx = ctx.config()->radioIDs()->add(new DMRRadioID(name(), radioID())); ctx.config()->settings()->defaultIdRef()->set( ctx.config()->radioIDs()->get(idx)->as()); } else { ctx.config()->settings()->defaultIdRef()->as()->setName(name()); ctx.config()->settings()->defaultIdRef()->as()->setNumber(radioID()); } ctx.config()->settings()->audio()->setVox(voxSensitivity()); if (animation()) ctx.config()->settings()->boot()->setBootDisplay(BootSettings::BootDisplay::Logo); ctx.config()->settings()->dmr()->setPreamble(preambleDuration()); ctx.config()->settings()->dmr()->setGroupCallHangTime(groupCallHangTime()); ctx.config()->settings()->dmr()->setPrivateCallHangTime(privateCallHangTime()); // Apply audio settings. // there is no global squelch settings either, so set it to 1 ctx.config()->settings()->audio()->setSquelch(Level::fromValue(1)); // Apply tone settings ctx.config()->settings()->tone()->enableSilent(allTonesDisabled()); ctx.config()->settings()->tone()->setTalkPermit( (digitalTalkPermitTone() ? Channel::Type::DMR : Channel::Type::None) | (analogTalkPermitTone() ? Channel::Type::FM : Channel::Type::None) ); ctx.config()->settings()->tone()->setCallEnd( txExitTone() ? (Channel::Type::FM|Channel::Type::DMR) : Channel::Type::None); ctx.config()->settings()->tone()->setChannelIdle( channelFreeIndicationTone() ? (Channel::Type::FM|Channel::Type::DMR) : Channel::Type::None); ctx.config()->settings()->tone()->enableCallReset(resetTone()); // Allocate Radioddity extension if needed RadiodditySettingsExtension *ext = ctx.config()->settings()->radioddityExtension(); if (nullptr == ext) { ext = new RadiodditySettingsExtension(); ctx.config()->settings()->setRadioddityExtension(ext); } // Update settings extension ext->setMonitorType(monitorType()); ext->tone()->setLowBatteryWarnInterval(Interval::fromSeconds(lowBatteryWarnInterval())); ext->tone()->setCallAlertDuration(Interval::fromSeconds(callAlertDuration())); ext->setLoneWorkerResponseTime(Interval::fromMinutes(loneWorkerResponsePeriod())); ext->setLoneWorkerReminderPeriod(Interval::fromSeconds(loneWorkerReminderPeriod())); ext->enableDownChannelModeVFO(downChannelModeVFO()); ext->enableUpChannelModeVFO(upChannelModeVFO()); ext->tone()->enableUnknownNumberTone(unknownNumberTone()); ext->tone()->setARTSToneMode(artsToneMode()); ext->tone()->enableSelftestTone(selftestTone()); ext->enablePowerSaveMode(batsaveRX()); ext->enableWakeupPreamble(batsavePreamble()); ext->disableAllLEDs(allLEDsDisabled()); ext->inhibitQuickKeyOverride(quickKeyOverrideInhibited()); ext->enableTXOnActiveChannel(txOnActiveChannel()); ext->setScanMode(scanMode()); ext->setRepeaterEndDelay(Interval::fromSeconds(repeaterEndDelay())); ext->setRepeaterSTE(Interval::fromSeconds(repeaterSTE())); if (hasProgPassword()) ext->boot()->setProgPassword(progPassword()); else ext->boot()->setProgPassword(""); return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::ButtonSettingsElement * ********************************************************************************************* */ uint8_t RadioddityCodeplug::ButtonSettingsElement::KeyFunction::encode(RadioddityButtonSettingsExtension::Function func) { switch (func) { case RadioddityButtonSettingsExtension::Function::None: return None; case RadioddityButtonSettingsExtension::Function::ToggleAllAlertTones: return ToggleAllAlertTones; case RadioddityButtonSettingsExtension::Function::EmergencyOn: return EmergencyOn; case RadioddityButtonSettingsExtension::Function::EmergencyOff: return EmergencyOff; case RadioddityButtonSettingsExtension::Function::ToggleMonitor: return ToggleMonitor; case RadioddityButtonSettingsExtension::Function::RadioDisable: return NuiaceDelete; case RadioddityButtonSettingsExtension::Function::OneTouch1: return OneTouch1; case RadioddityButtonSettingsExtension::Function::OneTouch2: return OneTouch2; case RadioddityButtonSettingsExtension::Function::OneTouch3: return OneTouch3; case RadioddityButtonSettingsExtension::Function::OneTouch4: return OneTouch4; case RadioddityButtonSettingsExtension::Function::OneTouch5: return OneTouch5; case RadioddityButtonSettingsExtension::Function::OneTouch6: return OneTouch6; case RadioddityButtonSettingsExtension::Function::ToggleTalkaround: return ToggleRepeatTalkaround; case RadioddityButtonSettingsExtension::Function::ToggleScan: return ToggleScan; case RadioddityButtonSettingsExtension::Function::ToggleEncryption: return TogglePrivacy; case RadioddityButtonSettingsExtension::Function::ToggleVox: return ToggleVox; case RadioddityButtonSettingsExtension::Function::ZoneSelect: return ZoneSelect; case RadioddityButtonSettingsExtension::Function::BatteryIndicator: return BatteryIndicator; case RadioddityButtonSettingsExtension::Function::ToggleLoneWorker: return ToggleLoneWorker; case RadioddityButtonSettingsExtension::Function::PhoneExit: return PhoneExit; case RadioddityButtonSettingsExtension::Function::ToggleFlashLight: return ToggleFlashLight; case RadioddityButtonSettingsExtension::Function::ToggleFMRadio: return ToggleFMRadio; default: break; } return Action::None; } RadioddityButtonSettingsExtension::Function RadioddityCodeplug::ButtonSettingsElement::KeyFunction::decode(uint8_t action) { switch ((Action) action) { case None: return RadioddityButtonSettingsExtension::Function::None; case ToggleAllAlertTones: return RadioddityButtonSettingsExtension::Function::ToggleAllAlertTones; case EmergencyOn: return RadioddityButtonSettingsExtension::Function::EmergencyOn; case EmergencyOff: return RadioddityButtonSettingsExtension::Function::EmergencyOff; case ToggleMonitor: return RadioddityButtonSettingsExtension::Function::ToggleMonitor; case NuiaceDelete: return RadioddityButtonSettingsExtension::Function::RadioDisable; case OneTouch1: return RadioddityButtonSettingsExtension::Function::OneTouch1; case OneTouch2: return RadioddityButtonSettingsExtension::Function::OneTouch2; case OneTouch3: return RadioddityButtonSettingsExtension::Function::OneTouch3; case OneTouch4: return RadioddityButtonSettingsExtension::Function::OneTouch4; case OneTouch5: return RadioddityButtonSettingsExtension::Function::OneTouch5; case OneTouch6: return RadioddityButtonSettingsExtension::Function::OneTouch6; case ToggleRepeatTalkaround: return RadioddityButtonSettingsExtension::Function::ToggleTalkaround; case ToggleScan: return RadioddityButtonSettingsExtension::Function::ToggleScan; case TogglePrivacy: return RadioddityButtonSettingsExtension::Function::ToggleEncryption; case ToggleVox: return RadioddityButtonSettingsExtension::Function::ToggleVox; case ZoneSelect: return RadioddityButtonSettingsExtension::Function::ZoneSelect; case BatteryIndicator: return RadioddityButtonSettingsExtension::Function::BatteryIndicator; case ToggleLoneWorker: return RadioddityButtonSettingsExtension::Function::ToggleLoneWorker; case PhoneExit: return RadioddityButtonSettingsExtension::Function::PhoneExit; case ToggleFlashLight: return RadioddityButtonSettingsExtension::Function::ToggleFlashLight; case ToggleFMRadio: return RadioddityButtonSettingsExtension::Function::ToggleFMRadio; } return RadioddityButtonSettingsExtension::Function::None; } RadioddityCodeplug::ButtonSettingsElement::ButtonSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::ButtonSettingsElement::ButtonSettingsElement(uint8_t *ptr) : Element(ptr, 0x0020) { // pass... } RadioddityCodeplug::ButtonSettingsElement::~ButtonSettingsElement() { // pass... } void RadioddityCodeplug::ButtonSettingsElement::clear() { setUInt8(0x0000, 0x01); setLongPressDuration(Interval::fromMilliseconds(1500)); setSK1ShortPress(RadioddityButtonSettingsExtension::Function::ZoneSelect); setSK1LongPress(RadioddityButtonSettingsExtension::Function::ToggleFMRadio); setSK2ShortPress(RadioddityButtonSettingsExtension::Function::ToggleMonitor); setSK2LongPress(RadioddityButtonSettingsExtension::Function::ToggleFlashLight); setTKShortPress(RadioddityButtonSettingsExtension::Function::BatteryIndicator); setTKLongPress(RadioddityButtonSettingsExtension::Function::ToggleVox); memset(_data+0x0008, 0xff, 6*4); } Interval RadioddityCodeplug::ButtonSettingsElement::longPressDuration() const { return Interval::fromMilliseconds(getUInt8(Offset::longPressDuration())*250); } void RadioddityCodeplug::ButtonSettingsElement::setLongPressDuration(Interval ms) { setUInt8(Offset::longPressDuration(), ms.milliseconds()/250); } RadioddityButtonSettingsExtension::Function RadioddityCodeplug::ButtonSettingsElement::sk1ShortPress() const { return KeyFunction::decode(getUInt8(Offset::sk1ShortPress())); } void RadioddityCodeplug::ButtonSettingsElement::setSK1ShortPress(RadioddityButtonSettingsExtension::Function action) { setUInt8(Offset::sk1ShortPress(), KeyFunction::encode(action)); } RadioddityButtonSettingsExtension::Function RadioddityCodeplug::ButtonSettingsElement::sk1LongPress() const { return KeyFunction::decode(getUInt8(0x0003)); } void RadioddityCodeplug::ButtonSettingsElement::setSK1LongPress(RadioddityButtonSettingsExtension::Function action) { setUInt8(Offset::sk1LongPress(), KeyFunction::encode(action)); } RadioddityButtonSettingsExtension::Function RadioddityCodeplug::ButtonSettingsElement::sk2ShortPress() const { return KeyFunction::decode(getUInt8(Offset::sk2ShortPress())); } void RadioddityCodeplug::ButtonSettingsElement::setSK2ShortPress(RadioddityButtonSettingsExtension::Function action) { setUInt8(Offset::sk2ShortPress(), KeyFunction::encode(action)); } RadioddityButtonSettingsExtension::Function RadioddityCodeplug::ButtonSettingsElement::sk2LongPress() const { return KeyFunction::decode(getUInt8(Offset::sk2LongPress())); } void RadioddityCodeplug::ButtonSettingsElement::setSK2LongPress(RadioddityButtonSettingsExtension::Function action) { setUInt8(Offset::sk2LongPress(), KeyFunction::encode(action)); } RadioddityButtonSettingsExtension::Function RadioddityCodeplug::ButtonSettingsElement::tkShortPress() const { return KeyFunction::decode(getUInt8(Offset::tkShortPress())); } void RadioddityCodeplug::ButtonSettingsElement::setTKShortPress(RadioddityButtonSettingsExtension::Function action) { setUInt8(Offset::tkShortPress(), KeyFunction::encode(action)); } RadioddityButtonSettingsExtension::Function RadioddityCodeplug::ButtonSettingsElement::tkLongPress() const { return KeyFunction::decode(getUInt8(Offset::tkLongPress())); } void RadioddityCodeplug::ButtonSettingsElement::setTKLongPress(RadioddityButtonSettingsExtension::Function action) { setUInt8(Offset::tkLongPress(), KeyFunction::encode(action)); } RadioddityCodeplug::ButtonSettingsElement::OneTouchAction RadioddityCodeplug::ButtonSettingsElement::oneTouchAction(unsigned n) const { return OneTouchAction(getUInt8(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 0)); } unsigned RadioddityCodeplug::ButtonSettingsElement::oneTouchContact(unsigned n) const { return getUInt16_be(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 1); } unsigned RadioddityCodeplug::ButtonSettingsElement::oneTouchMessage(unsigned n) const { return getUInt16_be(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 3); } void RadioddityCodeplug::ButtonSettingsElement::disableOneTouch(unsigned n) { setUInt8(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 0, (unsigned)OneTouchAction::None); } void RadioddityCodeplug::ButtonSettingsElement::setOneTouchDigitalCall(unsigned n, unsigned index) { setUInt8(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 0, (unsigned)OneTouchAction::DigitalCall); setUInt16_be(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 1, index); setUInt16_be(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 3, 0); } void RadioddityCodeplug::ButtonSettingsElement::setOneTouchDigitalMessage(unsigned n, unsigned index) { setUInt8(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 0, (unsigned)OneTouchAction::DigitalMessage); setUInt16_be(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 1, 0); setUInt16_be(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 3, index); } void RadioddityCodeplug::ButtonSettingsElement::setOneTouchAnalogCall(unsigned n) { setUInt8(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 0, (unsigned)OneTouchAction::AnalogCall); setUInt16_be(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 1, 0); setUInt16_be(Offset::oneTouchActions() + n*Offset::betweenOneTouchActions() + 3, 0); } bool RadioddityCodeplug::ButtonSettingsElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) if (! ctx.config()->settings()->radioddityExtension()) return true; RadiodditySettingsExtension *ext = ctx.config()->settings()->radioddityExtension(); setLongPressDuration(ext->buttons()->longPressDuration()); setSK1ShortPress(ext->buttons()->funcKey1Short()); setSK1LongPress(ext->buttons()->funcKey1Long()); setSK2ShortPress(ext->buttons()->funcKey2Short()); setSK2LongPress(ext->buttons()->funcKey2Long()); setTKShortPress(ext->buttons()->funcKey3Short()); setTKLongPress(ext->buttons()->funcKey3Long()); return true; } bool RadioddityCodeplug::ButtonSettingsElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) if (! ctx.config()->settings()->radioddityExtension()) ctx.config()->settings()->setRadioddityExtension(new RadiodditySettingsExtension()); RadiodditySettingsExtension *ext = ctx.config()->settings()->radioddityExtension(); ext->buttons()->setLongPressDuration(longPressDuration()); ext->buttons()->setFuncKey1Short(sk1ShortPress()); ext->buttons()->setFuncKey1Long(sk1LongPress()); ext->buttons()->setFuncKey2Short(sk2ShortPress()); ext->buttons()->setFuncKey2Long(sk2LongPress()); ext->buttons()->setFuncKey3Short(tkShortPress()); ext->buttons()->setFuncKey3Long(tkLongPress()); return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::MenuSettingsElement * ********************************************************************************************* */ RadioddityCodeplug::MenuSettingsElement::MenuSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::MenuSettingsElement::MenuSettingsElement(uint8_t *ptr) : Element(ptr, 0x0008) { // pass... } RadioddityCodeplug::MenuSettingsElement::~MenuSettingsElement() { // pass... } void RadioddityCodeplug::MenuSettingsElement::clear() { setMenuHangTime(10); enableMessage(true); enableScanStart(true); enableCallAlert(true); enableEditContact(true); enableManualDial(true); enableRadioCheck(true); enableRemoteMonitor(true); enableRadioEnable(true); enableRadioDisable(true); enableProgPassword(true); enableTalkaround(true); enableTone(true); enablePower(true); enableBacklight(true); enableIntroScreen(true); enableKeypadLock(true); enableLEDIndicator(true); enableSquelch(true); enablePrivacy(true); enableVOX(true); enablePasswordLock(true); enableMissedCalls(true); enableAnsweredCalls(true); enableOutgoingCalls(true); enableChannelDisplay(true); enableDualWatch(true); setBit(0x0004, 3, 0); setBit(0x0004, 4, 0); setBit(0x0004, 5, 1); setBit(0x0004, 6, 1); setBit(0x0004, 7, 1); setUInt8(0x0005, 0xff); setKeypadLockTime(0); setBacklightTime(15); setUInt2(0x0006, 4, 0); setChannelDisplayMode(ChannelDisplayMode::Name); setUInt4(0x0007, 0, 0); setBit(0x0007, 4, 1); enableKeyTone(true); setDualWatchMode(DualWatchMode::DualDual); } unsigned RadioddityCodeplug::MenuSettingsElement::menuHangTime() const { return getUInt8(0x0000); } void RadioddityCodeplug::MenuSettingsElement::setMenuHangTime(unsigned sec) { sec = std::min(10u, sec); setUInt8(0x0000, sec); } bool RadioddityCodeplug::MenuSettingsElement::message() const { return getBit(0x0001, 0); } void RadioddityCodeplug::MenuSettingsElement::enableMessage(bool enable) { setBit(0x0001, 0, enable); } bool RadioddityCodeplug::MenuSettingsElement::scanStart() const { return getBit(0x0001, 1); } void RadioddityCodeplug::MenuSettingsElement::enableScanStart(bool enable) { setBit(0x0001, 1, enable); } bool RadioddityCodeplug::MenuSettingsElement::editScanList() const { return getBit(0x0001, 2); } void RadioddityCodeplug::MenuSettingsElement::enableEditScanList(bool enable) { setBit(0x0001, 2, enable); } bool RadioddityCodeplug::MenuSettingsElement::callAlert() const { return getBit(0x0001, 3); } void RadioddityCodeplug::MenuSettingsElement::enableCallAlert(bool enable) { setBit(0x0001, 3, enable); } bool RadioddityCodeplug::MenuSettingsElement::editContact() const { return getBit(0x0001, 4); } void RadioddityCodeplug::MenuSettingsElement::enableEditContact(bool enable) { setBit(0x0001, 4, enable); } bool RadioddityCodeplug::MenuSettingsElement::manualDial() const { return getBit(0x0001, 5); } void RadioddityCodeplug::MenuSettingsElement::enableManualDial(bool enable) { setBit(0x0001, 5, enable); } bool RadioddityCodeplug::MenuSettingsElement::radioCheck() const { return getBit(0x0001, 6); } void RadioddityCodeplug::MenuSettingsElement::enableRadioCheck(bool enable) { setBit(0x0001, 6, enable); } bool RadioddityCodeplug::MenuSettingsElement::remoteMonitor() const { return getBit(0x0001, 7); } void RadioddityCodeplug::MenuSettingsElement::enableRemoteMonitor(bool enable) { setBit(0x0001, 7, enable); } bool RadioddityCodeplug::MenuSettingsElement::radioEnable() const { return getBit(0x0002, 0); } void RadioddityCodeplug::MenuSettingsElement::enableRadioEnable(bool enable) { setBit(0x0002, 0, enable); } bool RadioddityCodeplug::MenuSettingsElement::radioDisable() const { return getBit(0x0002, 1); } void RadioddityCodeplug::MenuSettingsElement::enableRadioDisable(bool enable) { setBit(0x0002, 1, enable); } bool RadioddityCodeplug::MenuSettingsElement::progPassword() const { return getBit(0x0002, 2); } void RadioddityCodeplug::MenuSettingsElement::enableProgPassword(bool enable) { setBit(0x0002, 2, enable); } bool RadioddityCodeplug::MenuSettingsElement::talkaround() const { return getBit(0x0002, 3); } void RadioddityCodeplug::MenuSettingsElement::enableTalkaround(bool enable) { setBit(0x0002, 3, enable); } bool RadioddityCodeplug::MenuSettingsElement::tone() const { return getBit(0x0002, 4); } void RadioddityCodeplug::MenuSettingsElement::enableTone(bool enable) { setBit(0x0002, 4, enable); } bool RadioddityCodeplug::MenuSettingsElement::power() const { return getBit(0x0002, 5); } void RadioddityCodeplug::MenuSettingsElement::enablePower(bool enable) { setBit(0x0002, 5, enable); } bool RadioddityCodeplug::MenuSettingsElement::backlight() const { return getBit(0x0002, 6); } void RadioddityCodeplug::MenuSettingsElement::enableBacklight(bool enable) { setBit(0x0002, 6, enable); } bool RadioddityCodeplug::MenuSettingsElement::introScreen() const { return getBit(0x0002, 7); } void RadioddityCodeplug::MenuSettingsElement::enableIntroScreen(bool enable) { setBit(0x0002, 7, enable); } bool RadioddityCodeplug::MenuSettingsElement::keypadLock() const { return getBit(0x0003, 0); } void RadioddityCodeplug::MenuSettingsElement::enableKeypadLock(bool enable) { setBit(0x0003, 0, enable); } bool RadioddityCodeplug::MenuSettingsElement::ledIndicator() const { return getBit(0x0003, 1); } void RadioddityCodeplug::MenuSettingsElement::enableLEDIndicator(bool enable) { setBit(0x0003, 1, enable); } bool RadioddityCodeplug::MenuSettingsElement::squelch() const { return getBit(0x0003, 2); } void RadioddityCodeplug::MenuSettingsElement::enableSquelch(bool enable) { setBit(0x0003, 2, enable); } bool RadioddityCodeplug::MenuSettingsElement::privacy() const { return getBit(0x0003, 3); } void RadioddityCodeplug::MenuSettingsElement::enablePrivacy(bool enable) { setBit(0x0003, 3, enable); } bool RadioddityCodeplug::MenuSettingsElement::vox() const { return getBit(0x0003, 4); } void RadioddityCodeplug::MenuSettingsElement::enableVOX(bool enable) { setBit(0x0003, 4, enable); } bool RadioddityCodeplug::MenuSettingsElement::passwordLock() const { return getBit(0x0003, 5); } void RadioddityCodeplug::MenuSettingsElement::enablePasswordLock(bool enable) { setBit(0x0003, 5, enable); } bool RadioddityCodeplug::MenuSettingsElement::missedCalls() const { return getBit(0x0003, 6); } void RadioddityCodeplug::MenuSettingsElement::enableMissedCalls(bool enable) { setBit(0x0003, 6, enable); } bool RadioddityCodeplug::MenuSettingsElement::answeredCalls() const { return getBit(0x0003, 7); } void RadioddityCodeplug::MenuSettingsElement::enableAnsweredCalls(bool enable) { setBit(0x0003, 7, enable); } bool RadioddityCodeplug::MenuSettingsElement::outgoingCalls() const { return getBit(0x0004, 0); } void RadioddityCodeplug::MenuSettingsElement::enableOutgoingCalls(bool enable) { setBit(0x0004, 0, enable); } bool RadioddityCodeplug::MenuSettingsElement::channelDisplay() const { return getBit(0x0004, 1); } void RadioddityCodeplug::MenuSettingsElement::enableChannelDisplay(bool enable) { setBit(0x0004, 1, enable); } bool RadioddityCodeplug::MenuSettingsElement::dualWatch() const { return getBit(0x0004, 2); } void RadioddityCodeplug::MenuSettingsElement::enableDualWatch(bool enable) { setBit(0x0004, 2, enable); } unsigned RadioddityCodeplug::MenuSettingsElement::keypadLockTime() const { return getUInt2(0x0006, 0)*5; } void RadioddityCodeplug::MenuSettingsElement::setKeypadLockTime(unsigned sec) { setUInt2(0x0006, 0, sec/5); } unsigned RadioddityCodeplug::MenuSettingsElement::backlightTime() const { return getUInt2(0x0006, 2)*5; } void RadioddityCodeplug::MenuSettingsElement::setBacklightTime(unsigned sec) { setUInt2(0x0006, 2, sec/5); } RadioddityCodeplug::MenuSettingsElement::ChannelDisplayMode RadioddityCodeplug::MenuSettingsElement::channelDisplayMode() const { return (ChannelDisplayMode)getUInt2(0x0006, 6); } void RadioddityCodeplug::MenuSettingsElement::setChannelDisplayMode(ChannelDisplayMode mode) { setUInt2(0x0006, 6, (unsigned)mode); } bool RadioddityCodeplug::MenuSettingsElement::keyTone() const { return getBit(0x0007, 5); } void RadioddityCodeplug::MenuSettingsElement::enableKeyTone(bool enable) { setBit(0x0007, 5, enable); } RadioddityCodeplug::MenuSettingsElement::DualWatchMode RadioddityCodeplug::MenuSettingsElement::dualWatchMode() const { return DualWatchMode(getUInt2(0x0007, 6)); } void RadioddityCodeplug::MenuSettingsElement::setDualWatchMode(DualWatchMode mode) { setUInt2(0x0007, 6, (unsigned)mode); } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::BootSettingsElement * ********************************************************************************************* */ RadioddityCodeplug::BootSettingsElement::BootSettingsElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::BootSettingsElement::BootSettingsElement(uint8_t *ptr) : Element(ptr, 0x20) { // pass... } RadioddityCodeplug::BootSettingsElement::~BootSettingsElement() { // pass... } void RadioddityCodeplug::BootSettingsElement::clear() { setBootDisplay(BootSettings::BootDisplay::Logo); enableBootPassword(false); setBCD8_be(0x0002, 0); setUInt8(0x0007, 0); memset(_data+0x0008, 0, 24); } BootSettings::BootDisplay RadioddityCodeplug::BootSettingsElement::bootDisplay() const { return (1 == getUInt8(Offset::bootText())) ? BootSettings::BootDisplay::Text : BootSettings::BootDisplay::Logo; } void RadioddityCodeplug::BootSettingsElement::setBootDisplay(BootSettings::BootDisplay mode) { switch (mode) { case BootSettings::BootDisplay::Text: setUInt8(Offset::bootText(), 1); break; default: setUInt8(Offset::bootText(), 0); break; } } bool RadioddityCodeplug::BootSettingsElement::bootPasswordEnabled() const { return (1 == getUInt8(Offset::bootPasswordEnable())); } void RadioddityCodeplug::BootSettingsElement::enableBootPassword(bool enable) { setUInt8(Offset::bootPasswordEnable(), (enable ? 1 : 0)); } QString RadioddityCodeplug::BootSettingsElement::bootPassword() const { return QString::number(getBCD8_be(Offset::bootPassword())); } bool RadioddityCodeplug::BootSettingsElement::setBootPassword(const QString &passwd) { if (passwd.isEmpty()) { enableBootPassword(false); return true; } static QRegularExpression valid(R"(^[0-9]{0,8}$)"); if (! valid.match(passwd).hasMatch()) { return false; } setBCD8_be(Offset::bootPassword(), passwd.toUInt()); return true; } bool RadioddityCodeplug::BootSettingsElement::encode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); setBootDisplay(ctx.config()->settings()->boot()->bootDisplay()); enableBootPassword(ctx.config()->settings()->boot()->bootPasswordEnabled()); setBootPassword(ctx.config()->settings()->boot()->bootPassword()); return true; } bool RadioddityCodeplug::BootSettingsElement::decode(Context &ctx, const ErrorStack &err) const { Q_UNUSED(err); ctx.config()->settings()->boot()->setBootDisplay(bootDisplay()); ctx.config()->settings()->boot()->enableBootPassword(bootPasswordEnabled() && (!bootPassword().isEmpty())); ctx.config()->settings()->boot()->setBootPassword(bootPassword()); return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::BootTextElement * ********************************************************************************************* */ RadioddityCodeplug::BootTextElement::BootTextElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::BootTextElement::BootTextElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::BootTextElement::~BootTextElement() { // pass... } void RadioddityCodeplug::BootTextElement::clear() { setLine1(""); setLine2(""); } QString RadioddityCodeplug::BootTextElement::line1() const { return readASCII(Offset::line1(), Limit::lineLength(), 0xff); } void RadioddityCodeplug::BootTextElement::setLine1(const QString &text) { writeASCII(Offset::line1(), text, Limit::lineLength(), 0xff); } QString RadioddityCodeplug::BootTextElement::line2() const { return readASCII(Offset::line2(), Limit::lineLength(), 0xff); } void RadioddityCodeplug::BootTextElement::setLine2(const QString &text) { writeASCII(Offset::line2(), text, Limit::lineLength(), 0xff); } bool RadioddityCodeplug::BootTextElement::fromConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) setLine1(ctx.config()->settings()->boot()->message1()); setLine2(ctx.config()->settings()->boot()->message2()); return true; } bool RadioddityCodeplug::BootTextElement::updateConfig(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ctx.config()->settings()->boot()->setMessage1(line1()); ctx.config()->settings()->boot()->setMessage2(line2()); return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::MessageBankElement * ********************************************************************************************* */ RadioddityCodeplug::MessageBankElement::MessageBankElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RadioddityCodeplug::MessageBankElement::MessageBankElement(uint8_t *ptr) : Element(ptr, 0x1248) { // pass... } RadioddityCodeplug::MessageBankElement::~MessageBankElement() { // pass... } void RadioddityCodeplug::MessageBankElement::clear() { setUInt8(Offset::messageConut(), 0); // set count to 0 memset(_data+0x0001, 0x00, 7); // Fill unused memset(_data+Offset::messageLengths(), 0x00, Limit::messages()); // Set message lengths to 0 memset(_data+0x0028, 0x00, 32); // Fill unused memset(_data+Offset::messages(), 0xff, Limit::messages()*Limit::messageLength()); // Clear all messages } unsigned RadioddityCodeplug::MessageBankElement::numMessages() const { return getUInt8(Offset::messageConut()); } QString RadioddityCodeplug::MessageBankElement::message(unsigned n) const { if (n >= numMessages()) return QString(); return readASCII(Offset::messages()+n*Offset::betweenMessages(), Limit::messageLength(), 0xff); } void RadioddityCodeplug::MessageBankElement::appendMessage(const QString msg) { unsigned idx = numMessages(); if (idx >= Limit::messages()) return; unsigned int len = std::min((unsigned int)msg.size(), Limit::messageLength()); // increment counter setUInt8(Offset::messageConut(), idx+1); // store length setUInt8(Offset::messageLengths()+idx, len); // store string writeASCII(Offset::messages()+Offset::betweenMessages()*idx, msg, Limit::messageLength(), 0xff); } bool RadioddityCodeplug::MessageBankElement::encode(Context &ctx, const Flags &flags, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) clear(); unsigned int count = std::min( Limit::messages(), (unsigned int)ctx.config()->smsExtension()->smsTemplates()->count()); for (unsigned int i=0; ismsExtension()->smsTemplates()->message(i)->message()); return true; } bool RadioddityCodeplug::MessageBankElement::decode(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); for (unsigned int i=0; isetName(QString("Message %1").arg(i+1)); sms->setMessage(message(i)); ctx.config()->smsExtension()->smsTemplates()->add(sms); } return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug::EncryptionElement * ********************************************************************************************* */ RadioddityCodeplug::EncryptionElement::EncryptionElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } RadioddityCodeplug::EncryptionElement::EncryptionElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RadioddityCodeplug::EncryptionElement::~EncryptionElement() { // pass... } void RadioddityCodeplug::EncryptionElement::clear() { setPrivacyType(PrivacyType::None); for (unsigned int i=0; i= Limit::keyCount()) return false; unsigned byte=n/8, bit =n%8; return getBit(Offset::bitmap()+byte, bit); } QByteArray RadioddityCodeplug::EncryptionElement::basicKey(unsigned n) const { if (n >= Limit::keyCount()) return QByteArray(); return QByteArray((const char *)_data+Offset::keys()+Offset::key()*n, Limit::keySize()); } void RadioddityCodeplug::EncryptionElement::setBasicKey(unsigned n, const QByteArray &key) { if ((n >= Limit::keyCount()) || (Limit::keySize() != key.size())) return; unsigned byte=n/8, bit =n%8; // Store key (twice?) memcpy(_data+Offset::keys() + Offset::key()*n, key.data(), Limit::keySize()); memcpy(_data+Offset::keys() + Offset::key()*n + Limit::keySize(), key.data(), Limit::keySize()); // Update bitmap setBit(Offset::bitmap()+byte, bit); setPrivacyType(PrivacyType::Basic); } void RadioddityCodeplug::EncryptionElement::clearBasicKey(unsigned n) { if (n >= Limit::keyCount()) return; unsigned byte=n/8, bit =n%8; memset(_data+Offset::keys() + Offset::key()*n, 0xff, Limit::keySize()); memset(_data+Offset::keys() + Offset::key()*n + Limit::keySize(), 0xff, Limit::keySize()); clearBit(Offset::bitmap()+byte, bit); } bool RadioddityCodeplug::EncryptionElement::fromCommercialExt(CommercialExtension *ext, Context &ctx, const ErrorStack &err) { clear(); if ((unsigned int)ext->encryptionKeys()->count() > Limit::keyCount()) { errMsg(err) << "Cannot encode encryption extension. Can only encode " << Limit::keyCount() << " keys."; return false; } for (int i=0; iencryptionKeys()->count(); i++) { if (! ext->encryptionKeys()->get(i)->is()) { errMsg(err) << "Can only encode basic encryption keys."; return false; } BasicEncryptionKey *key = ext->encryptionKeys()->get(i)->as(); if (key->key().size() != Limit::keySize()) { errMsg(err) << "Can only encode " << 8*Limit::keySize() << "bit basic encryption keys."; return false; } setBasicKey(i, key->key()); ctx.add(key, i+1); } return true; } bool RadioddityCodeplug::EncryptionElement::updateCommercialExt(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) if (PrivacyType::None == privacyType()) return false; CommercialExtension *ext = ctx.config()->commercialExtension(); for (unsigned int i=0; isetName(QString("Basic Key %1").arg(i+1)); key->fromHex(basicKey(i).toHex()); // add key to extension ext->encryptionKeys()->add(key); // register key index ctx.add(key, i+1); } return ext; } bool RadioddityCodeplug::EncryptionElement::linkCommercialExt(CommercialExtension *ext, Context &ctx, const ErrorStack &err) { Q_UNUSED(ext); Q_UNUSED(ctx); Q_UNUSED(err) // Keys do not need any linking step return true; } /* ********************************************************************************************* * * Implementation of RadioddityCodeplug * ********************************************************************************************* */ RadioddityCodeplug::RadioddityCodeplug(QObject *parent) : Codeplug(parent) { // pass... } RadioddityCodeplug::~RadioddityCodeplug() { // pass... } void RadioddityCodeplug::clear() { // Clear general config clearGeneralSettings(); // Clear button settings clearButtonSettings(); // Clear messages clearMessages(); // Clear contacts clearContacts(); // clear DTMF contacts clearDTMFContacts(); // clear boot settings clearBootSettings(); // clear menu settings clearMenuSettings(); // clear boot text clearBootSettings(); // clear VFO settings clearVFOSettings(); // clear zones clearZones(); // clear scan lists clearScanLists(); // clear group lists clearGroupLists(); // clear encryption keys clearEncryption(); } bool RadioddityCodeplug::index(Config *config, Context &ctx, const ErrorStack &err) const { Q_UNUSED(err) // All indices as 1-based. That is, the first channel gets index 1. // Map radio IDs for (int i=0; iradioIDs()->count(); i++) { if (config->radioIDs()->get(i)->is()) ctx.add(config->radioIDs()->get(i)->as(), i+1); } // Map digital and DTMF contacts for (int i=0, d=0, a=0; icontacts()->count(); i++) { Contact *contact = config->contacts()->contact(i); if (contact->is()) { ctx.add(contact->as(), d+1); d++; } else if (contact->is()) { ctx.add(contact->as(), a+1); a++; } else { logInfo() << "Cannot index contact '" << contact->name() << "'. Contact type '" << contact->metaObject()->className() << "' not supported by or implemented for Radioddity devices."; } } // Map rx group lists for (int i=0; irxGroupLists()->count(); i++) ctx.add(config->rxGroupLists()->list(i), i+1); // Map channels for (int i=0, c=1; ichannelList()->count(); i++) { Channel *channel = config->channelList()->channel(i); if (channel->is() || channel->is()) { ctx.add(channel, c); c++; } else { logInfo() << "Cannot index channel '" << channel->name() << "'. Channel type '" << channel->metaObject()->className() << "' not supported by or implemented for Radioddity devices."; } } // Map zones for (int i=0; izones()->count(); i++) ctx.add(config->zones()->zone(i), i+1); // Map scan lists for (int i=0; iscanlists()->count(); i++) ctx.add(config->scanlists()->scanlist(i), i+1); // Map DMR APRS systems for (int i=0,a=0,d=0; iposSystems()->count(); i++) { if (config->posSystems()->system(i)->is()) { ctx.add(config->posSystems()->system(i)->as(), d+1); d++; } else if (config->posSystems()->system(i)->is()) { ctx.add(config->posSystems()->system(i)->as(), a+1); a++; } } // Map roaming for (int i=0; iroamingZones()->count(); i++) ctx.add(config->roamingZones()->zone(i), i+1); return true; } Config * RadioddityCodeplug::preprocess(Config *config, const ErrorStack &err) const { Config *intermediate = Codeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot pre-process Radioddity codeplug."; return nullptr; } // Remove all AM & M17 channels ObjectFilterVisitor amFilter{AMChannel::staticMetaObject, M17Channel::staticMetaObject}; if (! amFilter.process(intermediate, err)) { errMsg(err) << "Remove AM & M17 channels."; delete intermediate; return nullptr; } ZoneSplitVisitor splitter; if (! splitter.process(intermediate, err)) { errMsg(err) << "Cannot split zone for Radioddity codeplug."; delete intermediate; return nullptr; } return intermediate; } bool RadioddityCodeplug::encode(Config *config, const Flags &flags, const ErrorStack &err) { // Check if default DMR id is set. if (config->settings()->defaultIdRef()->isNull()) { errMsg(err) << "No default radio ID specified."; return false; } // Create index<->object table. Context ctx(config); if (! index(config, ctx, err)) { errMsg(err) << "Cannot index configuration objects."; return false; } return this->encodeElements(flags, ctx); } bool RadioddityCodeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { // General config if (! this->encodeGeneralSettings(flags, ctx, err)) { errMsg(err) << "Cannot encode general settings."; return false; } if (! this->encodeButtonSettings(ctx, flags, err)) { errMsg(err) << "Cannot encode button settings."; return false; } if (! this->encodeMessages(ctx, flags, err)) { errMsg(err) << "Cannot encode preset messages."; return false; } // Define Contacts if (! this->encodeContacts(flags, ctx, err)) { errMsg(err) << "Cannot encode contacts."; return false; } if (! this->encodeDTMFContacts(flags, ctx, err)) { errMsg(err) << "Cannot encode DTMF contacts."; return false; } if (! this->encodeChannels(flags, ctx, err)) { errMsg(err) << "Cannot encode channels"; return false; } if (! this->encodeBootSettings(flags, ctx, err)) { errMsg(err) << "Cannot encode boot text."; return false; } if (! this->encodeZones(flags, ctx, err)) { errMsg(err) << "Cannot encode zones."; return false; } if (! this->encodeScanLists(flags, ctx, err)) { errMsg(err) << "Cannot encode scan lists."; return false; } if (! this->encodeGroupLists(flags, ctx, err)) { errMsg(err) << "Cannot encode group lists."; return false; } if (! this->encodeEncryption(flags, ctx, err)) { errMsg(err) << "Cannot encode encryption keys."; return true; } return true; } bool RadioddityCodeplug::decode(Config *config, const ErrorStack &err) { // Clear config object config->clear(); // Create index<->object table. Context ctx(config); return this->decodeElements(ctx, err); } bool RadioddityCodeplug::postprocess(Config *config, const ErrorStack &err) const { if (! Codeplug::postprocess(config, err)) { errMsg(err) << "Cannot post-process Radioddy codeplug."; return false; } ZoneMergeVisitor merger; if (! merger.process(config, err)) { errMsg(err) << "Cannot merg zones in decoded Radioddity codeplug."; return false; } return true; } bool RadioddityCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { if (! this->decodeGeneralSettings(ctx, err)) { errMsg(err) << "Cannot decode general settings."; return false; } if (! this->decodeButtonSettings(ctx, err)) { errMsg(err) << "Cannot decode button settings."; return false; } if (! this->decodeMessages(ctx, err)) { errMsg(err) << "Cannot decode preset messages."; return false; } if (! this->createContacts(ctx, err)) { errMsg(err) << "Cannot create contacts."; return false; } if (! this->createDTMFContacts(ctx, err)) { errMsg(err) << "Cannot create DTMF contacts"; return false; } if (! this->createChannels(ctx, err)) { errMsg(err) << "Cannot create channels."; return false; } if (! this->decodeBootSettings(ctx, err)) { errMsg(err) << "Cannot decode boot text."; return false; } if (! this->createEncryption(ctx, err)) { errMsg(err) << "Cannot decode encryption keys."; return false; } if (! this->createZones(ctx, err)) { errMsg(err) << "Cannot create zones."; return false; } if (! this->createScanLists(ctx, err)) { errMsg(err) << "Cannot create scan lists."; return false; } if (! this->createGroupLists(ctx, err)) { errMsg(err) << "Cannot create group lists."; return false; } if (! this->linkChannels(ctx, err)) { errMsg(err) << "Cannot link channels."; return false; } if (! this->linkZones(ctx, err)) { errMsg(err) << "Cannot link zones."; return false; } if (! this->linkScanLists(ctx, err)) { errMsg(err) << "Cannot link scan lists."; return false; } if (! this->linkGroupLists(ctx, err)) { errMsg(err) << "Cannot link group lists."; return false; } if (! this->linkEncryption(ctx, err)) { errMsg(err) << "Cannot link encryption keys."; return false; } return true; } ================================================ FILE: lib/radioddity_codeplug.hh ================================================ #ifndef RADIODDITYCODEPLUG_HH #define RADIODDITYCODEPLUG_HH #include "codeplug.hh" #include "signaling.hh" #include "channel.hh" #include "contact.hh" #include "radioddity_extensions.hh" #include "ranges.hh" #include "bootsettings.hh" class DMRContact; class Zone; class RXGroupList; class ScanList; /** Base class of all Radioddity codeplugs. This class implements the majority of all codeplug * elements present in all Radioddity codeplugs (also some derivatives like OpenGD77). This eases * the support of several Radioddity radios, as only the differences in the codeplug to this base * class must be implemented. * * @ingroup radioddity */ class RadioddityCodeplug : public Codeplug { Q_OBJECT public: /** Implements the base for all Radioddity channel encodings. * * Memory layout of encoded channel: * @verbinclude radioddity_channel.txt */ class ChannelElement: public Codeplug::Element { public: /** Possible channel types. */ enum Mode { MODE_ANALOG = 0, ///< Analog channel, aka FM. MODE_DIGITAL = 1 ///< Digital channel, aka DMR. }; /** Possible admit criteria. */ enum Admit { ADMIT_ALWAYS = 0, ///< Allow always. ADMIT_CH_FREE = 1, ///< Allow TX on channel free. ADMIT_COLOR = 2 ///< Allow TX on matching color-code. }; /** Possible privacy groups, not used in ham radio. */ enum PrivacyGroup { PRIVGR_NONE = 0, ///< No privacy group, default. PRIVGR_53474C39 = 1 ///< Privacy group 53474C39 (wtf?). }; protected: /** Constructs a channel from the given memory. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructs a channel from the given memory. */ explicit ChannelElement(uint8_t *ptr); /** Destructor. */ virtual ~ChannelElement(); /** The size of the channel. */ static constexpr unsigned int size() { return 0x0038; } /** Resets the channel. */ virtual void clear(); /** Returns the name of the channel. */ virtual QString name() const; /** Sets the name of the channel. */ virtual void setName(const QString &n); /** Returns the RX frequency of the channel. */ virtual uint32_t rxFrequency() const; /** Sets the RX frequency of the channel. */ virtual void setRXFrequency(uint32_t freq); /** Returns the TX frequency of the channel. */ virtual uint32_t txFrequency() const; /** Sets the TX frequency of the channel. */ virtual void setTXFrequency(uint32_t freq); /** Returns the channel mode. */ virtual Mode mode() const; /** Sets the channel mode. */ virtual void setMode(Mode mode); /** Returns the TX timeout in seconds. A value of 0 means disabled. */ virtual Interval txTimeOut() const; /** Sets the TX timeout in seconds. Setting it to 0 disables the timeout. */ virtual void setTXTimeOut(const Interval &tot); /** Returns the transmit time-out re-key delay in seconds. */ virtual unsigned txTimeOutRekeyDelay() const; /** Sets the transmit time-out re-key delay in seconds. */ virtual void setTXTimeOutRekeyDelay(unsigned delay); /** Returns the admit criterion. */ virtual Admit admitCriterion() const; /** Sets the admit criterion. */ virtual void setAdmitCriterion(Admit admit); /** Returns @c true if a scan list is set. */ virtual bool hasScanList() const; /** Returns the scan list index (+1). */ virtual unsigned scanListIndex() const; /** Sets the scan list index (+1). */ virtual void setScanListIndex(unsigned index); /** Returns the RX subtone. */ virtual SelectiveCall rxTone() const; /** Sets the RX subtone. */ virtual void setRXTone(const SelectiveCall &code); /** Returns the TX subtone. */ virtual SelectiveCall txTone() const; /** Sets the TX subtone. */ virtual void setTXTone(const SelectiveCall &code); /** Returns TX signaling index (+1). */ virtual unsigned txSignalingIndex() const; /** Sets TX signaling index (+1). */ virtual void setTXSignalingIndex(unsigned index); /** Returns RX signaling index (+1). */ virtual unsigned rxSignalingIndex() const; /** Sets RX signaling index (+1). */ virtual void setRXSignalingIndex(unsigned index); /** Returns the privacy group. */ virtual PrivacyGroup privacyGroup() const; /** Sets the privacy group. */ virtual void setPrivacyGroup(PrivacyGroup grp); /** Returns the TX color code. */ virtual unsigned txColorCode() const; /** Sets the TX color code. */ virtual void setTXColorCode(unsigned cc); /** Returns @c true if a group list is set. */ virtual bool hasGroupList() const; /** Returns the group-list index (+1). */ virtual unsigned groupListIndex() const; /** Sets the group-list index (+1). */ virtual void setGroupListIndex(unsigned index); /** Returns the RX color code. */ virtual unsigned rxColorCode() const; /** Sets the RX color code. */ virtual void setRXColorCode(unsigned cc); /** Returns @c true if an emergency system is set. */ virtual bool hasEmergencySystem() const; /** Returns the emergency system index (+1). */ virtual unsigned emergencySystemIndex() const; /** Sets the emergency system index (+1). */ virtual void setEmergencySystemIndex(unsigned index); /** Returns @c true if a TX contact is set. */ virtual bool hasContact() const; /** Returns the transmit contact index (+1). */ virtual unsigned contactIndex() const; /** Sets the transmit contact index (+1). */ virtual void setContactIndex(unsigned index); /** Returns @c true if data-call-confirm is enabled. */ virtual bool dataCallConfirm() const; /** Enables/disables data-call-confirm. */ virtual void enableDataCallConfirm(bool enable); /** Returns @c true if emergency alarm ACK is enabled. */ virtual bool emergencyAlarmACK() const; /** Enables/disables emergency alarm ACK. */ virtual void enableEmergencyAlarmACK(bool enable); /** Returns @c true if private-call-confirm is enabled. */ virtual bool privateCallConfirm() const; /** Enables/disables private-call-confirm. */ virtual void enablePrivateCallConfirm(bool enable); /** Returns @c true if privacy is enabled. */ virtual bool privacyEnabled() const; /** Enables/disables privacy. */ virtual void enablePrivacy(bool enable); /** Returns the time slot of the channel. */ virtual DMRChannel::TimeSlot timeSlot() const; /** Sets the time slot of the channel. */ virtual void setTimeSlot(DMRChannel::TimeSlot ts); /** Returns @c true if the dual-capacity direct mode is enabled. */ virtual bool dualCapacityDirectMode() const; /** Enables/disables the dual-capacity direct mode. */ virtual void enableDualCapacityDirectMode(bool enable); /** Returns @c true if non-STE is frequency (?!). */ virtual bool nonSTEFrequency() const; /** Enables/disables non-STE is frequency (?!). */ virtual void enableNonSTEFrequency(bool enable); /** Returns the bandwidth. */ virtual FMChannel::Bandwidth bandwidth() const; /** Sets the bandwidth. */ virtual void setBandwidth(FMChannel::Bandwidth bw); /** Returns @c true if RX only is enabled. */ virtual bool rxOnly() const; /** Enables/disables RX only. */ virtual void enableRXOnly(bool enable); /** Returns @c true if talkaround is enabled. */ virtual bool talkaround() const; /** Enables/disables talkaround. */ virtual void enableTalkaround(bool enable); /** Returns @c true if VOX is enabled. */ virtual bool vox() const; /** Enables/disables VOX. */ virtual void enableVOX(bool enable); /** Returns the power setting of the channel. */ virtual Channel::Power power() const; /** Sets the power setting of the channel. */ virtual void setPower(Channel::Power pwr); /** Constructs a generic @c Channel object from the codeplug channel. */ virtual Channel *toChannelObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links a previously constructed channel to the rest of the configuration. */ virtual bool linkChannelObj(Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Initializes this codeplug channel from the given generic configuration. */ virtual bool fromChannelObj(const Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for this element. */ struct Limit { /** The maximum length of the name. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets within the channel element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int rxFrequency() { return 0x0010; } static constexpr unsigned int txFrequency() { return 0x0014; } static constexpr unsigned int mode() { return 0x0018; } static constexpr unsigned int txTimeout() { return 0x001b; } static constexpr unsigned int txTimeoutRekeyDelay() { return 0x001c; } static constexpr unsigned int admitCriterion() { return 0x001d; } static constexpr unsigned int scanList() { return 0x001f; } static constexpr unsigned int rxTone() { return 0x0020; } static constexpr unsigned int txTone() { return 0x0022; } static constexpr unsigned int txSignaling() { return 0x0025; } static constexpr unsigned int rxSignaling() { return 0x0027; } static constexpr unsigned int privacyGroup() { return 0x0029; } static constexpr unsigned int txColorCode() { return 0x002a; } static constexpr unsigned int groupList() { return 0x002b; } static constexpr unsigned int rxColorCode() { return 0x002c; } static constexpr unsigned int emergencySystem() { return 0x002d; } static constexpr unsigned int transmitContact() { return 0x002e; } static constexpr Bit dataCallConfirm() { return {0x0030, 7}; } static constexpr Bit emergencyAlarmACK() { return {0x0030, 6}; } static constexpr Bit privateCallConfirm() { return {0x0031, 0}; } static constexpr Bit privacyEnabled() { return {0x0031, 4}; } static constexpr Bit timeSlot() { return {0x0031, 6}; } static constexpr Bit dualCapacityDirectMode() { return {0x0032, 0}; } static constexpr Bit nonSTEFrequency() { return {0x0032, 5}; } static constexpr Bit bandwidth() { return {0x0033, 1}; } static constexpr Bit rxOnly() { return {0x0033, 2}; } static constexpr Bit talkaround() { return {0x0033, 3}; } static constexpr Bit vox() { return {0x0033, 6}; } static constexpr Bit power() { return {0x0033, 7}; } /// @endcond }; }; /** Implements the base for channel banks in Radioddity codeplugs. * * Memory layout of a channel bank: * @verbinclude radioddity_channelbank.txt */ class ChannelBankElement: public Element { protected: /** Hidden constructor. */ ChannelBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ChannelBankElement(uint8_t *ptr); /** Destructor. */ virtual ~ChannelBankElement(); /** The size of the channel bank. */ static constexpr unsigned int size() { return 0x1c10; } /** Clears the bank. */ void clear(); /** Returns @c true if the channel is enabled. */ virtual bool isEnabled(unsigned idx) const ; /** Enable/disable a channel in the bank. */ virtual void enable(unsigned idx, bool enabled); /** Returns a pointer to the channel at the given index. */ virtual uint8_t *get(unsigned idx) const; /** Returns the n-th channel. */ ChannelElement channel(unsigned int n); public: /** Some limits for the channel bank. */ struct Limit { /** The maximum number of channels. */ static constexpr unsigned int channelCount() { return 128; } }; protected: /** Some internal offset within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bitmask() { return 0x0000; } static constexpr unsigned int channels() { return 0x0010; } /// @endcond }; }; /** VFO Channel representation within the binary codeplug. * * Each channel requires 0x38b: * @verbinclude radioddity_vfochannel.txt */ class VFOChannelElement: public ChannelElement { public: /** Possible offset frequency modes. */ enum class OffsetMode { Off = 0, ///< Disables transmit frequency offset. Positive = 1, ///< Transmit offset frequency is positive (TX above RX). Negative = 2 ///< Transmit offset frequency is negative (TX below RX). }; /** Possible tuning step sizes. */ enum class StepSize { SS2_5kHz = 0, ///< 2.5kHz SS5kHz = 1, ///< 5kHz SS6_25kHz = 2, ///< 6.25kHz SS10kHz = 3, ///< 10kHz SS12_5kHz = 4, ///< 12.5kHz SS20kHz = 5, ///< 20kHz SS30kHz = 6, ///< 30kHz SS50kHz = 7 ///< 50kHz }; protected: /** Hidden constructor. */ VFOChannelElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit VFOChannelElement(uint8_t *ptr); void clear(); /** The VFO channel has no name. */ QString name() const; /** The VFO channel has no name. */ void setName(const QString &name); /** Returns the tuning step-size in kHz. */ virtual double stepSize() const; /** Sets the tuning step-size in kHz. */ virtual void setStepSize(double kHz); /** Returns the transmit frequency offset mode. */ virtual OffsetMode offsetMode() const; /** Returns the transmit frequency offset. */ virtual double txOffset() const; /** Sets the transmit frequency offset in MHz. */ virtual void setTXOffset(double f); /** Sets the transmit frequency offset mode. */ virtual void setOffsetMode(OffsetMode mode); protected: /// @cond DO_NOT_DOCUMENT struct Offset: public ChannelElement::Offset { static constexpr Bit stepSize() { return {0x0036, 4} ; } static constexpr Bit offsetMode() { return {0x0036, 2} ; } static constexpr unsigned int txOffset() { return 0x0034; } }; /// @endcond }; /** Implements the base for digital contacts in Radioddity codeplugs. * * Memory layout of a digital contact: * @verbinclude radioddity_contact.txt */ class ContactElement: public Element { protected: /** Hidden constructor. */ ContactElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ContactElement(uint8_t *ptr); /** Destructor. */ virtual ~ContactElement(); /** The size of the contact element. */ static constexpr unsigned int size() { return 0x0018; } /** Resets the contact. */ void clear(); /** Returns @c true if the contact is valid. */ bool isValid() const; /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString name); /** Returns the DMR number of the contact. */ virtual unsigned number() const; /** Sets the DMR number of the contact. */ virtual void setNumber(unsigned id); /** Returns the call type. */ virtual DMRContact::Type type() const; /** Sets the call type. */ virtual void setType(DMRContact::Type type); /** Returns @c true if the ring tone is enabled for this contact. */ virtual bool ring() const; /** Enables/disables ring tone for this contact. */ virtual void enableRing(bool enable); /** Returns the ring tone style for this contact [0-10]. */ virtual unsigned ringStyle() const; /** Sets the ring tone style for this contact [0-10]. */ virtual void setRingStyle(unsigned style); /** Constructs a @c DigitalContact instance from this codeplug contact. */ virtual DMRContact *toContactObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Resets this codeplug contact from the given @c DigitalContact. */ virtual bool fromContactObj(const DMRContact *obj, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the contact. */ struct Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } /** Number of possible ring-styles [0,10]. */ static constexpr unsigned int ringStyle() { return 10; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int number() { return 0x0010; } static constexpr unsigned int type() { return 0x0014; } static constexpr unsigned int ring() { return 0x0015; } static constexpr unsigned int ringStyle() { return 0x0016; } /// @endcond }; }; /** Implements a base DTMF (analog) contact for Radioddity codeplugs. * * Memory layout of the DTMF contact: * @verbinclude radioddity_dtmfcontact.txt */ class DTMFContactElement: public Element { protected: /** Hidden constructor. */ DTMFContactElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit DTMFContactElement(uint8_t *ptr); /** Destructor. */ virtual ~DTMFContactElement(); /** The size of the contact element. */ static constexpr unsigned int size() { return 0x0020; } /** Resets the contact. */ void clear(); /** Returns @c true if the contact is valid. */ bool isValid() const; /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString &name); /** Returns the number of the contact. */ virtual QString number() const; /** Sets the number of the contact. */ virtual void setNumber(const QString &number); /** Constructs a @c DTMFContact instance from this codeplug contact. */ virtual DTMFContact *toContactObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Resets this codeplug contact from the given @c DTMFContact. */ virtual bool fromContactObj(const DTMFContact *obj, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { /** The maximum name length. */ static constexpr unsigned int nameLength() { return 16; } /** The maximum number length. */ static constexpr unsigned int numberLength() { return 16; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int number() { return 0x0010; } /// @endcond }; }; /** Represents a zone within Radioddity codeplugs. * * Memory layout of the zone: * @verbinclude radioddity_zone.txt */ class ZoneElement: public Element { protected: /** Hidden constructor. */ ZoneElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ZoneElement(uint8_t *ptr); virtual ~ZoneElement(); /** The size of the zone element. */ static constexpr unsigned int size() { return 0x0030; } /** Resets the zone. */ void clear(); /** Returns @c true if the zone is valid. */ bool isValid() const; /** Returns the name of the zone. */ virtual QString name() const; /** Sets the name of the zone. */ virtual void setName(const QString &name); /** Returns @c true if a member is stored at the given index. * That is, if the index is not 0. */ virtual bool hasMember(unsigned n) const; /** Returns the n-th member index (+1). */ virtual unsigned member(unsigned n) const; /** Sets the n-th member index (+1). */ virtual void setMember(unsigned n, unsigned idx); /** Clears the n-th member index. */ virtual void clearMember(unsigned n); /** Constructs a generic @c Zone object from this codeplug zone. */ virtual Zone *toZoneObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links a previously constructed @c Zone object to the rest of the configuration. That is * linking to the referred channels. */ virtual bool linkZoneObj(Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Resets this codeplug zone representation from the given generic @c Zone object. */ virtual bool fromZoneObjA(const Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); /** Resets this codeplug zone representation from the given generic @c Zone object. */ virtual bool fromZoneObjB(const Zone *zone, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for zone elements. */ struct Limit { /** The maximum length of the zone name. */ static constexpr unsigned int nameLength() { return 16; } /** The maximum number of members. */ static constexpr unsigned int memberCount() { return 16; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int channels() { return 0x0010; } static constexpr unsigned int betweenChannels() { return 0x0002; } /// @endcond }; }; /** Implements the base class for all zone banks of Radioddity codeplugs. * * Memory layout of the zone table/bank: * @verbinclude radioddity_zonebank.txt */ class ZoneBankElement: public Element { protected: /** Hidden constructor. */ ZoneBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ZoneBankElement(uint8_t *ptr); /** Destructor. */ ~ZoneBankElement(); /** The size of the zone element. */ static constexpr unsigned int size() { return 0x2f00; } /** Resets the bank. */ void clear(); /** Returns @c true if the channel is enabled. */ virtual bool isEnabled(unsigned idx) const ; /** Enable/disable a channel in the bank. */ virtual void enable(unsigned idx, bool enabled); /** Returns a pointer to the channel at the given index. */ virtual uint8_t *get(unsigned idx) const; /** Returns the n-th zone. */ ZoneElement zone(unsigned int n); public: /** Some limits for the zone bank. */ struct Limit { /** The maximum number of zones in this bank. */ static constexpr unsigned int zoneCount() { return 250; } }; protected: /** Some internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bitmap() { return 0x0000; } static constexpr unsigned int zones() { return 0x0020; } /// @endcond }; }; /** Represents a base class for all group lists within Radioddity codeplugs. * * Memory layout of the RX group list: * @verbinclude radioddity_grouplist.txt */ class GroupListElement: public Element { protected: /** Hidden constructor. */ GroupListElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit GroupListElement(uint8_t *ptr); /** Destructor. */ virtual ~GroupListElement(); /** Size of the group list element. */ static constexpr unsigned int size() { return 0x0030; } /** Resets the group list. */ void clear(); /** Returns the name of the group list. */ virtual QString name() const; /** Sets the name of the group list. */ virtual void setName(const QString &name); /** Returns @c true if the group list has an n-th member. * That is if the n-th index is not 0. */ virtual bool hasMember(unsigned n) const; /** Returns the n-th member index (+1). */ virtual unsigned member(unsigned n) const; /** Sets the n-th member index (+1). */ virtual void setMember(unsigned n, unsigned idx); /** Clears the n-th member index. */ virtual void clearMember(unsigned n); /** Constructs a @c RXGroupList object from the codeplug representation. */ virtual RXGroupList *toRXGroupListObj(Context &ctx, const ErrorStack &err = ErrorStack()); /** Links a previously constructed @c RXGroupList to the rest of the generic configuration. */ virtual bool linkRXGroupListObj(unsigned int ncnt, RXGroupList *lst, Context &ctx, const ErrorStack &err = ErrorStack()) const; /** Reset this codeplug representation from a @c RXGroupList object. */ virtual bool fromRXGroupListObj(const RXGroupList *lst, Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits for group lists. */ struct Limit { static constexpr unsigned int nameLength() { return 16; } ///< Maximum name length. static constexpr unsigned int memberCount() { return 16; } ///< Maximum number of entries. }; protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int name() { return 0x0000; } static constexpr unsigned int members() { return 0x0010; } static constexpr unsigned int betweenMembers() { return 0x0002; } /// @endcond }; }; /** Implements a base class of group list memory banks for all Radioddity codeplugs. * * Memory layout of the group list table: * @verbinclude radioddity_grouplistbank.txt */ class GroupListBankElement: public Element { protected: /** Hidden constructor. */ GroupListBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit GroupListBankElement(uint8_t *ptr); /** Destructor. */ virtual ~GroupListBankElement(); /** The size of the group list bank element. */ static constexpr unsigned int size() { return 0x0c80; } /** Resets the bank. */ void clear(); /** Returns @c true if the n-th group list is enabled. */ virtual bool isEnabled(unsigned n) const; /** Returns the number of contacts in the n-th group list. */ virtual unsigned contactCount(unsigned n) const; /** Sets the number of contacts in the n-th group list. * This also enables the n-th group list. */ virtual void setContactCount(unsigned n, unsigned size); /** Disables the n-th group list. */ virtual void disable(unsigned n); /** Returns a pointer to the n-th group list. */ virtual uint8_t *get(unsigned n) const; public: /** Some limits for the group list bank. */ struct Limit { static constexpr unsigned int groupListCount() { return 64; } ///< Maximum number of group lists. }; protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int contactCounts() { return 0x0000; } static constexpr unsigned int groupLists() { return 0x0080; } /// @endcond }; }; /** Implements the base class for scan lists of all Radioddity codeplugs. * * Memory layout of the scan list. * @verbinclude radioddity_scanlist.txt */ class ScanListElement: public Element { public: /** Possible priority channel types. */ enum Mode { PL_NONPRI = 0, ///< Only non-priority channels. PL_DISABLE = 1, ///< Disable priority channels. PL_PRI = 2, ///< Only priority channels. PL_PRI_NONPRI = 3 ///< Priority and non-priority channels. }; protected: /** Hidden constructor. */ ScanListElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ScanListElement(uint8_t *ptr); /** Destructor. */ virtual ~ScanListElement(); /** The size of the scan list. */ static constexpr unsigned int size() { return 0x0058; } /** Resets the scan list. */ void clear(); /** Returns the name of the scan list. */ virtual QString name() const; /** Sets the name of the scan list. */ virtual void setName(const QString &name); /** Returns @c true if channel mark is enabled. */ virtual bool channelMark() const; /** Enables/disables channel mark. */ virtual void enableChannelMark(bool enable); /** Returns the scan mode. */ virtual Mode mode() const; /** Sets the scan mode. */ virtual void setMode(Mode mode); /** Returns @c true if talk back is enabled. */ virtual bool talkback() const; /** Enables/disables talk back. */ virtual void enableTalkback(bool enable); /** Returns @c true if the n-th member is set. */ virtual bool hasMember(unsigned n) const; /** Returns @c true if the n-th member is selected channel. */ virtual bool isSelected(unsigned n) const; /** Returns the n-th member index. */ virtual unsigned member(unsigned n) const; /** Sets the n-th member index. */ virtual void setMember(unsigned n, unsigned idx); /** Sets the n-th member to be the selected channel. */ virtual void setSelected(unsigned n); /** Clears the n-th member. */ virtual void clearMember(unsigned n); /** Returns @c true if the primary priority channel is set. */ virtual bool hasPrimary() const; /** Returns @c true if the primary priority channel is the selected channel. */ virtual bool primaryIsSelected() const; /** Return the channel index for the primary priority channel. */ virtual unsigned primary() const; /** Sets the primary priority channel index. */ virtual void setPrimary(unsigned idx); /** Sets the primary priority channel to be the selected channel. */ virtual void setPrimarySelected(); /** Clears the primary priority channel. */ virtual void clearPrimary(); /** Returns @c true if the secondary priority channel is set. */ virtual bool hasSecondary() const; /** Returns @c true if the secondary priority channel is the selected channel. */ virtual bool secondaryIsSelected() const; /** Return the channel index for the secondary priority channel. */ virtual unsigned secondary() const; /** Sets the secondary priority channel index. */ virtual void setSecondary(unsigned idx); /** Sets the secondary priority channel to be the selected channel. */ virtual void setSecondarySelected(); /** Clears the secondary priority channel. */ virtual void clearSecondary(); /** Returns @c true if the revert channel is set, if @c false the radio will transmit on the * last active channel during scan. */ virtual bool hasRevert() const; /** Returns @c true if the revert channel is the selected channel. */ virtual bool revertIsSelected() const; /** Return the channel index for the revert channel. */ virtual unsigned revert() const; /** Sets the revert channel index. */ virtual void setRevert(unsigned idx); /** Sets the revert channel to be the selected one. */ virtual void setRevertSelected(); /** Clears the revert channel, sets it to last active. */ virtual void clearRevert(); /** Returns the hold time in ms. */ virtual unsigned holdTime() const; /** Sets the hold time in ms. */ virtual void setHoldTime(unsigned ms); /** Returns the priority sample time in ms. */ virtual unsigned prioritySampleTime() const; /** Sets the priority sample time in ms. */ virtual void setPrioritySampleTime(unsigned ms); /** Constructs a @c ScanList object from this codeplug representation. */ virtual ScanList *toScanListObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Links a previously constructed @c ScanList object to the rest of the generic configuration. */ virtual bool linkScanListObj(ScanList *lst, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Initializes this codeplug representation from the given @c ScanList object. */ virtual bool fromScanListObj(const ScanList *lst, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the scan list. */ struct Limit: public Element::Limit { /// Maximum name length. static constexpr unsigned int name() { return 15; } /// Maximum number of members. static constexpr unsigned int members() { return 32; } }; protected: /// @cond DO_NOT_DOCUMENT struct Offset: public Element::Offset { static constexpr unsigned int name() { return 0x0000; } static constexpr Bit channelMark() { return {0x000f, 4}; } static constexpr Bit mode() { return {0x000f, 6}; } static constexpr Bit talkback() { return {0x000f, 7}; } static constexpr unsigned int members() { return 0x0010; } static constexpr unsigned int betweenMembers() { return 0x0002; } static constexpr unsigned int primary() { return 0x0050; } static constexpr unsigned int secondary() { return 0x0052; } static constexpr unsigned int revert() { return 0x0054; } static constexpr unsigned int holdTime() { return 0x0056; } static constexpr unsigned int primaryHoldTime() { return 0x0057; } }; /// @endcond }; /** Implements the base class of scan lists banks for all Radioddity codeplugs. * * Memory layout of the scan list table. * @verbinclude radioddity_scanlistbank.txt */ class ScanListBankElement: public Element { protected: /** Hidden constructor. */ ScanListBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ScanListBankElement(uint8_t *ptr); /** Destructor. */ virtual ~ScanListBankElement(); /** The size of the scan list bank. */ static constexpr unsigned int size() { return 0x56f0; } /** Resets the scan list bank. */ void clear(); /** Returns @c true if the n-th scan list is enabled. */ virtual bool isEnabled(unsigned n) const; /** Enable/disable n-th scan list. */ virtual void enable(unsigned n, bool enabled); /** Returns a pointer to the n-th scan list. */ virtual uint8_t *get(unsigned n) const; public: /** Some limits for the scan list bank. */ struct Limit { static constexpr unsigned int scanListCount() { return 250; } ///< Maximum number of scan lists. }; protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bytemap() { return 0x0000; } static constexpr unsigned int scanLists() { return 0x0040; } /// @endcond }; }; /** Implements the base class of general settings for all Radioddity codeplugs. * * Memory layout of the general settings * @verbinclude radioddity_generalsettings.txt */ class GeneralSettingsElement: public Element { public: /** Use monitor type from extension. */ typedef RadiodditySettingsExtension::MonitorType MonitorType; /** Use ARTS tone mode from extension. */ typedef RadioddityToneSettingsExtension::ARTSTone ARTSTone; /** Use scan mode from extension. */ typedef RadiodditySettingsExtension::ScanMode ScanMode; protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit GeneralSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~GeneralSettingsElement(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0028; } /** Resets the general settings. */ void clear(); /** Returns the radio name. */ virtual QString name() const; /** Sets the radio name. */ virtual void setName(const QString &name); /** Returns the DMR radio ID. */ virtual unsigned radioID() const; /** Sets the DMR radio ID. */ virtual void setRadioID(unsigned id); /** Returns the preamble duration in ms. */ virtual Interval preambleDuration() const; /** Sets the preamble duration in ms. */ virtual void setPreambleDuration(const Interval &dur); /** Returns the monitor type. */ virtual MonitorType monitorType() const; /** Sets the monitor type. */ virtual void setMonitorType(MonitorType type); /** Returns the VOX sensitivity [1-10], 0=disabled. */ virtual Level voxSensitivity() const; /** Sets the VOX sensitivity. */ virtual void setVOXSensitivity(Level value); /** Returns the low-battery warn interval in seconds. */ virtual unsigned lowBatteryWarnInterval() const; /** Sets the low-battery warn interval in seconds. */ virtual void setLowBatteryWarnInterval(unsigned sec); /** Returns the call-alert duration in seconds. */ virtual unsigned callAlertDuration() const; /** Sets the call-allert duration in seconds. */ virtual void setCallAlertDuration(unsigned sec); /** Returns the lone-worker response period in minutes. */ virtual unsigned loneWorkerResponsePeriod() const; /** Sets the lone-worker response period in minutes. */ virtual void setLoneWorkerResponsePeriod(unsigned min); /** Returns the lone-worker reminder period in seconds. */ virtual unsigned loneWorkerReminderPeriod() const; /** Sets the lone-worker reminder period in seconds. */ virtual void setLoneWorkerReminderPeriod(unsigned sec); /** Returns the group call hang time in ms. */ virtual Interval groupCallHangTime() const; /** Sets the group call hang time in ms. */ virtual void setGroupCallHangTime(const Interval &dur); /** Returns the private call hang time in ms. */ virtual Interval privateCallHangTime() const; /** Sets the private call hang time in ms. */ virtual void setPrivateCallHangTime(const Interval &dur); /** Returns @c true if the down-channel mode is VFO. */ virtual bool downChannelModeVFO() const; /** Enables/disables down-channel mode is VFO. */ virtual void enableDownChannelModeVFO(bool enable); /** Returns @c true if the up-channel mode is VFO. */ virtual bool upChannelModeVFO() const; /** Enables/disables up-channel mode is VFO. */ virtual void enableUpChannelModeVFO(bool enable); /** Returns @c true if the reset tone is enabled. */ virtual bool resetTone() const; /** Enables/disables reset tone. */ virtual void enableResetTone(bool enable); /** Returns @c true if the unknown number tone is enabled. */ virtual bool unknownNumberTone() const; /** Enables/disables reset tone. */ virtual void enableUnknownNumberTone(bool enable); /** Returns the ARTS tone mode. */ virtual ARTSTone artsToneMode() const; /** Sets the ARTS tone mode. */ virtual void setARTSToneMode(ARTSTone mode); /** Returns @c true if the digital channel talk permit tone is enabled. */ virtual bool digitalTalkPermitTone() const; /** Enables/disables digital channel talk permit tone. */ virtual void enableDigitalTalkPermitTone(bool enable); /** Returns @c true if the analog channel talk permit tone is enabled. */ virtual bool analogTalkPermitTone() const; /** Enables/disables analog channel talk permit tone. */ virtual void enableAnalogTalkPermitTone(bool enable); /** Returns @c true if the reset tone is enabled. */ virtual bool selftestTone() const; /** Enables/disables reset tone. */ virtual void enableSelftestTone(bool enable); /** Returns @c true if the channel free indication tone is enabled. */ virtual bool channelFreeIndicationTone() const; /** Enables/disables channel free indication tone. */ virtual void enableChannelFreeIndicationTone(bool enable); /** Returns @c true if all tones are disabled. */ virtual bool allTonesDisabled() const; /** Disables/enables all tones. */ virtual void disableAllTones(bool disable); /** Returns @c true if reception is disabled for battery saving. */ virtual bool batsaveRX() const; /** Enables/disables battery saving by disabling RX. */ virtual void enableBatsaveRX(bool enable); /** Returns @c true if preable is disabled for battery saving. */ virtual bool batsavePreamble() const; /** Enables/disables battery saving by disabling preamble. */ virtual void enableBatsavePreamble(bool enable); /** Returns @c true if all LEDs are disabled. */ virtual bool allLEDsDisabled() const; /** Disables/enables all LEDs. */ virtual void disableAllLEDs(bool disable); /** Returns true if quick-key override is inhibited. */ virtual bool quickKeyOverrideInhibited() const; /** Inhibits quick-key override. */ virtual void inhibitQuickKeyOverride(bool inhibit); /** Returns @c true if the TX exit tone is enabled. */ virtual bool txExitTone() const; /** Enables/disables TX exit tone. */ virtual void enableTXExitTone(bool enable); /** Returns @c true if the radio transmits on the active channel on double monitor. */ virtual bool txOnActiveChannel() const; /** Enables/disables transmission on active channel on double monitor. */ virtual void enableTXOnActiveChannel(bool enable); /** Returns @c true if animation is enabled. */ virtual bool animation() const; /** Enables/disables animation. */ virtual void enableAnimation(bool enable); /** Returns the scan mode. */ virtual ScanMode scanMode() const; /** Sets the scan mode. */ virtual void setScanMode(ScanMode mode); /** Returns the repeater end delay in [0-10]. */ virtual unsigned repeaterEndDelay() const; /** Sets the repeater end delay in [0-10]. */ virtual void setRepeaterEndDelay(unsigned delay); /** Returns the repeater STE in [0-10]. */ virtual unsigned repeaterSTE() const; /** Sets the repeater STE in [0-10]. */ virtual void setRepeaterSTE(unsigned ste); /** Returns @c true if a programming password is set. */ virtual bool hasProgPassword() const; /** Returns the programming password. */ virtual QString progPassword() const; /** Sets the programming password. */ virtual void setProgPassword(const QString &pwd); /** Resets the programming password. */ virtual void clearProgPassword(); /** Encodes the general setting from the given config. */ virtual bool fromConfig(Context &ctx, const ErrorStack &err=ErrorStack()); /** Updates the given config from this settings. */ virtual bool updateConfig(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the settings. */ struct Limit: Element::Limit { // VOX sensitivity settings. static constexpr Range vox() { return {1,10}; } }; }; /** Implements the base class of button settings for all Radioddity codeplugs. * * Encoding of button settings (size 0x20b): * @verbinclude radioddity_buttonsettings.txt */ class ButtonSettingsElement: public Element { public: /** Encoding/decoding of function key actions. */ struct KeyFunction { public: /** Encodes the given function. */ static uint8_t encode(RadioddityButtonSettingsExtension::Function func); /** Decodes the action. */ static RadioddityButtonSettingsExtension::Function decode(uint8_t action); protected: /** Possible function key actions. */ enum Action { None = 0x00, ///< Disables button. ToggleAllAlertTones = 0x01, EmergencyOn = 0x02, EmergencyOff = 0x03, ToggleMonitor = 0x05, ///< Toggle monitor on channel. NuiaceDelete = 0x06, OneTouch1 = 0x07, ///< Performs the first of 6 user-programmable actions (call, message). OneTouch2 = 0x08, ///< Performs the second of 6 user-programmable actions (call, message). OneTouch3 = 0x09, ///< Performs the third of 6 user-programmable actions (call, message). OneTouch4 = 0x0a, ///< Performs the fourth of 6 user-programmable actions (call, message). OneTouch5 = 0x0b, ///< Performs the fifth of 6 user-programmable actions (call, message). OneTouch6 = 0x0c, ///< Performs the sixt of 6 user-programmable actions (call, message). ToggleRepeatTalkaround = 0x0d, ToggleScan = 0x0e, TogglePrivacy = 0x10, ToggleVox = 0x11, ZoneSelect = 0x12, BatteryIndicator = 0x13, ToggleLoneWorker = 0x14, PhoneExit = 0x16, ToggleFlashLight = 0x1a, ToggleFMRadio = 0x1b }; }; /** Possible one-touch actions. */ enum class OneTouchAction { None = 0x00, ///< Disabled. DigitalCall = 0x10, ///< Calls a digital contact. DigitalMessage = 0x11, ///< Sends a SMS. AnalogCall = 0x20 ///< Calls an analog contact. }; protected: /** Hidden constructor */ ButtonSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit ButtonSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~ButtonSettingsElement(); /** Clears the button settings. */ void clear(); /** Returns the long-press duration in ms. */ virtual Interval longPressDuration() const; /** Sets the long-press duration in ms. */ virtual void setLongPressDuration(Interval ms); /** Returns the side-key 1 short-press action. */ virtual RadioddityButtonSettingsExtension::Function sk1ShortPress() const; /** Sets the side-key 1 short-press action. */ virtual void setSK1ShortPress(RadioddityButtonSettingsExtension::Function action); /** Returns the side-key 1 long-press action. */ virtual RadioddityButtonSettingsExtension::Function sk1LongPress() const; /** Sets the side-key 1 long-press action. */ virtual void setSK1LongPress(RadioddityButtonSettingsExtension::Function action); /** Returns the side-key 2 short-press action. */ virtual RadioddityButtonSettingsExtension::Function sk2ShortPress() const; /** Sets the side-key 2 short-press action. */ virtual void setSK2ShortPress(RadioddityButtonSettingsExtension::Function action); /** Returns the side-key 2 long-press action. */ virtual RadioddityButtonSettingsExtension::Function sk2LongPress() const; /** Sets the side-key 2 long-press action. */ virtual void setSK2LongPress(RadioddityButtonSettingsExtension::Function action); /** Returns the top-key short-press action. */ virtual RadioddityButtonSettingsExtension::Function tkShortPress() const; /** Sets the top-key short-press action. */ virtual void setTKShortPress(RadioddityButtonSettingsExtension::Function action); /** Returns the top-key long-press action. */ virtual RadioddityButtonSettingsExtension::Function tkLongPress() const; /** Sets the top-key long-press action. */ virtual void setTKLongPress(RadioddityButtonSettingsExtension::Function action); /** Returns the n-th one-touch action. */ virtual OneTouchAction oneTouchAction(unsigned n) const; /** Returns the n-th one-touch contact index (if action is @c OneTouchAction::DigitalCall). */ virtual unsigned oneTouchContact(unsigned n) const; /** Returns the n-th one-touch message index (if action is @c OneTouchAction::DigitalMessage). */ virtual unsigned oneTouchMessage(unsigned n) const; /** Disables the n-th one-touch action. */ virtual void disableOneTouch(unsigned n); /** Configures n-th one-touch action as a digital call to contact index. */ virtual void setOneTouchDigitalCall(unsigned n, unsigned index); /** Configures n-th one-touch action as a digital message using given index. */ virtual void setOneTouchDigitalMessage(unsigned n, unsigned index); /** Configures n-th one-touch action as a analog call. */ virtual void setOneTouchAnalogCall(unsigned n); /** Encodes the button settings (if set). */ bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); /** Decodes the button settings. */ bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limits { /** Range of valid long-press durations. */ static constexpr TimeRange longPressDuration() { return TimeRange{Interval::fromMilliseconds(0), Interval::fromMilliseconds(255*250)}; } /** Number of one-touch actions. */ static constexpr unsigned int oneTouchActions() { return 6; } }; protected: /** Internal used offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int longPressDuration() { return 0x0001; } static constexpr unsigned int sk1ShortPress() { return 0x0002; } static constexpr unsigned int sk1LongPress() { return 0x0003; } static constexpr unsigned int sk2ShortPress() { return 0x0004; } static constexpr unsigned int sk2LongPress() { return 0x0005; } static constexpr unsigned int tkShortPress() { return 0x0006; } static constexpr unsigned int tkLongPress() { return 0x0007; } static constexpr unsigned int oneTouchActions() { return 0x0008; } static constexpr unsigned int betweenOneTouchActions() { return 0x0004; } /// @endcond }; }; /** Implements the base class of menu settings for all Radioddity codeplugs. * * Encoding of Menu settings (size 0x08b): * @verbinclude radioddity_menusettings.txt */ class MenuSettingsElement: public Element { public: /** Possible channel display modes. */ enum class ChannelDisplayMode { Number = 0, ///< Show channel number. Name = 1, ///< Show channel name. Frequency = 2 ///< Show channel frequency. }; /** Possible dual-watch modes. */ enum class DualWatchMode { DualDual = 1, DualSingle = 2 }; protected: /** Hidden constructor. */ MenuSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit MenuSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~MenuSettingsElement(); /** Resets the menu settings. */ void clear(); /** Returns the menu hang-time in seconds. */ virtual unsigned menuHangTime() const; /** Sets the menu hang time in seconds. */ virtual void setMenuHangTime(unsigned sec); /** Returns @c true if the message menu is shown. */ virtual bool message() const; /** Enables/disables the message menu. */ virtual void enableMessage(bool enable); /** Returns @c true if the scan-start menu is shown. */ virtual bool scanStart() const; /** Enables/disables the scan-start menu. */ virtual void enableScanStart(bool enable); /** Returns @c true if the edit scan-list menu is shown. */ virtual bool editScanList() const; /** Enables/disables the edit scan-list menu. */ virtual void enableEditScanList(bool enable); /** Returns @c true if the call-alert menu is shown. */ virtual bool callAlert() const; /** Enables/disables the call-alert menu. */ virtual void enableCallAlert(bool enable); /** Returns @c true if the edit-contact menu is shown. */ virtual bool editContact() const; /** Enables/disables the edit-contact menu. */ virtual void enableEditContact(bool enable); /** Returns @c true if the manual-dial menu is shown. */ virtual bool manualDial() const; /** Enables/disables the manual-dial menu. */ virtual void enableManualDial(bool enable); /** Returns @c true if the radio-check menu is shown. */ virtual bool radioCheck() const; /** Enables/disables the radioCheck menu. */ virtual void enableRadioCheck(bool enable); /** Returns @c true if the remote-monitor menu is shown. */ virtual bool remoteMonitor() const; /** Enables/disables the message menu. */ virtual void enableRemoteMonitor(bool enable); /** Returns @c true if the radio-enable menu is shown. */ virtual bool radioEnable() const; /** Enables/disables the radio-enable menu. */ virtual void enableRadioEnable(bool enable); /** Returns @c true if the radio-disable menu is shown. */ virtual bool radioDisable() const; /** Enables/disables the radio-disable menu. */ virtual void enableRadioDisable(bool enable); /** Returns @c true if the programming-password menu is shown. */ virtual bool progPassword() const; /** Enables/disables the programming-password menu. */ virtual void enableProgPassword(bool enable); /** Returns @c true if the talkaround menu is shown. */ virtual bool talkaround() const; /** Enables/disables the talkaround menu. */ virtual void enableTalkaround(bool enable); /** Returns @c true if the tone menu is shown. */ virtual bool tone() const; /** Enables/disables the tone menu. */ virtual void enableTone(bool enable); /** Returns @c true if the power menu is shown. */ virtual bool power() const; /** Enables/disables the power menu. */ virtual void enablePower(bool enable); /** Returns @c true if the backlight menu is shown. */ virtual bool backlight() const; /** Enables/disables the backlight menu. */ virtual void enableBacklight(bool enable); /** Returns @c true if the intro-screen menu is shown. */ virtual bool introScreen() const; /** Enables/disables the message menu. */ virtual void enableIntroScreen(bool enable); /** Returns @c true if the keypad-lock menu is shown. */ virtual bool keypadLock() const; /** Enables/disables the keypad-lock menu. */ virtual void enableKeypadLock(bool enable); /** Returns @c true if the LED-indicator menu is shown. */ virtual bool ledIndicator() const; /** Enables/disables the LED-indicator menu. */ virtual void enableLEDIndicator(bool enable); /** Returns @c true if the squelch menu is shown. */ virtual bool squelch() const; /** Enables/disables the squelch menu. */ virtual void enableSquelch(bool enable); /** Returns @c true if the privacy menu is shown. */ virtual bool privacy() const; /** Enables/disables the privacy menu. */ virtual void enablePrivacy(bool enable); /** Returns @c true if the VOX menu is shown. */ virtual bool vox() const; /** Enables/disables the VOX menu. */ virtual void enableVOX(bool enable); /** Returns @c true if the password-lock menu is shown. */ virtual bool passwordLock() const; /** Enables/disables the password-lock menu. */ virtual void enablePasswordLock(bool enable); /** Returns @c true if the missed-calls menu is shown. */ virtual bool missedCalls() const; /** Enables/disables the missed-calls menu. */ virtual void enableMissedCalls(bool enable); /** Returns @c true if the answered-calls menu is shown. */ virtual bool answeredCalls() const; /** Enables/disables the answered-calls menu. */ virtual void enableAnsweredCalls(bool enable); /** Returns @c true if the outgoing-calls menu is shown. */ virtual bool outgoingCalls() const; /** Enables/disables the outgoing-calls menu. */ virtual void enableOutgoingCalls(bool enable); /** Returns @c true if the channel display-mode menu is shown. */ virtual bool channelDisplay() const; /** Enables/disables the channel display mode menu. */ virtual void enableChannelDisplay(bool enable); /** Returns @c true if the dual-watch menu is shown. */ virtual bool dualWatch() const; /** Enables/disables the dual-watch menu. */ virtual void enableDualWatch(bool enable); /** Returns the keypad lock time in seconds. */ virtual unsigned keypadLockTime() const; /** Sets the keypad lock time in seconds. */ virtual void setKeypadLockTime(unsigned sec); /** Returns the backlight time in seconds. */ virtual unsigned backlightTime() const; /** Sets the backlight time in seconds. */ virtual void setBacklightTime(unsigned sec); /** Returns the channel display mode. */ virtual ChannelDisplayMode channelDisplayMode() const; /** Sets the channel display mode. */ virtual void setChannelDisplayMode(ChannelDisplayMode mode); /** Returns @c true if the keytone is enabled. */ virtual bool keyTone() const; /** Enables/disables the keytone. */ virtual void enableKeyTone(bool enable); /** Returns the dual-watch mode. */ virtual DualWatchMode dualWatchMode() const; /** Sets the dual-watch mode. */ virtual void setDualWatchMode(DualWatchMode mode); }; /** Implements the base class of boot settings for all Radioddity codeplugs. * * Encoding of boot settings (size 0x20b): * @verbinclude radioddity_bootsettings.txt */ class BootSettingsElement: public Element { protected: /** Hidden constructor. */ BootSettingsElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit BootSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~BootSettingsElement(); /** Resets the settings. */ void clear(); /** Returns the boot display mode. */ virtual BootSettings::BootDisplay bootDisplay() const; /** Sets the boot display mode. */ virtual void setBootDisplay(BootSettings::BootDisplay mode); /** Returns @c true if the boot password is enabled. */ virtual bool bootPasswordEnabled() const; /** Enables/disables the boot password. */ virtual void enableBootPassword(bool enable); /** Returns the boot password (6 digit). */ virtual QString bootPassword() const; /** Sets the boot password (6 digit). */ virtual bool setBootPassword(const QString &passwd); /** Decodes boot settings and updates config. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Encodes boot settings from config. */ virtual bool encode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for the settings. */ struct Limit: Element::Limit { // Maximum password length. static constexpr unsigned passwordLength() { return 8; } }; protected: /** Internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int bootText() { return 0x0000; } static constexpr unsigned int bootPasswordEnable() { return 0x0001; } static constexpr unsigned int bootPassword() { return 0x0002; } /// @endcond }; }; /** Implements the base class of boot messages for all Radioddity codeplugs. * * Encoding of boot messages (size 0x20b): * @verbinclude radioddity_boottext.txt */ class BootTextElement: public Element { protected: /** Hidden constructor. */ BootTextElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit BootTextElement(uint8_t *ptr); /** Destructor. */ virtual ~BootTextElement(); /** The size of the boot text element. */ static constexpr unsigned int size() { return 0x0020; } /** Resets the intro text. */ void clear(); /** Returns the first line. */ virtual QString line1() const; /** Sets the first line. */ virtual void setLine1(const QString &text); /** Returns the Second line. */ virtual QString line2() const; /** Sets the second line. */ virtual void setLine2(const QString &text); /** Encodes boot text settings from configuration. */ virtual bool fromConfig(Context &ctx, const ErrorStack &err = ErrorStack()); /** Updates the configuration with the boot text settings. */ virtual bool updateConfig(Context &ctx, const ErrorStack &err = ErrorStack()); public: /** Some limits for this element. */ struct Limit { static constexpr unsigned int lineLength() { return 16; } ///< The maximum length of the boot text line. }; protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int line1() { return 0x0000;} static constexpr unsigned int line2() { return 0x0010;} /// @endcond }; }; /** Implements the base class of a message bank for all Radioddity message banks. * * Encoding of messages (size: 0x1248b): * @verbinclude radioddity_messagebank.txt */ class MessageBankElement: public Element { protected: /** Hidden constructor. */ MessageBankElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit MessageBankElement(uint8_t *ptr); /** Destructor. */ virtual ~MessageBankElement(); /** Returns the size of the message bank. */ static constexpr unsigned int size() { return 0x1248; } /** Resets all messages. */ void clear(); /** Returns the number of messages. */ virtual unsigned numMessages() const; /** Returns the n-th message. */ virtual QString message(unsigned n) const; /** Appends a message to the list. */ virtual void appendMessage(const QString msg); /** Encodes all preset messages. */ virtual bool encode(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); /** Decodes all preset messages. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { static constexpr unsigned int messages() { return 32; } ///< Maximum number of messages. static constexpr unsigned int messageLength() { return 144; } ///< Maximum length of each message. }; protected: /** Some internal used offset. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int messageConut() { return 0x0000; } static constexpr unsigned int messageLengths() { return 0x0008; } static constexpr unsigned int messages() { return 0x0048; } static constexpr unsigned int betweenMessages() { return Limit::messageLength(); } /// @endcond }; }; /** Represents all encryption keys and settings within the codeplug on the device. * * Memory representation of encryption settings: * @verbinclude radioddity_privacy.txt */ class EncryptionElement: public Codeplug::Element { public: /** Encodes possible privacy types. For now, only none (encryption disabled) and basic are * supported. */ enum class PrivacyType { None, ///< No encryption at all. Basic ///< Use basic DMR encryption. }; protected: /** Hidden constructor. */ EncryptionElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit EncryptionElement(uint8_t *ptr); /** Destructor. */ virtual ~EncryptionElement(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0088; } void clear(); /** Returns the privacy type set. */ virtual PrivacyType privacyType() const; /** Sets the privacy type. */ virtual void setPrivacyType(PrivacyType type); /** Returns @c true if the n-th "basic" key (32bit) is set. * That is, if it is not filled with 0xff. */ virtual bool isBasicKeySet(unsigned n) const; /** Returns the n-th "basic" key (32bit). */ virtual QByteArray basicKey(unsigned n) const; /** Sets the n-th "basic" key (32bit). */ virtual void setBasicKey(unsigned n, const QByteArray &key); /** Resets the n-th basic key. */ virtual void clearBasicKey(unsigned n); /** Encodes given encryption extension. */ virtual bool fromCommercialExt(CommercialExtension *ext, Context &ctx, const ErrorStack &err=ErrorStack()); /** Constructs the encryption extension. */ virtual bool updateCommercialExt(Context &ctx, const ErrorStack &err=ErrorStack()); /** Links the given encryption extension. */ virtual bool linkCommercialExt(CommercialExtension *ext, Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some limits for this element. */ struct Limit { /** The maximum number of keys. */ static constexpr unsigned int keyCount() { return 16; } /** The required key size. */ static constexpr unsigned int keySize() { return 4; } }; protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int privacyType() { return 0x0000; } static constexpr unsigned int bitmap() { return 0x0002; } static constexpr unsigned int keys() { return 0x0008; } ///< Offset of the first key. static constexpr unsigned int key() { return 0x0008;} ///< Offset between keys. /// @endcond }; }; protected: /** Hidden constructor, use a device specific class to instantiate. */ explicit RadioddityCodeplug(QObject *parent=nullptr); public: /** Destructor. */ virtual ~RadioddityCodeplug(); /** Clears and resets the complete codeplug to some default values. */ virtual void clear(); bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const; bool decode(Config *config, const ErrorStack &err=ErrorStack()); bool postprocess(Config *config, const ErrorStack &err) const; Config *preprocess(Config *config, const ErrorStack &err) const; bool encode(Config *config, const Flags &flags = Flags(), const ErrorStack &err=ErrorStack()); public: /** Decodes the binary codeplug and stores its content in the given generic configuration using * the given context. */ virtual bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given generic configuration as a binary codeplug using the given context. */ virtual bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Clears the general settings in the codeplug. */ virtual void clearGeneralSettings() = 0; /** Updates the general settings from the given configuration. */ virtual bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Updates the given configuration from the general settings. */ virtual bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears the button settings. */ virtual void clearButtonSettings() = 0; /** Encodes button settings. */ virtual bool encodeButtonSettings(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()) = 0; /** Decodes the button settings. */ virtual bool decodeButtonSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears the messages. */ virtual void clearMessages() = 0; /** Encodes preset messages. */ virtual bool encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()) = 0; /** Decodes preset messages. */ virtual bool decodeMessages(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all contacts in the codeplug. */ virtual void clearContacts() = 0; /** Encodes all digital contacts in the configuration into the codeplug. */ virtual bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds a digital contact to the configuration for each one in the codeplug. */ virtual bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all DTMF contacts in the codeplug. */ virtual void clearDTMFContacts() = 0; /** Encodes all DTMF contacts. */ virtual bool encodeDTMFContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds all DTMF contacts to the configuration. */ virtual bool createDTMFContacts(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clear all channels. */ virtual void clearChannels() = 0; /** Encode all channels. */ virtual bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds all defined channels to the configuration. */ virtual bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all channels. */ virtual bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears menu settings. */ virtual void clearMenuSettings() = 0; /** Clears boot text. */ virtual void clearBootSettings() = 0; /** Encodes boot text. */ virtual bool encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Updates the given configuration from the boot text settings. */ virtual bool decodeBootSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears the VFO settings. */ virtual void clearVFOSettings() = 0; /** Clears all zones. */ virtual void clearZones() = 0; /** Encodes zones. */ virtual bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds zones to the configuration. */ virtual bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all zones within the configuration. */ virtual bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all scan lists. */ virtual void clearScanLists() = 0; /** Encodes all scan lists. */ virtual bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Creates all scan lists. */ virtual bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all scan lists. */ virtual bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all group lists. */ virtual void clearGroupLists() = 0; /** Encodes all group lists. */ virtual bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Creates all group lists. */ virtual bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all group lists. */ virtual bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all encryption keys. */ virtual void clearEncryption() = 0; /** Encodes all encryption keys defined. */ virtual bool encodeEncryption(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Creates all encryption keys. */ virtual bool createEncryption(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all encryption keys. */ virtual bool linkEncryption(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; }; #endif // RADIODDITYCODEPLUG_HH ================================================ FILE: lib/radioddity_extensions.cc ================================================ #include "radioddity_extensions.hh" /* ********************************************************************************************* * * Implementation of RadioddityButtonSettingsExtension * ********************************************************************************************* */ RadioddityButtonSettingsExtension::RadioddityButtonSettingsExtension(QObject *parent) : ConfigItem(parent), _longPressDuration(Interval::fromMilliseconds(1000)), _funcKey1Short(Function::ZoneSelect), _funcKey1Long(Function::ToggleFMRadio), _funcKey2Short(Function::ToggleMonitor), _funcKey2Long(Function::ToggleFlashLight), _funcKey3Short(Function::BatteryIndicator), _funcKey3Long(Function::ToggleVox) { // pass... } ConfigItem * RadioddityButtonSettingsExtension::clone() const { ConfigItem *clone = new RadioddityButtonSettingsExtension(); if (! clone->copy(*this)) { delete clone; return nullptr; } return clone; } Interval RadioddityButtonSettingsExtension::longPressDuration() const { return _longPressDuration; } void RadioddityButtonSettingsExtension::setLongPressDuration(Interval interval) { if (interval == _longPressDuration) return; _longPressDuration = interval; emit modified(this); } RadioddityButtonSettingsExtension::Function RadioddityButtonSettingsExtension::funcKey1Short() const { return _funcKey1Short; } void RadioddityButtonSettingsExtension::setFuncKey1Short(Function func) { if (func == _funcKey1Short) return; _funcKey1Short = func; emit modified(this); } RadioddityButtonSettingsExtension::Function RadioddityButtonSettingsExtension::funcKey1Long() const { return _funcKey1Long; } void RadioddityButtonSettingsExtension::setFuncKey1Long(Function func) { if (func == _funcKey1Long) return; _funcKey1Long = func; emit modified(this); } RadioddityButtonSettingsExtension::Function RadioddityButtonSettingsExtension::funcKey2Short() const { return _funcKey2Short; } void RadioddityButtonSettingsExtension::setFuncKey2Short(Function func) { if (func == _funcKey2Short) return; _funcKey2Short = func; emit modified(this); } RadioddityButtonSettingsExtension::Function RadioddityButtonSettingsExtension::funcKey2Long() const { return _funcKey2Long; } void RadioddityButtonSettingsExtension::setFuncKey2Long(Function func) { if (func == _funcKey2Long) return; _funcKey2Long = func; emit modified(this); } RadioddityButtonSettingsExtension::Function RadioddityButtonSettingsExtension::funcKey3Short() const { return _funcKey3Short; } void RadioddityButtonSettingsExtension::setFuncKey3Short(Function func) { if (func == _funcKey3Short) return; _funcKey3Short = func; emit modified(this); } RadioddityButtonSettingsExtension::Function RadioddityButtonSettingsExtension::funcKey3Long() const { return _funcKey3Long; } void RadioddityButtonSettingsExtension::setFuncKey3Long(Function func) { if (func == _funcKey3Long) return; _funcKey3Long = func; emit modified(this); } /* ********************************************************************************************* * * Implementation of RadioddityToneSettingsExtension * ********************************************************************************************* */ RadioddityToneSettingsExtension::RadioddityToneSettingsExtension(QObject *parent) : ConfigItem(parent), _lowBatteryWarn(true), _lowBatteryWarnInterval(Interval::fromSeconds(30)), _lowBatteryWarnVolume(5), _callAlertDuration(Interval::fromSeconds(120)), _unknownNumberTone(false), _artsToneMode(ARTSTone::Once), _selftestTone(true) { // pass... } ConfigItem * RadioddityToneSettingsExtension::clone() const { RadioddityToneSettingsExtension *ext = new RadioddityToneSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } bool RadioddityToneSettingsExtension::lowBatteryWarn() const { return _lowBatteryWarn; } void RadioddityToneSettingsExtension::enableLowBatteryWarn(bool enable) { if (enable == _lowBatteryWarn) return; _lowBatteryWarn = enable; emit modified(this); } Interval RadioddityToneSettingsExtension::lowBatteryWarnInterval() const { return _lowBatteryWarnInterval; } void RadioddityToneSettingsExtension::setLowBatteryWarnInterval(Interval sec) { if (_lowBatteryWarnInterval == sec) return; _lowBatteryWarnInterval = sec; emit modified(this); } unsigned int RadioddityToneSettingsExtension::lowBatteryWarnVolume() const { return _lowBatteryWarnVolume; } void RadioddityToneSettingsExtension::setLowBatteryWarnVolume(unsigned int volume) { volume = std::min(10U, std::max(1U, volume)); if (volume == _lowBatteryWarnVolume) return; _lowBatteryWarnVolume = volume; emit modified(this); } Interval RadioddityToneSettingsExtension::callAlertDuration() const { return _callAlertDuration; } void RadioddityToneSettingsExtension::setCallAlertDuration(Interval sec) { if (_callAlertDuration == sec) return; _callAlertDuration = sec; emit modified(this); } bool RadioddityToneSettingsExtension::unknownNumberTone() const { return _unknownNumberTone; } void RadioddityToneSettingsExtension::enableUnknownNumberTone(bool enable) { if (_unknownNumberTone == enable) return; _unknownNumberTone = enable; emit modified(this); } RadioddityToneSettingsExtension::ARTSTone RadioddityToneSettingsExtension::artsToneMode() const { return _artsToneMode; } void RadioddityToneSettingsExtension::setARTSToneMode(ARTSTone mode) { if (_artsToneMode == mode) return; _artsToneMode = mode; emit modified(this); } bool RadioddityToneSettingsExtension::selftestTone() const { return _selftestTone; } void RadioddityToneSettingsExtension::enableSelftestTone(bool enable) { if (_selftestTone == enable) return; _selftestTone = enable; emit modified(this); } /* ********************************************************************************************* * * Implementation of RadioddityBootSettingsExtension * ********************************************************************************************* */ RadioddityBootSettingsExtension::RadioddityBootSettingsExtension(QObject *parent) : ConfigItem(parent), _progPasswd() { // pass... } ConfigItem * RadioddityBootSettingsExtension::clone() const { auto *ext = new RadioddityBootSettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } const QString & RadioddityBootSettingsExtension::progPassword() const { return _progPasswd; } void RadioddityBootSettingsExtension::setProgPassword(const QString &pwd) { if (_progPasswd == pwd) return; _progPasswd = pwd; emit modified(this); } /* ********************************************************************************************* * * Implementation of RadiodditySettingsExtension * ********************************************************************************************* */ RadiodditySettingsExtension::RadiodditySettingsExtension(QObject *parent) : ConfigExtension(parent), _monitorType(MonitorType::Silent), _loneWorkerResponseTime(Interval::fromMinutes(1)), _loneWorkerReminderPeriod(Interval::fromSeconds(10)), _downChannelModeVFO(false), _upChannelModeVFO(false), _powerSaveMode(true), _wakeupPreamble(true), _powerSaveDelay(Interval::fromSeconds(10)), _disableAllLEDs(false), _quickKeyOverrideInhibited(false), _txOnActiveChannel(true), _scanMode(ScanMode::Time), _repeaterEndDelay(), _repeaterSTE(), _txInterrupt(false), _language(Language::English), _buttonSettings(new RadioddityButtonSettingsExtension(this)), _toneSettings(new RadioddityToneSettingsExtension(this)), _bootSettings(new RadioddityBootSettingsExtension(this)) { // pass... } ConfigItem * RadiodditySettingsExtension::clone() const { RadiodditySettingsExtension *ext = new RadiodditySettingsExtension(); if (! ext->copy(*this)) { ext->deleteLater(); return nullptr; } return ext; } RadiodditySettingsExtension::MonitorType RadiodditySettingsExtension::monitorType() const { return _monitorType; } void RadiodditySettingsExtension::setMonitorType(MonitorType type) { if (_monitorType == type) return; _monitorType = type; emit modified(this); } Interval RadiodditySettingsExtension::loneWorkerResponseTime() const { return _loneWorkerResponseTime; } void RadiodditySettingsExtension::setLoneWorkerResponseTime(Interval min) { if (_loneWorkerResponseTime == min) return; _loneWorkerResponseTime = min; emit modified(this); } Interval RadiodditySettingsExtension::loneWorkerReminderPeriod() const { return _loneWorkerReminderPeriod; } void RadiodditySettingsExtension::setLoneWorkerReminderPeriod(Interval sec) { if (_loneWorkerReminderPeriod == sec) return; _loneWorkerReminderPeriod = sec; emit modified(this); } bool RadiodditySettingsExtension::downChannelModeVFO() const { return _downChannelModeVFO; } void RadiodditySettingsExtension::enableDownChannelModeVFO(bool enable) { if (_downChannelModeVFO == enable) return; _downChannelModeVFO = enable; emit modified(this); } bool RadiodditySettingsExtension::upChannelModeVFO() const { return _upChannelModeVFO; } void RadiodditySettingsExtension::enableUpChannelModeVFO(bool enable) { if (_upChannelModeVFO == enable) return; _upChannelModeVFO = enable; emit modified(this); } bool RadiodditySettingsExtension::powerSaveMode() const { return _powerSaveMode; } void RadiodditySettingsExtension::enablePowerSaveMode(bool enable) { if (_powerSaveMode == enable) return; _powerSaveMode = enable; emit modified(this); } bool RadiodditySettingsExtension::wakeupPreamble() const { return _wakeupPreamble; } void RadiodditySettingsExtension::enableWakeupPreamble(bool enable) { if (_wakeupPreamble == enable) return; _wakeupPreamble = enable; emit modified(this); } Interval RadiodditySettingsExtension::powerSaveDelay() const { return _powerSaveDelay; } void RadiodditySettingsExtension::setPowerSaveDelay(Interval interv) { if (interv == _powerSaveDelay) return; _powerSaveDelay = interv; emit modified(this); } bool RadiodditySettingsExtension::allLEDsDisabled() const { return _disableAllLEDs; } void RadiodditySettingsExtension::disableAllLEDs(bool disable) { if (_disableAllLEDs == disable) return; _disableAllLEDs = disable; emit modified(this); } bool RadiodditySettingsExtension::quickKeyOverrideInhibited() const { return _quickKeyOverrideInhibited; } void RadiodditySettingsExtension::inhibitQuickKeyOverride(bool inhibit) { if (_quickKeyOverrideInhibited == inhibit) return; _quickKeyOverrideInhibited = inhibit; emit modified(this); } bool RadiodditySettingsExtension::txOnActiveChannel() const { return _txOnActiveChannel; } void RadiodditySettingsExtension::enableTXOnActiveChannel(bool enable) { if (_txOnActiveChannel == enable) return; _txOnActiveChannel = enable; emit modified(this); } RadiodditySettingsExtension::ScanMode RadiodditySettingsExtension::scanMode() const { return _scanMode; } void RadiodditySettingsExtension::setScanMode(ScanMode mode) { if (_scanMode == mode) return; _scanMode = mode; emit modified(this); } Interval RadiodditySettingsExtension::repeaterEndDelay() const { return _repeaterEndDelay; } void RadiodditySettingsExtension::setRepeaterEndDelay(Interval delay) { if (_repeaterEndDelay == delay) return; _repeaterEndDelay = delay; emit modified(this); } Interval RadiodditySettingsExtension::repeaterSTE() const { return _repeaterSTE; } void RadiodditySettingsExtension::setRepeaterSTE(Interval ste) { if (_repeaterSTE == ste) return; _repeaterSTE = ste; emit modified(this); } bool RadiodditySettingsExtension::txInterrupt() const { return _txInterrupt; } void RadiodditySettingsExtension::enableTXInterrupt(bool enable) { if (enable == _txInterrupt) return; _txInterrupt = enable; emit modified(this); } RadiodditySettingsExtension::Language RadiodditySettingsExtension::language() const { return _language; } void RadiodditySettingsExtension::setLanguage(Language lang) { if (lang == _language) return; _language = lang; emit modified(this); } RadioddityButtonSettingsExtension * RadiodditySettingsExtension::buttons() const { return _buttonSettings; } RadioddityToneSettingsExtension * RadiodditySettingsExtension::tone() const { return _toneSettings; } RadioddityBootSettingsExtension * RadiodditySettingsExtension::boot() const { return _bootSettings; } ================================================ FILE: lib/radioddity_extensions.hh ================================================ #ifndef RADIODDITYEXTENSIONS_HH #define RADIODDITYEXTENSIONS_HH #include "configobject.hh" #include "interval.hh" #include "level.hh" /** Represents the button settings extension for all radioddity devices. * This object is part of the RadiodditySettingsExtension instance. */ class RadioddityButtonSettingsExtension: public ConfigItem { Q_OBJECT /** The long-press duration. */ Q_PROPERTY(Interval longPressDuration READ longPressDuration WRITE setLongPressDuration) /** The short-press action for the programmable function key 1 (SK1, P1). */ Q_PROPERTY(Function funcKey1Short READ funcKey1Short WRITE setFuncKey1Short) /** The long-press action for the programmable function key 1 (SK1, P1). */ Q_PROPERTY(Function funcKey1Long READ funcKey1Long WRITE setFuncKey1Long) /** The short-press action for the programmable function key 2 (SK2, P2). */ Q_PROPERTY(Function funcKey2Short READ funcKey2Short WRITE setFuncKey2Short) /** The long-press action for the programmable function key 2 (SK2, P2). */ Q_PROPERTY(Function funcKey2Long READ funcKey2Long WRITE setFuncKey2Long) /** The short-press action for the programmable function key 3 (TK). */ Q_PROPERTY(Function funcKey3Short READ funcKey3Short WRITE setFuncKey3Short) /** The long-press action for the programmable function key 3 (TK). */ Q_PROPERTY(Function funcKey3Long READ funcKey3Long WRITE setFuncKey3Long) public: /** Possible function key actions. Not all functions are present on all devices. */ enum class Function { None, ToggleAllAlertTones, EmergencyOn, EmergencyOff, ToggleMonitor, OneTouch1, OneTouch2, OneTouch3, OneTouch4, OneTouch5, OneTouch6, ToggleTalkaround, ToggleScan, ToggleEncryption, ToggleVox, ZoneSelect, BatteryIndicator, ToggleLoneWorker, PhoneExit, ToggleFlashLight, ToggleFMRadio, RadioEnable, RadioCheck, RadioDisable, PowerLevel, TBST, CallSwell }; Q_ENUM(Function) public: /** Default constructor. */ explicit RadioddityButtonSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the long-press duration, usually specified in ms. */ Interval longPressDuration() const; /** Sets the long-press duration, usually specified in ms. */ void setLongPressDuration(Interval interval); /** Returns the short-press function of the programmable function key 1 (SK1, P1). */ Function funcKey1Short() const; /** Sets the short-press function of the programmable function key 1 (SK1, P1). */ void setFuncKey1Short(Function func); /** Returns the long-press function of the programmable function key 1 (SK1, P1). */ Function funcKey1Long() const; /** Sets the long-press function of the programmable function key 1 (SK1, P1). */ void setFuncKey1Long(Function func); /** Returns the short-press function of the programmable function key 2 (SK2, P2). */ Function funcKey2Short() const; /** Sets the short-press function of the programmable function key 2 (SK2, P2). */ void setFuncKey2Short(Function func); /** Returns the long-press function of the programmable function key 2 (SK2, P2). */ Function funcKey2Long() const; /** Sets the long-press function of the programmable function key 2 (SK2, P2). */ void setFuncKey2Long(Function func); /** Returns the short-press function of the programmable function key 3 (TK). */ Function funcKey3Short() const; /** Sets the short-press function of the programmable function key 3 (TK). */ void setFuncKey3Short(Function func); /** Returns the long-press function of the programmable function key 3 (TK). */ Function funcKey3Long() const; /** Sets the long-press function of the programmable function key 3 (TK). */ void setFuncKey3Long(Function func); protected: /** The long-press duration. */ Interval _longPressDuration; /** The short-press action for the programmable function key 1 (SK1, P1). */ Function _funcKey1Short; /** The long-press action for the programmable function key 1 (SK1, P1). */ Function _funcKey1Long; /** The short-press action for the programmable function key 2 (SK2, P2). */ Function _funcKey2Short; /** The long-press action for the programmable function key 2 (SK2, P2). */ Function _funcKey2Long; /** The short-press action for the programmable function key 3 (TK). */ Function _funcKey3Short; /** The long-press action for the programmable function key 3 (TK). */ Function _funcKey3Long; }; /** Tone settings for Radioddity devices. */ class RadioddityToneSettingsExtension: public ConfigItem { Q_OBJECT /** If @c true, the low battery warning is enabled. (GD-73 only) */ Q_PROPERTY(bool lowBatteryWarn READ lowBatteryWarn WRITE enableLowBatteryWarn) /** The low-battery warn interval in seconds. */ Q_PROPERTY(Interval lowBatteryWarnInterval READ lowBatteryWarnInterval WRITE setLowBatteryWarnInterval) /** Returns the low-battery warning volume [1,10]. (GD-73 only)*/ Q_PROPERTY(bool lowBatteryWarnVolume READ lowBatteryWarnVolume WRITE setLowBatteryWarnVolume) /** The call-alert duration in seconds. */ Q_PROPERTY(Interval callAlertDuration READ callAlertDuration WRITE setCallAlertDuration) /** @c true, the unknown number tone is enabled. */ Q_PROPERTY(bool unknownNumberTone READ unknownNumberTone WRITE enableUnknownNumberTone) /** The ARTS tone mode. */ Q_PROPERTY(ARTSTone artsToneMode READ artsToneMode WRITE setARTSToneMode) /** If @c true, the self-test tone is enabled. */ Q_PROPERTY(bool selftestTone READ selftestTone WRITE enableSelftestTone) public: /** Possible ARTS tone settings. */ enum class ARTSTone { Disabled = 0, ///< ARTS tone is disabled. Once = 4, ///< ARTS tone once. Always = 8 ///< ARTS tone always. }; Q_ENUM(ARTSTone) public: /** Default constructor. */ explicit RadioddityToneSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true if a low battery charge is indicated by a warning. */ bool lowBatteryWarn() const; /** Enables/disables low-battery warning. */ void enableLowBatteryWarn(bool enable); /** Returns the low-battery warn interval in seconds. */ Interval lowBatteryWarnInterval() const; /** Sets the low-battery warn interval in seconds. */ void setLowBatteryWarnInterval(Interval sec); /** Returns the volume of the low-battery warning tone [1,10]. */ unsigned int lowBatteryWarnVolume() const; /** Sets the volume of the low-battery warning tone [1,10]. */ void setLowBatteryWarnVolume(unsigned int); /** Returns the call-alert duration in seconds. */ Interval callAlertDuration() const; /** Sets the call-allert duration in seconds. */ void setCallAlertDuration(Interval sec); /** Returns @c true if the unknown number tone is enabled. */ bool unknownNumberTone() const; /** Enables/disables reset tone. */ void enableUnknownNumberTone(bool enable); /** Returns the ARTS tone mode. */ ARTSTone artsToneMode() const; /** Sets the ARTS tone mode. */ void setARTSToneMode(ARTSTone mode); /** Returns @c true if the self-test tone is enabled. */ bool selftestTone() const; /** Enables/disables self-test tone. */ void enableSelftestTone(bool enable); protected: /** If @c true, a low-battery charge is indicated by a warning. */ bool _lowBatteryWarn; /** Holds the low-battery warn interval in seconds. */ Interval _lowBatteryWarnInterval; /** Holds the volume of the low-battery warning tone. */ unsigned int _lowBatteryWarnVolume; /** Holds the call alert duration in seconds. */ Interval _callAlertDuration; /** If @c true, the unknown number tone is enabled. */ bool _unknownNumberTone; /** Holds the ARTS tone mode. */ ARTSTone _artsToneMode; /** If @c true, the self-test tone is enabled. */ bool _selftestTone; }; /** Represents the boot settings for Radioddity devices. * This settings extension is part of the RadiodditySettingsExtension. */ class RadioddityBootSettingsExtension: public ConfigItem { Q_OBJECT /** The programming password, disabled if empty. */ Q_PROPERTY(QString progPassword READ progPassword WRITE setProgPassword) public: /** Default constructor. */ explicit RadioddityBootSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the programming password. */ const QString &progPassword() const; /** Sets the programming password. */ void setProgPassword(const QString &pwd); protected: /** Holds the programming password, disabled if empty. */ QString _progPasswd; }; /** Represents the general settings extension for Radioddity devices. * @ingroup radioddity */ class RadiodditySettingsExtension: public ConfigExtension { Q_OBJECT /** The monitor type. */ Q_PROPERTY(MonitorType monitorType READ monitorType WRITE setMonitorType) /** The lone-worker response time in minutes. */ Q_PROPERTY(Interval loneWorkerResponseTime READ loneWorkerResponseTime WRITE setLoneWorkerResponseTime) /** The lonw-worker reminder period in seconds. */ Q_PROPERTY(Interval loneWorkerReminderPeriod READ loneWorkerReminderPeriod WRITE setLoneWorkerReminderPeriod) /** If @c true the down-channel mode is VFO. */ Q_PROPERTY(bool downChannelModeVFO READ downChannelModeVFO WRITE enableDownChannelModeVFO) /** If @c true the up-channel mode is VFO. */ Q_PROPERTY(bool upChannelModeVFO READ upChannelModeVFO WRITE enableUpChannelModeVFO) /** If @c true, the power save mode is enabled. */ Q_PROPERTY(bool powerSaveMode READ powerSaveMode WRITE enablePowerSaveMode) Q_CLASSINFO("powerSaveModeDescription", "Puts the radio into sleep-mode when idle.") Q_CLASSINFO("powerSaveModeLongDescription", "When enabled, the radio enters a sleep mode when idle. That is, when on receive and " "there is no activity on the current channel. However, the radio may need some time " "to wake up from this mode. Hence, the 'wakeupPreamble' need to be enabled by all " "radios in the network to provide this wake-up delay.") /** If @c true, a wakeup preamble is sent. */ Q_PROPERTY(bool wakeupPreamble READ wakeupPreamble WRITE enableWakeupPreamble) /** The delay, before the idle radio enters power save mode (if enabled). */ Q_PROPERTY(Interval powerSaveDelay READ powerSaveDelay WRITE setPowerSaveDelay) /** If @c true, all LEDs are disabled. */ Q_PROPERTY(bool allLEDsDisabled READ allLEDsDisabled WRITE disableAllLEDs) /** If @c true, the quick-key override is inhibited. */ Q_PROPERTY(bool quickKeyOverrideInhibited READ quickKeyOverrideInhibited WRITE inhibitQuickKeyOverride) /** If @c true, the radio will transmit on the active channel when double-wait is enabled. */ Q_PROPERTY(bool txOnActiveChannel READ txOnActiveChannel WRITE enableTXOnActiveChannel) /** The scan mode. */ Q_PROPERTY(ScanMode scanMode READ scanMode WRITE setScanMode) /** The repeater end delay in seconds. */ Q_PROPERTY(Interval repeaterEndDelay READ repeaterEndDelay WRITE setRepeaterEndDelay) /** The repeater STE in seconds. */ Q_PROPERTY(Interval repeaterSTE READ repeaterSTE WRITE setRepeaterSTE) /** Returns @c true, if the TX interrupt is enabled. */ Q_PROPERTY(bool txInterrupt READ txInterrupt WRITE enableTXInterrupt) /** UI language. */ Q_PROPERTY(Language language READ language WRITE setLanguage) /** The button settings. */ Q_PROPERTY(RadioddityButtonSettingsExtension *buttons READ buttons) /** The tone settings. */ Q_PROPERTY(RadioddityToneSettingsExtension *tone READ tone) /** The boot settings. */ Q_PROPERTY(RadioddityBootSettingsExtension *boot READ boot) public: /** Possible monitor types. */ enum class MonitorType { Open = 0, ///< Monitoring by opening the squelch. Silent = 1 ///< Silent monitoring. }; Q_ENUM(MonitorType) /** Possible scan modes. */ enum class ScanMode { Time = 0, Carrier = 1, Search = 2 }; Q_ENUM(ScanMode) /** Possible UI languages. */ enum class Language { Chinese, English }; Q_ENUM(Language) public: /** Default constructor. */ Q_INVOKABLE explicit RadiodditySettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the monitor type. */ MonitorType monitorType() const; /** Sets the monitor type. */ void setMonitorType(MonitorType type); /** Returns the lone-worker response time in minutes. */ Interval loneWorkerResponseTime() const; /** Sets the lone-worker response time in minutes. */ void setLoneWorkerResponseTime(Interval min); /** Returns the lone-worker reminder period in seconds. */ Interval loneWorkerReminderPeriod() const; /** Sets the lone-worker reminder period in seconds. */ void setLoneWorkerReminderPeriod(Interval sec); /** Returns @c true if the down-channel mode is VFO. */ bool downChannelModeVFO() const; /** Enables/disables down-channel mode is VFO. */ void enableDownChannelModeVFO(bool enable); /** Returns @c true if the up-channel mode is VFO. */ bool upChannelModeVFO() const; /** Enables/disables up-channel mode is VFO. */ void enableUpChannelModeVFO(bool enable); /** Returns @c true if the power save mode is enabled. */ bool powerSaveMode() const; /** Enables the power save mode. */ void enablePowerSaveMode(bool enable); /** Returns @c true if the wake-up preamble is sent. */ bool wakeupPreamble() const; /** Enables transmission of wakeup preamble. */ void enableWakeupPreamble(bool enable); /** Returns the delay, before an idle radio enters power save mode. */ Interval powerSaveDelay() const; /** Sets the delay before an idle radio enters power save mode. */ void setPowerSaveDelay(Interval interv); /** Returns @c true if all LEDs are disabled. */ bool allLEDsDisabled() const; /** Disables/enables all LEDs. */ void disableAllLEDs(bool disable); /** Returns true if quick-key override is inhibited. */ bool quickKeyOverrideInhibited() const; /** Inhibits quick-key override. */ void inhibitQuickKeyOverride(bool inhibit); /** Returns @c true if the radio transmits on the active channel on double monitor. */ bool txOnActiveChannel() const; /** Enables/disables transmission on active channel on double monitor. */ void enableTXOnActiveChannel(bool enable); /** Returns the scan mode. */ ScanMode scanMode() const; /** Sets the scan mode. */ void setScanMode(ScanMode mode); /** Returns the repeater end delay in seconds. */ Interval repeaterEndDelay() const; /** Sets the repeater end delay in seconds. */ void setRepeaterEndDelay(Interval delay); /** Returns the repeater STE in seconds. */ Interval repeaterSTE() const; /** Sets the repeater STE in seconds. */ void setRepeaterSTE(Interval ste); /** Returns @c true if the TX interrupt is enabled. */ bool txInterrupt() const; /** Enables TX interrupt. */ void enableTXInterrupt(bool enable); /** Returns the UI language. */ Language language() const; /** Sets the language. */ void setLanguage(Language lang); /** Returns a weak reference to the button settings. */ RadioddityButtonSettingsExtension *buttons() const; /** Returns a weak reference to the tone settings. */ RadioddityToneSettingsExtension *tone() const; /** Returns a weak reference to the boot settings. */ RadioddityBootSettingsExtension *boot() const; protected: /** Holds the monitor type. */ MonitorType _monitorType; /** Holds the lone-worker response time in minutes. */ Interval _loneWorkerResponseTime; /** Holds the lone-worker reminder period in seconds. */ Interval _loneWorkerReminderPeriod; /** If @c true down-channel mode is VFO. */ bool _downChannelModeVFO; /** If @c true the up-channel mode is VFO. */ bool _upChannelModeVFO; /** If @c true, the power save mode is enabled. */ bool _powerSaveMode; /** If @c true, the wake-up preamble is sent. */ bool _wakeupPreamble; /** Delay before an idle radio enters the power save mode. */ Interval _powerSaveDelay; /** If @c true, all LEDs are disabled. */ bool _disableAllLEDs; /** If @c true, the quick-key override is inhibited. */ bool _quickKeyOverrideInhibited; /** If @c true, the radio will transmit on the active channel when double-wait is enabled. */ bool _txOnActiveChannel; /** Holds the scan mode. */ ScanMode _scanMode; /** Holds the repeater end delay in seconds. */ Interval _repeaterEndDelay; /** Holds the repeater STE in seconds. */ Interval _repeaterSTE; /** If @c true, TX interrupt is enabled. */ bool _txInterrupt; /** UI language. */ Language _language; /** Button settings. */ RadioddityButtonSettingsExtension *_buttonSettings; /** Tone settings. */ RadioddityToneSettingsExtension *_toneSettings; /** Boot settings. */ RadioddityBootSettingsExtension *_bootSettings; }; #endif // RADIODDITYEXTENSIONS_HH ================================================ FILE: lib/radioddity_interface.cc ================================================ #include "radioddity_interface.hh" #include #include #include #include #include "logger.hh" #define USB_VID 0x15a2 #define USB_PID 0x0073 #define MAX_RETRY 10 static const unsigned char CMD_PRG[] = "\2PROGRA"; static const unsigned char CMD_PRG2[] = "M\2"; static const unsigned char CMD_ACK[] = "A"; static const unsigned char CMD_READ[] = "Raan"; static const unsigned char CMD_WRITE[] = "Waan..."; static const unsigned char CMD_ENDR[] = "ENDR"; static const unsigned char CMD_ENDW[] = "ENDW"; static const unsigned char CMD_CWB0[] = "CWB\4\0\0\0\0"; static const unsigned char CMD_CWB1[] = "CWB\4\0\1\0\0"; static const unsigned char CMD_CWB3[] = "CWB\4\0\3\0\0"; static const unsigned char CMD_CWB4[] = "CWB\4\0\4\0\0"; RadioddityInterface::RadioddityInterface(const USBDeviceDescriptor &descr, const ErrorStack &err, QObject *parent) : HIDevice(descr, err, parent), _current_bank(MEMBANK_NONE), _identifier() { if (isOpen()) identifier(); } RadioddityInterface::~RadioddityInterface() { if (isOpen()) close(); } USBDeviceInfo RadioddityInterface::interfaceInfo() { return USBDeviceInfo(USBDeviceInfo::Class::HID, USB_VID, USB_PID); } QList RadioddityInterface::detect(bool saveOnly) { Q_UNUSED(saveOnly) return HIDevice::detect(USB_VID, USB_PID); } bool RadioddityInterface::isOpen() const { return HIDevice::isOpen(); } void RadioddityInterface::close() { logDebug() << "Close HID connection."; _identifier = RadioInfo(); HIDevice::close(); } RadioInfo RadioddityInterface::identifier(const ErrorStack &err) { static unsigned char reply[38]; unsigned char ack; if (_identifier.isValid()) return _identifier; logDebug() << "Radioddity HID interface: Enter program mode."; if (! hid_send_recv(CMD_PRG, 7, &ack, 1, err)) { errMsg(err) << "Cannot identify radio."; return RadioInfo(); } if (ack != CMD_ACK[0]) { errMsg(err) << "Cannot identify radio: Wrong PRD acknowledge " << (int)ack << ", expected "<< int(CMD_ACK[0]) << "."; return RadioInfo(); } if (! hid_send_recv(CMD_PRG2, 2, reply, 16, err)) { errMsg(err) << "Cannot identify radio."; return RadioInfo(); } if (! hid_send_recv(CMD_ACK, 1, &ack, 1, err)) { errMsg(err) << "Cannot identify radio."; return RadioInfo(); } if (ack != CMD_ACK[0]) { errMsg(err) << "Cannot identify radio: Wrong PRG2 acknowledge " << (int) ack << ", expected " << (int)CMD_ACK[0] << "."; return RadioInfo(); } // Reply: // 42 46 2d 35 52 ff ff ff 56 32 31 30 00 04 80 04 // B F - 5 R V 2 1 0 // Terminate the string. char *p = (char *)memchr(reply, 0xff, sizeof(reply)); if (p) *p = 0; if (0 == strcmp((char*)reply, "BF-5R")) { _identifier = RadioInfo::byID(RadioInfo::RD5R); } else if (0 == strcmp((char*)reply, "MD-760P")) { _identifier = RadioInfo::byID(RadioInfo::GD77); } else { errMsg(err) << "Unknown Radioddity device '" << (char*)reply << "'."; return RadioInfo(); } logDebug() << "Got device '" << _identifier.name() << "'."; return _identifier; } bool RadioddityInterface::read_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { Q_UNUSED(addr) if (! selectMemoryBank(MemoryBank(bank), err)) { errMsg(err) << "Cannot select memory bank " << bank << "."; return false; } return true; } bool RadioddityInterface::read(uint32_t bank, uint32_t addr, unsigned char *data, int nbytes, const ErrorStack &err) { unsigned char cmd[4], reply[32+4]; int n; if (! selectMemoryBank(MemoryBank(bank), err)) { errMsg(err) << "Cannot select memory bank " << bank << "."; return false; } // send data for (n=0; n> 8; cmd[2] = addr + n; cmd[3] = 32; if (! hid_send_recv(cmd, 4, reply, sizeof(reply), err)) return false; else memcpy(data + n, reply + 4, 32); } return true; } bool RadioddityInterface::read_finish(const ErrorStack &err) { unsigned char ack; if (! hid_send_recv(CMD_ENDR, 4, &ack, 1, err)) { errMsg(err) << "Cannot finish read()."; return false; } if (ack != CMD_ACK[0]) { errMsg(err) << "Cannot finish read(): Wrong acknowledge " << (int)ack << ", expected " << (int)CMD_ACK[0] << "."; return false; } logDebug() << "Left program mode."; _identifier = RadioInfo(); return true; } bool RadioddityInterface::write_start(uint32_t bank, uint32_t addr, const ErrorStack &err) { Q_UNUSED(addr) if (! selectMemoryBank(MemoryBank(bank), err)) { errMsg(err) << "Cannot select memory bank " << bank << "."; return false; } return true; } bool RadioddityInterface::write(uint32_t bank, uint32_t addr, unsigned char *data, int nbytes, const ErrorStack &err) { unsigned char ack, cmd[4+32]; if (! selectMemoryBank(MemoryBank(bank), err)) { errMsg(err) << "Cannot select memory bank " << bank << "."; return false; } // send data unsigned int count=0; for (int n=0; n> 8; cmd[2] = addr + n; cmd[3] = 32; memcpy(cmd + 4, data + n, 32); if (! hid_send_recv(cmd, 4+32, &ack, 1, err)) return false; else if (ack != CMD_ACK[0]) { errMsg(err) << "Cannot write block: Wrong acknowledge " << (int)ack << ", expected " << (int)CMD_ACK[0] << "."; n-=32; if ((++count) > MAX_RETRY) { errMsg(err) << "Maximum retry count reached. Abort."; return false; } } else { count = 0; } } return true; } bool RadioddityInterface::write_finish(const ErrorStack &err) { unsigned char ack; if (! hid_send_recv(CMD_ENDW, 4, &ack, 1, err)) { errMsg(err) << "Cannot finish write()."; return false; } if (ack != CMD_ACK[0]) { errMsg(err) << "Cannot finish write(): Wrong acknowledge " << (int)ack << ", expected " << (int)CMD_ACK[0] << "."; return false; } logDebug() << "Left program mode."; _identifier = RadioInfo(); return true; } bool RadioddityInterface::selectMemoryBank(MemoryBank bank, const ErrorStack &err) { unsigned char ack; const uint8_t *cmd = nullptr; if (_current_bank == bank) return true; // Select command by memory bank switch (bank) { case MEMBANK_CODEPLUG_LOWER : cmd = CMD_CWB0; break; case MEMBANK_CODEPLUG_UPPER : cmd = CMD_CWB1; break; case MEMBANK_CALLSIGN_LOWER : cmd = CMD_CWB3; break; case MEMBANK_CALLSIGN_UPPER : cmd = CMD_CWB4; break; default: errMsg(err) << "Cannot set memory bank: Unknown bank " << bank << "."; return false; } logDebug() << "Selecting memory bank " << bank << "..."; // select memory bank if (! hid_send_recv(cmd, 8, &ack, 1, err)) { errMsg(err) << "Cannot send memory bank select command."; return false; } if (ack != CMD_ACK[0]) { errMsg(err) << "Cannot select memory bank: Wrong acknowledge " << (int)ack << ", expected " << (int)CMD_ACK[0] << "."; return false; } logDebug() << "Memory bank " << bank << " selected."; _current_bank = bank; return true; } ================================================ FILE: lib/radioddity_interface.hh ================================================ #ifndef RADIODDITY_INTERFACE_HH #define RADIODDITY_INTERFACE_HH #include #include #include "radiointerface.hh" #ifdef Q_OS_MACOS #include "hid_macos.hh" #else #include "hid_libusb.hh" #endif /** Implements a radio interface for radios using the HID USB schema (i.e. Radioddity devices). * * @ingroup radioddity */ class RadioddityInterface: public HIDevice, public RadioInterface { Q_OBJECT public: /** Possible memory banks to select. */ enum MemoryBank { MEMBANK_NONE = -1, ///< No bank selected. MEMBANK_CODEPLUG_LOWER = 0, ///< Lower memory bank (EEPROM). MEMBANK_CODEPLUG_UPPER = 1, ///< Upper memory bank (FLASH). MEMBANK_CALLSIGN_LOWER = 3, ///< Callsign DB memory lower bank (also FLASH). MEMBANK_CALLSIGN_UPPER = 4 ///< Callsign DB memory upper bank (also FLASH). }; public: /** Connects to the radio via the given descriptor. */ explicit RadioddityInterface(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent = nullptr); /** Destructor. */ virtual ~RadioddityInterface(); /** Returns @c true if the connection was established. */ bool isOpen() const; void close(); /** Returns radio identifier string. */ RadioInfo identifier(const ErrorStack &err=ErrorStack()); bool read_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); /** Reads a block of data from the device at the given block number. * @param bank The memory bank to read from. * @param addr The address to read from within the memory bank. * @param data Pointer to memory where the read data is stored. * @param nbytes The number of bytes to read. * @param err The error stack, messages are put onto. * @returns @c true on success. */ bool read(uint32_t bank, uint32_t addr, unsigned char *data, int nbytes, const ErrorStack &err=ErrorStack()); bool read_finish(const ErrorStack &err=ErrorStack()); bool write_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); /** Writes a block of data to the device at the given block number. * @param bank The memory bank to read from. * @param addr The address to read from within the memory bank. * @param data Pointer to memory where the read data is stored. * @param nbytes The number of bytes to read. * @param err The error stack, messages are put onto. * @returns @c true on success. */ bool write(uint32_t bank, uint32_t addr, unsigned char *data, int nbytes, const ErrorStack &err=ErrorStack()); bool write_finish(const ErrorStack &err=ErrorStack()); public: /** Returns some information about the interface. */ static USBDeviceInfo interfaceInfo(); /** Tries to find all interfaces connected AnyTone radios. */ static QList detect(bool saveOnly=true); protected: /** Internal used function to select a memory bank. */ bool selectMemoryBank(MemoryBank bank, const ErrorStack &err=ErrorStack()); private: /** The currently selected memory bank. */ MemoryBank _current_bank; /** Identifier received when entering the prog mode. */ RadioInfo _identifier; }; #endif // RADIODDITY_INTERFACE_HH ================================================ FILE: lib/radioddity_radio.cc ================================================ #include "radioddity_radio.hh" #include "config.hh" #include "logger.hh" #include "utils.hh" #define BSIZE 32 RadioddityRadio::RadioddityRadio(RadioddityInterface *device, QObject *parent) : Radio(parent), _dev(device), _codeplugFlags(), _config(nullptr) { // pass... } RadioddityRadio::~RadioddityRadio() { if (_dev && _dev->isOpen()) { logDebug() << "Reboot and close connection to radio."; _dev->reboot(); _dev->close(); } if (_dev) { _dev->deleteLater(); _dev = nullptr; } } bool RadioddityRadio::startDownload(const TransferFlags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; _task = StatusDownload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } start(); return true; } bool RadioddityRadio::startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; if (_config) delete _config; if (! (_config = config)) return false; _config->setParent(this); _task = StatusUpload; _codeplugFlags = flags; if (flags.blocking()) { this->run(); return (StatusIdle == _task); } _errorStack = err; this->start(); return true; } bool RadioddityRadio::startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(selection); errMsg(err) << "Radio does not support a callsign DB."; return false; } bool RadioddityRadio::startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(flags); errMsg(err) << "Satellite config upload is not implemented yet."; return false; } void RadioddityRadio::run() { if (StatusDownload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit downloadError(this); return; } if (! download()) { _dev->read_finish(); _dev->reboot(); _dev->close(); _task = StatusError; emit downloadError(this); return; } _task = StatusIdle; _dev->reboot(); _dev->close(); emit downloadFinished(this, &codeplug()); _config = nullptr; } else if (StatusUpload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if (! upload()) { _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _dev->write_finish(); _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadCallsigns == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if(! uploadCallsigns()) { _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _task = StatusIdle; _dev->reboot(); _dev->close(); emit uploadComplete(this); } } bool RadioddityRadio::download() { emit downloadStarted(); unsigned btot = 0; for (int n=0; n addr) ? RadioddityInterface::MEMBANK_CODEPLUG_LOWER : RadioddityInterface::MEMBANK_CODEPLUG_UPPER ); // read if (! _dev->read(bank, (b0+i)*BSIZE, codeplug().data((b0+i)*BSIZE), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot download codeplug."; return false; } emit downloadProgress(float(bcount*100)/btot); } } _dev->read_finish(_errorStack); return true; } bool RadioddityRadio::upload() { emit uploadStarted(); unsigned btot = 0; for (int n=0; n addr) ? RadioddityInterface::MEMBANK_CODEPLUG_LOWER : RadioddityInterface::MEMBANK_CODEPLUG_UPPER ); // read if (! _dev->read(bank, addr, codeplug().data(addr), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot upload codeplug."; return false; } emit uploadProgress(float(bcount*50)/btot); } } } // Encode config into codeplug if (! codeplug().encode(_config, _codeplugFlags, _errorStack)) { errMsg(_errorStack) << "Codeplug upload failed."; return false; } // then, upload modified codeplug bcount = 0; for (int n=0; n addr) ? RadioddityInterface::MEMBANK_CODEPLUG_LOWER : RadioddityInterface::MEMBANK_CODEPLUG_UPPER ); // write block if (! _dev->write(bank, addr, codeplug().data(addr), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot upload codeplug."; return false; } emit uploadProgress(50+float(bcount*50)/btot); } } return true; } bool RadioddityRadio::uploadCallsigns() { return false; } ================================================ FILE: lib/radioddity_radio.hh ================================================ /** @defgroup radioddity Radioddity radios * Abstract classes for Radioddity radios. * * @ingroup dsc */ #ifndef RADIODDITY_RADIO_HH #define RADIODDITY_RADIO_HH #include "radio.hh" #include "radioddity_interface.hh" /** Base class for all Radioddity radios. * * @ingroup radioddity */ class RadioddityRadio: public Radio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit RadioddityRadio(RadioddityInterface *device=nullptr, QObject *parent=nullptr); virtual ~RadioddityRadio(); public slots: /** Starts the download of the codeplug and derives the generic configuration from it. */ bool startDownload(const TransferFlags &flags, const ErrorStack &err=ErrorStack()); /** Derives the device-specific codeplug from the generic configuration and uploads that * codeplug to the radio. */ bool startUpload(Config *config, const Codeplug::Flags &flags = Codeplug::Flags(), const ErrorStack &err=ErrorStack()); /** Encodes the given user-database and uploads it to the device. */ bool startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection=CallsignDB::Flags(), const ErrorStack &err=ErrorStack()); bool startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err); protected: /** Thread main routine, performs all blocking IO operations for codeplug up- and download. */ void run(); private: virtual bool download(); virtual bool upload(); virtual bool uploadCallsigns(); protected: /** The interface to the radio. */ RadioddityInterface *_dev; /** Holds the flags to control assembly and upload of code-plugs. */ Codeplug::Flags _codeplugFlags; /** The generic configuration. */ Config *_config; /** A weak reference to the user-database. */ UserDatabase *_userDB; }; #endif // RADIODDITY_RADIO_HH ================================================ FILE: lib/radioid.cc ================================================ #include "radioid.hh" #include "utils.hh" #include "contact.hh" #include "config.hh" #include "radiosettings.hh" /* ********************************************************************************************* * * Implementation of RadioID * ********************************************************************************************* */ RadioID::RadioID(QObject *parent) : ConfigObject(parent) { // pass... } RadioID::RadioID(const QString &name, QObject *parent) : ConfigObject(name, parent) { // pass... } /* ********************************************************************************************* * * Implementation of DMRRadioID * ********************************************************************************************* */ DMRRadioID::DMRRadioID(QObject *parent) : RadioID(parent), _number(0) { // pass... } DMRRadioID::DMRRadioID(const QString &name, uint32_t id, QObject *parent) : RadioID(name, parent), _number(id) { // pass... } ConfigItem * DMRRadioID::clone() const { DMRRadioID *id = new DMRRadioID(); if (! id->copy(*this)) { id->deleteLater(); return nullptr; } return id; } uint32_t DMRRadioID::number() const { return _number; } void DMRRadioID::setNumber(uint32_t id) { if (id == _number) return; _number = id; emit modified(this); } YAML::Node DMRRadioID::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = RadioID::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; node.SetStyle(YAML::EmitterStyle::Flow); type["dmr"] = node; return type; } bool DMRRadioID::parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse radio id: Expected object with one child."; return false; } return ConfigObject::parse(node.begin()->second, ctx, err); } bool DMRRadioID::link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot link radio id: Expected object with one child."; return false; } return ConfigObject::link(node.begin()->second, ctx, err); } /* ********************************************************************************************* * * Implementation of DefaultRadioID * ********************************************************************************************* */ DefaultRadioID *DefaultRadioID::_instance = nullptr; DefaultRadioID::DefaultRadioID(QObject *parent) : DMRRadioID(tr("[Default]"),0,parent) { // pass... } DefaultRadioID * DefaultRadioID::get() { if (nullptr == _instance) _instance = new DefaultRadioID(); return _instance; } /* ********************************************************************************************* * * Implementation of M17RadioID * ********************************************************************************************* */ M17RadioID::M17RadioID(QObject *parent) : RadioID(parent), _call() { // pass... } M17RadioID::M17RadioID(const QString &name, const QString &call, QObject *parent) : RadioID(name, parent), _call() { setCall(call); } ConfigItem * M17RadioID::clone() const { M17RadioID *id = new M17RadioID(); if (! id->copy(*this)) { id->deleteLater(); return nullptr; } return id; } const QString & M17RadioID::call() const { return _call; } void M17RadioID::setCall(const QString &call) { if (call == _call) return; _call = M17Contact::normalizeCall(call); emit modified(this); } YAML::Node M17RadioID::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = RadioID::serialize(context, err); if (node.IsNull()) return node; YAML::Node type; node.SetStyle(YAML::EmitterStyle::Flow); type["m17"] = node; return type; } bool M17RadioID::parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse M17 radio id: Expected object with one child."; return false; } return ConfigObject::parse(node.begin()->second, ctx, err); } bool M17RadioID::link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) return false; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot link M17 radio id: Expected object with one child."; return false; } return ConfigObject::link(node.begin()->second, ctx, err); } /* ********************************************************************************************* * * Implementation of DTMFRadioID * ********************************************************************************************* */ DTMFRadioID::DTMFRadioID(QObject *parent) : RadioID(parent) { // pass... } DTMFRadioID::DTMFRadioID(const QString &name, const QString &number, QObject *parent) : RadioID(name, parent), _number() { setNumber(number.simplified()); } ConfigItem * DTMFRadioID::clone() const { DTMFRadioID *newId = new DTMFRadioID(); if (! newId->copy(*this)) { newId->deleteLater(); return nullptr; } return newId; } const QString & DTMFRadioID::number() const { return _number; } void DTMFRadioID::setNumber(const QString &number) { if (! validDTMFNumber(number)) return; _number = number.simplified(); emit modified(this); return; } /* ********************************************************************************************* * * Implementation of RadioIDList * ********************************************************************************************* */ RadioIDList::RadioIDList(QObject *parent) : ConfigObjectList(DMRRadioID::staticMetaObject, parent) { // pass... } void RadioIDList::clear() { ConfigObjectList::clear(); } DMRRadioID * RadioIDList::getId(int idx) const { if (ConfigItem *obj = get(idx)) return obj->as(); return nullptr; } DMRRadioID * RadioIDList::find(uint32_t id) const { for (int i=0; iis() && (id == get(i)->as()->number())) return get(i)->as(); } return nullptr; } int RadioIDList::add(ConfigObject *obj, int row, bool unique) { if ((nullptr == obj) || (! obj->is())) return -1; int idx = ConfigObjectList::add(obj, row, unique); if (parent() && obj->is() && qobject_cast(parent())->settings()->defaultIdRef()->isNull()) qobject_cast(parent())->settings()->setDefaultId(obj->as()); return idx; } int RadioIDList::addId(const QString &name, uint32_t id) { return add(new DMRRadioID(name, id, this)); } bool RadioIDList::delId(uint32_t id) { return del(find(id)); } ConfigItem * RadioIDList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) return nullptr; if ((! node.IsMap()) || (1 != node.size())) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create radio id: Expected object with one child."; return nullptr; } QString type = QString::fromStdString(node.begin()->first.as()); if ("dmr" == type) { return new DMRRadioID(); } else if ("m17" == type) { return new M17RadioID(); } else if ("dtmf" == type) { return new DTMFRadioID(); } errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create radio id: Unknown type '" << type << "'."; return nullptr; } ================================================ FILE: lib/radioid.hh ================================================ #ifndef RADIOID_HH #define RADIOID_HH #include "configobject.hh" class DMRRadioIDReference; /** Abstract base class for all radio IDs. * * That is, DMR radio IDs as well as M17, DTMF, ZVEI, 5-tone etc PTT-IDs. * * @ingroup conf */ class RadioID: public ConfigObject { Q_OBJECT protected: /** Hidden default constructor. * Use one of the derived classes to instantiate radio IDs. */ explicit RadioID(QObject *parent=nullptr); /** Hidden constructor with name. */ RadioID(const QString &name, QObject *parent=nullptr); }; /** Represents a DMR radio ID within the abstract config. * * This class is used to store the DMR ID(s) of the radio. * * @ingroup conf */ class DMRRadioID : public RadioID { Q_OBJECT Q_CLASSINFO("IdPrefix", "id") /** The number of the radio ID. */ Q_PROPERTY(unsigned number READ number WRITE setNumber) public: /** Default constructor. */ Q_INVOKABLE explicit DMRRadioID(QObject *parent=nullptr); /** Constructor. * @param name Specifies the name of the ID. * @param number Specifies the DMR ID. * @param parent Specifies the parent QObject owning this object. */ DMRRadioID(const QString &name, uint32_t number, QObject *parent = nullptr); ConfigItem *clone() const; /** Returns the DMR ID. */ uint32_t number() const; /** Sets the DMR ID. */ void setNumber(uint32_t number); YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); bool link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Holds the DMR ID. */ uint32_t _number; }; /** A singleton radio ID representing the default DMR radio ID within the abstract config. * @ingroup conf */ class DefaultRadioID: public DMRRadioID { Q_OBJECT protected: /** Constructor. */ explicit DefaultRadioID(QObject *parent=nullptr); public: /** Factory method returning the singleton instance. */ static DefaultRadioID *get(); private: /** The singleton instance. */ static DefaultRadioID *_instance; }; /** Implements the M17 radio ID. * @ingroup conf */ class M17RadioID: public RadioID { Q_OBJECT Q_CLASSINFO("IdPrefix", "id") /** The callsign of the radio ID. */ Q_PROPERTY(QString call READ call WRITE setCall) public: /** Default constructor. */ explicit M17RadioID(QObject *parent=nullptr); /** Constructor. * @param name Specifies the name of the ID. * @param call Specifies the M17 callsign/ID. * @param parent Specifies the parent QObject owning this object. */ M17RadioID(const QString &name, const QString &call, QObject *parent = nullptr); ConfigItem *clone() const; /** Returns the M17 call/ID. */ const QString &call() const; /** Sets the M17 call/ID. */ void setCall(const QString &call); YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); bool parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); bool link(const YAML::Node &node, const ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); protected: /** Holds the M17 call/ID. */ QString _call; }; /** Represents a DTMF radio ID as used for PTT-ID on analog channels. * * This class just holds the name and DTMF number of the ID. * @ingroup conf */ class DTMFRadioID: public RadioID { Q_OBJECT Q_CLASSINFO("IdPrefix", "dtmf") /** The DTMF number of the radio ID. */ Q_PROPERTY(QString number READ number WRITE setNumber) public: /** Default constructor. */ Q_INVOKABLE explicit DTMFRadioID(QObject *parent=nullptr); /** Constructor from name and number. * @param name Specifies the name of the DTMF radio ID. * @param number Specifies the DTMF number of the radio ID. * @param parent Specifies the QObject parent, the object that owns this one. */ explicit DTMFRadioID(const QString &name, const QString &number, QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the DTMF number of the radio ID. */ const QString &number() const; /** Sets the DTMF number of the radio ID. */ void setNumber(const QString &number); protected: /** Holds the DTMF number of the radio ID. */ QString _number; }; /** Represents the list of configured DMR IDs (radio IDs) within the abstract config. * @ingroup conf */ class RadioIDList: public ConfigObjectList { Q_OBJECT public: /** Constructor. */ explicit RadioIDList(QObject *parent=nullptr); void clear(); /** Returns the radio ID at the given index. */ [[deprecated("Use indexing instead.")]] DMRRadioID *getId(int idx) const; /** Searches the DMR ID object associated with the given DMR ID. */ DMRRadioID *find(uint32_t id) const; int add(ConfigObject *obj, int row=-1, bool unique=true); /** Adds the given DMR ID. */ virtual int addId(const QString &name, uint32_t id); /** Deletes and removes the given DMR ID. */ virtual bool delId(uint32_t id); public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // RADIOID_HH ================================================ FILE: lib/radioinfo.cc ================================================ #include "radioinfo.hh" #include "opengd77.hh" #include "openuv380.hh" #include "openrtx.hh" #include "gd73.hh" #include "gd77.hh" #include "rd5r.hh" #include "md390.hh" #include "uv390.hh" #include "md2017.hh" #include "d868uv.hh" #include "d878uv.hh" #include "d878uv2.hh" #include "d578uv.hh" #include "d168uv.hh" #include "dmr6x2uv.hh" #include "dmr6x2uv2.hh" #include "dm1701.hh" #include "dr1801uv.hh" #include "dm32uv.hh" QHash RadioInfo::_radiosByName = QHash{ {"opengd77", RadioInfo::OpenGD77}, {"openuv380", RadioInfo::OpenUV380}, {"openrtx", RadioInfo::OpenRTX}, {"rd5r", RadioInfo::RD5R}, {"gd73", RadioInfo::GD73}, {"gd77", RadioInfo::GD77}, {"md380", RadioInfo::MD380}, {"md390", RadioInfo::MD390}, {"rt8", RadioInfo::RT8}, {"uv380", RadioInfo::UV380}, {"uv390", RadioInfo::UV390}, {"rt3s", RadioInfo::RT3S}, {"md2017", RadioInfo::MD2017}, {"rt82", RadioInfo::RT82}, {"dm1701", RadioInfo::DM1701}, {"rt84", RadioInfo::RT84}, {"d868uv", RadioInfo::D868UV}, {"d868uve", RadioInfo::D868UVE}, {"dmr6x2uv", RadioInfo::DMR6X2UV}, {"dmr6x2uv2", RadioInfo::DMR6X2UV2}, {"d878uv", RadioInfo::D878UV}, {"d878uv2", RadioInfo::D878UVII}, {"d578uv", RadioInfo::D578UV}, {"d578uv2", RadioInfo::D578UVII}, {"d168uv", RadioInfo::D168UV}, {"dr1801uv", RadioInfo::DR1801UV}, {"dm32uv", RadioInfo::DM32UV} }; QHash RadioInfo::_radiosById = QHash{ {RadioInfo::OpenGD77, OpenGD77::defaultRadioInfo()}, {RadioInfo::OpenUV380, OpenUV380::defaultRadioInfo()}, {RadioInfo::OpenRTX, OpenRTX::defaultRadioInfo()}, {RadioInfo::RD5R, RD5R::defaultRadioInfo()}, {RadioInfo::GD73, GD73::defaultRadioInfo()}, {RadioInfo::GD77, GD77::defaultRadioInfo()}, {RadioInfo::MD390, MD390::defaultRadioInfo()}, {RadioInfo::UV390, UV390::defaultRadioInfo()}, {RadioInfo::MD2017, MD2017::defaultRadioInfo()}, {RadioInfo::DM1701, DM1701::defaultRadioInfo()}, {RadioInfo::D868UVE, D868UV::defaultRadioInfo()}, {RadioInfo::D878UV, D878UV::defaultRadioInfo()}, {RadioInfo::D878UVII, D878UV2::defaultRadioInfo()}, {RadioInfo::D578UV, D578UV::defaultRadioInfo()}, {RadioInfo::D168UV, D168UV::defaultRadioInfo()}, {RadioInfo::DMR6X2UV, DMR6X2UV::defaultRadioInfo()}, {RadioInfo::DMR6X2UV2, DMR6X2UV2::defaultRadioInfo()}, {RadioInfo::DR1801UV, DR1801UV::defaultRadioInfo()}, {RadioInfo::DM32UV, DM32UV::defaultRadioInfo()} }; /* ********************************************************************************************* * * Implementation of RadioInfo * ********************************************************************************************* */ RadioInfo::RadioInfo(Radio radio, const QString &name, const QString manufacturer, const QSet &interfaces, const QList &alias) : _radio(radio), _key(name.toLower()), _name(name), _manufacturer(manufacturer), _alias(alias), _interfaces(interfaces) { // pass... } RadioInfo::RadioInfo(Radio radio, const QString &key, const QString &name, const QString manufacturer, const QSet &interfaces, const QList &alias) : _radio(radio), _key(key), _name(name), _manufacturer(manufacturer), _alias(alias), _interfaces(interfaces) { // pass... } RadioInfo::RadioInfo() : _key("") { // pass... } bool RadioInfo::isValid() const { return ! _key.isEmpty(); } const QString & RadioInfo::key() const { return _key; } const QString & RadioInfo::name() const { return _name; } const QString & RadioInfo::manufacturer() const { return _manufacturer; } bool RadioInfo::interfaceMatches(const USBDeviceInfo &other) const { foreach (auto interface, _interfaces) if (interface == other) return true; return false; } bool RadioInfo::hasAlias() const { return 0 != _alias.count(); } const QList & RadioInfo::alias() const { return _alias; } RadioInfo::Radio RadioInfo::id() const { return _radio; } bool RadioInfo::hasRadioKey(const QString &key) { return _radiosByName.contains(key); } RadioInfo RadioInfo::byKey(const QString &key) { if (! hasRadioKey(key)) return RadioInfo(); return byID(_radiosByName[key]); } RadioInfo RadioInfo::byID(Radio radio) { return _radiosById[radio]; } QList RadioInfo::allRadios(bool flat) { QList radios; QHash::const_iterator it = _radiosById.constBegin(); for (; it!=_radiosById.constEnd(); it++) { radios.push_back(*it); if (flat) radios.append(it->_alias); } std::sort(radios.begin(), radios.end(), [](const RadioInfo &a, const RadioInfo &b) { return a.id() RadioInfo::allRadios(const USBDeviceInfo &interface, bool flat) { QList radios; QHash::const_iterator it = _radiosById.constBegin(); for (; it!=_radiosById.constEnd(); it++) { if (! it->interfaceMatches(interface)) continue; radios.push_back(*it); if (flat) radios.append(it->_alias); } std::sort(radios.begin(), radios.end(), [](const RadioInfo &a, const RadioInfo &b) { return a.id() #include #include #include "usbdevice.hh" /** Provides some information about a radio model. * * This class is used to unify radio enumeration and detection. * * @since 0.9.0 */ class RadioInfo { public: /** Known radios. */ enum Radio { // Open source firmware OpenGD77, OpenUV380, OpenRTX, // Radioddity devices RD5R, GD73, GD77, // TyT devices MD390, MD380 = MD390, RT8 = MD390, UV390, UV380 = UV390, RT3S = UV390, MD2017, RT82 = MD2017, // Anytone devices D868UVE, D868UV = D868UVE, DMR6X2UV, DMR6X2UV2, D878UV, D878UVII, D578UV, D578UVII = D578UV, D168UV, // Baofeng/BTECH DM1701, RT84 = DM1701, DR1801UV, DM32UV }; public: /** Use static methods the access radio info or call @c Radio::defaultRadioInfo. */ RadioInfo(Radio radio, const QString &name, const QString manufacturer, const QSet &interfaces, const QList &alias=QList()); /** Use static methods the access radio info or call @c Radio::defaultRadioInfo. */ RadioInfo(Radio radio, const QString &key, const QString &name, const QString manufacturer, const QSet &interfaces, const QList &alias=QList()); /** Empty constructor. */ RadioInfo(); /** Returns @c true if the info is valid. */ bool isValid() const; /** Returns the radio key (used to identify radios in the command line). */ const QString &key() const; /** Returns the radio name. */ const QString &name() const; /** Returns the manufacturer name. */ const QString &manufacturer() const; /** Returns some information about the interface to the radio. */ bool interfaceMatches(const USBDeviceInfo &other) const; /** Returns @c true if the radio has aliases. * That is other radios that are identical. */ bool hasAlias() const; /** Returns the list of alias radios. */ const QList &alias() const; /** Returns the unique device ID (alias radios share ID). */ Radio id() const; public: /** Returns @c true if the given key is known. */ static bool hasRadioKey(const QString &key); /** Returns the radio info by key. */ static RadioInfo byKey(const QString &key); /** Returns the radio info by id. */ static RadioInfo byID(Radio radio); /** Returns the list of all known radios. */ static QList allRadios(bool flat=true); /** Returns a list of all known radios for the specified interface. */ static QList allRadios(const USBDeviceInfo &interface, bool flat=true); protected: /** Holds the radio id. */ Radio _radio; /** Holds the key of the radio. */ QString _key; /** Holds the name of the radio. */ QString _name; /** Holds the name of the manufacturer. */ QString _manufacturer; /** Holds possible identical radios from other manufacturers. */ QList _alias; /** Holds some information about the interface to the radio. */ QSet _interfaces; protected: /** Key->ID map. */ static QHash _radiosByName; /** ID->Info map. */ static QHash _radiosById; }; #endif // RADIOINFO_HH ================================================ FILE: lib/radiointerface.cc ================================================ #include "radiointerface.hh" #include "anytone_interface.hh" #include "opengd77_interface.hh" #include "radioddity_interface.hh" #include "tyt_interface.hh" /* ********************************************************************************************* * * Implementation of RadioInterface * ********************************************************************************************* */ RadioInterface::RadioInterface() { // pass... } RadioInterface::~RadioInterface() { // pass... } bool RadioInterface::write_finish(const ErrorStack &err) { Q_UNUSED(err) return true; } bool RadioInterface::reboot(const ErrorStack &err) { Q_UNUSED(err) return true; } bool RadioInterface::setDateTime(const QDateTime &datetime, const ErrorStack &err) { Q_UNUSED(datetime); Q_UNUSED(err); return true; } ================================================ FILE: lib/radiointerface.hh ================================================ /** @defgroup rif Radio interfaces * Depending on the manufacturer or model, different radios have different computer-radio * interfaces. This module collects all classes that provide these interfaces. */ #ifndef RADIOINFERFACE_HH #define RADIOINFERFACE_HH #include #include "usbdevice.hh" #include "radioinfo.hh" #include "errorstack.hh" /** Abstract radio interface. * A radion interface must provide means to communicate with the device. That is, open a connection * to the device, allow for reading and writing specific memory blocks. * * This class defines the common interface for all radio-interface classes, irrespective of the * actual communication protocol being used by the device. * * @ingroup rif */ class RadioInterface { protected: /** Hidden constructor. */ explicit RadioInterface(); public: /** Destructor. */ virtual ~RadioInterface(); /** Return @c true if a connection to the device has been established. */ virtual bool isOpen() const = 0; /** Closes the connection to the device. */ virtual void close() = 0; /** Returns a device identifier. */ virtual RadioInfo identifier(const ErrorStack &err=ErrorStack()) = 0; /** Starts the write process into the specified bank and at the given address. * @param bank Specifies the memory bank to write to. Usually there is only one bank. Some radios, * however, to have several memory banks to hold the codeplug. For example the Open GD77 has * EEPROM and Flash memory banks with independent addresses. * @param addr Specifies the address to write to. * @param err Passes an error stack to put error messages on. */ virtual bool write_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack())=0; /** Writes a chunk of @c data at the address @c addr. * @param bank Specifies the memory bank to write to. Usually there is only one bank. Some radios, * however, to have several memory banks to hold the codeplug. For example the Open GD77 has * EEPROM and Flash memory banks with independent addresses. * @param addr Specifies the address to write to. * @param data Pointer to the actual data to be written. * @param nbytes Specifies the number of bytes to write. * @param err Passes an error stack to put error messages on. * @returns @c true on success. */ virtual bool write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()) = 0; /** This function ends a series of @c write operations. * This function will be implemented by certain interfaces that need completion of write * operations (e.g., HID). * @param err Passes an error stack to put error messages on. */ virtual bool write_finish(const ErrorStack &err=ErrorStack()) = 0; /** Starts the read process from the specified bank and at the given address. * @param bank Specifies the memory bank to read from. Usually there is only one bank. Some radios, * however, to have several memory banks to hold the codeplug. For example the Open GD77 has * EEPROM and Flash memory banks with independent addresses. * @param addr Specifies the address to read from. * @param err Passes an error stack to put error messages on. */ virtual bool read_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()) = 0; /** Reads a chunk of data from the block-address @c bno (block number). * @param bank Specifies the memory bank to read from. Usually there is only one bank. Some radios, * however, to have several memory banks to hold the codeplug. For example the Open GD77 has * EEPROM and Flash memory banks with independent addresses. * @param addr Specifies the address to read from. * @param data Pointer where to store the read data. * @param nbytes Specifies the number of bytes to read. * @param err Passes an error stack to put error messages on. * @returns @c true on success. */ virtual bool read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()) = 0; /** This function ends a series of @c read operations. * This function will be re-implemented by certain interfaces that need completion of read * operations (e.g., HID). * @param err Passes an error stack to put error messages on. */ virtual bool read_finish(const ErrorStack &err=ErrorStack()) = 0; /** Some radios need to be rebooted after being read or programmed. This function * will be re-implemented by some interfaces (e.g., DFUDevice) to reboot the radio. By default * this function does nothing. * @param err Passes an error stack to put error messages on. */ virtual bool reboot(const ErrorStack &err=ErrorStack()); /** Some radios allow to set date and time of the internal clock during codeplug upload. * This function might be re-implemented by interfaces for those radios * (e.g., the OpenGD77 one). * @param datetime [in] Specifies the timestamp to set. * @param err Passes an error stack to put error messages on. */ virtual bool setDateTime(const QDateTime &datetime, const ErrorStack &err=ErrorStack()); }; #endif // RADIOINFERFACE_HH ================================================ FILE: lib/radiolimits.cc ================================================ #include "radiolimits.hh" #include "configobject.hh" #include "config.hh" #include #include // Utility function to check string content for ASCII encoding inline bool qstring_is_ascii(const QString &text) { foreach (QChar c, text) { if ((c.unicode() < 0x20) || (c.unicode() > 0x7e)) return false; } return true; } // Utility function to check string content for DTMF encoding inline bool qstring_is_dtmf(const QString &text) { return QRegularExpression("^[0-9A-Da-d*#]*$").match(text).hasMatch(); } /* ********************************************************************************************* * * Implementation of RadioLimitIssue * ********************************************************************************************* */ RadioLimitIssue::RadioLimitIssue(Severity severity, const QStringList &stack) : QTextStream(), _severity(severity), _stack(stack), _message() { setString(&_message); } RadioLimitIssue::RadioLimitIssue(const RadioLimitIssue &other) : QTextStream(), _severity(other._severity), _stack(other._stack), _message(other._message) { setString(&_message); } RadioLimitIssue & RadioLimitIssue::operator =(const RadioLimitIssue &other) { QTextStream::flush(); _severity = other._severity; _stack = other._stack; _message = other._message; return *this; } RadioLimitIssue & RadioLimitIssue::operator =(const QString &message) { _message = message; return *this; } RadioLimitIssue::Severity RadioLimitIssue::severity() const { return _severity; } const QString & RadioLimitIssue::message() const { return _message; } const QStringList & RadioLimitIssue::stack() const { return _stack; } QString RadioLimitIssue::format() const { QString res; QTextStream stream(&res); switch (_severity) { case Silent: stream << "Silent: "; break; case Hint: stream << "Hint: "; break; case Warning: stream << "Warn: "; break; case Critical: stream << "Crit: "; break; } stream << _stack.join(", ") << ": " << _message; stream.flush(); return res; } /* ********************************************************************************************* * * Implementation of RadioLimitContext * ********************************************************************************************* */ RadioLimitContext::RadioLimitContext(bool ignoreFrequencyLimits) : _stack(), _ignoreFrequencyLimits(ignoreFrequencyLimits), _maxSeverity(RadioLimitIssue::Silent) { // pass... } RadioLimitIssue & RadioLimitContext::newMessage(RadioLimitIssue::Severity severity) { _messages.push_back(RadioLimitIssue(severity, _stack)); if (severity > _maxSeverity) _maxSeverity = severity; return _messages.back(); } int RadioLimitContext::count() const { return _messages.count(); } const RadioLimitIssue & RadioLimitContext::message(int n) const { return _messages.at(n); } void RadioLimitContext::push(const QString &element) { _stack.append(element); } void RadioLimitContext::pop() { _stack.pop_back(); } bool RadioLimitContext::ignoreFrequencyLimits() const { return _ignoreFrequencyLimits; } void RadioLimitContext::enableIgnoreFrequencyLimits(bool enable) { _ignoreFrequencyLimits = enable; } RadioLimitIssue::Severity RadioLimitContext::maxSeverity() const { return _maxSeverity; } /* ********************************************************************************************* * * Implementation of RadioLimitElement * ********************************************************************************************* */ RadioLimitElement::RadioLimitElement(QObject *parent) : QObject(parent) { // pass... } RadioLimitElement::~RadioLimitElement() { // pass ... } /* ********************************************************************************************* * * Implementation of RadioLimitIgnored * ********************************************************************************************* */ RadioLimitIgnored::RadioLimitIgnored(RadioLimitIssue::Severity notify, QObject *parent) : RadioLimitObject(parent), _notification(notify) { // pass... } bool RadioLimitIgnored::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { auto obj = prop.read(item).value(); if (nullptr != obj) return verifyObject(obj, context); return true; } bool RadioLimitIgnored::verifyObject(const ConfigObject *item, RadioLimitContext &context) const { if (nullptr == item) return true; auto &msg = context.newMessage(_notification); msg = tr("Ignore %1 '%2'. Not applicable/supported by this radio.") .arg(item->metaObject()->className()).arg(item->name()); return true; } /* ********************************************************************************************* * * Implementation of RadioLimitValue * ********************************************************************************************* */ RadioLimitValue::RadioLimitValue(RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitElement(parent), _severity(severity) { // pass... } /* ********************************************************************************************* * * Implementation of RadioLimitString * ********************************************************************************************* */ RadioLimitString::RadioLimitString(int minLen, int maxLen, Encoding enc, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _minLen(minLen), _maxLen(maxLen), _encoding(enc) { // pass... } bool RadioLimitString::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (QMetaType::QString != prop.typeId()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected string."; return false; } QString value = prop.read(item).toString(); bool success = true; if ((0<_maxLen) && (value.size() > int(_maxLen))) { auto &msg = context.newMessage(_severity); msg << "String length of " << prop.name() << " ('" << value << "', " << value.size() << ") exceeds maximum length " << _maxLen << "."; success &= (_severity < RadioLimitIssue::Severity::Critical); } if ((0<_minLen) && (value.size() < int(_minLen))) { auto &msg = context.newMessage(_severity); msg << "String length of " << prop.name() << " ('" << value << "', " << value.size() << ") is shorter than minimum size " << _minLen << "."; success &= (_severity < RadioLimitIssue::Severity::Critical); } if ((ASCII == _encoding) && (! qstring_is_ascii(value))) { auto &msg = context.newMessage(_severity); msg << "Cannot encode string '" << value << "' in ASCII."; success &= (_severity < RadioLimitIssue::Severity::Critical); } else if ((DTMF == _encoding) && (! qstring_is_dtmf(value))) { auto &msg = context.newMessage(_severity); msg << "Cannot encode string '" << value << "' in DTMF."; success &= (_severity < RadioLimitIssue::Severity::Critical); } return success; } /* ********************************************************************************************* * * Implementation of RadioLimitStringRegEx * ********************************************************************************************* */ RadioLimitStringRegEx::RadioLimitStringRegEx(const QString &pattern, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _pattern(pattern) { // pass... } bool RadioLimitStringRegEx::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (QMetaType::QString != prop.typeId()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected string."; return false; } bool success = true; QString value = prop.read(item).toString(); auto match = _pattern.match(value); if (! match.hasMatch()) { auto &msg = context.newMessage(_severity); msg << "Value '" << value << "' of property " << prop.name() << " does not match pattern '" << _pattern.pattern() << "'."; success &= (_severity < RadioLimitIssue::Severity::Critical); } return success; } /* ********************************************************************************************* * * Implementation of RadioLimitPin * ********************************************************************************************* */ RadioLimitPin::RadioLimitPin(int length, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitStringRegEx("", severity, parent) { if (length <= 0) _pattern = QRegularExpression("^[0-9]+"); else _pattern = QRegularExpression(QString("[0-9]{0,%1}").arg(length)); } /* ********************************************************************************************* * * Implementation of RadioLimitStringIgnored * ********************************************************************************************* */ RadioLimitStringIgnored::RadioLimitStringIgnored(RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent) { // pass... } bool RadioLimitStringIgnored::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (QMetaType::QString != prop.typeId()) { auto &msg = context.newMessage(RadioLimitIssue::Warning); msg = tr("Expected value of '%1' to be string.").arg(prop.name()); return true; } bool success = true; QVariant value = prop.read(item); if (! value.toString().isEmpty()) { auto &msg = context.newMessage(_severity); msg = tr("Value of '%1' is ignored. Not applicable/supported by the radio.").arg(prop.name()); success &= (_severity < RadioLimitIssue::Severity::Critical); } return success; } /* ********************************************************************************************* * * Implementation of RadioLimitBool * ********************************************************************************************* */ RadioLimitBool::RadioLimitBool(QObject *parent) : RadioLimitValue(RadioLimitIssue::Severity::Hint, parent) { // pass... } bool RadioLimitBool::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { Q_UNUSED(item) if (QMetaType::Bool != prop.typeId()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected bool."; return false; } return true; } /* ********************************************************************************************* * * Implementation of RadioLimitIgnoredBool * ********************************************************************************************* */ RadioLimitIgnoredBool::RadioLimitIgnoredBool(RadioLimitIssue::Severity notify, QObject *parent) : RadioLimitBool(parent), _severity(notify) { // pass... } bool RadioLimitIgnoredBool::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (QMetaType::Bool != prop.typeId()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected bool."; return false; } bool success = true; bool value = prop.read(item).toBool(); if (value) { auto &msg = context.newMessage(_severity); msg << "Setting " << prop.name() << " is ignored."; success &= (_severity < RadioLimitIssue::Severity::Critical); } return success; } /* ********************************************************************************************* * * Implementation of RadioLimitUInt * ********************************************************************************************* */ RadioLimitUInt::RadioLimitUInt(qint64 minValue, qint64 maxValue, qint64 defValue, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _minValue(minValue), _maxValue(maxValue), _defValue(defValue) { // pass... } bool RadioLimitUInt::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (QMetaType::UInt != prop.typeId()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected uint."; return false; } bool success = true; unsigned value = prop.read(item).toUInt(); if ((0<_maxValue) && (value > qint64(_maxValue)) && ((0>_defValue) || (value!=_defValue))) { auto &msg = context.newMessage(_severity); msg << "Value " << value << " of " << prop.name() << " exceeds maximum " << _maxValue << "."; success &= (_severity < RadioLimitIssue::Severity::Critical); } if ((0<_minValue) && (value < qint64(_minValue)) && ((0>_defValue) || (value!=_defValue))) { auto &msg = context.newMessage(_severity); msg << "Value " << value << " of " << prop.name() << " is smaller than minimum " << _minValue << "."; success &= (_severity < RadioLimitIssue::Severity::Critical); } return success; } /* ********************************************************************************************* * * Implementation of RadioLimitDMRId * ********************************************************************************************* */ RadioLimitDMRId::RadioLimitDMRId(RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitUInt(1, 16777215, -1, severity, parent) { // pass... } /* ********************************************************************************************* * * Implementation of RadioLimitEnum * ********************************************************************************************* */ RadioLimitEnum::RadioLimitEnum(const std::initializer_list &values, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _values(values) { // pass... } bool RadioLimitEnum::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (! prop.isEnumType()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected enum type."; return false; } bool success = true; unsigned value = prop.read(item).toUInt(); if (! _values.contains(value)) { QMetaEnum e = prop.enumerator(); QStringList possible; foreach (unsigned val, _values) possible.append(e.valueToKey(val)); auto &msg = context.newMessage(RadioLimitIssue::Warning); msg << "The enum value '" << e.valueToKey(value) << "' cannot be encoded. " << "Valid values are " << possible.join(", ") << ". " << "Another value might be chosen automatically."; success &= (_severity < RadioLimitIssue::Severity::Critical); } return success; } /* ********************************************************************************************* * * Implementation of RadioLimitFrequencies * ********************************************************************************************* */ RadioLimitFrequencies::RadioLimitFrequencies(RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _frequencyRanges() { // pass... } RadioLimitFrequencies::RadioLimitFrequencies(const RangeList &ranges, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _frequencyRanges() { for (auto range=ranges.begin(); range!=ranges.end(); range++) { _frequencyRanges.append({range->first, range->second}); } } bool RadioLimitFrequencies::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (qMetaTypeId() != prop.userType()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected frequency."; return false; } if (0 == _frequencyRanges.size()) return true; Frequency value = prop.read(item).value(); foreach (const FrequencyRange &range, _frequencyRanges) { if (range.contains(value)) return true; } if (context.ignoreFrequencyLimits()) return true; auto &msg = context.newMessage(_severity); msg << "Frequency " << value.inMHz() << "MHz is outside of allowed frequency ranges."; return _severity < RadioLimitIssue::Severity::Critical; } /* ********************************************************************************************* * * Implementation of RadioLimitTransmitFrequencies * ********************************************************************************************* */ RadioLimitTransmitFrequencies::RadioLimitTransmitFrequencies(RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitFrequencies(severity, parent) { // pass... } RadioLimitTransmitFrequencies::RadioLimitTransmitFrequencies(const RangeList &ranges, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitFrequencies(ranges, severity, parent) { // pass... } bool RadioLimitTransmitFrequencies::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (qMetaTypeId() != prop.userType()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected frequency in MHz."; return false; } if (item->is() && (! item->as()->rxOnly())) { return RadioLimitFrequencies::verify(item, prop, context); } return true; } /* ********************************************************************************************* * * Implementation of RadioLimitInterval * ********************************************************************************************* */ RadioLimitInterval::RadioLimitInterval(RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _durations{Interval::null(), Interval::infinity()} { // pass... } RadioLimitInterval::RadioLimitInterval(const Range &range, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _durations{range} { // pass... } bool RadioLimitInterval::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (qMetaTypeId() != prop.userType()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected interval."; return false; } auto value = prop.read(item).value(); if (_durations.contains(value)) return true; auto &msg = context.newMessage(_severity); msg << "Interval " << value.format() << " is outside of allowed range."; return _severity < RadioLimitIssue::Severity::Critical; } /* ********************************************************************************************* * * Implementation of RadioLimitLevel * ********************************************************************************************* */ RadioLimitLevel::RadioLimitLevel(const Range &range, bool allowInvalid, RadioLimitIssue::Severity severity, QObject *parent) : RadioLimitValue(severity, parent), _range(range), _allowInvalid(allowInvalid) { // pass... } bool RadioLimitLevel::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { Q_UNUSED(item); if (qMetaTypeId() != prop.userType()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Expected interval."; return false; } Level value = prop.read(item).value(); if (value.isInvalid() && (! _allowInvalid)) { auto &msg = context.newMessage(_severity); msg << "Invalid level not allowed for property " << prop.name() << "."; return _severity <= RadioLimitIssue::Warning; } if (!_range.contains(value.value())) { auto &msg = context.newMessage(_severity); msg << "Value of property " << prop.name() << " " << value.value() << " exceeds range [" << _range.lower << ", " << _range.upper << "]."; return _severity <= RadioLimitIssue::Warning; } return true; } /* ********************************************************************************************* * * Implementation of RadioLimitItem * ********************************************************************************************* */ RadioLimitItem::RadioLimitItem(QObject *parent) : RadioLimitElement(parent), _elements() { // pass... } RadioLimitItem::RadioLimitItem(const PropList &list, QObject *parent) : RadioLimitElement(parent), _elements(list) { for (QHash::iterator item=_elements.begin(); item != _elements.end(); item++) { item.value()->setParent(this); } } bool RadioLimitItem::add(const QString &prop, RadioLimitElement *structure) { if (_elements.contains(prop) || (nullptr == structure)) return false; _elements.insert(prop, structure); structure->setParent(this); return true; } bool RadioLimitItem::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (! prop.isReadable()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Not readable."; return false; } if (! propIsInstance(prop)) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Not an instance of ConfigItem."; return false; } if (prop.read(item).isNull()) return true; context.push(QString("In property '%1'").arg(prop.name())); bool success = verifyItem(prop.read(item).value(), context); context.pop(); return success; } bool RadioLimitItem::verifyItem(const ConfigItem *item, RadioLimitContext &context) const { const QMetaObject *meta = item->metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { // This property QMetaProperty prop = meta->property(p); // Should never happen if (! prop.isValid()) continue; // Verify property if (_elements.contains(prop.name())) { if (! _elements[prop.name()]->verify(item, prop, context)) return false; } } return true; } /* ********************************************************************************************* * * Implementation of RadioLimitObject * ********************************************************************************************* */ RadioLimitObject::RadioLimitObject(QObject *parent) : RadioLimitItem(parent) { // pass... } RadioLimitObject::RadioLimitObject(const PropList &list, QObject *parent) : RadioLimitItem(list, parent) { // pass... } bool RadioLimitObject::verifyObject(const ConfigObject *item, RadioLimitContext &context) const { context.push(QString("In object '%1'").arg(item->name())); bool success = verifyItem(item, context); context.pop(); return success; } /* ********************************************************************************************* * * Implementation of RadioLimitObjects * ********************************************************************************************* */ RadioLimitObjects::RadioLimitObjects(const TypeList &list, QObject *parent) : RadioLimitObject(parent), _types() { for (auto type=list.begin(); type!=list.end(); type++) { _types[type->first.className()] = type->second; type->second->setParent(this); } } bool RadioLimitObjects::verifyItem(const ConfigItem *item, RadioLimitContext &context) const { if (! _types.contains(item->metaObject()->className())) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check item of type " << item->metaObject()->className() << ". Unexpected type. Expected one of " << QStringList(_types.keys()).join(", ") << "."; return false; } return _types[item->metaObject()->className()]->verifyItem(item, context); } /* ********************************************************************************************* * * Implementation of RadioLimitObjRef * ********************************************************************************************* */ RadioLimitObjRef::RadioLimitObjRef(const QMetaObject &type, bool allowNull, QObject *parent) : RadioLimitElement(parent), _allowNull(allowNull), _types() { _types.insert(type.className()); } RadioLimitObjRef::RadioLimitObjRef(const MetaObjectList &types, bool allowNull, QObject *parent) : RadioLimitElement(parent), _allowNull(allowNull), _types() { for (auto type=types.begin(); type!=types.end(); type++) _types.insert(type->className()); } bool RadioLimitObjRef::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { ConfigObjectReference *ref = prop.read(item).value(); if (nullptr == ref) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check type of property '" << prop.name() << "'. Expected ConfigObjectReference."; return false; } if (ref->isNull()) { if (_allowNull) return true; auto &msg = context.newMessage(RadioLimitIssue::Warning); msg << "Property '" << prop.name() << "' must refer to an instances of " << QStringList(_types.begin(), _types.end()).join(", ") << "."; return true; } if (! validType(ref->as()->metaObject())) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Property '" << prop.name() << "' must refer to an instances of " << QStringList(_types.begin(), _types.end()).join(", ") << "."; return false; } return true; } bool RadioLimitObjRef::validType(const QMetaObject *type) const { if (_types.contains(type->className())) return true; if (type->superClass()) return validType(type->superClass()); return false; } /* ********************************************************************************************* * * Implementation of RadioLimitObjRefIgnored * ********************************************************************************************* */ RadioLimitObjRefIgnored::RadioLimitObjRefIgnored( ConfigObject *defObj, RadioLimitIssue::Severity notify, QObject *parent) : RadioLimitObjRef(ConfigObject::staticMetaObject, true, parent), _severity(notify), _default(defObj) { // pass... } bool RadioLimitObjRefIgnored::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { // Get referenced object ConfigObjectReference *ref = prop.read(item).value(); // If reference is set and not default reference if ((! ref->isNull()) && (_default != ref->as())) { auto &msg = context.newMessage(_severity); msg << "The reference '" << prop.name() << "' is ignored. Not applicable/supported by this radio."; } return true; } /* ********************************************************************************************* * * Implementation of RadioLimitList * ********************************************************************************************* */ RadioLimitList::RadioLimitList(const QMetaObject &type, int minSize, int maxSize, RadioLimitObject *element, QObject *parent) : RadioLimitElement(parent), _elements(), _minCount(), _maxCount() { _elements.insert(type.className(), element); _minCount.insert(type.className(), minSize); _maxCount.insert(type.className(), maxSize); element->setParent(this); } RadioLimitList::RadioLimitList(const std::initializer_list &elements, QObject *parent) : RadioLimitElement(parent), _elements(), _minCount(), _maxCount() { for (auto el=elements.begin(); el!=elements.end(); el++) { QString className = el->type.className(); _elements.insert(className, el->structure); el->structure->setParent(this); _minCount.insert(className, el->minCount); _maxCount.insert(className, el->maxCount); } } bool RadioLimitList::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (! prop.isReadable()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Not readable."; return false; } if (nullptr == prop.read(item).value()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Not an instance of ConfigObjectList."; return false; } const ConfigObjectList *plist = prop.read(item).value(); QHash counts; foreach (QString type, _elements.keys()) counts.insert(type,0); context.push(QString("In list '%1'").arg(prop.name())); // Check type and structure for (int i=0; icount(); i++) { // Check type ConfigObject *obj = plist->get(i); QString className = findClassName(*(obj->metaObject())); if (className.isEmpty()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Unexpected element type '" << obj->metaObject()->className() << "'. Expected one of " << _elements.keys().join(", ") << "."; context.pop(); return false; } counts[className]++; context.push(QString("In element %1 ('%2')").arg(i).arg(obj->name())); if (! _elements[className]->verifyObject(obj, context)) { context.pop(); context.pop(); return false; } context.pop(); } // Check counts foreach (QString className, _elements.keys()) { if ((0 <= _minCount[className]) && (counts[className]<_minCount[className])) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "The number of elements of type '" << className << "' " << counts[className] << " is less than the required count " << _minCount[className] << "."; } if ((0 <= _maxCount[className]) && (counts[className]>_maxCount[className])) { auto &msg = context.newMessage(RadioLimitIssue::Warning); msg << "The number of elements of type '" << className << "' " << counts[className] << " is greater than the maximum count " << _maxCount[className] << "."; } } context.pop(); return true; } QString RadioLimitList::findClassName(const QMetaObject &type) const { if (_elements.contains(type.className())) return type.className(); if (const QMetaObject *super = type.superClass()) return findClassName(*super); return ""; } /* ********************************************************************************************* * * Implementation of RadioLimitRefList * ********************************************************************************************* */ RadioLimitRefList::RadioLimitRefList(int minSize, int maxSize, const QMetaObject &type, QObject *parent) : RadioLimitElement(parent), _minSize(minSize), _maxSize(maxSize), _types() { _types.insert(type.className()); } bool RadioLimitRefList::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (! prop.isReadable()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Not readable."; return false; } if (nullptr == prop.read(item).value()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Not an instance of ConfigObjectRefList."; return false; } const ConfigObjectRefList *plist = prop.read(item).value(); if ((0 <= _minSize) && (_minSize > plist->count())) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "List '" << prop.name() << "' requires at least " << _minSize << " elements, " << plist->count() << " elements found."; return false; } if ((0 <= _maxSize) && (_maxSize < plist->count())) { auto &msg = context.newMessage(RadioLimitIssue::Warning); msg << "List '" << prop.name() << "' takes at most " << _maxSize << " elements, " << plist->count() << " elements found."; return false; } for (int i=0; icount(); i++) { if (! validType(plist->get(i)->metaObject())) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Reference to " << plist->get(i)->metaObject()->className() << " is not allowed here. " << "Must be one of " << QStringList(_types.begin(), _types.end()).join(", ") << "."; return false; } } return true; } bool RadioLimitRefList::validType(const QMetaObject *type) const { if (_types.contains(type->className())) return true; if (type->superClass()) return validType(type->superClass()); return false; } /* ********************************************************************************************* * * Implementation of RadioLimitPrivateCallRefList * ********************************************************************************************* */ RadioLimitGroupCallRefList::RadioLimitGroupCallRefList(int minSize, int maxSize, QObject *parent) : RadioLimitElement(parent), _minSize(minSize), _maxSize(maxSize) { // pass... } bool RadioLimitGroupCallRefList::verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const { if (! prop.isReadable()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Not readable."; return false; } if (nullptr == prop.read(item).value()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Cannot check property " << prop.name() << ": Not an instance of ConfigObjectRefList."; return false; } const ConfigObjectRefList *plist = prop.read(item).value(); if ((0 <= _minSize) && (_minSize > plist->count())) { auto &msg = context.newMessage(RadioLimitIssue::Warning); msg << "List '" << prop.name() << "' requires at least " << _minSize << " elements, " << plist->count() << " elements found."; return false; } if ((0 <= _maxSize) && (_maxSize < plist->count())) { auto &msg = context.newMessage(RadioLimitIssue::Warning); msg << "List '" << prop.name() << "' takes at most " << _maxSize << " elements, " << plist->count() << " elements found."; return false; } for (int i=0; icount(); i++) { if (! plist->get(i)->is()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Reference to " << plist->get(i)->metaObject()->className() << " is not allowed here. " << "Must be DigtialContact."; return false; } if (DMRContact::GroupCall != plist->get(i)->as()->type()) { auto &msg = context.newMessage(RadioLimitIssue::Critical); msg << "Expected reference to a group call digital contact."; return false; } } return true; } /* ********************************************************************************************* * * Implementation of RadioLimitSingleZone * ********************************************************************************************* */ RadioLimitSingleZone::RadioLimitSingleZone(qint64 maxSize, const PropList &list, QObject *parent) : RadioLimitObject(list, parent) { // A and B may hold up to maxSize references to channels _elements["A"] = new RadioLimitRefList(1, maxSize, Channel::staticMetaObject, this); _elements["B"] = new RadioLimitRefList(0, maxSize, Channel::staticMetaObject, this); } bool RadioLimitSingleZone::verifyItem(const ConfigItem *item, RadioLimitContext &context) const { if (! RadioLimitObject::verifyItem(item, context)) return false; const Zone *zone = item->as(); if (zone && zone->B()->count()) { auto &msg = context.newMessage(); msg << "This radio does not support dual channel zones. The zone '" << zone->name() << "' will be split into two."; } return true; } /* ********************************************************************************************* * * Implementation of RadioLimits * ********************************************************************************************* */ RadioLimits::RadioLimits(bool betaWarning, QObject *parent) : RadioLimitItem(parent), _betaWarning(betaWarning), _hasCallSignDB(false), _callSignDBImplemented(false), _numCallSignDBEntries(0), _hasSatelliteConfig(false), _satelliteConfigImplemented(false), _numSatellites(0) { // pass... } RadioLimits::RadioLimits(const std::initializer_list > &list, QObject *parent) : RadioLimitItem(list, parent), _betaWarning(false), _hasCallSignDB(false), _callSignDBImplemented(false), _numCallSignDBEntries(0), _hasSatelliteConfig(false), _satelliteConfigImplemented(false), _numSatellites(0) { // pass... } bool RadioLimits::hasCallSignDB() const { return _hasCallSignDB; } bool RadioLimits::callSignDBImplemented() const { return _callSignDBImplemented; } unsigned RadioLimits::numCallSignDBEntries() const { return _numCallSignDBEntries; } bool RadioLimits::hasSatelliteConfig() const { return _hasSatelliteConfig; } bool RadioLimits::satelliteConfigImplemented() const { return _satelliteConfigImplemented; } unsigned RadioLimits::numSatellites() const { return _numSatellites; } bool RadioLimits::verifyConfig(const Config *config, RadioLimitContext &context) const { if (_betaWarning) { auto &msg = context.newMessage(RadioLimitIssue::Warning); msg = tr("The support for this radio is still under development. Some features may sill be " "missing or are not well tested."); } return verifyItem(config, context); } ================================================ FILE: lib/radiolimits.hh ================================================ /** @defgroup limits Radio Limits * This module collects all classes used to define the limits for each supported radio. That is, * a tree of objects that hold the limitations like string length, number of elements in a list etc. * for the various settings of a radio including their extensions. * * This system will replace the static @c Radio::Features struct. The associated limits for each * radio can be obtained using the @c Radio::limits method. * * Many classes in this module provide an initializer list constructor. This allows for an easy * construction of radio limits programmatically like * @code * new RadioLimitItem { // < Describes an ConfigItem * { "radioIDs", // < with an 'radioIDs' property * new RadioLimitList( // < that is a list, * RadioId::staticMetaObject, // < holding instances of RadioId, * 1, 10, // of at least one but max 10 elements * new RadioLimitObject { // < of objects, with * { "name", // < a name * new RadioLimitString(1, 16) }, // being a string between 1 and 10 chars * { "id", // < and an ID * new RadioLimitUInt(0, 16777216) } // being an unsigned integer between 0 and 16777216 * } * ) * } * }; * @endcode * * @ingroup conf */ #ifndef RADIOLIMITS_HH #define RADIOLIMITS_HH #include #include #include #include #include #include "frequency.hh" #include "ranges.hh" // Forward declaration class Config; class ConfigItem; class ConfigObject; class RadioLimits; /** Represents a single issue found during verification. * @ingroup limits */ class RadioLimitIssue: public QTextStream { public: /** Defines the possible severity levels. */ enum Severity { Silent, ///< The user will not be notified. Hint, ///< Just a hint, a working codeplug will be assembled. Warning, ///< The codeplug gets changed but a working codeplug might be assembled. Critical ///< Assembly of the codeplug will fail or a non-functional codeplug might be created. }; public: /** Constructs an empty message for the specified severity at the specified point of the * verification. */ RadioLimitIssue(Severity severity, const QStringList &stack); /** Copy constructor. */ RadioLimitIssue(const RadioLimitIssue &other); /** Copy assignment. */ RadioLimitIssue &operator =(const RadioLimitIssue &other); /** Set message. */ RadioLimitIssue &operator =(const QString &message); /** Returns the severity of the issue. */ Severity severity() const; /** Returns the text message. */ const QString &message() const; /** Returns the traceback of the issue. */ const QStringList &stack() const; /** Formats the message. */ QString format() const; protected: /** Holds the severity of the issue. */ Severity _severity; /** Holds the item-stack (where the issue occurred). */ QStringList _stack; /** Holds the text message. */ QString _message; }; /** Collects the issues found during verification. * This class also tracks where the issues arise. * * @ingroup limits */ class RadioLimitContext { public: /** Empty constructor. */ explicit RadioLimitContext(bool ignoreFrequencyLimits=false); /** Constructs a new message and puts it into the list of issues. */ RadioLimitIssue &newMessage(RadioLimitIssue::Severity severity = RadioLimitIssue::Hint); /** Returns the number of issues. */ int count() const; /** Returns the n-th issue. */ const RadioLimitIssue &message(int n) const; /** Push a property name/element index onto the stack. * This method is used to track the origin of an issue. */ void push(const QString &element); /** Pops the top-most property name/element index from the stack. */ void pop(); /** If @c true, frequency limit voilations are warnings. */ bool ignoreFrequencyLimits() const; /** Enables/disables that frequency range voilations are handled as warnings. */ void enableIgnoreFrequencyLimits(bool enable=true); /** Returns the highest severity of the messages. */ RadioLimitIssue::Severity maxSeverity() const; protected: /** The current item stack. */ QStringList _stack; /** The list of issues found. */ QList _messages; /** If @c true, any frequency range voilation is a warning. */ bool _ignoreFrequencyLimits; /** Holds the highest severity of all messages. */ RadioLimitIssue::Severity _maxSeverity; }; /** Abstract base class for all radio limits. * * @ingroup limits */ class RadioLimitElement: public QObject { Q_OBJECT public: /** Initializer lists of ConfigItem properties. */ typedef std::initializer_list< std::pair > PropList; protected: /** Hidden constructor. */ explicit RadioLimitElement(QObject *parent=nullptr); public: /** Verifies the given property of the specified item. * This method gets implemented by the specialized classes to implement the actual verification. */ virtual bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const = 0; public: /** Destructor. */ virtual ~RadioLimitElement(); }; /** Base class to verify values. * * That is, the verification of strings, integers, floats, etc. * @ingroup limits */ class RadioLimitValue: public RadioLimitElement { Q_OBJECT protected: /** Hidden constructor. */ explicit RadioLimitValue(RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Hint, QObject *parent=nullptr); protected: /** The severity of the issue, if the test fails. */ RadioLimitIssue::Severity _severity; }; /** Checks a string valued property. * * Instances of this class can be used to verify string values. That is, checking the length of the * string and its encoding. * * @ingroup limits */ class RadioLimitString: public RadioLimitValue { Q_OBJECT public: /** Possible encoding of strings. */ enum Encoding { DTMF, ///< Just DTMF symbols are allowed (0-9, A-D, *, #). ASCII, ///< Just ASCII is allowed. Unicode ///< Any Unicode character is allowed. }; public: /** Constructor. * @param minLen Specifies the minimum length of the string. If -1, check is disabled. * @param maxLen Specifies the maximum length of the string. If -1, check is disabled. * @param enc Specifies the allowed string encoding. * @param parent Specifies the QObject parent object. */ RadioLimitString(int minLen, int maxLen, Encoding enc, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Hint, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Holds the minimum length of the string. If -1, the check is disabled. */ int _minLen; /** Holds the maximum length of the string. If -1, the check is disabled. */ int _maxLen; /** Holds the allowed character encoding. */ Encoding _encoding; }; /** Verifies that a string matches a regular expression. * @ingroup limits */ class RadioLimitStringRegEx: public RadioLimitValue { Q_OBJECT public: /** Constructor. * @param pattern Specifies the regular expression pattern, the string must match. * @param severity Specifies the severity of the issue. * @param parent Specifies the QObject parent. */ RadioLimitStringRegEx(const QString &pattern, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Holds the regular expression pattern. */ QRegularExpression _pattern; }; /** Verifies string values containing only numbers. * @ingroup limits */ class RadioLimitPin: public RadioLimitStringRegEx { Q_OBJECT public: /** Constructor. * @param length Specifies the maximum length of the pin. If -1, length is not limited. * @param severity Specifies the severity of the issue. * @param parent Specifies the QObject parent. */ RadioLimitPin(int length = -1, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); }; /** Notifies the user that a string gets ignored. * This is usually the case, when named elements are referenced within the codeplug by index. * @ingroup limits */ class RadioLimitStringIgnored: public RadioLimitValue { Q_OBJECT public: /** Constructor. */ RadioLimitStringIgnored(RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Hint, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; }; /** Checks if a property is a boolean value. * @ingroup limits */ class RadioLimitBool: public RadioLimitValue { Q_OBJECT public: /** Constructor. */ explicit RadioLimitBool(QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; }; /** Specifies an boolean value as ignored. * If the boolean value is @c true, a message is generated indicating that this property is ignored. * If the value is @c false, nothing happens. */ class RadioLimitIgnoredBool: public RadioLimitBool { Q_OBJECT public: /** Constructor. * @param notify Specifies the severity of the generated message. * @param parent Specifies the QObject parent. */ explicit RadioLimitIgnoredBool(RadioLimitIssue::Severity notify=RadioLimitIssue::Hint, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** The severity of the issue generated. */ RadioLimitIssue::Severity _severity; }; /** Represents a limit for an unsigned integer value. * @ingroup limits */ class RadioLimitUInt: public RadioLimitValue { Q_OBJECT public: /** Constructor. * @param minValue Specifies the minimum value. If -1, no check is performed. * @param maxValue Specifies the maximum value. If -1, no check is performed. * @param defValue Specifies the default value. If -1, no default value is set. * @param parent Specifies the QObject parent. */ RadioLimitUInt(qint64 minValue=-1, qint64 maxValue=-1, qint64 defValue=-1, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Hint, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Holds the minimum value. If -1, the check is disabled. */ qint64 _minValue; /** Holds the maximum value. If -1, the check is disabled. */ qint64 _maxValue; /** Holds the default value. If -1, no default value is set. */ qint64 _defValue; }; /** Represents a DMR ID. * That is an uint between 1 and 16777215 without any default value. * @ingroup limits */ class RadioLimitDMRId: public RadioLimitUInt { Q_OBJECT public: /** Constructor. * @param parent Specifies the QObject parent. */ explicit RadioLimitDMRId(RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Hint, QObject *parent=nullptr); }; /** Represents a limit for a set of enum values. * @ingroup limits */ class RadioLimitEnum: public RadioLimitValue { Q_OBJECT public: /** Constructor from initializer list of possible enum values. */ RadioLimitEnum(const std::initializer_list &values, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Hint, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Holds the set of valid values. */ QSet _values; }; /** Represents a limit on frequencies in MHz. * @ingroup limits */ class RadioLimitFrequencies: public RadioLimitValue { Q_OBJECT public: /** Typedef for the initializer list. */ typedef std::initializer_list> RangeList; public: /** Empty constructor. */ explicit RadioLimitFrequencies(RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); /** Constructor from initializer list. */ RadioLimitFrequencies(const RangeList &ranges, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Holds the frequency ranges for the device. */ QList _frequencyRanges; }; /** Specialization for transmit frequency limits. * The verification is only performed if the channel is not "RX Only". * @ingroup limits */ class RadioLimitTransmitFrequencies: public RadioLimitFrequencies { Q_OBJECT public: /** Empty constructor. */ explicit RadioLimitTransmitFrequencies(RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); /** Constructor from initializer list. */ RadioLimitTransmitFrequencies(const RangeList &ranges, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; }; /** Represents a limit on intervals. * @ingroup limits */ class RadioLimitInterval: public RadioLimitValue { Q_OBJECT public: /** Empty constructor. */ explicit RadioLimitInterval(RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); /** Constructor from range. */ RadioLimitInterval(const Range &range, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Holds the duration ranges for the interval. */ Range _durations; }; /** Represents a limit on levels. * @ingroup limits */ class RadioLimitLevel: public RadioLimitValue { Q_OBJECT public: /** Empty constructor. */ explicit RadioLimitLevel(const Range &range = {0, 10}, bool allowInvalid=true, RadioLimitIssue::Severity severity=RadioLimitIssue::Severity::Warning, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: Range _range; bool _allowInvalid; }; /** Represents the limits for a @c ConfigItem instance. * * That is, it holds the limits for every property of the @c ConfigItem instance. This class * provides a initializer list constructor for easy programmatic construction of limits. * * @ingroup limits */ class RadioLimitItem: public RadioLimitElement { Q_OBJECT public: /** Empty constructor. */ explicit RadioLimitItem(QObject *parent=nullptr); /** Constructor from initializer list. * The ownership of all passed elements are taken. */ RadioLimitItem(const PropList &list, QObject *parent=nullptr); /** Adds a property declaration. * * The item takes the ownership of the structure declaration. If a property is already defined * with the same name, @c false is returned. * * @param prop Specifies the name of the property. * @param structure Specifies the structure declaration of the property value. * @returns @c false If a property with the same name is already defined. */ bool add(const QString &prop, RadioLimitElement *structure); virtual bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; /** Verifies the properties of the given item. */ virtual bool verifyItem(const ConfigItem *item, RadioLimitContext &context) const; protected: /** Holds the property <-> limits map. */ QHash _elements; }; /** Represents the limits for all properties of a @c ConfigObject instance. * @ingroup limits */ class RadioLimitObject: public RadioLimitItem { Q_OBJECT public: /** Empty constructor. */ explicit RadioLimitObject(QObject *parent=nullptr); /** Constructor from initializer list. * The ownership of all passed elements are taken. */ RadioLimitObject(const PropList &list, QObject *parent=nullptr); /** Verifies the properties of the given object. */ virtual bool verifyObject(const ConfigObject *item, RadioLimitContext &context) const; }; /** Represents an ignored element in the codeplug. * * Instances of this class might be used to inform the user about a configured feature not present * in the particular radio. * * @ingroup limits */ class RadioLimitIgnored: public RadioLimitObject { Q_OBJECT public: /** Constructor for a ignored setting verification element. */ RadioLimitIgnored(RadioLimitIssue::Severity notify=RadioLimitIssue::Hint, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; bool verifyObject(const ConfigObject *item, RadioLimitContext &context) const; protected: /** Holds the level of the notification. */ RadioLimitIssue::Severity _notification; }; /** Dispatch by class. * * Sometimes, a property may hold objects of different type. In these cases, a dispatcher is needed * to specify which limits to apply based on the type of the object. This class implements this * dispatcher. * * @ingroup limits */ class RadioLimitObjects: public RadioLimitObject { Q_OBJECT public: /** Initializer lists of type properties. */ typedef std::initializer_list > TypeList; public: /** Constructor from initializer list. * * A list of pairs of a @c QMetaObject and a @c RadioLimitObject must be given. The meta object * specifies the type of the @c ConfigObject and the associated @c RadioLimitObject the limits * for this type. * * A dispatch for Analog and DigitalChannel may look like * @code * new RadioLimitObjects{ * { AnalogChannel::staticMetaObject, new RadioLimitObject{ * // Limits for analog channel objects * } }, * {DigialChannel::staticMetaObject, new RadioLimitObject{ * // Limits for digital channel objects * } } * }; * @endcode */ RadioLimitObjects(const TypeList &list, QObject *parent=nullptr); bool verifyItem(const ConfigItem *item, RadioLimitContext &context) const; protected: /** Maps class-names to object limits. */ QHash _types; }; /** Limits the possible classes a @c ConfigObjectReference may refer to. * @ingroup limits */ class RadioLimitObjRef: public RadioLimitElement { Q_OBJECT public: /** Initializer lists of allowed classes. */ typedef std::initializer_list MetaObjectList; public: /** Constructor. * @param type Specifies the type that might be referenced. * @param allowNull If @c true, the reference may be a nullptr. * @param parent Specifies the QObject parent. */ RadioLimitObjRef(const QMetaObject &type, bool allowNull=true, QObject *parent=nullptr); /** Constructor. * @param types Specifies the types that might be referenced. * @param allowNull If @c true, the reference may be a nullptr. * @param parent Specifies the QObject parent. */ RadioLimitObjRef(const MetaObjectList &types, bool allowNull=true, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Checks if the given type is one of the valid ones in @c _types. */ bool validType(const QMetaObject *type) const; protected: /** If @c true, a null reference is allowed. */ bool _allowNull; /** Possible classes of instances, the reference may point to. */ QSet _types; }; /** Issues a notification if a reference is set. * @ingroup limits */ class RadioLimitObjRefIgnored: public RadioLimitObjRef { Q_OBJECT public: /** Constructor. * @param defObj Specifies a weak reference to a default object that gets silently ignored. * @param notify Specifies the issue severity. * @param parent Specifies the QObject parent. */ RadioLimitObjRefIgnored( ConfigObject *defObj=nullptr, RadioLimitIssue::Severity notify=RadioLimitIssue::Hint, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** The severity of the issue. */ RadioLimitIssue::Severity _severity; /** A weak reference to a default value, that gets silently ignored. */ ConfigObject *_default; }; /** Specifies the limits for a list of @c ConfigObject instances. * @ingroup limits */ class RadioLimitList: public RadioLimitElement { Q_OBJECT public: /** Helper struct to pass list entry definitions */ struct ElementLimits { const QMetaObject &type; ///< The type of the object int minCount; ///< Minimum count of elements. int maxCount; ///< Maximum count of elements. RadioLimitObject *structure; ///< The structure of the elements. }; public: /** Constructor. * @param type Specifies the type of the elements. * @param minSize Specifies the minimum size of the list. If -1, no check is performed. * @param maxSize Specifies the maximum size of the list. If -1, no check is performed. * @param element Specifies the limits for all objects in the list. If the list contains instances * of different ConfigObject types, use @c RadioLimitObjects dispatcher. * @param parent Specifies the QObject parent. */ RadioLimitList(const QMetaObject &type, int minSize, int maxSize, RadioLimitObject *element, QObject *parent=nullptr); /** Constructor from initializer list. */ RadioLimitList(const std::initializer_list &elements, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Searches for the specified type or one of its super-clsases in the set of allowed types. */ QString findClassName(const QMetaObject &type) const; protected: /** Maps typename to element definition. */ QHash _elements; /** Maps typename to minimum count. */ QHash _minCount; /** Maps typename to maximum count. */ QHash _maxCount; }; /** Implements the limits for reference lists. * @ingroup limits */ class RadioLimitRefList: public RadioLimitElement { Q_OBJECT public: /** Constructor. * @param minSize Specifies the minimum size of the list. If -1, no check is performed. * @param maxSize Specifies the maximum size of the list. If -1, no check is performed. * @param type Specifies the type, the references must be instances of. * @param parent Specifies the QObject parent. */ RadioLimitRefList(int minSize, int maxSize, const QMetaObject &type, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Checks if the given type is one of the valid ones in @c _types. */ bool validType(const QMetaObject *type) const; protected: /** Holds the minimum size of the list. */ qint64 _minSize; /** Holds the maximum size of the list. */ qint64 _maxSize; /** Possible classes of instances, the references may point to. */ QSet _types; }; /** Implements the limits for a list of references to group call contacts. * This is used to restrict the elements of group lists to group calls. * @ingroup limits */ class RadioLimitGroupCallRefList: public RadioLimitElement { Q_OBJECT public: /** Constructor * @param minSize Specifies the minimum size of the list. If -1, no check is performed. * @param maxSize Specifies the maximum size of the list. If -1, no check is performed. * @param parent Specifies the QObject parent. */ RadioLimitGroupCallRefList(int minSize, int maxSize, QObject *parent=nullptr); bool verify(const ConfigItem *item, const QMetaProperty &prop, RadioLimitContext &context) const; protected: /** Holds the minimum size of the list. */ qint64 _minSize; /** Holds the maximum size of the list. */ qint64 _maxSize; }; /** Specialized RadioLimitObject handling a zone for radio supporting only a single channel list * per zone. * * Checks if a zone contains two sets of channel lists and notifies the user that the zone gets * split. * * @ingroup limits */ class RadioLimitSingleZone: public RadioLimitObject { Q_OBJECT public: /** Constructor. * @param maxSize Specifies the maximum size of the zone. If -1, no check is performed. * @param list Initializer list for further zone properties. * @param parent Specifies the QObject parent. */ RadioLimitSingleZone(qint64 maxSize, const PropList &list, QObject *parent=nullptr); bool verifyItem(const ConfigItem *item, RadioLimitContext &context) const; }; /** Represents the limits or the entire codeplug. * * Use @c Radio::limits to obtain an instance. * @ingroup limits */ class RadioLimits : public RadioLimitItem { Q_OBJECT public: /** Empty constructor. */ explicit RadioLimits(bool betaWarning, QObject *parent = nullptr); /** Constructor from initializer list. */ RadioLimits(const std::initializer_list > &list, QObject *parent=nullptr); /** Verifies the given configuration. */ virtual bool verifyConfig(const Config *config, RadioLimitContext &context) const; /** Returns @c true if the radio supports a call-sign DB. */ bool hasCallSignDB() const; /** Returns @c true if the call-sign DB is implemented. */ bool callSignDBImplemented() const; /** Returns the maximum number of entries in the call-sign DB. */ unsigned numCallSignDBEntries() const; /** Returns @c true if the radio supports satellite config. */ bool hasSatelliteConfig() const; /** Returns @c true if satellite config is implemented. */ bool satelliteConfigImplemented() const; /** Returns the maximum number of satellites. */ unsigned numSatellites() const; protected: /** If @c true, a warning is issued that the radio is still under development and not well * tested yet. */ bool _betaWarning; /** If @c true, the radio supports a call-sign DB. */ bool _hasCallSignDB; /** If @c true, the call-sign is implemented. */ bool _callSignDBImplemented; /** Holds the number of possible call-sign DB entries. */ unsigned _numCallSignDBEntries; /** If @c true, the radio supports satellite config. */ bool _hasSatelliteConfig; /** If @c true, satellite config is implemented. */ bool _satelliteConfigImplemented; /** Holds the number of possible satellites. */ unsigned _numSatellites; }; #endif // RADIOLIMITS_HH ================================================ FILE: lib/radiosettings.cc ================================================ #include "radiosettings.hh" #include "radioid.hh" #include "gnsssettings.hh" RadioSettings::RadioSettings(QObject *parent) : ConfigItem(parent), _power(Channel::Power::High), _transmitTimeOut(Interval::infinity()), _defaultId(new DMRRadioIDReference(this)), _boot(new BootSettings(this)), _audio(new AudioSettings(this)), _tone(new ToneSettings(this)), _gnss(new GNSSSettings(this)), _dmr(new DMRSettings(this)), _tytExtension(nullptr), _radioddityExtension(nullptr), _anytoneExtension(nullptr) { connect(_boot, &BootSettings::modified, this, &RadioSettings::modified); connect(_audio, &AudioSettings::modified, this, &RadioSettings::modified); connect(_tone, &ToneSettings::modified, this, &RadioSettings::modified); connect(_gnss, &GNSSSettings::modified, this, &RadioSettings::modified); connect(_dmr, &DMRSettings::modified, this, &RadioSettings::modified); } bool RadioSettings::copy(const ConfigItem &other) { const RadioSettings *set = other.as(); if ((nullptr==set) || (!ConfigItem::copy(other))) return false; if (set->totDisabled()) disableTOT(); return true; } ConfigItem * RadioSettings::clone() const { RadioSettings *set = new RadioSettings(); if (! set->copy(*this)) { set->deleteLater(); return nullptr; } return set; } void RadioSettings::clear() { ConfigItem::clear(); _power = Channel::Power::High; disableTOT(); defaultIdRef()->clear(); // delete extensions setTyTExtension(nullptr); setRadioddityExtension(nullptr); setAnytoneExtension(nullptr); } Channel::Power RadioSettings::power() const { return _power; } void RadioSettings::setPower(Channel::Power power) { _power = power; emit modified(this); } bool RadioSettings::totDisabled() const { return _transmitTimeOut.isInfinite() || _transmitTimeOut.isNull(); } Interval RadioSettings::tot() const { return _transmitTimeOut; } void RadioSettings::setTOT(const Interval &sec) { if (_transmitTimeOut == sec) return; _transmitTimeOut = sec; emit modified(this); } void RadioSettings::disableTOT() { setTOT(Interval::infinity()); } DMRRadioIDReference * RadioSettings::defaultIdRef() const { return _defaultId; } DMRRadioID * RadioSettings::defaultId() const { if (_defaultId->isNull()) return nullptr; return _defaultId->as(); } void RadioSettings::setDefaultId(DMRRadioID *id) { _defaultId->set(id); } BootSettings * RadioSettings::boot() const { return _boot; } AudioSettings * RadioSettings::audio() const { return _audio; } ToneSettings * RadioSettings::tone() const { return _tone; } GNSSSettings * RadioSettings::gnss() const { return _gnss; } DMRSettings * RadioSettings::dmr() const { return _dmr; } TyTSettingsExtension * RadioSettings::tytExtension() const { return _tytExtension; } void RadioSettings::setTyTExtension(TyTSettingsExtension *ext) { if (_tytExtension) { disconnect(_tytExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onExtensionModified())); _tytExtension->deleteLater(); } _tytExtension = ext; if (_tytExtension) { _tytExtension->setParent(this); connect(_tytExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onExtensionModified())); } emit modified(this); } RadiodditySettingsExtension * RadioSettings::radioddityExtension() const { return _radioddityExtension; } void RadioSettings::setRadioddityExtension(RadiodditySettingsExtension *ext) { if (_radioddityExtension) { disconnect(_radioddityExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onExtensionModified())); _radioddityExtension->deleteLater(); } _radioddityExtension = ext; if (_radioddityExtension) { _radioddityExtension->setParent(this); connect(_radioddityExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onExtensionModified())); } emit modified(this); } AnytoneSettingsExtension * RadioSettings::anytoneExtension() const { return _anytoneExtension; } void RadioSettings::setAnytoneExtension(AnytoneSettingsExtension *ext) { if (_anytoneExtension) { disconnect(_anytoneExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onExtensionModified())); _anytoneExtension->deleteLater(); } _anytoneExtension = ext; if (_anytoneExtension) { _anytoneExtension->setParent(this); connect(_anytoneExtension, SIGNAL(modified(ConfigItem*)), this, SLOT(onExtensionModified())); } emit modified(this); } void RadioSettings::onExtensionModified() { emit modified(this); } bool RadioSettings::parse(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { if (! node) return false; if (! node.IsMap()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot parse radio settings: Expected object."; return false; } if (! node["tot"]) { disableTOT(); } else if (node["tot"] && node["tot"].IsScalar()) { Interval to; if (! to.parse(QString::fromStdString(node["tot"].as()), Interval::Format::Seconds)) disableTOT(); else setTOT(to); } /// @todo Remove with 0.17.0. Only parse "old" global settings, will be serialized in boot and audio sections. if (Level micLevel; node["micLevel"] && node["micLevel"].IsScalar() && micLevel.parse(QString::fromStdString(node["micLevel"].as()))) audio()->setMicGain(micLevel); if (node["speech"] && node["speech"].IsScalar()) audio()->enableSpeechSynthesis("true" == node["speech"].as()); if (Level squelch; node["squelch"] && node["squelch"].IsScalar() && squelch.parse(QString::fromStdString(node["squelch"].as()))) audio()->setSquelch(squelch); if (Level vox; node["vox"] && node["vox"].IsScalar() && vox.parse(QString::fromStdString(node["vox"].as()))) audio()->setVox(vox); if (node["introLine1"] && node["introLine1"].IsScalar()) boot()->setMessage1(QString::fromStdString(node["introLine1"].as())); if (node["introLine2"] && node["introLine2"].IsScalar()) boot()->setMessage2(QString::fromStdString(node["introLine2"].as())); return ConfigItem::parse(node, ctx, err); } bool RadioSettings::populate(YAML::Node &node, const Context &context, const ErrorStack &err) { if (! ConfigItem::populate(node, context, err)) return false; if (! totDisabled()) node["tot"] = tot().format().toStdString(); return true; } ================================================ FILE: lib/radiosettings.hh ================================================ #ifndef RADIOSETTINGS_HH #define RADIOSETTINGS_HH #include "configobject.hh" #include "channel.hh" #include "bootsettings.hh" #include "audiosettings.hh" #include "tonesettings.hh" #include "gnsssettings.hh" #include "dmrsettings.hh" #include "radioddity_extensions.hh" #include "anytone_settingsextension.hh" #include "tyt_extensions.hh" /** Represents the common radio-global settings. * @ingroup conf */ class RadioSettings : public ConfigItem { Q_OBJECT /** The default channel power */ Q_PROPERTY(Channel::Power power READ power WRITE setPower) /** The default transmit timeout */ Q_PROPERTY(Interval tot READ tot WRITE setTOT SCRIPTABLE false) /** The default DMR radio ID. */ Q_PROPERTY(DMRRadioIDReference *defaultID READ defaultIdRef) /** Common boot settings. */ Q_PROPERTY(BootSettings* boot READ boot); /** Common audio and tone settings. */ Q_PROPERTY(AudioSettings *audio READ audio); /** Common tone settings. */ Q_PROPERTY(ToneSettings *tone READ tone); /** The GNSS settings. */ Q_PROPERTY(GNSSSettings *gnss READ gnss); /** The common DMR settings. */ Q_PROPERTY(DMRSettings *dmr READ dmr); /** The settings extension for TyT devices. */ Q_PROPERTY(TyTSettingsExtension* tyt READ tytExtension WRITE setTyTExtension) /** The settings extension for Radioddity devices. */ Q_PROPERTY(RadiodditySettingsExtension * radioddity READ radioddityExtension WRITE setRadioddityExtension) /** Settings for AnyTone devices. */ Q_PROPERTY(AnytoneSettingsExtension *anytone READ anytoneExtension WRITE setAnytoneExtension) public: /** Default constructor. */ explicit RadioSettings(QObject *parent=nullptr); bool copy(const ConfigItem &other); ConfigItem *clone() const; /** Resets the settings. */ void clear(); /** Returns the default channel power. */ Channel::Power power() const; /** Sets the default channel power. */ void setPower(Channel::Power power); /** Returns @c true if the transmit timeout (TOT) is disabled. */ bool totDisabled() const; /** Returns the default transmit timeout (TOT) in seconds, 0=disabled. */ Interval tot() const; /** Sets the default transmit timeout (TOT) in seconds, 0=disabled. */ void setTOT(const Interval &sec); /** Disables the transmit timeout (TOT). */ void disableTOT(); /** Returns a reference to the default DMR radio Id. */ DMRRadioIDReference *defaultIdRef() const; /** Returns the default DMR ID or nullptr, if non is set. */ DMRRadioID *defaultId() const; /** Sets the default DMR ID. */ void setDefaultId(DMRRadioID *id); /** Returns the boot settings. */ BootSettings *boot() const; /** Returns the audio/tone settings. */ AudioSettings *audio() const; /** Returns the tone settings. */ ToneSettings *tone() const; /** Returns the GNSS settings. */ GNSSSettings *gnss() const; /** Returns the DMR settings. */ DMRSettings *dmr() const; /** Returns the TyT device specific radio settings. */ TyTSettingsExtension *tytExtension() const; /** Sets the TyT device specific radio settings. */ void setTyTExtension(TyTSettingsExtension *ext); /** Returns the Radioddity device specific radio settings. */ RadiodditySettingsExtension *radioddityExtension() const; /** Sets the Radioddity device specific radio settings. */ void setRadioddityExtension(RadiodditySettingsExtension *ext); /** Returns the AnyTone device specific radio settings. */ AnytoneSettingsExtension *anytoneExtension() const; /** Sets the AnyTone device specific radio settings. */ void setAnytoneExtension(AnytoneSettingsExtension *ext); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err=ErrorStack()); protected: bool populate(YAML::Node &node, const Context &context, const ErrorStack &err=ErrorStack()); protected slots: /** Internal used callback whenever an extension is modified. */ void onExtensionModified(); protected: /** Holds the global power setting. */ Channel::Power _power; /** Holds the global transmit timeout. */ Interval _transmitTimeOut; /** Reference to the default DMR radio ID. */ DMRRadioIDReference *_defaultId; /** The boot settings. */ BootSettings *_boot; /** The audio/tone settings. */ AudioSettings *_audio; /** The tone settings. */ ToneSettings *_tone; /** The GNSS settings. */ GNSSSettings *_gnss; /** The DMR settings. */ DMRSettings *_dmr; /** Device specific settings extension for TyT devices. */ TyTSettingsExtension *_tytExtension; /** Device specific settings extension for Radioddity devices. */ RadiodditySettingsExtension *_radioddityExtension; /** Device specific settings extension for AnyTone devices. */ AnytoneSettingsExtension *_anytoneExtension; }; #endif // RADIOCONFIG_HH ================================================ FILE: lib/ranges.cc ================================================ #include "ranges.hh" ================================================ FILE: lib/ranges.hh ================================================ #ifndef RANGES_HH #define RANGES_HH #include #include "interval.hh" #include "frequency.hh" /** Simple range class representing some range in some data type. Provides some methods to limit * values to these ranges. * * @ingroup utils */ template class Range { public: /** Maps a given value onto the range. */ inline T map(const T &n) const { if (n < lower) return lower; if (upper < n) return upper; return n; } /** Checks, if the given value lays within the range. */ inline bool contains(const T &n) const { return ((lower class Ranges { public: /** Checks, if the given value lays within the range. */ inline bool contains(const T &n) const { for (auto range: ranges) { if (range.contains(n)) return true; } return false; } public: /** The set of ranges. */ QSet> ranges; }; /** An integer range. */ typedef Range IntRange; /** A time range. */ typedef Range TimeRange; /** A frequency range. */ typedef Range FrequencyRange; #endif // RANGES_HH ================================================ FILE: lib/rd5r.cc ================================================ #include "rd5r.hh" #include "config.hh" #include "radiolimits.hh" #include "rd5r_limits.hh" #define BSIZE 128 RadioLimits *RD5R::_limits = nullptr; RD5R::RD5R(RadioddityInterface *device, QObject *parent) : RadioddityRadio(device, parent), _name("Baofeng/Radioddity RD-5R"), _codeplug() { // pass... } RD5R::~RD5R() { // pass... } const QString & RD5R::name() const { return _name; } const RadioLimits & RD5R::limits() const { if (nullptr == _limits) _limits = new RD5RLimits(); return *_limits; } const Codeplug & RD5R::codeplug() const { return _codeplug; } Codeplug & RD5R::codeplug() { return _codeplug; } RadioInfo RD5R::defaultRadioInfo() { return RadioInfo( RadioInfo::RD5R, "rd5r", "RD-5R", "Radioddity", {RadioddityInterface::interfaceInfo()}); } ================================================ FILE: lib/rd5r.hh ================================================ /** @defgroup rd5r Baofeng/Radioddity RD-5R * Device specific classes for Baofeng/Radioddity RD-5R. * * \image html rd5r.jpg "RD-5R" width=200px * \image latex rd5r.jpg "RD-5R" width=200px * * The Baofeng/Radioddity RD-5R radio is likely the cheapest fully DMR compatiple (Tier I&II) VHF/UHF * radio on the market. Consequently, it is quiet popular and is usually the first DMR radio * many operators may buy. In my opinion it is a decent radio with reasonable sensitivity and audio * quality (for a handheld). However, the receiver frontend seems to be identical to the analog * Baofeng handhelds and thus suffers from the same well known issue of blocking whenever a strong * signal is nearby (even across bands). But you get a lot for a $70 radio. * * It features up to 1024 channels organized in 250 zones, where each zone may contain up to * 16 channels. The radio is a dual VFO and each VFO might be assigned to a different zone. Hence, a * zone is just a single list of up to 16 channels (in contrast to many other radios where a zone * contains two lists of channels for each VFO). * * The radio can also hold up to 255 contacts (actually 256, but due to a bug in the firmware RX is * disabled whenever all 256 contacts are set), 64 RX group lists and 250 scanlists. * * @ingroup radioddity */ #ifndef RD5R_HH #define RD5R_HH #include "radioddity_radio.hh" #include "rd5r_codeplug.hh" /** Implements an interface to the Baofeng/Radioddity RD-5R VHF/UHF 5W DMR (Tier I/II) radio. * * The Baofeng/Radioddity RD-5R radio uses a weird HID (human-interface device, see @c HID and * @c HIDevice) protocol for communication. This class implements the communication details with * the radio to read and write codeplugs on the device. * * @ingroup rd5r */ class RD5R: public RadioddityRadio { Q_OBJECT public: /** Constructor. * Do not call this constructor directly. Consider using the factory method * @c Radio::detect. */ RD5R(RadioddityInterface *device=nullptr, QObject *parent=nullptr); virtual ~RD5R(); const QString &name() const; const RadioLimits &limits() const; const Codeplug &codeplug() const; Codeplug &codeplug(); /** Returns the default info about the radio. */ static RadioInfo defaultRadioInfo(); private: /** Device identifier string. */ QString _name; /** Current device specific codeplug. */ RD5RCodeplug _codeplug; private: /** Holds the singleton instance of the radio limits. */ static RadioLimits *_limits; }; #endif // RD5R_HH ================================================ FILE: lib/rd5r_codeplug.cc ================================================ #include "rd5r_codeplug.hh" #include "config.hh" #include "channel.hh" #include "logger.hh" #include /* ******************************************************************************************** * * Implementation of RD5RCodeplug::ChannelElement * ******************************************************************************************** */ RD5RCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : RadioddityCodeplug::ChannelElement(ptr, size) { // pass... } RD5RCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : RadioddityCodeplug::ChannelElement(ptr) { // pass... } void RD5RCodeplug::ChannelElement::clear() { RadioddityCodeplug::ChannelElement::clear(); setSquelch(Level::null()); } Level RD5RCodeplug::ChannelElement::squelch() const { return Level::fromValue(getUInt8(Offset::squelch()), {1,9}); } void RD5RCodeplug::ChannelElement::setSquelch(Level level) { setUInt8(Offset::squelch(), level.mapTo({1,9})); } bool RD5RCodeplug::ChannelElement::fromChannelObj(const Channel *c, Context &ctx, const ErrorStack& err) { if (! RadioddityCodeplug::ChannelElement::fromChannelObj(c, ctx, err)) return false; if (c->is()) { const FMChannel *ac = c->as(); if (ac->defaultSquelch()) setSquelch(ctx.config()->settings()->audio()->squelch()); else if (ac->squelchDisabled()) setSquelch(Level::null()); else setSquelch(ac->squelch()); } else { // If digital channel, reuse global squelch setting setSquelch(ctx.config()->settings()->audio()->squelch()); } return true; } Channel * RD5RCodeplug::ChannelElement::toChannelObj(Context &ctx, const ErrorStack& err) const { Channel *ch = RadioddityCodeplug::ChannelElement::toChannelObj(ctx, err); if (nullptr == ch) return nullptr; if (ch->is()) { FMChannel *ac = ch->as(); ac->setSquelch(squelch()); } return ch; } bool RD5RCodeplug::ChannelElement::linkChannelObj(Channel *c, Context &ctx, const ErrorStack& err) const { if (! RadioddityCodeplug::ChannelElement::linkChannelObj(c, ctx, err)) return false; /* if (c->is()) { AnalogChannel *ac = c->as(); if (ctx.config()->settings()->squelch() == ac->squelch()) { ac->setSquelchDefault(); } } */ return true; } /* ********************************************************************************************* * * Implementation of RD5RCodeplug::TimestampElement * ********************************************************************************************* */ RD5RCodeplug::TimestampElement::TimestampElement(uint8_t *ptr, unsigned size) : Element(ptr, size) { // pass... } RD5RCodeplug::TimestampElement::TimestampElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } RD5RCodeplug::TimestampElement::~TimestampElement() { // pass... } void RD5RCodeplug::TimestampElement::clear() { set(); } QDateTime RD5RCodeplug::TimestampElement::get() const { return QDateTime(QDate(getBCD4_be(Offset::year()), getBCD2(Offset::month()), getBCD2(Offset::day())), QTime(getBCD2(Offset::hour()), getBCD2(Offset::minute()))); } void RD5RCodeplug::TimestampElement::set(const QDateTime &ts) { setBCD4_be(Offset::year(), ts.date().year()); setBCD2(Offset::month(), ts.date().month()); setBCD2(Offset::day(), ts.date().day()); setBCD2(Offset::hour(), ts.time().hour()); setBCD2(Offset::minute(), ts.time().minute()); } /* ******************************************************************************************** * * Implementation of RD5RCodeplug::EncryptionElement * ******************************************************************************************** */ RD5RCodeplug::EncryptionElement::EncryptionElement(uint8_t *ptr) : RadioddityCodeplug::EncryptionElement(ptr) { // pass... } bool RD5RCodeplug::EncryptionElement::isBasicKeySet(unsigned n) const { if (n>0) return false; return RadioddityCodeplug::EncryptionElement::isBasicKeySet(n); } QByteArray RD5RCodeplug::EncryptionElement::basicKey(unsigned n) const { if (n>0) return QByteArray(); return QByteArray("\x53\x47\x4c\x39"); } void RD5RCodeplug::EncryptionElement::setBasicKey(unsigned n, const QByteArray &key) { if ((0 != n) || (key != "\x53\x47\x4c\x39")){ logError() << "The RD5R only supports a single fixed DMR basic key '53474c39'."; return; } RD5RCodeplug::EncryptionElement::setBasicKey(n, key); } /* ******************************************************************************************** * * Implementation of RD5RCodeplug * ******************************************************************************************** */ RD5RCodeplug::RD5RCodeplug(QObject *parent) : RadioddityCodeplug(parent) { addImage("Radioddity RD5R Codeplug"); image(0).addElement(0x00080, 0x07b80); image(0).addElement(0x08000, 0x16300); } void RD5RCodeplug::clear() { RadioddityCodeplug::clear(); this->clearTimestamp(); } bool RD5RCodeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { if (! RadioddityCodeplug::encodeElements(flags, ctx, err)) return false; // Set timestamp if (! this->encodeTimestamp(err)) { errMsg(err) << "Cannot encode time-stamp."; return false; } return true; } bool RD5RCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { if (! RadioddityCodeplug::decodeElements(ctx, err)) return false; return true; } void RD5RCodeplug::clearTimestamp() { encodeTimestamp(); } bool RD5RCodeplug::encodeTimestamp(const ErrorStack &err) { Q_UNUSED(err) TimestampElement(data(Offset::timestamp())).set(); return true; } void RD5RCodeplug::clearGeneralSettings() { TimestampElement(data(Offset::settings())).clear(); } bool RD5RCodeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(err) GeneralSettingsElement el(data(Offset::settings())); if (! flags.updateCodeplug()) el.clear(); return el.fromConfig(ctx, err); } bool RD5RCodeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { return GeneralSettingsElement(data(Offset::settings())).updateConfig(ctx, err); } void RD5RCodeplug::clearButtonSettings() { ButtonSettingsElement(data(Offset::buttons())).clear(); } bool RD5RCodeplug::encodeButtonSettings(Context &ctx, const Flags &flags, const ErrorStack &err) { Q_UNUSED(flags); return ButtonSettingsElement(data(Offset::buttons())).encode(ctx, err); } bool RD5RCodeplug::decodeButtonSettings(Context &ctx, const ErrorStack &err) { return ButtonSettingsElement(data(Offset::buttons())).decode(ctx, err); } void RD5RCodeplug::clearMessages() { MessageBankElement(data(Offset::messages())).clear(); } bool RD5RCodeplug::encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err) { if (! MessageBankElement(data(Offset::messages())).encode(ctx, flags, err)) { errMsg(err) << "Cannot encode preset messages."; return false; } return true; } bool RD5RCodeplug::decodeMessages(Context &ctx, const ErrorStack &err) { if (! MessageBankElement(data(Offset::messages())).decode(ctx, err)) { errMsg(err) << "Cannot decode preset messages."; return false; } return true; } void RD5RCodeplug::clearContacts() { for (unsigned int i=0; i= (unsigned int) ctx.count()) continue; if (! el.fromContactObj(ctx.get(i+1), ctx, err)) { errMsg(err) << "Cannot encode contact '" << ctx.get(i+1)->name() << "' at index " << i+1 << "."; return false; } } return true; } bool RD5RCodeplug::createContacts(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) /* Unpack Contacts */ for (unsigned int i=0; icontacts()->add(cont); } return true; } void RD5RCodeplug::clearDTMFContacts() { for (unsigned int i=0; i= (unsigned int) ctx.count()) continue; if (! el.fromContactObj(ctx.get(i+1), ctx, err)) { errMsg(err) << "Cannot encode DTMF contact '" << ctx.get(i+1)->name() << "' at index " << i+1 << "."; return false; } } return true; } bool RD5RCodeplug::createDTMFContacts(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) for (unsigned int i=0; icontacts()->add(cont); } return true; } void RD5RCodeplug::clearChannels() { for (unsigned int b=0,c=0; b()) { if (! el.fromChannelObj(ctx.get(c+1), ctx)) { errMsg(err) << "Cannot encode channel " << c << " (" << i << " of bank " << b <<")."; return false; } bank.enable(i,true); } else { el.clear(); bank.enable(i, false); } } } return true; } bool RD5RCodeplug::createChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) for (unsigned int b=0,c=0; bchannelList()->add(ch); ctx.add(ch, c+1); } } return true; } bool RD5RCodeplug::linkChannels(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) for (unsigned int b=0,c=0; b(c+1), ctx, err)) return false; } } return true; } void RD5RCodeplug::clearMenuSettings() { MenuSettingsElement(data(Offset::menuSettings())).clear(); } void RD5RCodeplug::clearBootSettings() { BootSettingsElement(data(Offset::bootSettings())).clear(); BootTextElement(data(Offset::bootText())).clear(); } bool RD5RCodeplug::encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); BootSettingsElement(data(Offset::bootSettings())).encode(ctx, err); BootTextElement(data(Offset::bootText())).fromConfig(ctx, err); return true; } bool RD5RCodeplug::decodeBootSettings(Context &ctx, const ErrorStack &err) { BootSettingsElement(data(Offset::bootSettings())).decode(ctx, err); BootTextElement(data(Offset::bootText())).updateConfig(ctx, err); return true; } void RD5RCodeplug::clearVFOSettings() { ChannelElement(data(Offset::vfoA())).clear(); ChannelElement(data(Offset::vfoB())).clear(); } void RD5RCodeplug::clearZones() { ZoneBankElement bank(data(Offset::zoneBank())); bank.clear(); for (unsigned int i=0; i(i+1)) { bank.enable(i, false); continue; } // Construct from Zone obj z.fromZoneObjA(ctx.get(i+1), ctx, err); bank.enable(i, true); } return true; } bool RD5RCodeplug::createZones(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) ZoneBankElement bank(data(Offset::zoneBank())); for (unsigned int i=0; izones()->add(zone); ctx.add(zone, i+1); } return true; } bool RD5RCodeplug::linkZones(Context &ctx, const ErrorStack &err) { ZoneBankElement bank(data(Offset::zoneBank())); for (unsigned int i=0; i(i+1); if (! z.linkZoneObj(zone, ctx)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } } return true; } void RD5RCodeplug::clearScanLists() { ScanListBankElement bank(data(Offset::scanListBank())); bank.clear(); for (unsigned int i=0; i= ctx.count()) { bank.enable(i, false); continue; } if (! ScanListElement(bank.get(i)).fromScanListObj(ctx.get(i+1), ctx, err)) { errMsg(err) << "Cannot encode scan list at index " << i << "."; return false; } bank.enable(i, true); } return true; } bool RD5RCodeplug::createScanLists(Context &ctx, const ErrorStack &err) { ScanListBankElement bank(data(Offset::scanListBank())); for (unsigned int i=0; iscanlists()->add(scan); ctx.add(scan, i+1); } return true; } bool RD5RCodeplug::linkScanLists(Context &ctx, const ErrorStack &err) { ScanListBankElement bank(data(Offset::scanListBank())); for (unsigned int i=0; i(i+1), ctx, err)) { errMsg(err) << "Cannot link scan list '" << ctx.get(i+1) << "' at index " << i+1 << "."; return false; } } return true; } void RD5RCodeplug::clearGroupLists() { GroupListBankElement bank(data(Offset::groupListBank())); bank.clear(); for (unsigned int i=0; i= ctx.count()) continue; GroupListElement el(bank.get(i)); el.fromRXGroupListObj(ctx.get(i+1), ctx, err); // Only group calls are encoded int count = 0; for (int j=0; j(i+1)->count(); j++) if (DMRContact::GroupCall == ctx.get(i+1)->contact(j)->type()) count++; bank.setContactCount(i, count); } return true; } bool RD5RCodeplug::createGroupLists(Context &ctx, const ErrorStack &err) { GroupListBankElement bank(data(Offset::groupListBank())); for (unsigned int i=0; irxGroupLists()->add(list); ctx.add(list, i+1); } return true; } bool RD5RCodeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { GroupListBankElement bank(data(Offset::groupListBank())); for (unsigned int i=0; i(i+1)->name() << "'.";*/ if (! el.linkRXGroupListObj(bank.contactCount(i), ctx.get(i+1), ctx, err)) { errMsg(err) << "Cannot link group list '" << ctx.get(i+1)->name() << "'."; return false; } } return true; } void RD5RCodeplug::clearEncryption() { EncryptionElement enc(data(Offset::encryption())); enc.clear(); } bool RD5RCodeplug::encodeEncryption(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); clearEncryption(); EncryptionElement enc(data(Offset::encryption())); return enc.fromCommercialExt(ctx.config()->commercialExtension(), ctx, err); } bool RD5RCodeplug::createEncryption(Context &ctx, const ErrorStack &err) { Q_UNUSED(err); EncryptionElement enc(data(Offset::encryption())); if (EncryptionElement::PrivacyType::None == enc.privacyType()) return true; return enc.updateCommercialExt(ctx, err); } bool RD5RCodeplug::linkEncryption(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); Q_UNUSED(err); return true; } ================================================ FILE: lib/rd5r_codeplug.hh ================================================ #ifndef RD5R_CODEPLUG_HH #define RD5R_CODEPLUG_HH #include #include "radioddity_codeplug.hh" #include "signaling.hh" class Channel; /** Represents, encodes and decodes the device specific codeplug for a Baofeng/Radioddity RD-5R. * * This codeplug format is quiet funny. It reveals some history of this device. First of all, the * channels are organizes in two blocks. The first block contains only a single bank of 128 channels, * while the second block contains 7 banks with a total of 896 channels. I would guess there was a * previous firmware or even hardware version with only 128 channels. * * Moreover, channels, zones, rx group lists and scan lists are organized in tables or banks, with * some preceding bitfield indicating which channel is enabled/valid. Contacts, however, are just * organized in a list, where each entry has a field, indicating whether that contact is valid. * * This difference looks like, as if the firmware code for the contacts stems from a different * device or was developed by a different engineer. Moreover, the message list again, uses yet * another method. Here a simple counter precedes the messages, indicating how many valid messages * there are. All in all, a rather inconsistent way of representing variable length lists in the * codeplug. I would guess, that over time, different people/teams worked on different revisions * of the firmware. It must have been a real nightmare to Serge Vakulenko reverse-engineering this * codeplug. * * @section rd5rcpl Codeplug structure within radio * This implementation targets firmware version 2.1.6. * * The memory representation of the codeplug within the radio is divided into two segments. * The first segment starts at the address 0x00080 and ends at 0x07c00 while the second section * starts at 0x08000 and ends at 0x1e300. * * Please note, that the codeplug is not yet fully understood and a full codeplug cannot be build * from scratch. That is, it is necessary to update an existing codeplug on the radio. * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
Start End Size Content
First segment 0x00080-0x07c00
0x00080 0x00088 0x0008 ??? Unknown ???
0x00088 0x0008e 0x0006 Timestamp, see @c RadioddityCodeplug::TimestampElement.
0x0008e 0x000e0 0x0052 CPS, firmware, DSP version numbers (not touched).
0x000e0 0x00108 0x0028 General settings, see @c RadioddityCodeplug::GeneralSettingsElement.
0x00108 0x00128 0x0020 Button settings, see @c RadioddityCodeplug::ButtonSettingsElement.
0x00128 0x01370 0x1248 32 preset message texts, see @c RadioddityCodeplug::MessageBankElement.
0x01370 0x01588 0x0218 ??? Unknown ???
0x01588 0x01788 0x0200 ??? 32 Emergency systems ???
0x01788 0x02f88 0x1800 256 contacts, see @c RadioddityCodeplug::ContactElement.
0x02f88 0x03388 0x0400 32 DTMF contacts, see @c RadioddityCodeplug::DTMFContactElement.
0x03388 0x03780 0x03f8 ??? Unknown ???
0x03780 0x05390 0x1c10 First 128 channels (bank 0), see @c RadioddityCodeplug::ChannelBankElement * and @c RD5RCodeplug::ChannelElement.
0x05390 0x07518 0x2188 ??? Unknown ???
0x07518 0x07538 0x0020 Boot settings, see @c RadioddityCodeplug::BootSettingsElement.
0x07538 0x07540 0x0008 Menu settings, see @c RadioddityCodeplug::MenuSettingsElement.
0x07540 0x07560 0x0020 2 intro lines, @c RadioddityCodeplug::BootTextElement.
0x07560 0x07590 0x0030 ??? Unknown ???
0x07590 0x075c8 0x0038 VFO A settings @c RadioddityCodeplug::ChannelElement
0x075c8 0x07600 0x0038 VFO B settings @c RadioddityCodeplug::ChannelElement
0x07600 0x07c00 0x0600 ??? Unknown ???
Second segment 0x08000-0x1e300
0x08000 0x08010 0x0010 ??? Unknown ???
0x08010 0x0af10 0x2f00 250 zones, see @c RadioddityCodeplug::ZoneBankElement.
0x0af10 0x0b1b0 0x02a0 ??? Unknown ???
0x0b1b0 0x17620 0xc470 Remaining 896 channels (bank 1-7), see @c RadioddityCodeplug::ChannelBankElement * and @c RD5RCodeplug::ChannelElement.
0x17620 0x1cd10 0x56f0 250 scan lists, see @c RadioddityCodeplug::ScanListBankElement
0x1cd10 0x1d620 0x0910 ??? Unknown ???
0x1d620 0x1e2a0 0x0c80 64 RX group lists, see @c RadioddityCodeplug::GroupListBankElement
0x1e2a0 0x1e300 0x0060 ??? Unknown ???
* * @ingroup rd5r */ class RD5RCodeplug : public RadioddityCodeplug { Q_OBJECT public: /** Implements the specialization of the Radioddity channel for the RD5R radio. * * Memory layout of encoded channel: * @verbinclude rd5r_channel.txt */ class ChannelElement: public RadioddityCodeplug::ChannelElement { protected: /** Hidden constructor. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit ChannelElement(uint8_t *ptr); void clear(); /** Returns the squelch level. */ virtual Level squelch() const; /** Sets the squelch level. */ virtual void setSquelch(Level level); bool fromChannelObj(const Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()); Channel *toChannelObj(Context &ctx, const ErrorStack &err=ErrorStack()) const; bool linkChannelObj(Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()) const; protected: /** Internal offsets within the channel element. */ struct Offset: RadioddityCodeplug::ChannelElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int squelch() { return 0x0037; } /// @endcond }; }; /** Implements the timestamp for RD-5R codeplugs. * * Encoding of messages (size: 0x0006b): * @verbinclude rd5r_timestamp.txt */ class TimestampElement: Element { protected: /** Hidden constructor. */ TimestampElement(uint8_t *ptr, unsigned size); public: /** Constructor. */ explicit TimestampElement(uint8_t *ptr); /** Destructor. */ virtual ~TimestampElement(); /** The size of the element. */ static constexpr unsigned int size() { return 0x0006; } /** Resets the timestamp. */ void clear(); /** Returns the time stamp. */ virtual QDateTime get() const; /** Sets the time stamp. */ virtual void set(const QDateTime &ts=QDateTime::currentDateTime()); protected: /** Internal offsets within the element. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int year() { return 0x0000; } static constexpr unsigned int month() { return 0x0002; } static constexpr unsigned int day() { return 0x0003; } static constexpr unsigned int hour() { return 0x0004; } static constexpr unsigned int minute() { return 0x0005; } /// @endcond }; }; /** Implements the encoding/decoding of encryption keys for the RD-5R radio. * @note The RD5R only supports a single basic DMR encryption key with a fixed value! * * Encoding of encryption keys (size: 0x00088): * @verbinclude radioddity_privacy.txt */ class EncryptionElement: public RadioddityCodeplug::EncryptionElement { public: /** Constructor. */ EncryptionElement(uint8_t *ptr); bool isBasicKeySet(unsigned n) const; QByteArray basicKey(unsigned n) const; void setBasicKey(unsigned n, const QByteArray &key); }; public: /** Empty constructor. */ RD5RCodeplug(QObject *parent=0); void clear(); public: bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Clears the time-stamp in the codeplug. */ virtual void clearTimestamp(); /** Sets the time-stamp. */ virtual bool encodeTimestamp(const ErrorStack &err=ErrorStack()); void clearGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearButtonSettings(); bool encodeButtonSettings(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); bool decodeButtonSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearMessages(); bool encodeMessages(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); bool decodeMessages(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearDTMFContacts(); bool encodeDTMFContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createDTMFContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearChannels(); bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void clearMenuSettings(); void clearBootSettings(); bool encodeBootSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeBootSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearVFOSettings(); void clearZones(); bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); void clearScanLists(); bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearGroupLists(); bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearEncryption(); bool encodeEncryption(const Flags &flags, Context &ctx, const ErrorStack &err); bool createEncryption(Context &ctx, const ErrorStack &err); bool linkEncryption(Context &ctx, const ErrorStack &err); public: /** Some limits for the codeplug. */ struct Limit { static constexpr unsigned int channelBankCount() { return 8; } ///< The number of channel banks. static constexpr unsigned int channelCount() { return 1024; } ///< Maximum number of channels in the codeplug. static constexpr unsigned int contactCount() { return 256; } ///< Maximum number of DMR contacts. static constexpr unsigned int dtmfContactCount() { return 32; } ///< Maximum number of DTMF contacts. static constexpr unsigned int zoneCount() { return 250; } ///< Maximum number of zones. }; protected: /** Some internal offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int timestamp() { return 0x000088; } static constexpr unsigned int settings() { return 0x0000e0; } static constexpr unsigned int buttons() { return 0x000108; } static constexpr unsigned int messages() { return 0x000128; } static constexpr unsigned int encryption() { return 0x001370; } static constexpr unsigned int contacts() { return 0x001788; } static constexpr unsigned int dtmfContacts() { return 0x002f88; } static constexpr unsigned int channelBank0() { return 0x003780; } static constexpr unsigned int bootSettings() { return 0x007518; } static constexpr unsigned int menuSettings() { return 0x007538; } static constexpr unsigned int bootText() { return 0x007540; } static constexpr unsigned int vfoA() { return 0x007590; } static constexpr unsigned int vfoB() { return 0x0075c8; } static constexpr unsigned int zoneBank() { return 0x008010; } static constexpr unsigned int channelBank1() { return 0x00b1b0; } static constexpr unsigned int scanListBank() { return 0x017620; } static constexpr unsigned int groupListBank() { return 0x01d620; } /// @endcond }; }; #endif // RD5R_CODEPLUG_HH ================================================ FILE: lib/rd5r_filereader.cc ================================================ #include "rd5r_filereader.hh" #include #include #define SEGMENT0_ADDR 0x00000080 #define SEGMENT0_SIZE 0x00007b80 #define SEGMENT1_ADDR 0x00008000 #define SEGMENT1_SIZE 0x00016300 bool RD5RFileReader::read(const QString &filename, RD5RCodeplug *codeplug, const ErrorStack &err) { // Check file properties QFileInfo info(filename); if (! info.exists()) { errMsg(err) << "Cannot open file '" << filename << "': File does not exisist."; return false; } if (131072 != info.size()) { errMsg(err) << "Cannot read codeplug file '" << filename << "': File size is not 131072 bytes."; return false; } // Open file QFile file(filename); if (! file.open(QFile::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; return false; } // Read file content if (! file.seek(SEGMENT0_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } char *ptr = (char *)codeplug->data(SEGMENT0_ADDR); size_t n = SEGMENT0_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } if (! file.seek(SEGMENT1_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } ptr = (char *)codeplug->data(SEGMENT1_ADDR); n = SEGMENT1_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } return true; } ================================================ FILE: lib/rd5r_filereader.hh ================================================ #ifndef RD5RFILEREADER_HH #define RD5RFILEREADER_HH #include "rd5r_codeplug.hh" /** Methods to read manufacturer codeplug files. * * The file format of the stock CPS is pretty simple. It is a one-to-one dump of the codeplug * data as written to the device. This makes the decoding of the manufacturer codeplug files very * easy. Some memory regions, however, are not written to the deivice although they are present in * the codeplug file. * * * * * *
Start End Size
0x00080 0x07c00 0x07b80
0x08000 0x1e300 0x16300
* * @ingroup rd5r */ class RD5RFileReader { public: /** Reads manufacturer codeplug file into given codeplug object. * @param filename Specifies the file to read. * @param codeplug Specifies the codeplug object to store read codeplug. * @param err Error stack. * @returns @c true on success and @c false on error. */ static bool read(const QString &filename, RD5RCodeplug *codeplug, const ErrorStack &err=ErrorStack()); }; #endif // RD5RFILEREADER_HH ================================================ FILE: lib/rd5r_limits.cc ================================================ #include "rd5r_limits.hh" #include "rd5r_codeplug.hh" #include "radioid.hh" #include "channel.hh" #include "scanlist.hh" #include "zone.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "roamingzone.hh" RD5RLimits::RD5RLimits(QObject *parent) : RadioLimits(false, parent) { // Define limits for call-sign DB _hasCallSignDB = false; _callSignDBImplemented = false; _numCallSignDBEntries = 0; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; /* Define limits for the general settings. */ add("settings", new RadioLimitItem{ { "introLine1", new RadioLimitString(-1, 16, RadioLimitString::ASCII) }, { "introLine2", new RadioLimitString(-1, 16, RadioLimitString::ASCII) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum({unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}) }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(RD5RCodeplug::BootSettingsElement::Limit::passwordLength(), RadioLimitIssue::Critical) } } } }); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList { { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString(1,8, RadioLimitString::ASCII) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } /// @todo check default radio ID. }); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 256, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum{ (unsigned)DMRContact::PrivateCall, (unsigned)DMRContact::GroupCall, (unsigned)DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, 0, 8, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "ring", new RadioLimitBool() }, { "number", new RadioLimitStringRegEx("^[0-9A-Fa-f]+$") } } } }); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 64, new RadioLimitObject { { "name", new RadioLimitString(1,16, RadioLimitString::ASCII) }, { "contacts", new RadioLimitGroupCallRefList(1,16) } })); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, 1024, // < up to 1024 channels new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored()} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1,16, RadioLimitString::ASCII)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(470.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored() }, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get()) }, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } )); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitSingleZone( 16, { { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, // 16 ASCII chars in name { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions }) ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::ASCII) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, false) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } })); /* Check encryption keys. */ add("commercial", new RadioLimitItem { {"encryptionKeys", new RadioLimitList( BasicEncryptionKey::staticMetaObject, 0, RadioddityCodeplug::EncryptionElement::Limit::keyCount(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{8}")} })} }); /* Ignore positioning systems. */ add("positioning", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); } ================================================ FILE: lib/rd5r_limits.hh ================================================ #ifndef RD5RLIMITS_HH #define RD5RLIMITS_HH #include "radiolimits.hh" /** Implements the configuration limits for the Radioddity RD-5R. * @ingroup rd5r */ class RD5RLimits : public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit RD5RLimits(QObject *parent=nullptr); }; #endif // RD5RLIMITS_HH ================================================ FILE: lib/roamingchannel.cc ================================================ #include "roamingchannel.hh" /* ********************************************************************************************* * * Implementation of RoamingChannel * ********************************************************************************************* */ RoamingChannel::RoamingChannel(QObject *parent) : ConfigObject(parent), _rxFrequency(Frequency::fromHz(0)), _txFrequency(Frequency::fromHz(0)), _overrideColorCode(false), _colorCode(0), _overrideTimeSlot(false), _timeSlot(DMRChannel::TimeSlot::TS1) { // pass... } RoamingChannel::RoamingChannel(const RoamingChannel &other, QObject *parent) : ConfigObject(parent) { copy(other); } void RoamingChannel::clear() { ConfigObject::clear(); _rxFrequency = _txFrequency = Frequency::fromHz(0); _overrideColorCode = false; _colorCode = 0; _overrideTimeSlot = false; _timeSlot = DMRChannel::TimeSlot::TS1; } ConfigItem * RoamingChannel::clone() const { RoamingChannel *c = new RoamingChannel(); if (! c->copy(*this)) { c->deleteLater(); return nullptr; } return c; } Frequency RoamingChannel::rxFrequency() const { return _rxFrequency; } void RoamingChannel::setRXFrequency(Frequency f) { if (f == _rxFrequency) return; _rxFrequency = f; emit modified(this); } Frequency RoamingChannel::txFrequency() const { return _txFrequency; } void RoamingChannel::setTXFrequency(Frequency f) { if (f == _txFrequency) return; _txFrequency = f; emit modified(this); } bool RoamingChannel::colorCodeOverridden() const { return _overrideColorCode; } void RoamingChannel::overrideColorCode(bool override) { if (override == _overrideColorCode) return; _overrideColorCode = override; emit modified(this); } unsigned int RoamingChannel::colorCode() const { return _colorCode; } void RoamingChannel::setColorCode(unsigned int cc) { cc = std::min(15U, cc); if (_colorCode == cc) return; _colorCode = cc; emit modified(this); } bool RoamingChannel::timeSlotOverridden() const { return _overrideTimeSlot; } void RoamingChannel::overrideTimeSlot(bool override) { if (override == _overrideTimeSlot) return; _overrideTimeSlot = override; emit modified(this); } DMRChannel::TimeSlot RoamingChannel::timeSlot() const { return _timeSlot; } void RoamingChannel::setTimeSlot(DMRChannel::TimeSlot ts) { if (_timeSlot == ts) return; _timeSlot = ts; emit modified(this); } RoamingChannel * RoamingChannel::fromDMRChannel(DMRChannel *ch, DMRChannel* ref) { RoamingChannel *rch = new RoamingChannel(); rch->setName(QString("R %1").arg(ch->name())); rch->setRXFrequency(ch->rxFrequency()); rch->setTXFrequency(ch->txFrequency()); rch->overrideColorCode(true); rch->setColorCode(ch->colorCode()); rch->overrideTimeSlot(true); rch->setTimeSlot(ch->timeSlot()); if (nullptr != ref) { if (ch->colorCode() == ref->colorCode()) rch->overrideColorCode(false); if (ch->timeSlot() == ref->timeSlot()) rch->overrideTimeSlot(false); } return rch; } bool RoamingChannel::parse(const YAML::Node &node, Context &ctx, const ErrorStack &err) { if (! ConfigObject::parse(node, ctx, err)) return false; setRXFrequency(node["rxFrequency"].as()); if (node["txFrequency"].IsNull()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << "Cannot parse roaming channel. No txFrequency specified."; return false; } setTXFrequency(node["txFrequency"].as()); if (! node["timeSlot"]) { this->overrideTimeSlot(false); } else { if (! node["timeSlot"].IsScalar()) { errMsg(err) << node["timeSlot"].Mark().line << ":" << node["timeSlot"].Mark().column << "Cannot parse 'timeSlot' of RoamingChannel: time slot is not scalar."; return false; } QMetaEnum e = QMetaEnum::fromType(); std::string key = node["timeSlot"].as(); bool ok=true; int value = e.keyToValue(key.c_str(), &ok); if (! ok) { QStringList lst; for (int i=0; ioverrideTimeSlot(true); this->setTimeSlot((DMRChannel::TimeSlot)value); } if (! node["colorCode"]) { this->overrideTimeSlot(false); } else { if (! node["colorCode"].IsScalar()) { errMsg(err) << node["colorCode"].Mark().line << ":" << node["colorCode"].Mark().column << "Cannot parse 'colorCode' of RoamingChannel: color code is not scalar."; return false; } // finally set property this->overrideColorCode(true); this->setColorCode(node["colorCode"].as()); } return true; } bool RoamingChannel::populate(YAML::Node &node, const Context &context, const ErrorStack &err) { // First, populate scriptable properties if (! ConfigObject::populate(node, context, err)) return false; // Serialize frequency in MHz node["rxFrequency"] = _rxFrequency; node["txFrequency"] = _txFrequency; if (timeSlotOverridden()) { QMetaEnum e = QMetaEnum::fromType(); node["timeSlot"] = e.valueToKey((int)timeSlot()); } if (colorCodeOverridden()) { node["colorCode"] = colorCode(); } return true; } /* ********************************************************************************************* * * Implementation of RoamingChannelList * ********************************************************************************************* */ RoamingChannelList::RoamingChannelList(QObject *parent) : ConfigObjectList(RoamingChannel::staticMetaObject, parent) { // pass... } RoamingChannel * RoamingChannelList::channel(int idx) const { if (ConfigItem *obj = get(idx)) return obj->as(); return nullptr; } int RoamingChannelList::add(ConfigObject *obj, int row, bool unique) { if (obj && obj->is()) return ConfigObjectList::add(obj, row, unique); return -1; } ConfigItem * RoamingChannelList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) return nullptr; if (! node.IsMap()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create roaming zone: Expected object."; return nullptr; } return new RoamingChannel(); } ================================================ FILE: lib/roamingchannel.hh ================================================ #ifndef ROAMINGCHANNEL_HH #define ROAMINGCHANNEL_HH #include "channel.hh" /** Represents a roaming channel. * * That is, an incomplete DMR channel, that overrides some channel * settings to allow for roaming between repeaters. To this end, the roaming channel may override * only those channel settings, that are repeater specific like frequencies and color-codes but * keeps DMR contact settings. Some of these properties are overridden optionally (time slot and * color code) while the RX/TX frequencies are overridden always. * * @ingroup config */ class RoamingChannel : public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "rch") /** Holds the RX frequency in Hz. */ Q_PROPERTY(Frequency rxFrequency READ rxFrequency WRITE setRXFrequency SCRIPTABLE false) /** Holds the TX frequency in Hz. */ Q_PROPERTY(Frequency txFrequency READ txFrequency WRITE setTXFrequency SCRIPTABLE false) /** If @c true, the color code of the channel gets overridden by the one specified in @c colorCode. */ Q_PROPERTY(bool overrideColorCode READ colorCodeOverridden WRITE overrideColorCode SCRIPTABLE false) /** If @c overrideColorCode is @c true, specifies the color code. */ Q_PROPERTY(unsigned int colorCode READ colorCode WRITE setColorCode SCRIPTABLE false) /** If @c true, the time slot of the channel gets overridden by the one specified in @c timeSlot. */ Q_PROPERTY(bool overrideTimeSlot READ timeSlotOverridden WRITE overrideTimeSlot SCRIPTABLE false) /** If @c overrideTimeSlot is @c true, specifies the time slot. */ Q_PROPERTY(DMRChannel::TimeSlot timeSlot READ timeSlot WRITE setTimeSlot SCRIPTABLE false) public: /** Default constructor for a roaming channel. */ Q_INVOKABLE explicit RoamingChannel(QObject *parent = nullptr); /** Copy constructor. */ RoamingChannel(const RoamingChannel &other, QObject *parent=nullptr); ConfigItem *clone() const; void clear(); /** Returns the RX frequency in Hz. */ Frequency rxFrequency() const; /** Sets the RX frequency in Hz. */ void setRXFrequency(Frequency f); /** Returns the TX frequency in Hz. */ Frequency txFrequency() const; /** Sets the TX frequency in Hz. */ void setTXFrequency(Frequency f); /** Returns @c true, if the color code of the channel gets overridden. */ bool colorCodeOverridden() const; /** Enables/disables overriding the color code of the channel. */ void overrideColorCode(bool override); /** Returns the color code. */ unsigned int colorCode() const; /** Sets the color code. */ void setColorCode(unsigned int cc); /** Returns @c true, if the time slot of the channel gets overridden. */ bool timeSlotOverridden() const; /** Enables/disables overriding the time slot of the channel. */ void overrideTimeSlot(bool override); /** Returns the time slot. */ DMRChannel::TimeSlot timeSlot() const; /** Sets the time slot. */ void setTimeSlot(DMRChannel::TimeSlot ts); bool parse(const YAML::Node &node, Context &ctx, const ErrorStack &err); public: /** Helper method to construct a Roaming channel from a given DMR channel. Optionally with * reference to a third one. */ static RoamingChannel *fromDMRChannel(DMRChannel *ch, DMRChannel *ref=nullptr); protected: bool populate(YAML::Node &node, const Context &context, const ErrorStack &err); protected: /** Holds the RX frequency in Hz. */ Frequency _rxFrequency; /** Holds the TX frequency in Hz. */ Frequency _txFrequency; /** If @c true, the color code of the channel gets overridden by the one specified in @c _colorCode. */ bool _overrideColorCode; /** If @c _overrideColorCode is @c true, specifies the color code. */ unsigned int _colorCode; /** If @c true, the time slot of the channel gets overridden by the one specified in @c _timeSlot. */ bool _overrideTimeSlot; /** If @c _overrideTimeSlot is @c true, specifies the time slot. */ DMRChannel::TimeSlot _timeSlot; }; /** Represents the list of roaming channels within the abstract device configuration. * * @ingroup config */ class RoamingChannelList: public ConfigObjectList { Q_OBJECT public: /** Constructor. */ explicit RoamingChannelList(QObject *parent=nullptr); /** Returns the roaming channel at the given index. */ RoamingChannel *channel(int idx) const; int add(ConfigObject *obj, int row=-1, bool unique=true); public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // ROAMINGCHANNEL_HH ================================================ FILE: lib/roamingzone.cc ================================================ #include "roamingzone.hh" #include "channel.hh" #include #include "config.hh" /* ********************************************************************************************* * * Implementation of RoamingZone * ********************************************************************************************* */ RoamingZone::RoamingZone(QObject *parent) : ConfigObject("roam", parent), _channel() { // pass... } RoamingZone::RoamingZone(const QString &name, QObject *parent) : ConfigObject(name, parent), _channel() { // pass... } RoamingZone & RoamingZone::operator =(const RoamingZone &other) { copy(other); return *this; } ConfigItem * RoamingZone::clone() const { RoamingZone *z = new RoamingZone(); if (! z->copy(*this)) { z->deleteLater(); return nullptr; } return z; } int RoamingZone::count() const { return _channel.count(); } void RoamingZone::clear() { _channel.clear(); } bool RoamingZone::contains(const RoamingChannel *ch) const { for (int i=0; i= count())) return nullptr; return _channel.get(idx)->as(); } int RoamingZone::addChannel(RoamingChannel* ch, int row) { row = _channel.add(ch, row); if (0 > row) return row; emit modified(this); return row; } bool RoamingZone::remChannel(int row) { return _channel.del(_channel.get(row)); } bool RoamingZone::remChannel(RoamingChannel* ch) { return _channel.del(ch); } const RoamingChannelRefList * RoamingZone::channels() const { return &_channel; } RoamingChannelRefList* RoamingZone::channels() { return &_channel; } bool RoamingZone::link(const YAML::Node &node, const Context &ctx, const ErrorStack &err) { // First, run default link if (! ConfigObject::link(node, ctx, err)) return false; // Handle channel references separately if (! node["channels"]) return true; // check type if (! node["channels"].IsSequence()) { errMsg(err) << node["channels"].Mark().line << ":" << node["channels"].Mark().column << ": Cannot link 'channels' of 'RoamingZone': Expected sequence."; return false; } YAML::Node lst = node["channels"]; for (YAML::const_iterator it=lst.begin(); it!=lst.end(); it++) { if (! it->IsScalar()) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link 'channels' of 'RoamingZone': Expected ID string."; return false; } QString id = QString::fromStdString(it->as()); if (! ctx.contains(id)) { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link 'channels' of 'RoamingZone': Reference '" << id << "' not defined."; return false; } // Handle referenced object (either DMR channel or roaming channel) ConfigObject *obj = ctx.getObj(id); if (obj->is()) { RoamingChannel *rch = RoamingChannel::fromDMRChannel(obj->as()); config()->roamingChannels()->add(rch); addChannel(rch); } else if (obj->is()) { addChannel(obj->as()); } else { errMsg(err) << it->Mark().line << ":" << it->Mark().column << ": Cannot link 'channels' of 'RoamingZone': " << "Cannot add reference to '" << id << "' to list. " << "Not a roaming channel."; return false; } } return true; } bool RoamingZone::populate(YAML::Node &node, const Context &context, const ErrorStack &err) { if (! ConfigObject::populate(node, context, err)) return false; // Serialize list of channel references. for (int i=0; iname() << "': No ID assigned."; return false; } node["channels"].push_back(context.getId(channel(i)).toStdString()); } return true; } /* ********************************************************************************************* * * Implementation of DefaultRoamingZone * ********************************************************************************************* */ DefaultRoamingZone *DefaultRoamingZone::_instance = nullptr; DefaultRoamingZone::DefaultRoamingZone(QObject *parent) : RoamingZone(tr("[Default]"), parent) { // pass... } DefaultRoamingZone * DefaultRoamingZone::get() { if (nullptr == _instance) _instance = new DefaultRoamingZone(); return _instance; } /* ********************************************************************************************* * * Implementation of RoamingZoneList * ********************************************************************************************* */ RoamingZoneList::RoamingZoneList(QObject *parent) : ConfigObjectList(RoamingZone::staticMetaObject, parent) { // pass... } RoamingZone * RoamingZoneList::zone(int idx) const { if (ConfigItem *obj = get(idx)) return obj->as(); return nullptr; } int RoamingZoneList::add(ConfigObject *obj, int row, bool unique) { if (obj && obj->is()) return ConfigObjectList::add(obj, row, unique); return -1; } ConfigItem * RoamingZoneList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) return nullptr; if (! node.IsMap()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create roaming zone: Expected object."; return nullptr; } return new RoamingZone(); } ================================================ FILE: lib/roamingzone.hh ================================================ #ifndef ROAMINGZONE_HH #define ROAMINGZONE_HH #include #include "configreference.hh" #include "roamingchannel.hh" /** Represents a RoamingZone within the abstract device configuration. * * A roaming zone collects a set of repeaters that act as alternatives to each other. When a selected * repeater gets out of range, another one might be found automatically from within the roaming zone. * * @ingroup config */ class RoamingZone : public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "roam") /** The channels in the roaming zone. * @todo This property is marked non-scriptable to handle references to DMR channels before * version 0.11.0. Remove in future. */ Q_PROPERTY(RoamingChannelRefList * channels READ channels SCRIPTABLE false) public: /** Default constructor. */ Q_INVOKABLE explicit RoamingZone(QObject *parent=nullptr); /** Constructor. * @param name Specifies the name of the roaming zone. * @param parent Specifies the QObject parent of this zone. */ RoamingZone(const QString &name, QObject *parent = nullptr); /** Copies the given zone. */ RoamingZone &operator =(const RoamingZone &other); ConfigItem *clone() const; /** Returns the number of zones. */ int count() const; /** Clears the zone list. */ void clear(); /** Returns @c true, if the given roaming channel is member of this zone. */ bool contains(const RoamingChannel *ch) const; /** Returns the roaming channel, which is the member at index @c idx (0-based). * @param idx Specifies the index of the member channel. */ RoamingChannel *channel(int idx) const; /** Adds a channel to the roaming zone. * @param ch Specifies the channel to add. * @param row Speicifies the index where to insert the channel * (optional, default insert at end). */ int addChannel(RoamingChannel *ch, int row=-1); /** Removes the channel from the roaming zone at index @c row. */ bool remChannel(int row); /** Removes the given channel from the roaming zone. */ bool remChannel(RoamingChannel *ch); /** Returns the list of digital channels in this roaming zone. */ const RoamingChannelRefList *channels() const; /** Returns the list of digital channels in this roaming zone. */ RoamingChannelRefList *channels(); /** Links the channel reference list. * @todo Implemented for backward compatibility with version 0.10.0, remove for 1.0.0. */ bool link(const YAML::Node &node, const Context &ctx, const ErrorStack &err); /** Serializes the channel reference list. * @todo Implemented for backward compatibility with version 0.10.0, remove for 1.0.0. */ bool populate(YAML::Node &node, const Context &context, const ErrorStack &err); protected: /** Holds the actual channels of the roaming zone. */ RoamingChannelRefList _channel; }; /** Dummy roaming zone class that represents the default roaming zone. * * This is a singleton class. That is, there can only be one instance of this class. * @ingroup config */ class DefaultRoamingZone: public RoamingZone { Q_OBJECT protected: /** Hidden constructor. * Use @c DefaultRoamingZone::get() to obtain an instance. */ explicit DefaultRoamingZone(QObject *parent=nullptr); public: /** Returns the singleton instance of this class. */ static DefaultRoamingZone *get(); protected: /** Holds a reference to the singleton instance of this class. */ static DefaultRoamingZone *_instance; }; /** Represents the list of roaming zones within the abstract device configuration. * * @ingroup config */ class RoamingZoneList: public ConfigObjectList { Q_OBJECT public: /** Constructor. */ explicit RoamingZoneList(QObject *parent=nullptr); /** Returns the roaming zone at the given index. */ RoamingZone *zone(int idx) const; int add(ConfigObject *obj, int row=-1, bool unique=true); public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // ROAMINGZONE_HH ================================================ FILE: lib/rxgrouplist.cc ================================================ #include "rxgrouplist.hh" #include "contact.hh" #include "config.hh" /* ********************************************************************************************* * * Implementation of RXGroupList * ********************************************************************************************* */ RXGroupList::RXGroupList(QObject *parent) : ConfigObject(parent), _contacts() { connect(&_contacts, SIGNAL(elementModified(int)), this, SLOT(onModified())); connect(&_contacts, SIGNAL(elementRemoved(int)), this, SLOT(onModified())); connect(&_contacts, SIGNAL(elementAdded(int)), this, SLOT(onModified())); } RXGroupList::RXGroupList(const QString &name, QObject *parent) : ConfigObject(name, parent), _contacts() { connect(&_contacts, SIGNAL(elementModified(int)), this, SLOT(onModified())); connect(&_contacts, SIGNAL(elementRemoved(int)), this, SLOT(onModified())); connect(&_contacts, SIGNAL(elementAdded(int)), this, SLOT(onModified())); } RXGroupList & RXGroupList::operator =(const RXGroupList &other) { copy(other); return *this; } ConfigItem * RXGroupList::clone() const { RXGroupList *lst = new RXGroupList(); if (! lst->copy(*this)) { lst->deleteLater(); return nullptr; } return lst; } int RXGroupList::count() const { return _contacts.count(); } void RXGroupList::clear() { _contacts.clear(); emit modified(this); } DMRContact * RXGroupList::contact(int idx) const { if (idx >= _contacts.count()) return nullptr; return _contacts.get(idx)->as(); } int RXGroupList::addContact(DMRContact *contact, int idx) { return _contacts.add(contact, idx); } bool RXGroupList::remContact(int idx) { return _contacts.del(_contacts.get(idx)); } const DMRContactRefList * RXGroupList::contacts() const { return &_contacts; } DMRContactRefList * RXGroupList::contacts() { return &_contacts; } YAML::Node RXGroupList::serialize(const Context &context, const ErrorStack &err) { YAML::Node node = ConfigObject::serialize(context, err); node.SetStyle(YAML::EmitterStyle::Flow); return node; } void RXGroupList::onModified() { emit modified(this); } /* ********************************************************************************************* * * Implementation of RXGroupLists * ********************************************************************************************* */ RXGroupLists::RXGroupLists(QObject *parent) : ConfigObjectList(RXGroupList::staticMetaObject, parent) { // pass... } RXGroupList * RXGroupLists::list(int idx) const { if (ConfigItem *obj = get(idx)) return obj->as(); return nullptr; } int RXGroupLists::add(ConfigObject *obj, int row, bool unique) { if (obj && obj->is()) return ConfigObjectList::add(obj, row, unique); return -1; } ConfigItem * RXGroupLists::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) return nullptr; if (! node.IsMap()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create group list: Expected object."; return nullptr; } return new RXGroupList(); } ================================================ FILE: lib/rxgrouplist.hh ================================================ #ifndef RXGROUPLIST_HH #define RXGROUPLIST_HH #include #include "configreference.hh" class Config; class DMRContact; /** Generic representation of a RX group list. * @ingroup conf */ class RXGroupList: public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "grp") /** The list of contacts. */ Q_PROPERTY(DMRContactRefList* contacts READ contacts) public: /** Default constructor. */ Q_INVOKABLE explicit RXGroupList(QObject *parent=nullptr); /** Constructor. * @param name Specifies the name of the group list. * @param parent @c QObject parent instance. */ RXGroupList(const QString &name, QObject *parent=nullptr); /** Copy from other group list. */ RXGroupList &operator =(const RXGroupList &other); ConfigItem *clone() const; /** Returns the number of contacts within the group list. */ int count() const; /** Resets & clears this group list. */ void clear(); /** Returns the contact at the given list index. */ DMRContact *contact(int idx) const; /** Adds a contact to the list. */ int addContact(DMRContact *contact, int idx=-1); /** Removes the given contact from the list. */ bool remContact(DMRContact *contact); /** Removes the contact from the list at the given position. */ bool remContact(int idx); /** Returns the contact list. */ const DMRContactRefList *contacts() const; /** Returns the contact list. */ DMRContactRefList *contacts(); public: YAML::Node serialize(const Context &context, const ErrorStack &err=ErrorStack()); protected slots: /** Internal used callback to handle list modifications. */ void onModified(); protected: /** The list of contacts. */ DMRContactRefList _contacts; }; /** Represents the list of RX group lists within the generic configuration. * @ingroup conf */ class RXGroupLists: public ConfigObjectList { Q_OBJECT public: /** Constructor. */ explicit RXGroupLists(QObject *parent=nullptr); /** Returns the group list at the given index. */ RXGroupList *list(int idx) const; int add(ConfigObject *obj, int row=-1, bool unique=true); public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // RXGROUPLIST_HH ================================================ FILE: lib/satelliteconfig.cc ================================================ #include "satelliteconfig.hh" SatelliteConfig::SatelliteConfig(QObject *parent) : DFUFile{parent} { // pass... } ================================================ FILE: lib/satelliteconfig.hh ================================================ #ifndef SATELLITECONFIG_HH #define SATELLITECONFIG_HH #include "dfufile.hh" // Forward declarations class SatelliteDatabase; /** Baseclass of all satellite database configurations. * That is, the device specific encoding of the satellite orbitals and transponder information. * @ingroup conf */ class SatelliteConfig : public DFUFile { Q_OBJECT protected: /** Hidden constructor. */ explicit SatelliteConfig(QObject *parent = nullptr); public: /** Encodes the given satellite db into the device specific satellite configuration. */ virtual bool encode(SatelliteDatabase *db, const ErrorStack &err) = 0; }; #endif // SATELLITECONFIG_HH ================================================ FILE: lib/satellitedatabase.cc ================================================ #include "satellitedatabase.hh" #include #include #include #include #include #include #include #include #include "logger.hh" /* ********************************************************************************************* * * Implementation of Satellite * ********************************************************************************************* */ Satellite::Satellite() : OrbitalElement(), _name(), _fmUplink(), _fmDownlink(), _fmUplinkTone(), _fmDownlinkTone(), _aprsUplink(), _aprsDownlink(), _aprsUplinkTone(), _aprsDownlinkTone(), _beacon() { // pass... } Satellite::Satellite(const OrbitalElement &orbit) : OrbitalElement(orbit), _name(orbit.name()), _fmUplink(), _fmDownlink(), _fmUplinkTone(), _fmDownlinkTone(), _aprsUplink(), _aprsDownlink(), _aprsUplinkTone(), _aprsDownlinkTone(), _beacon() { // pass... } const QString & Satellite::name() const { return _name; } void Satellite::setName(const QString &name) { _name = name.simplified(); } const Frequency & Satellite::fmUplink() const { return _fmUplink; } void Satellite::setFMUplink(const Frequency &f) { _fmUplink = f; } const Frequency & Satellite::fmDownlink() const { return _fmDownlink; } void Satellite::setFMDownlink(const Frequency &f) { _fmDownlink = f; } const SelectiveCall & Satellite::fmUplinkTone() const { return _fmUplinkTone; } void Satellite::setFMUplinkTone(const SelectiveCall &tone) { _fmUplinkTone = tone; } const SelectiveCall & Satellite::fmDownlinkTone() const { return _fmDownlinkTone; } void Satellite::setFMDownlinkTone(const SelectiveCall &tone) { _fmDownlinkTone = tone; } const Frequency & Satellite::aprsUplink() const { return _aprsUplink; } void Satellite::setAPRSUplink(const Frequency &f) { _aprsUplink = f; } const Frequency & Satellite::aprsDownlink() const { return _aprsDownlink; } void Satellite::setAPRSDownlink(const Frequency &f) { _aprsDownlink = f; } const SelectiveCall & Satellite::aprsUplinkTone() const { return _aprsUplinkTone; } void Satellite::setAPRSUplinkTone(const SelectiveCall &tone) { _aprsUplinkTone = tone; } const SelectiveCall & Satellite::aprsDownlinkTone() const { return _aprsDownlinkTone; } void Satellite::setAPRSDownlinkTone(const SelectiveCall &tone) { _aprsDownlinkTone = tone; } const Frequency & Satellite::beacon() const { return _beacon; } void Satellite::setBeacon(const Frequency &f) { _beacon = f; } QJsonObject Satellite::toJson() const { if (! isValid()) return QJsonObject(); QJsonObject o = QJsonObject(); o.insert("norad", QJsonValue(qint64(id()))); o.insert("name", name()); if (0 != fmUplink().inHz()) o.insert("fm_uplink", fmUplink().format()); if (fmUplinkTone().isValid()) o.insert("fm_uplink_tone", fmUplinkTone().format()); if (0 != fmDownlink().inHz()) o.insert("fm_downlink", fmDownlink().format()); if (fmDownlinkTone().isValid()) o.insert("fm_downlink_tone", fmDownlinkTone().format()); if (0 != aprsUplink().inHz()) o.insert("aprs_uplink", aprsUplink().format()); if (aprsUplinkTone().isValid()) o.insert("aprs_uplink_tone", aprsUplinkTone().format()); if (0 != aprsDownlink().inHz()) o.insert("aprs_downlink", aprsDownlink().format()); if (aprsDownlinkTone().isValid()) o.insert("aprs_downlink_tone", aprsDownlinkTone().format()); if (0 != beacon().inHz()) o.insert("beacon", beacon().format()); return o; } Satellite Satellite::fromJson(const QJsonObject &obj, const OrbitalElementsDatabase &db) { unsigned int id = obj.value("norad").toInt(); QString name = obj.value("name").toString(); if (! db.contains(id)) return Satellite(); Satellite sat(db.getById(id)); sat._name = name; if (obj.contains("fm_uplink")) sat._fmUplink.parse(obj.value("fm_uplink").toString()); if (obj.contains("fm_uplink_tone")) sat._fmUplinkTone = SelectiveCall::parseCTCSS(obj.value("fm_uplink_tone").toString()); if (obj.contains("fm_downlink")) sat._fmDownlink.parse(obj.value("fm_downlink").toString()); if (obj.contains("fm_downlink_tone")) sat._fmDownlinkTone = SelectiveCall::parseCTCSS(obj.value("fm_downlink_tone").toString()); if (obj.contains("aprs_uplink")) sat._aprsUplink.parse(obj.value("aprs_uplink").toString()); if (obj.contains("aprs_uplink_tone")) sat._aprsUplinkTone = SelectiveCall::parseCTCSS(obj.value("aprs_uplink_tone").toString()); if (obj.contains("aprs_downlink")) sat._aprsDownlink.parse(obj.value("aprs_downlink").toString()); if (obj.contains("aprs_downlink_tone")) sat._aprsDownlinkTone = SelectiveCall::parseCTCSS(obj.value("aprs_downlink_tone").toString()); if (obj.contains("beacon")) sat._beacon.parse(obj.value("beacon").toString()); return sat; } /* ********************************************************************************************* * * Implementation of SatelliteDatabase * ********************************************************************************************* */ SatelliteDatabase::SatelliteDatabase(unsigned int updatePeriod, QObject *parent) : QAbstractTableModel{parent}, _satellites(), _orbitalElements(false, updatePeriod), _transponders(false, updatePeriod) { connect(&_orbitalElements, &OrbitalElementsDatabase::loaded, this, &SatelliteDatabase::load); _orbitalElements.load(); _transponders.load(); } const OrbitalElementsDatabase & SatelliteDatabase::orbitalElements() const { return _orbitalElements; } const TransponderDatabase & SatelliteDatabase::transponders() const { return _transponders; } OrbitalElementsDatabase & SatelliteDatabase::orbitalElements() { return _orbitalElements; } TransponderDatabase & SatelliteDatabase::transponders() { return _transponders; } unsigned int SatelliteDatabase::count() const { return _satellites.count(); } const Satellite & SatelliteDatabase::getAt(unsigned int idx) const { return _satellites[idx]; } bool SatelliteDatabase::setAt(const Satellite &sat, unsigned int idx) { if (idx >= count()) return false; _satellites[idx] = sat; emit dataChanged(index(idx, 0), index(idx,columnCount())); return true; } void SatelliteDatabase::add(const Satellite &sat) { if (! sat.isValid()) return; beginInsertRows(QModelIndex(), _satellites.count(), _satellites.count()); _satellites.append(sat); endInsertRows(); } bool SatelliteDatabase::removeRows(int row, int count, const QModelIndex &parent) { if ((row >= _satellites.count()) || ((row+count) > _satellites.count())) return false; if (0 == count) return true; beginRemoveRows(parent, row, row+count-1); _satellites.remove(row, count); endRemoveRows(); return true; } int SatelliteDatabase::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return _satellites.size(); } int SatelliteDatabase::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 11; } QVariant SatelliteDatabase::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::Horizontal != orientation) || (Qt::DisplayRole != role)) return QVariant(); switch (section) { case 0: return tr("NORAD"); case 1: return tr("Name"); case 2: return tr("FM Downlink Frequency"); case 3: return tr("FM Uplink Frequency"); case 4: return tr("FM Downlink Tone"); case 5: return tr("FM Uplink Tone"); case 6: return tr("APRS Downlink Frequency"); case 7: return tr("APRS Uplink Frequency"); case 8: return tr("APRS Downlink Tone"); case 9: return tr("APRS Uplink Tone"); case 10: return tr("Beacon Frequency"); } return QVariant(); } Qt::ItemFlags SatelliteDatabase::flags(const QModelIndex &index) const { Qt::ItemFlags f = Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; // Name if (1 == index.column()) f |= Qt::ItemIsEditable; // FM up/downlink frequencies if ((2 == index.column()) || (3 == index.column())) f |= Qt::ItemIsEditable; // FM up/downlink sub tones if ((4 == index.column()) || (5 == index.column())) f |= Qt::ItemIsEditable; // APRS up/downlink frequencies if ((6 == index.column()) || (7 == index.column())) f |= Qt::ItemIsEditable; // APRS up/downlink sub tones if ((8 == index.column()) || (9 == index.column())) f |= Qt::ItemIsEditable; // Beacon if (10 == index.column()) f |= Qt::ItemIsEditable; return f; } QVariant SatelliteDatabase::data(const QModelIndex &index, int role) const { if (index.row() >= _satellites.count()) return QVariant(); if (Qt::DisplayRole == role) { switch (index.column()) { case 0: return _satellites.at(index.row()).id(); case 1: return _satellites.at(index.row()).name(); case 2: return (0 == _satellites.at(index.row()).fmDownlink().inHz()) ? tr("None") : _satellites.at(index.row()).fmDownlink().format(); case 3: return (0 == _satellites.at(index.row()).fmUplink().inHz()) ? tr("None") : _satellites.at(index.row()).fmUplink().format(); case 4: return _satellites.at(index.row()).fmDownlinkTone().format(); case 5: return _satellites.at(index.row()).fmUplinkTone().format(); case 6: return (0 == _satellites.at(index.row()).aprsDownlink().inHz()) ? tr("None") : _satellites.at(index.row()).aprsDownlink().format(); case 7: return (0 == _satellites.at(index.row()).aprsUplink().inHz()) ? tr("None") : _satellites.at(index.row()).aprsUplink().format(); case 8: return _satellites.at(index.row()).aprsDownlinkTone().format(); case 9: return _satellites.at(index.row()).aprsUplinkTone().format(); case 10: return (0 == _satellites.at(index.row()).beacon().inHz()) ? tr("None") : _satellites.at(index.row()).beacon().format(); } } else if (Qt::EditRole == role) { switch (index.column()) { case 0: return _satellites.at(index.row()).id(); case 1: return _satellites.at(index.row()).name(); case 2: return QVariant::fromValue(_satellites.at(index.row()).fmDownlink()); case 3: return QVariant::fromValue(_satellites.at(index.row()).fmUplink()); case 4: return QVariant::fromValue(_satellites.at(index.row()).fmDownlinkTone()); case 5: return QVariant::fromValue(_satellites.at(index.row()).fmUplinkTone()); case 6: return QVariant::fromValue(_satellites.at(index.row()).aprsDownlink()); case 7: return QVariant::fromValue(_satellites.at(index.row()).aprsUplink()); case 8: return QVariant::fromValue(_satellites.at(index.row()).aprsDownlinkTone()); case 9: return QVariant::fromValue(_satellites.at(index.row()).aprsUplinkTone()); case 10: return QVariant::fromValue(_satellites.at(index.row()).beacon()); } } return QVariant(); } bool SatelliteDatabase::setData(const QModelIndex &index, const QVariant &value, int role) { if (Qt::EditRole != role) return false; if (index.row() >= _satellites.count()) return false; switch (index.column()) { case 1: _satellites[index.row()].setName(value.toString().simplified()); return true; case 2: _satellites[index.row()].setFMDownlink(value.value()); return true; case 3: _satellites[index.row()].setFMUplink(value.value()); return true; case 4: _satellites[index.row()].setFMDownlinkTone(value.value()); return true; case 5: _satellites[index.row()].setFMUplinkTone(value.value()); return true; case 6: _satellites[index.row()].setAPRSDownlink(value.value()); return true; case 7: _satellites[index.row()].setAPRSUplink(value.value()); return true; case 8: _satellites[index.row()].setAPRSDownlinkTone(value.value()); return true; case 9: _satellites[index.row()].setAPRSUplinkTone(value.value()); return true; case 10: _satellites[index.row()].setBeacon(value.value()); return true; } return false; } void SatelliteDatabase::update() { _orbitalElements.download(); _transponders.download(); } void SatelliteDatabase::load() { QString filename = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/satellites.json"; QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { QString msg = QString("Cannot open satellites '%1': %2").arg(filename).arg(file.errorString()); logError() << msg; emit error(msg); return; } QByteArray data = file.readAll(); file.close(); QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (doc.isEmpty()) { QString msg = "Failed to load satellites: " + err.errorString(); logError() << msg; emit error(msg); return; } if (! doc.isArray()) { QString msg = "Failed to load satellites: JSON document is not an array!"; logError() << msg; emit error(msg); return; } beginResetModel(); QJsonArray array = doc.array(); _satellites.clear(); _satellites.reserve(array.size()); for (int i=0; i _satellites; /** Holds the orbital element database. */ OrbitalElementsDatabase _orbitalElements; /** Holds the transponder database. */ TransponderDatabase _transponders; }; #endif // SATELLITEDATABASE_HH ================================================ FILE: lib/scanlist.cc ================================================ #include "scanlist.hh" #include "channel.hh" /* ********************************************************************************************* * * Implementation of ScanList * ********************************************************************************************* */ ScanList::ScanList(QObject *parent) : ConfigObject(parent), _channels(), _primary(), _secondary(), _revert(), _tyt(nullptr) { // Register "selected" channel tags for primary, secondary, revert and the channel list. Context::setTag(staticMetaObject.className(), "primary", "!selected", QVariant::fromValue(SelectedChannel::get())); Context::setTag(staticMetaObject.className(), "secondary", "!selected", QVariant::fromValue(SelectedChannel::get())); Context::setTag(staticMetaObject.className(), "revert", "!selected", QVariant::fromValue(SelectedChannel::get())); Context::setTag(staticMetaObject.className(), "channels", "!selected", QVariant::fromValue(SelectedChannel::get())); } ScanList::ScanList(const QString &name, QObject *parent) : ConfigObject(name, parent), _channels(), _primary(), _secondary(), _revert(), _tyt(nullptr) { // Register "selected" channel tags for primary, secondary, revert and the channel list. Context::setTag(staticMetaObject.className(), "primary", "!selected", QVariant::fromValue(SelectedChannel::get())); Context::setTag(staticMetaObject.className(), "secondary", "!selected", QVariant::fromValue(SelectedChannel::get())); Context::setTag(staticMetaObject.className(), "revert", "!selected", QVariant::fromValue(SelectedChannel::get())); Context::setTag(staticMetaObject.className(), "channels", "!selected", QVariant::fromValue(SelectedChannel::get())); } ScanList & ScanList::operator =(const ScanList &other) { copy(other); return *this; } ConfigItem * ScanList::clone() const { ScanList *list = new ScanList(); if (! list->copy(*this)) { list->deleteLater(); return nullptr; } return list; } void ScanList::clear() { _name.clear(); _primary.clear(); _secondary.clear(); _revert.clear(); _channels.clear(); emit modified(this); } const ChannelRefList * ScanList::channels() const { return &_channels; } ChannelRefList * ScanList::channels() { return &_channels; } int ScanList::count() const { return _channels.count(); } bool ScanList::contains(Channel *channel) const { return (0 <= _channels.indexOf(channel)); } Channel * ScanList::channel(int idx) const { return _channels.get(idx)->as(); } int ScanList::addChannel(Channel *channel, int idx) { idx = _channels.add(channel, idx); if (0 > idx) return idx; return idx; } bool ScanList::remChannel(int idx) { return _channels.del(_channels.get(idx)); emit modified(this); return true; } bool ScanList::remChannel(Channel *channel) { return _channels.del(channel); } const ChannelReference * ScanList::primaryChannelRef() const { return &_primary; } ChannelReference * ScanList::primaryChannelRef() { return &_primary; } Channel * ScanList::primaryChannel() const { return _primary.as(); } void ScanList::setPrimaryChannel(Channel *channel) { _primary.set(channel); emit modified(this); } const ChannelReference * ScanList::secondaryChannelRef() const { return &_secondary; } ChannelReference * ScanList::secondaryChannelRef() { return &_secondary; } Channel * ScanList::secondaryChannel() const { return _secondary.as(); } void ScanList::setSecondaryChannel(Channel *channel) { _secondary.set(channel); emit modified(this); } const ChannelReference * ScanList::revertChannelRef() const { return &_revert; } ChannelReference * ScanList::revertChannelRef() { return &_revert; } Channel * ScanList::revertChannel() const { return _revert.as(); } void ScanList::setRevertChannel(Channel *channel) { _revert.set(channel); emit modified(this); } TyTScanListExtension * ScanList::tytScanListExtension() const { return _tyt; } void ScanList::setTyTScanListExtension(TyTScanListExtension *tyt) { if (_tyt) { _tyt->deleteLater(); _tyt = nullptr; } _tyt = tyt; if (_tyt) _tyt->setParent(this); } /* ********************************************************************************************* * * Implementation of ScanLists * ********************************************************************************************* */ ScanLists::ScanLists(QObject *parent) : ConfigObjectList(ScanList::staticMetaObject, parent) { // pass... } ScanList * ScanLists::scanlist(int idx) const { if (ConfigItem *obj = get(idx)) return obj->as(); return nullptr; } int ScanLists::add(ConfigObject *obj, int row, bool unique) { if (obj && obj->is()) return ConfigObjectList::add(obj, row, unique); return -1; } ConfigItem * ScanLists::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx); if (! node) return nullptr; if (! node.IsMap()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create scan list: Expected object."; return nullptr; } return new ScanList(); } ================================================ FILE: lib/scanlist.hh ================================================ #ifndef SCANLIST_HH #define SCANLIST_HH #include "configobject.hh" #include "configreference.hh" #include "tyt_extensions.hh" class Channel; /** Generic representation of a scan list. * @ingroup conf */ class ScanList : public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "scan") /** The primaryRef channel. */ Q_PROPERTY(ChannelReference* primary READ primaryChannelRef) /** The secondary channel. */ Q_PROPERTY(ChannelReference* secondary READ secondaryChannelRef) /** The revert channel. */ Q_PROPERTY(ChannelReference* revert READ revertChannelRef) /** The list of channels. */ Q_PROPERTY(ChannelRefList * channels READ channels) /** The TyT scan-list extension. */ Q_PROPERTY(TyTScanListExtension* tyt READ tytScanListExtension WRITE setTyTScanListExtension) public: /** Default constructor. */ Q_INVOKABLE explicit ScanList(QObject *parent=nullptr); /** Constructs a scan list with the given name. */ ScanList(const QString &name, QObject *parent=nullptr); /** Copies the given scan list. */ ScanList &operator= (const ScanList &other); ConfigItem *clone() const; /** Returns the number of channels within the scanlist. */ int count() const; /** Clears the scan list. */ void clear(); /** Returns @c true if the given channel is part of this scanlist. */ bool contains(Channel *channel) const; /** Returns the channel at the given index. */ Channel *channel(int idx) const; /** Adds a channel to the scan list. */ int addChannel(Channel *channel, int idx=-1); /** Removes the channel at the given index. */ bool remChannel(int idx); /** Removes the given channel. */ bool remChannel(Channel *channel); /** Returns the channels of the scan list. */ const ChannelRefList *channels() const; /** Returns the channels of the scan list. */ ChannelRefList *channels(); /** Returns the primary channel reference. */ const ChannelReference *primaryChannelRef() const; /** Returns the primary channel reference. */ ChannelReference *primaryChannelRef(); /** Returns the priority channel. */ Channel *primaryChannel() const; /** Sets the priority channel. */ void setPrimaryChannel(Channel *channel); /** Returns the secondary channel reference. */ const ChannelReference *secondaryChannelRef() const; /** Returns the secondary channel reference. */ ChannelReference *secondaryChannelRef(); /** Returns the secondary priority channel. */ Channel *secondaryChannel() const; /** Sets the secondary priority channel. */ void setSecondaryChannel(Channel *channel); /** Returns the revert channel reference. */ const ChannelReference *revertChannelRef() const; /** Returns the revert channel reference. */ ChannelReference *revertChannelRef(); /** Returns the TX channel. */ Channel *revertChannel() const; /** Sets the TX channel. */ void setRevertChannel(Channel *channel); /** Returns the TyT scan-list extension instance (if set). */ TyTScanListExtension *tytScanListExtension() const; /** Sets the TyT scan-list extension. */ void setTyTScanListExtension(TyTScanListExtension *tyt); protected: /** The channel list. */ ChannelRefList _channels; /** The priority channel. */ ChannelReference _primary; /** The secondary priority channel. */ ChannelReference _secondary; /** The transmit channel. */ ChannelReference _revert; /** TyT scan-list settings extension. */ TyTScanListExtension *_tyt; }; /** Represents the list of scan lists. * @ingroup conf */ class ScanLists: public ConfigObjectList { Q_OBJECT public: /** Constructs an empty list. */ explicit ScanLists(QObject *parent = nullptr); /** Returns the scanlist at the given index. */ ScanList *scanlist(int idx) const; int add(ConfigObject *obj, int row=-1, bool unique=true); public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // SCANLIST_HH ================================================ FILE: lib/signaling.cc ================================================ #include "signaling.hh" #include #include #include #include #include /* ********************************************************************************************* * * Implementation of SelectiveCall * ********************************************************************************************* */ QVector SelectiveCall::_standard = { SelectiveCall(), 67.0, 71.9, 74.4, 77.0, 79.7, 82.5, 85.4, 88.5, 91.5, 94.8, 97.4, 100.0, 103.5, 107.2, 110.9, 114.8, 118.8, 123.0, 127.3, 131.8, 136.5, 141.3, 146.2, 151.4, 156.7, 162.2, 167.9, 173.8, 179.9, 186.2, 192.8, 203.5, 210.7, 218.1, 225.7, 233.6, 241.8, 250.3, { 23, false}, { 25, false}, { 26, false}, { 31, false}, { 32, false}, { 36, false}, { 43, false}, { 47, false}, { 51, false}, { 53, false}, { 54, false}, { 71, false}, { 72, false}, { 73, false}, { 74, false}, {114, false}, {115, false}, {116, false}, {122, false}, {125, false}, {131, false}, {132, false}, {134, false}, {143, false}, {145, false}, {152, false}, {155, false}, {156, false}, {162, false}, {165, false}, {172, false}, {174, false}, {205, false}, {212, false}, {223, false}, {225, false}, {226, false}, {243, false}, {244, false}, {245, false}, {246, false}, {251, false}, {252, false}, {255, false}, {261, false}, {263, false}, {265, false}, {266, false}, {267, false}, {271, false}, {274, false}, {306, false}, {311, false}, {315, false}, {325, false}, {331, false}, {332, false}, {343, false}, {346, false}, {351, false}, {356, false}, {364, false}, {365, false}, {371, false}, {411, false}, {412, false}, {413, false}, {423, false}, {431, false}, {432, false}, {445, false}, {446, false}, {452, false}, {454, false}, {455, false}, {462, false}, {464, false}, {465, false}, {466, false}, {503, false}, {506, false}, {516, false}, {523, false}, {526, false}, {532, false}, {546, false}, {565, false}, {606, false}, {612, false}, {624, false}, {627, false}, {631, false}, {632, false}, {654, false}, {662, false}, {664, false}, {703, false}, {712, false}, {723, false}, {731, false}, {732, false}, {734, false}, {743, false}, {754, false}, { 23, true}, { 25, true}, { 26, true}, { 31, true}, { 32, true}, { 36, true}, { 43, true}, { 47, true}, { 51, true}, { 53, true}, { 54, true}, { 71, true}, { 72, true}, { 73, true}, { 74, true}, {114, true}, {115, true}, {116, true}, {122, true}, {125, true}, {131, true}, {132, true}, {134, true}, {143, true}, {145, true}, {152, true}, {155, true}, {156, true}, {162, true}, {165, true}, {172, true}, {174, true}, {205, true}, {212, true}, {223, true}, {225, true}, {226, true}, {243, true}, {244, true}, {245, true}, {246, true}, {251, true}, {252, true}, {255, true}, {261, true}, {263, true}, {265, true}, {266, true}, {267, true}, {271, true}, {274, true}, {306, true}, {311, true}, {315, true}, {325, true}, {331, true}, {332, true}, {343, true}, {346, true}, {351, true}, {356, true}, {364, true}, {365, true}, {371, true}, {411, true}, {412, true}, {413, true}, {423, true}, {431, true}, {432, true}, {445, true}, {446, true}, {452, true}, {454, true}, {455, true}, {462, true}, {464, true}, {465, true}, {466, true}, {503, true}, {506, true}, {516, true}, {523, true}, {526, true}, {532, true}, {546, true}, {565, true}, {606, true}, {612, true}, {624, true}, {627, true}, {631, true}, {632, true}, {654, true}, {662, true}, {664, true}, {703, true}, {712, true}, {723, true}, {731, true}, {732, true}, {734, true}, {743, true}, {754, true} }; SelectiveCall::SelectiveCall() : type(Type::None), dcs{0,false} { // Pass... } SelectiveCall::SelectiveCall(double ctcssFreq) : type(Type::CTCSS), ctcss(ctcssFreq*10) { // pass... } SelectiveCall::SelectiveCall(unsigned int octalDSCCode, bool inverted) : type(Type::DCS), dcs{0, inverted} { unsigned int e = 1; while (octalDSCCode) { dcs.code += std::min(7U, (octalDSCCode % 10)) * e; e *= 8; octalDSCCode /= 10; } } bool SelectiveCall::operator==(const SelectiveCall &other) const { if (type != other.type) return false; if (Type::CTCSS == type) return ctcss == other.ctcss; return (dcs.code == other.dcs.code) && (dcs.inverted == other.dcs.inverted); } bool SelectiveCall::operator !=(const SelectiveCall &other) const { return !(*this == other); } bool SelectiveCall::isInvalid() const { return Type::None == type; } bool SelectiveCall::isValid() const { return Type::None != type; } bool SelectiveCall::isCTCSS() const { return Type::CTCSS == type; } bool SelectiveCall::isDCS() const { return Type::DCS == type; } double SelectiveCall::Hz() const { return double(ctcss)/10; } unsigned int SelectiveCall::mHz() const { return ((unsigned int)ctcss)*100; } unsigned int SelectiveCall::binCode() const { return dcs.code; } unsigned int SelectiveCall::octalCode() const { unsigned int o=0, e=1, c=dcs.code; while (c) { o += (c%8)*e; e *= 10; c/= 8; } return o; } bool SelectiveCall::isInverted() const { return dcs.inverted; } QString SelectiveCall::format() const { if (! isValid()) return QString(); if (isCTCSS()) return QString("%1 Hz").arg(Hz(), 0, 'f', 1); return QString("%1%2") .arg(isInverted() ? "i" : "n") .arg(binCode(), 3, 8, QChar('0')); } SelectiveCall SelectiveCall::parseCTCSS(const QString &text) { QRegularExpression re(R"(([0-9]+(?:\.[0-9]|))\s*(?:Hz|))"); QRegularExpressionMatch match = re.match(text); if (! match.hasMatch()) return SelectiveCall(); return SelectiveCall(match.captured(1).toDouble()); } SelectiveCall SelectiveCall::parseDCS(const QString &text) { QRegularExpression re(R"(([\-iInN]?)([0-7]{1,3}))"); QRegularExpressionMatch match = re.match(text); if (! match.hasMatch()) return SelectiveCall(); bool inverted = false; if (("-" == match.captured(1)) || ("i" == match.captured(1)) || ("I" == match.captured(1))) inverted = true; return SelectiveCall(match.captured(2).toUInt(), inverted); } SelectiveCall SelectiveCall::fromBinaryDCS(unsigned int code, bool inverted) { unsigned int o=0, e=1; while (code) { o += (code % 8) * e; e *= 10; code /= 8; } return SelectiveCall(o, inverted); } const QVector & SelectiveCall::standard() { return _standard; } ================================================ FILE: lib/signaling.hh ================================================ #ifndef SIGNALING_HH #define SIGNALING_HH #include #include #include /** Encodes a selective call. * This can be CTCSS sub tones or DSC codes. * @ingroup conf */ struct SelectiveCall { protected: /** Type of the subtone. */ enum class Type { None, CTCSS, DCS }; public: /** Empty constructor, no selective call defined. */ SelectiveCall(); /** Constructs a CTCSS sub tone for the specified frequency in Hz. */ SelectiveCall(double ctcssFreq); /** Constructs a DCS code for the specified ocal code and inversion. */ SelectiveCall(unsigned int octalDSCCode, bool inverted); /** Comparison operator. */ bool operator==(const SelectiveCall &other) const; /** Comparison operator. */ bool operator!=(const SelectiveCall &other) const; /** Returns @c false, if a selective call is set. */ bool isInvalid() const; /** Returns @c true, if a selective call is set. */ bool isValid() const; /** Returns @c true, if a CTCSS sub tone is set. */ bool isCTCSS() const; /** Returns @c true, if a DCS code is set. */ bool isDCS() const; /** If a CTCSS sub tone is set, returns the frequency in Hz (floating point). */ double Hz() const; /** If a CTCSS sub tone is set, returns the frequency in mHz (integer). */ unsigned int mHz() const; /** If a DCS code is set, returns the binary code. */ unsigned int binCode() const; /** If a DCS code is set, returns the octal code. */ unsigned int octalCode() const; /** If a DCS code is set, returns the inversion flag. */ bool isInverted() const; /** Formats the selective call. */ QString format() const; public: /** Parses a CTCSS frequency. */ static SelectiveCall parseCTCSS(const QString &text); /** Parses a DCS code. */ static SelectiveCall parseDCS(const QString &text); /** Construct from binary DCS code. */ static SelectiveCall fromBinaryDCS(unsigned int code, bool inverted); /** Returns a vector of standard selective calls. */ static const QVector &standard(); protected: /// Specifies the selective call type. Type type; union { /// CTCSS frequency in 0.1Hz uint16_t ctcss; struct { /// Binary DCS code uint16_t code; /// If @c true, the code is inverted. bool inverted; } dcs; }; protected: /** Fixed table of standard values. */ static QVector _standard; }; Q_DECLARE_METATYPE(SelectiveCall) namespace YAML { /** Implements the conversion to and from YAML::Node. */ template<> struct convert { /** Serializes the selective call. */ static Node encode(const SelectiveCall& rhs) { Node node; if (rhs.isCTCSS()) node["ctcss"] = rhs.format().toStdString(); else if (rhs.isDCS()) node["dcs"] = rhs.format().toStdString(); return node; } /** Parses the selective call. */ static bool decode(const Node& node, SelectiveCall& rhs) { if (node.IsNull()) { rhs = SelectiveCall(); return true; } if ((! node.IsMap()) || (1 != node.size())) return false; if (node["ctcss"]) rhs = SelectiveCall::parseCTCSS(QString::fromStdString(node["ctcss"].as())); if (node["dcs"]) rhs = SelectiveCall::parseDCS(QString::fromStdString(node["dcs"].as())); return rhs.isValid(); } }; } #endif // SIGNALING_HH ================================================ FILE: lib/smsextension.cc ================================================ #include "smsextension.hh" /* ********************************************************************************************* * * Implementation of SMSTemplate * ********************************************************************************************* */ SMSTemplate::SMSTemplate(QObject *parent) : ConfigObject{parent}, _message() { // pass... } ConfigItem * SMSTemplate::clone() const { ConfigItem *item = new SMSTemplate(); if (! item->copy(*this)) { delete item; return nullptr; } return item; } const QString & SMSTemplate::message() const { return _message; } void SMSTemplate::setMessage(const QString message) { if (_message == message) return; _message = message; emit modified(this); } /* ********************************************************************************************* * * Implementation of SMSTemplates * ********************************************************************************************* */ SMSTemplates::SMSTemplates(QObject *parent) : ConfigObjectList(SMSTemplate::staticMetaObject, parent) { // pass... } ConfigItem * SMSTemplates::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(node); Q_UNUSED(ctx); Q_UNUSED(err); return new SMSTemplate(); } SMSTemplate * SMSTemplates::message(unsigned int i) const { return get(i)->as(); } /* ********************************************************************************************* * * Implementation of RadioSettingsExtension * ********************************************************************************************* */ SMSExtension::SMSExtension(QObject *parent) : ConfigExtension{parent}, _format(Format::Motorola), _smsTemplates(new SMSTemplates(this)) { // pass... } SMSExtension::Format SMSExtension::format() const { return _format; } void SMSExtension::setFormat(Format format) { if (_format == format) return; _format = format; emit modified(this); } ConfigItem * SMSExtension::clone() const { ConfigItem *item = new SMSExtension(); if (! item->copy(*this)) { delete item; return nullptr; } return item; } SMSTemplates * SMSExtension::smsTemplates() const { return _smsTemplates; } ================================================ FILE: lib/smsextension.hh ================================================ #ifndef SMSEXTENSION_HH #define SMSEXTENSION_HH #include "configobject.hh" #include "interval.hh" /** Represents a SMS message template (pre defined message). * Instances of this class are held in the @c SMSExtension. */ class SMSTemplate: public ConfigObject { Q_OBJECT /** Specifies the prefix for every ID assigned to every message during serialization. */ Q_CLASSINFO("IdPrefix", "sms") /** The message text. */ Q_PROPERTY(QString message READ message WRITE setMessage) public: /** Default constructor. */ Q_INVOKABLE explicit SMSTemplate(QObject *parent = nullptr); ConfigItem *clone() const; /** Returns the message text. */ const QString &message() const; /** Sets the message text. */ void setMessage(const QString message); protected: /** Holds the message text. */ QString _message; }; /** Just a list, holding the SMS templates. */ class SMSTemplates: public ConfigObjectList { Q_OBJECT public: /** Default constructor. */ Q_INVOKABLE SMSTemplates(QObject *parent=nullptr); ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); /** Returns the i-th message. */ SMSTemplate *message(unsigned int i) const; }; /** Extension collecting all settings associated with SMS messages. */ class SMSExtension : public ConfigExtension { Q_OBJECT /** The format for the SMS. */ Q_PROPERTY(Format format READ format WRITE setFormat) /** All predefined SMS messages. */ Q_PROPERTY(SMSTemplates *templates READ smsTemplates) public: /** Possible SMS formats, DMR is usually a good idea. */ enum class Format { Motorola, Hytera, DMR }; Q_ENUM(Format) public: /** Default constructor. */ Q_INVOKABLE explicit SMSExtension(QObject *parent = nullptr); ConfigItem *clone() const; /** Returns the SMS format setting. */ Format format() const; /** Sets the SMS format. */ void setFormat(Format format); /** Returns a weak reference to the list of SMS templates. */ SMSTemplates *smsTemplates() const; protected: /** Holds the SMS format. */ Format _format; /** Owns a reference to the list of SMS templates. */ SMSTemplates *_smsTemplates; }; #endif // SMSEXTENSION_HH ================================================ FILE: lib/talkgroupdatabase.cc ================================================ #include "talkgroupdatabase.hh" #include #include #include "logger.hh" #include #include #include #include /* ********************************************************************************************* * * Implementation of TalkGroupDatabase::TalkGroup * ********************************************************************************************* */ TalkGroupDatabase::TalkGroup::TalkGroup() : id(0), name() { // pass... } TalkGroupDatabase::TalkGroup::TalkGroup(const QString &name, unsigned number) { this->id = number; this->name = name; } /* ********************************************************************************************* * * Implementation of TalkGroupDatabase * ********************************************************************************************* */ TalkGroupDatabase::TalkGroupDatabase(unsigned updatePeriodDays, QObject *parent) : QAbstractTableModel(parent), _talkgroups(), _network() { connect(&_network, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*))); if ((! load()) || (updatePeriodDays < dbAge())) download(); } qint64 TalkGroupDatabase::count() const { return _talkgroups.count(); } unsigned TalkGroupDatabase::dbAge() const { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/talkgroups.json"; QFileInfo info(path); if (! info.exists()) return -1; return info.lastModified().daysTo(QDateTime::currentDateTime()); } TalkGroupDatabase::TalkGroup TalkGroupDatabase::talkgroup(int index) const { if ((0 > index) || (index >= count())) return TalkGroup(); return _talkgroups[index]; } void TalkGroupDatabase::download() { QUrl url("https://api.brandmeister.network/v2/talkgroup/"); QNetworkRequest request(url); _network.get(request); } void TalkGroupDatabase::downloadFinished(QNetworkReply *reply) { if (reply->error()) { QString msg = QString("Cannot download user database: %1").arg(reply->errorString()); logError() << msg; emit error(msg); return; } QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QFile file(path+"/talkgroups.json"); QDir directory; if ((! directory.exists(path)) && (!directory.mkpath(path))) { QString msg = QString("Cannot create path '%1'.").arg(path); logError() << msg; emit error(msg); return; } if (! file.open(QIODevice::WriteOnly)) { QString msg = QString("Cannot save user database at '%1'.").arg(path+"/talkgroups.json"); logError() << msg; emit error(msg); return; } file.write(reply->readAll()); file.flush(); file.close(); load(); reply->deleteLater(); } bool TalkGroupDatabase::load() { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); return load(path+"/talkgroups.json"); } bool TalkGroupDatabase::load(const QString &filename) { QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { QString msg = QString("Cannot open talk group list '%1': ").arg(filename).arg(file.errorString()); logError() << msg; emit error(msg); return false; } QByteArray data = file.readAll(); file.close(); QJsonDocument doc = QJsonDocument::fromJson(data); if (! doc.isObject()) { QString msg = "Failed to load talk groups: JSON document is not an object!"; logError() << msg; emit error(msg); return false; } beginResetModel(); QJsonObject tgs = doc.object(); _talkgroups.clear(); _talkgroups.reserve(tgs.count()); for (QJsonObject::const_iterator tg = tgs.begin(); tg!=tgs.end(); tg++) { _talkgroups.append(TalkGroup(tg.value().toString(), tg.key().toUInt())); } // Sort repeater w.r.t. their IDs std::stable_sort(_talkgroups.begin(), _talkgroups.end(), [](const TalkGroup &a, const TalkGroup &b){ return a.id < b.id; }); // Done. endResetModel(); logDebug() << "Loaded talk group database with " << _talkgroups.size() << " entries from " << filename << "."; emit loaded(); return true; } int TalkGroupDatabase::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent) return _talkgroups.count(); } int TalkGroupDatabase::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 2; } QVariant TalkGroupDatabase::data(const QModelIndex &index, int role) const { if ((Qt::EditRole != role) && ((Qt::DisplayRole != role))) return QVariant(); if (index.row() >= _talkgroups.size()) return QVariant(); if (0 == index.column()) { // Call if (Qt::DisplayRole == role) { return tr("%1 (%2)").arg(_talkgroups[index.row()].name).arg(_talkgroups[index.row()].name); } else { return _talkgroups[index.row()].name; } } else if (1 == index.column()) { // ID return _talkgroups[index.row()].id; } return QVariant(); } ================================================ FILE: lib/talkgroupdatabase.hh ================================================ #ifndef TALKGROUPDATABASE_HH #define TALKGROUPDATABASE_HH #include #include /** Downloads, periodically updates and provides a list of talk group IDs and their names. * * @ingroup utils */ class TalkGroupDatabase : public QAbstractTableModel { Q_OBJECT /** A talk group entry in the database. */ class TalkGroup { public: /** Empty constructor. */ TalkGroup(); /** Constructor form name and DMR ID. */ TalkGroup(const QString &name, unsigned number); /** The DMR ID of the talk group. */ unsigned id; /** The Name of the talk group. */ QString name; }; public: /** Constructs a talk group database. * @param updatePeriodDays Specifies the update period of the DB in days. * @param parent Specifies the QObject parent. */ TalkGroupDatabase(unsigned updatePeriodDays=30, QObject *parent=nullptr); /** Returns the number of talk groups. */ qint64 count() const; /** Returns the age of the database in days. */ unsigned dbAge() const; /** Returns the talk group entry at the given index. */ TalkGroup talkgroup(int index) const; /** Loads all entries from the downloaded talk group db. */ bool load(); /** Loads all entries from the talk group db at the specified location. */ bool load(const QString &filename); /** Implements the QAbstractTableModel interface, returns the number of rows (number of entries). */ int rowCount(const QModelIndex &parent=QModelIndex()) const; /** Implements the QAbstractTableModel interface, returns the number of columns. */ int columnCount(const QModelIndex &parent=QModelIndex()) const; /** Implements the QAbstractTableModel interface, return the entry data. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; signals: /** Gets emitted once the talk group database has been loaded. */ void loaded(); /** Gets emitted if the loading of the talk group database fails. */ void error(const QString &msg); public slots: /** Starts the download of the talk group database. */ void download(); private slots: /** Gets called whenever the download is complete. */ void downloadFinished(QNetworkReply *reply); protected: /** Holds all talk groups as id->name table. */ QVector _talkgroups; /** The network access used for downloading. */ QNetworkAccessManager _network; }; #endif // TALKGROUPDATABASE_HH ================================================ FILE: lib/tonesettings.cc ================================================ #include "tonesettings.hh" ToneSettings::ToneSettings(QObject *parent) : ConfigItem(parent), _silent(false), _keyTone(Level::null()), _smsTone(true), _ringtone(true), _bootTone(false), _bootMelody(new Melody(100, this)), _talkPermit(Channel::Type::None), _callStart(Channel::Type::Digital), _callStartMelody(new Melody(100, this)), _callEnd(Channel::Type::None), _callEndMelody(new Melody(100, this)), _channelIdle(Channel::Type::All), _channelIdleMelody(new Melody(100, this)), _callReset(true), _callResetMelody(new Melody(100, this)) { connect(_bootMelody, &Melody::modified, this, &ToneSettings::modified); connect(_callStartMelody, &Melody::modified, this, &ToneSettings::modified); connect(_callEndMelody, &Melody::modified, this, &ToneSettings::modified); connect(_channelIdleMelody, &Melody::modified, this, &ToneSettings::modified); connect(_callResetMelody, &Melody::modified, this, &ToneSettings::modified); } ConfigItem * ToneSettings::clone() const { auto obj = new ToneSettings(); if (! obj->copy(*this)) { delete obj; return nullptr; } return obj; } bool ToneSettings::silent() const { return _silent; } void ToneSettings::enableSilent(bool enable) { if (_silent == enable) return; _silent = enable; emit modified(this); } bool ToneSettings::keyToneEnabled() const { return _keyTone.isFinite(); } Level ToneSettings::keyToneVolume() const { return _keyTone; } void ToneSettings::setKeyToneVolume(Level volume) { if (_keyTone == volume) return; _keyTone = volume; emit modified(this); } void ToneSettings::disableKeyTone() { setKeyToneVolume(Level::null()); } bool ToneSettings::smsToneEnabled() const { return _smsTone; } void ToneSettings::enableSMSTone(bool enabled) { if (_smsTone == enabled) return; _smsTone = enabled; emit modified(this); } bool ToneSettings::ringtoneEnabled() const { return _ringtone; } void ToneSettings::enableRingtone(bool enabled) { if (_ringtone == enabled) return; _ringtone = enabled; emit modified(this); } bool ToneSettings::bootToneEnabled() const { return _bootTone; } void ToneSettings::enableBootTone(bool enabled) { if (_bootTone == enabled) return; _bootTone = enabled; emit modified(this); } Melody * ToneSettings::bootMelody() const { return _bootMelody; } Channel::Types ToneSettings::talkPermit() const { return _talkPermit; } void ToneSettings::setTalkPermit(Channel::Types type) { if (_talkPermit == type) return; _talkPermit = type; emit modified(this); } Channel::Types ToneSettings::callStart() const { return _callStart; } void ToneSettings::setCallStart(Channel::Types type) { if (_callStart == type) return; _callStart = type; emit modified(this); } Melody * ToneSettings::callStartMelody() const { return _callStartMelody; } Channel::Types ToneSettings::callEnd() const { return _callEnd; } void ToneSettings::setCallEnd(Channel::Types type) { if (_callEnd == type) return; _callEnd = type; emit modified(this); } Melody * ToneSettings::callEndMelody() const { return _callEndMelody; } Channel::Types ToneSettings::channelIdle() const { return _channelIdle; } void ToneSettings::setChannelIdle(Channel::Types type) { if (_channelIdle == type) return; _channelIdle = type; emit modified(this); } Melody * ToneSettings::channelIdleMelody() const { return _channelIdleMelody; } bool ToneSettings::callResetEnabled() const { return _callReset; } void ToneSettings::enableCallReset(bool enabled) { if (_callReset == enabled) return; _callReset = enabled; emit modified(this); } Melody * ToneSettings::callResetMelody() const { return _callResetMelody; } ================================================ FILE: lib/tonesettings.hh ================================================ #ifndef TONESETTINGS_HH #define TONESETTINGS_HH #include "configobject.hh" #include "level.hh" #include "channel.hh" class ToneSettings : public ConfigItem { Q_OBJECT /** Disables all tones. */ Q_PROPERTY(bool silent READ silent WRITE enableSilent) /** Key tone volume. */ Q_PROPERTY(Level keyTone READ keyToneVolume WRITE setKeyToneVolume) /** Enables notification tone for SMS reception. */ Q_PROPERTY(bool smsTone READ smsToneEnabled WRITE enableSMSTone) /** Enables ringtones. */ Q_PROPERTY(bool ringtone READ ringtoneEnabled WRITE enableRingtone); /** Enables boot melody. */ Q_PROPERTY(bool bootTone READ bootToneEnabled WRITE enableBootTone) /** Enables boot melody. */ Q_PROPERTY(Melody * bootMelody READ bootMelody) /** Enables talk-permit tone for different channel types. * This tone sounds, whenever the PTT is pressed and the admit criterion is met. */ Q_PROPERTY(Channel::Types talkPermit READ talkPermit WRITE setTalkPermit) /** Enables call-start tone for different channel types. */ Q_PROPERTY(Channel::Types callStart READ callStart WRITE setCallStart) /** The call-start melody. */ Q_PROPERTY(Melody * callStartMelody READ callStartMelody) /** Enables call-end tone for different channel types. */ Q_PROPERTY(Channel::Types callEnd READ callEnd WRITE setCallEnd) /** The call-end melody. */ Q_PROPERTY(Melody * callEndMelody READ callEndMelody) /** Enables channel-idle tone for different channel types. * This tone sounds, once the channel becomes free after a call. */ Q_PROPERTY(Channel::Types channelIdle READ channelIdle WRITE setChannelIdle) /** The channel-idle melody. */ Q_PROPERTY(Melody * channelIdleMelody READ channelIdleMelody) /** Enables call reset tone. * This tone sounds, once the (private or group-call) hang-time passed. * This affects only DMR and M17 channels. */ Q_PROPERTY(bool callReset READ callResetEnabled WRITE enableCallReset) /** The call-reset melody. */ Q_PROPERTY(Melody * callResetMelody READ callResetMelody) public: /** Default constructor. */ explicit ToneSettings(QObject *parent = nullptr); ConfigItem *clone() const override; /** Returns @c true if all tones are disabled. */ bool silent() const; /** Disables all tones. */ void enableSilent(bool enable); /** Returns @c true, if the key-tones are enabled. */ bool keyToneEnabled() const; /** Returns the key tone volume. */ Level keyToneVolume() const; /** Sets key tone volume. */ void setKeyToneVolume(Level volume); /** Disables key-tones. */ void disableKeyTone(); /** Returns @c true, if the SMS tone is enabled. */ bool smsToneEnabled() const; /** Enables/disables SMS tone. */ void enableSMSTone(bool enable); /** Returns @c true, if the ringtone is enabled. */ bool ringtoneEnabled() const; /** Enables ringtone. */ void enableRingtone(bool enable); /** Returns @c true, if the boot-tone is enabled. */ bool bootToneEnabled() const; /** Enables boot-tone. */ void enableBootTone(bool enable); /** Returns a reference to the boot melody. */ Melody *bootMelody() const; /** Returns channel types, for which the talk-permit tone is enabled. */ Channel::Types talkPermit() const; /** Sets channel types, for which the talk-permit tone is enabled. */ void setTalkPermit(Channel::Types permit); /** Returns channel types, for which the call-start tone is enabled. */ Channel::Types callStart() const; /** Sets channel types, for which the call-start tone is enabled. */ void setCallStart(Channel::Types permit); /** Returns a reference to the call-start melody. */ Melody *callStartMelody() const; /** Returns channel types, for which the call-end tone is enabled. */ Channel::Types callEnd() const; /** Sets channel types, for which the call-end tone is enabled. */ void setCallEnd(Channel::Types permit); /** Returns a reference to the call-end melody. */ Melody *callEndMelody() const; /** Returns channel types, for which the channel-idle tone is enabled. */ Channel::Types channelIdle() const; /** Sets channel types, for which the channel-idle tone is enabled. */ void setChannelIdle(Channel::Types permit); /** Returns a reference to the channel-idle melody. */ Melody *channelIdleMelody() const; /** Returns @c true, if the call-reset tone is enabled. */ bool callResetEnabled() const; /** Enables call-reset tone. */ void enableCallReset(bool enable); /** Returns a reference to the call-reset melody. */ Melody *callResetMelody() const; protected: /** If @c true, all tones are disabled. */ bool _silent; /** The key tone volume. */ Level _keyTone; /** Enables SMS tones. */ bool _smsTone; /** Enables ringtones. */ bool _ringtone; /** Enables boot-tone. */ bool _bootTone; /** The boot melody. */ Melody *_bootMelody; /** Enables talk-permit tones for several channel types. */ Channel::Types _talkPermit; /** Enables call-start tones for several channel types. */ Channel::Types _callStart; Melody *_callStartMelody; ///< Call melody. /** Enables call-end tones for several channel types. */ Channel::Types _callEnd; Melody *_callEndMelody; ///< Call end melody. /** Enables channel-idle tones for several channel types. */ Channel::Types _channelIdle; Melody *_channelIdleMelody; ///< Idle melody. /** Enables call-reset tones. */ bool _callReset; Melody *_callResetMelody; ///< Reset melody. }; #endif //TONESETTINGS_HH ================================================ FILE: lib/transferflags.cc ================================================ #include "transferflags.hh" TransferFlags::TransferFlags() : _blocking(false), _updateDeviceClock(false) { // pass... } TransferFlags::TransferFlags(bool blocking, bool updateDeviceClock) : _blocking(blocking), _updateDeviceClock(updateDeviceClock) { // pass... } bool TransferFlags::blocking() const { return _blocking; } void TransferFlags::setBlocking(bool enable) { _blocking = enable; } bool TransferFlags::updateDeviceClock() const { return _updateDeviceClock; } void TransferFlags::setUpdateDeviceClock(bool enable) { _updateDeviceClock = enable; } ================================================ FILE: lib/transferflags.hh ================================================ #ifndef TRANSFERFLAGS_HH #define TRANSFERFLAGS_HH /** Controls the transfer of codeplugs, callsign DBs etc to the device. */ class TransferFlags { public: /** Default constructor. */ TransferFlags(); /** Constructor. */ TransferFlags(bool blocking, bool updateDeviceClock); /** Returns @c true if the transfer is blocking. */ bool blocking() const; /** Set if transfer is blocking. */ void setBlocking(bool enable); /** Returns @c true if the device clock gets updated during transfer. */ bool updateDeviceClock() const; /** Sets if the device clock gets updated during the transfer. */ void setUpdateDeviceClock(bool enable); protected: /** If @c true, the transfer is blocking. */ bool _blocking; /** If @c true, the device clock gets updated during the transfer. */ bool _updateDeviceClock; }; #endif // TRANSFERFLAGS_HH ================================================ FILE: lib/transponderdatabase.cc ================================================ #include "transponderdatabase.hh" #include #include #include #include #include #include #include #include #include "logger.hh" /* ********************************************************************************************* * * Implementation of Transponder * ********************************************************************************************* */ Transponder::Transponder() : _satellite(0), _type(Type::Transmitter), _mode(Mode::CW), _name(), _downlink(), _uplink() { // pass... } unsigned int Transponder::satellite() const { return _satellite; } bool Transponder::isValid() const { return 0 != _satellite; } Transponder::Type Transponder::type() const { return _type; } Transponder::Mode Transponder::mode() const { return _mode; } const QString & Transponder::name() const { return _name; } const Frequency & Transponder::uplink() const { return _uplink; } const Frequency & Transponder::downlink() const { return _downlink; } Transponder Transponder::fromSATNOGS(const QJsonObject &obj) { Transponder t; t._satellite = obj.value("norad_cat_id").toInt(); t._name = obj.value("description").toString(); if ((! obj.contains("alive")) || (! obj.value("alive").toBool())) { //logInfo() << "Skip transponder '" << t._name << "': no alive flag set or false."; return Transponder(); } if ((! obj.contains("mode")) || obj.value("mode").isNull()) { //logInfo() << "Skip transponder '" << t._name << "': no mode set."; return Transponder(); } if ((! obj.contains("downlink_low")) || (obj.value("downlink_low").isNull())) { //logInfo() << "Skip transponder '" << t._name << "': no downlink frequency given."; return Transponder(); } QString tn = obj.value("type").toString(); if (("Transmitter" == tn) || ("Beacon" == tn)) { t._type = Type::Transmitter; } else if ("Transceiver" == tn) { t._type = Type::Transponder; } else { //logInfo() << "Skip transponder '" << t._name << "': unknown type '" << tn << "'."; return Transponder(); } QString mode = obj.value("mode").toString(); if ("FM" == mode) { t._mode = Mode::FM; } else if ("AFSK" == mode) { t._mode = Mode::APRS; } else if ("CW" == mode) { t._mode = Mode::CW; } else if ("BPSK" == mode) { t._mode = Mode::BPSK; } else { //logInfo() << "Skip transponder '" << t._name << "': unknown mode '" << mode << "'."; return Transponder(); } t._downlink = Frequency::fromHz(obj.value("downlink_low").toInt()); if (obj.contains("uplink_low") && (! obj.value("uplink_low").isNull())) t._uplink = Frequency::fromHz(obj.value("uplink_low").toInt()); return t; } /* ********************************************************************************************* * * Implementation of TransponderDatabase * ********************************************************************************************* */ TransponderDatabase::TransponderDatabase(bool autoLoad, unsigned int updatePeriod, QObject *parent) : QAbstractTableModel{parent}, _updatePeriod(updatePeriod), _transponders(), _network() { connect(&_network, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*))); if (autoLoad) load(); } const Transponder & TransponderDatabase::getAt(unsigned int idx) const { return _transponders[idx]; } int TransponderDatabase::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return _transponders.size(); } int TransponderDatabase::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 5; } QVariant TransponderDatabase::data(const QModelIndex &index, int role) const { if (index.row() >= _transponders.size()) return QVariant(); if (Qt::DisplayRole == role) { if (0 == index.column()) { switch (_transponders.at(index.row()).type()) { case Transponder::Type::Transmitter: return "Transmitter"; case Transponder::Type::Transponder: return "Transponder"; } } else if (1 == index.column()) { switch (_transponders.at(index.row()).mode()) { case Transponder::Mode::CW: return "CW"; case Transponder::Mode::BPSK: return "BPSK"; case Transponder::Mode::FM: return "FM"; case Transponder::Mode::APRS: return "APRS"; } } else if (2 == index.column()) { return _transponders.at(index.row()).name(); } else if (3 == index.column()) { return _transponders.at(index.row()).uplink().format(); } else if (4 == index.column()) { return _transponders.at(index.row()).downlink().format(); } } return QVariant(); } TransponderDatabase::const_iterator TransponderDatabase::begin() const { return _transponders.begin(); } TransponderDatabase::const_iterator TransponderDatabase::end() const { return _transponders.end(); } unsigned TransponderDatabase::dbAge() const { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/transponders.json"; QFileInfo info(path); if (! info.exists()) return -1; return info.lastModified().daysTo(QDateTime::currentDateTime()); } void TransponderDatabase::load() { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/transponders.json"; if ((! load(path)) || (_updatePeriod < dbAge())) download(); } bool TransponderDatabase::load(const QString &filename) { QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { QString msg = QString("Cannot open transponders '%1': %2").arg(filename).arg(file.errorString()); logError() << msg; emit error(msg); return false; } QByteArray data = file.readAll(); file.close(); QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (doc.isEmpty()) { QString msg = "Failed to load transponders: " + err.errorString(); logError() << msg; emit error(msg); return false; } if (! doc.isArray()) { QString msg = "Failed to load transponders: JSON document is not an array!"; logError() << msg; emit error(msg); return false; } beginResetModel(); QJsonArray array = doc.array(); _transponders.clear(); _transponders.reserve(array.size()); for (int i=0; ierror()) { QString msg = QString("Cannot download transponders: %1").arg(reply->errorString()); logError() << msg; emit error(msg); return; } QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QFile file(path+"/transponders.json"); QDir directory; if ((! directory.exists(path)) && (!directory.mkpath(path))) { QString msg = QString("Cannot create path '%1'.").arg(path); logError() << msg; emit error(msg); return; } if (! file.open(QIODevice::WriteOnly)) { QString msg = QString("Cannot save transponders at '%1'.").arg(file.fileName()); logError() << msg; emit error(msg); return; } file.write(reply->readAll()); file.flush(); file.close(); load(); reply->deleteLater(); } ================================================ FILE: lib/transponderdatabase.hh ================================================ #ifndef TRANSPONDERDATABASE_HH #define TRANSPONDERDATABASE_HH #include #include #include "frequency.hh" /** Represents a single transponder of a satellite. */ struct Transponder { public: /** Possible transponder types. */ enum class Type { Transponder, ///< Proper transponder (repeater). Transmitter ///< Just a transmitter (beacon). }; /** Possible transponder modes. */ enum class Mode { FM, ///< Plain FM. CW, ///< Simple CW. APRS, ///< AFSK APRS. BPSK ///< BPSK. }; public: /** Default constructor. */ Transponder(); /** Returns @c true, if the transponder is valid. * I.e., it is associated with a satellite. */ bool isValid() const; /** Returns the NORAD id of the associated satellite. */ unsigned int satellite() const; /** Returns the transponder type. */ Type type() const; /** Returns the transponder mode. */ Mode mode() const; /** Returns a descriptive name of the transponder. */ const QString &name() const; /** Returns the uplink frequency, if there is one. * An upload frequency is usually missing, if the type is @c Type::Transmitter. */ const Frequency &uplink() const; /** Returns the downlink frequency. */ const Frequency &downlink() const; public: /** Parses a transponder from the given SatNOGS JSON object. */ static Transponder fromSATNOGS(const QJsonObject &obj); protected: /** Holds the NORAD id of the satellite. */ unsigned int _satellite; /** Holds the transponder type. */ Type _type; /** Holds the transponder mode. */ Mode _mode; /** Holds the name. */ QString _name; /** Holds the downlink frequency. */ Frequency _downlink; /** Holds the uplink frequency. */ Frequency _uplink; }; /** Implements the database of all known transponder. * @ingroup sat */ class TransponderDatabase : public QAbstractTableModel { Q_OBJECT public: /** Just a const iterator over all transponder. */ typedef QVector::const_iterator const_iterator; public: /** Constructor. * @param autoLoad If @c true, the transponder information gets downloaded and loaded * automatically. * @param updatePeriod Specifies the maximum age of the cache in days. * @param parent The QObject parent. */ explicit TransponderDatabase(bool autoLoad, unsigned int updatePeriod = 7, QObject *parent = nullptr); /** The current age of the cache. */ unsigned int dbAge() const; /** Returns the i-th transponder. */ const Transponder &getAt(unsigned int idx) const; /** Implements the QAbstractTableModel interface. * Returns the number of rows in the table. That is, the number of transponder. */ int rowCount(const QModelIndex &parent) const; /** Implements the QAbstractTableModel interface. Returns the number of columns in the table. */ int columnCount(const QModelIndex &parent) const; /** Implements the QAbstractTableModel interface. Returns the data for the cell. */ QVariant data(const QModelIndex &index, int role) const; /** Returns an iterator, pointing at the first transponder. */ const_iterator begin() const; /** Returns an iterator, pointing right after the last transponder. */ const_iterator end() const; public slots: /** Downloads and loads all transponder information. */ void load(); signals: /** Gets emitted once the transponder has been loaded. */ void loaded(); /** Gets emitted if the loading one of the sources fails. */ void error(const QString &msg); public slots: /** Starts the download of the transponder. */ void download(); private slots: /** Gets called whenever the transponder download is complete. */ void downloadFinished(QNetworkReply *reply); protected: /** Loads the transponder information from the given file. */ bool load(const QString &filename); private: /** The update period of the transponders in days. */ unsigned int _updatePeriod; /** Holds all transponder sorted by the catalog number of their sats. */ QVector _transponders; /** The network access used for downloading. */ QNetworkAccessManager _network; }; #endif // TRANSPONDERDATABASE_HH ================================================ FILE: lib/tyt_callsigndb.cc ================================================ #include "tyt_callsigndb.hh" #include #include "utils.hh" #define MAX_CALLSIGNS 122197LL // Maximum number of callsings in DB #define ADDR_CALLSIGN_INDEX 0x00200000 // Start of callsign database #define NUM_INDEX_ENTRIES 4096 #define INDEX_ENTRY_SIZE 0x00000004 // Size of a index entry #define ADDR_CALLSIGNS 0x00204003 // Start of callsign entries #define CALLSIGN_ENTRY_SIZE 0x00000078 // Size of a call-sign entry /* ********************************************************************************************* * * Implementation of TyTCallsignDB::IndexElement * ********************************************************************************************* */ TyTCallsignDB::IndexElement::IndexElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCallsignDB::IndexElement::IndexElement(uint8_t *ptr) : Codeplug::Element(ptr, 0x0003 + NUM_INDEX_ENTRIES*INDEX_ENTRY_SIZE) { // pass... } TyTCallsignDB::IndexElement::~IndexElement() { // pass... } void TyTCallsignDB::IndexElement::clear() { setNumEntries(0); for (int i=0; i>16) & 0xff); ptr[1] = ((n>> 8) & 0xff); ptr[2] = ((n>> 0) & 0xff); } void TyTCallsignDB::IndexElement::setIndexEntry(unsigned n, unsigned id, unsigned index) { Entry(_data+0x03 + n*INDEX_ENTRY_SIZE).set(id, index); } /* ********************************************************************************************* * * Implementation of TyTCallsignDB::IndexElement::Entry * ********************************************************************************************* */ TyTCallsignDB::IndexElement::Entry::Entry(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCallsignDB::IndexElement::Entry::Entry(uint8_t *ptr) : Codeplug::Element(ptr, INDEX_ENTRY_SIZE) { // pass... } TyTCallsignDB::IndexElement::Entry::~Entry() { // pass... } void TyTCallsignDB::IndexElement::Entry::clear() { memset(_data, 0xff, INDEX_ENTRY_SIZE); } void TyTCallsignDB::IndexElement::Entry::set(unsigned id, unsigned index) { _data[0] = id>>16; _data[1] = ((id>>8)&0xf0) | ((index>>16) & 0xf); _data[2] = index>>8; _data[3] = index; } /* ********************************************************************************************* * * Implementation of TyTCallsignDB::EntryElement * ********************************************************************************************* */ TyTCallsignDB::EntryElement::EntryElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCallsignDB::EntryElement::EntryElement(uint8_t *ptr) : Codeplug::Element(ptr, CALLSIGN_ENTRY_SIZE) { // pass... } TyTCallsignDB::EntryElement::~EntryElement() { // pass... } void TyTCallsignDB::EntryElement::clear() { memset(_data, 0xff, CALLSIGN_ENTRY_SIZE); } void TyTCallsignDB::EntryElement::set(const UserDatabase::User &user) { // Set id *((uint32_t *)(_data + 0x0000)) = qToLittleEndian(user.id); _data[3] = 0xff; // Set call encode_ascii(_data + 0x0004, user.call, 16); // Set name QString name = user.name; if ((! user.surname.isEmpty()) && (100 >= name.length() + 1 + user.surname.size())) name += " " + user.surname; if ((! user.city.isEmpty()) && (100 >= name.length() + 2 + user.city.size())) name += ", " + user.city; if ((! user.state.isEmpty()) && (100 >= name.length() + 2 + user.state.size())) name += ", " + user.state; if ((! user.country.isEmpty()) && (100 >= name.length() + 2 + user.country.size())) name += ", " + user.country; if ((! user.comment.isEmpty()) && (100 >= name.length() + 2 + user.comment.size())) name += ". " + user.comment; encode_ascii(_data + 0x0014, name, 100); } /* ********************************************************************************************* * * Implementation of TyTCallsignDB * ********************************************************************************************* */ TyTCallsignDB::TyTCallsignDB(QObject *parent) : CallsignDB(parent) { // allocate and clear DB memory addImage("TYT Callsign database."); } TyTCallsignDB::~TyTCallsignDB() { // pass... } bool TyTCallsignDB::encode(UserDatabase *db, const Flags &selection, const ErrorStack &err) { Q_UNUSED(err) // Allocate space for callsign db size_t n = std::min(MAX_CALLSIGNS, db->count()); if (selection.hasCountLimit()) n = std::min(n, selection.countLimit()); allocate(n); // Clear DB index clearIndex(); // Select n users and sort them in ascending order of their IDs QVector users; for (unsigned i=0; iuser(i)); std::sort(users.begin(), users.end(), [](const UserDatabase::User &a, const UserDatabase::User &b) { return a.id < b.id; }); // Store number of entries setNumEntries(n); // First index entry int j = 0; setIndexEntry(j++, users[0].id, 1); unsigned cidh = (users[0].id >> 12); // Store users and update index for (unsigned i=0; i> 12); if (idh != cidh) { setIndexEntry(j++,users[i].id, i+1); cidh = idh; } } return true; } void TyTCallsignDB::allocate(unsigned n) { n = std::min(n, unsigned(MAX_CALLSIGNS)); qint64 size = align_size(0x0003 + INDEX_ENTRY_SIZE*NUM_INDEX_ENTRIES + CALLSIGN_ENTRY_SIZE*n, 1024); // allocate & clear memory if (0 == image(0).numElements()) this->image(0).addElement(ADDR_CALLSIGN_INDEX, size); memset(data(ADDR_CALLSIGN_INDEX), 0xff, size); } void TyTCallsignDB::clearIndex() { IndexElement(data(ADDR_CALLSIGN_INDEX)).clear(); } void TyTCallsignDB::setNumEntries(unsigned n) { IndexElement(data(ADDR_CALLSIGN_INDEX)).setNumEntries(n); } void TyTCallsignDB::setIndexEntry(unsigned n, unsigned id, unsigned index) { IndexElement(data(ADDR_CALLSIGN_INDEX)).setIndexEntry(n, id, index); } void TyTCallsignDB::setEntry(unsigned n, const UserDatabase::User &user) { // Get pointer to entry EntryElement(data(ADDR_CALLSIGNS + n*CALLSIGN_ENTRY_SIZE)).set(user); } ================================================ FILE: lib/tyt_callsigndb.hh ================================================ #ifndef TYTCALLSIGNDB_HH #define TYTCALLSIGNDB_HH #include "codeplug.hh" #include "callsigndb.hh" #include "userdatabase.hh" /** Base class for all call-sign DBs of TyT/Retevis devices. * * @section tytcdb Callsign database structure * * * * * * *
Start End Size Content
Callsign database 0x0200000-0x1000000
0x200000 0x204004 0x04004 Callsign database index table, see @c TyTCallsignDB::IndexEntryElement
0x204004 0xffffdc 0xdfbfd8 max 122197 callsign database entries, see @c TyTCallsignDB::EntryElement.
0xffffdc 0x1000000 0x00025 Padding, filled with @c 0xff.
* @ingroup tyt */ class TyTCallsignDB : public CallsignDB { Q_OBJECT public: /** Represents a search index over the complete call-sign database. * * Memory layout of encoded call-sign/user database: * @verbinclude tytcallsigndbindex.txt */ class IndexElement: public Codeplug::Element { public: /** Represents an index entry, a pair of DMR ID and call-sign DB index. * * Memory layout of encoded call-sign/user database index entry: * @verbinclude tytcallsigndbindexentry.txt */ class Entry: public Codeplug::Element { protected: /** Hidden constructor. */ Entry(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit Entry(uint8_t *ptr); /** Destructor. */ virtual ~Entry(); void clear(); /** Sets the index entry. */ virtual void set(unsigned id, unsigned index); }; protected: /** Hidden constructor. */ IndexElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit IndexElement(uint8_t *ptr); /** Destructor. */ virtual ~IndexElement(); void clear(); /** Sets the number of entries in the DB. */ virtual void setNumEntries(unsigned n); /** Sets the given index entry. */ virtual void setIndexEntry(unsigned n, unsigned id, unsigned index); }; /** Represents an entry within the call-sign database. * The call-sign DB entries must be ordered by their DMR IDs. * * Memory layout of encoded call-sign/user database index entry: * @verbinclude tytcallsigndbentry.txt */ class EntryElement: public Codeplug::Element { protected: /** Hidden constructor. */ EntryElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit EntryElement(uint8_t *ptr); /** Destructor. */ virtual ~EntryElement(); void clear(); /** Encodes the given user. */ virtual void set(const UserDatabase::User &user); }; protected: /** Hidden constructor. Use one of the device specific call-sign DB classes. */ explicit TyTCallsignDB(QObject *parent=nullptr); public: /** Destructor. */ virtual ~TyTCallsignDB(); bool encode(UserDatabase *db, const Flags &selection,const ErrorStack &err=ErrorStack()); protected: /** Allocates required space for index and @c n call-signs. */ virtual void allocate(unsigned n); /** Clears the call-sign DB index. */ virtual void clearIndex(); /** Sets the number of entries in the DB. */ virtual void setNumEntries(unsigned n); /** Sets the given index entry. */ virtual void setIndexEntry(unsigned n, unsigned id, unsigned index); /** Sets a given call-sign entry. */ virtual void setEntry(unsigned n, const UserDatabase::User &user); }; #endif // TYTCALLSIGNDB_HH ================================================ FILE: lib/tyt_codeplug.cc ================================================ #include "tyt_codeplug.hh" #include "config.hh" #include "utils.hh" #include "channel.hh" #include "gpssystem.hh" #include "config.h" #include "logger.hh" #include "tyt_extensions.hh" #include "encryptionextension.hh" #include "commercial_extension.hh" #include "intermediaterepresentation.hh" #include #include #include #define SETTINGS_SIZE 0x000090 #define MENUSETTINGS_SIZE 0x000010 /* ******************************************************************************************** * * Implementation of TyTCodeplug::ChannelElement * ******************************************************************************************** */ TyTCodeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCodeplug::ChannelElement::ChannelElement(uint8_t *ptr) : Codeplug::Element(ptr, size()) { // pass... } TyTCodeplug::ChannelElement::~ChannelElement() { // pass... } bool TyTCodeplug::ChannelElement::isValid() const { return Element::isValid() && QChar::isPrint(getUInt16_le(Offset::name())); } void TyTCodeplug::ChannelElement::clear() { Element::clear(); setMode(MODE_ANALOG); setBandwidth(FMChannel::Bandwidth::Narrow); enableAutoScan(0); setBit(0, 1); setBit(0,2); enableLoneWorker(false); enableTalkaround(false); enableRXOnly(false); setTimeSlot(DMRChannel::TimeSlot::TS1); setColorCode(1); setPrivacyIndex(0); setPrivacyType(PRIV_NONE); enablePrivateCallConfirm(false); enableDataCallConfirm(false); setRXRefFrequency(TyTChannelExtension::RefFrequency::Low); clearBit(3,2); enableEmergencyAlarmACK(false); clearBit(3,4); setBit(3,5); setBit(3,6); enableDisplayPTTId(true); setTXRefFrequency(TyTChannelExtension::RefFrequency::Low); setBit(4,2); clearBit(4,3); enableVOX(false); setBit(4,5); setAdmitCriterion(ADMIT_ALWAYS); clearBit(5,0); setContactIndex(0); resetTXTimeOut(); clearBit(8,6); setTXTimeOutRekeyDelay(0); setEmergencySystemIndex(0); setScanListIndex(0); setGroupListIndex(0); setPositioningSystemIndex(0); for (uint8_t i=0; i<8; i++) setDTMFDecode(i, false); setRXFrequency(Frequency::fromMHz(400)); setTXFrequency(Frequency::fromMHz(400)); setRXSignaling(SelectiveCall()); setTXSignaling(SelectiveCall()); setRXSignalingSystemIndex(0); setTXSignalingSystemIndex(0); setBit(30,2); setBit(30,3); setBit(30,4); setBit(30,5); setBit(30,6); setBit(30,7); enableTXGPSInfo(true); enableRXGPSInfo(true); setBit(31, 5); setBit(31, 6); setBit(31, 7); memset((_data+32), 0x00, sizeof(32)); } TyTCodeplug::ChannelElement::Mode TyTCodeplug::ChannelElement::mode() const { return TyTCodeplug::ChannelElement::Mode(getUInt2(Offset::mode())); } void TyTCodeplug::ChannelElement::setMode(Mode mode) { setUInt2(Offset::mode(), uint8_t(mode)); } FMChannel::Bandwidth TyTCodeplug::ChannelElement::bandwidth() const { if (0 == getUInt2(Offset::bandwidth())) return FMChannel::Bandwidth::Narrow; return FMChannel::Bandwidth::Wide; } void TyTCodeplug::ChannelElement::setBandwidth(FMChannel::Bandwidth bw) { if (FMChannel::Bandwidth::Narrow == bw) setUInt2(Offset::bandwidth(), BW_12_5_KHZ); else setUInt2(Offset::bandwidth(), BW_25_KHZ); } bool TyTCodeplug::ChannelElement::autoScan() const { return getBit(Offset::autoscan()); } void TyTCodeplug::ChannelElement::enableAutoScan(bool enable) { setBit(Offset::autoscan(), enable); } bool TyTCodeplug::ChannelElement::loneWorker() const { return getBit(Offset::loneworker()); } void TyTCodeplug::ChannelElement::enableLoneWorker(bool enable) { setBit(Offset::loneworker(), enable); } bool TyTCodeplug::ChannelElement::talkaround() const { return ! getBit(Offset::talkaround()); } void TyTCodeplug::ChannelElement::enableTalkaround(bool enable) { setBit(Offset::talkaround(), !enable); } bool TyTCodeplug::ChannelElement::rxOnly() const { return getBit(Offset::rxonly()); } void TyTCodeplug::ChannelElement::enableRXOnly(bool enable) { setBit(Offset::rxonly(), enable); } DMRChannel::TimeSlot TyTCodeplug::ChannelElement::timeSlot() const { if (2 == getUInt2(Offset::timeslot())) return DMRChannel::TimeSlot::TS2; return DMRChannel::TimeSlot::TS1; } void TyTCodeplug::ChannelElement::setTimeSlot(DMRChannel::TimeSlot ts) { if (DMRChannel::TimeSlot::TS1 == ts) setUInt2(Offset::timeslot(), 1); else setUInt2(Offset::timeslot(),2); } uint8_t TyTCodeplug::ChannelElement::colorCode() const { return getUInt4(Offset::colorcode()); } void TyTCodeplug::ChannelElement::setColorCode(uint8_t cc) { cc = std::min(uint8_t(16), cc); setUInt4(Offset::colorcode(), cc); } uint8_t TyTCodeplug::ChannelElement::privacyIndex() const { return getUInt4(Offset::privacyIndex()); } void TyTCodeplug::ChannelElement::setPrivacyIndex(uint8_t idx) { setUInt4(Offset::privacyIndex(), idx); } TyTCodeplug::ChannelElement::PrivacyType TyTCodeplug::ChannelElement::privacyType() const { return TyTCodeplug::ChannelElement::PrivacyType(getUInt2(Offset::privacyType())); } void TyTCodeplug::ChannelElement::setPrivacyType(TyTCodeplug::ChannelElement::PrivacyType type) { setUInt2(Offset::privacyType(), uint8_t(type)); } bool TyTCodeplug::ChannelElement::privateCallConfirm() const { return getBit(Offset::privateCallConfirm()); } void TyTCodeplug::ChannelElement::enablePrivateCallConfirm(bool enable) { setBit(Offset::privateCallConfirm(), enable); } bool TyTCodeplug::ChannelElement::dataCallConfirm() const { return getBit(Offset::dataCallConfirm()); } void TyTCodeplug::ChannelElement::enableDataCallConfirm(bool enable) { setBit(Offset::dataCallConfirm(), enable); } TyTChannelExtension::RefFrequency TyTCodeplug::ChannelElement::rxRefFrequency() const { return TyTChannelExtension::RefFrequency(getUInt2(Offset::rxRefFrequency())); } void TyTCodeplug::ChannelElement::setRXRefFrequency(TyTChannelExtension::RefFrequency ref) { setUInt2(Offset::rxRefFrequency(), uint8_t(ref)); } bool TyTCodeplug::ChannelElement::emergencyAlarmACK() const { return getBit(Offset::emergencyAlarmACK()); } void TyTCodeplug::ChannelElement::enableEmergencyAlarmACK(bool enable) { setBit(Offset::emergencyAlarmACK(), enable); } bool TyTCodeplug::ChannelElement::displayPTTId() const { return ! getBit(Offset::displayPTTId()); } void TyTCodeplug::ChannelElement::enableDisplayPTTId(bool enable) { setBit(Offset::displayPTTId(), !enable); } TyTChannelExtension::RefFrequency TyTCodeplug::ChannelElement::txRefFrequency() const { return TyTChannelExtension::RefFrequency(getUInt2(Offset::txRefFrequency())); } void TyTCodeplug::ChannelElement::setTXRefFrequency(TyTChannelExtension::RefFrequency ref) { setUInt2(Offset::txRefFrequency(), uint8_t(ref)); } bool TyTCodeplug::ChannelElement::vox() const { return getBit(Offset::vox()); } void TyTCodeplug::ChannelElement::enableVOX(bool enable) { setBit(Offset::vox(), enable); } TyTCodeplug::ChannelElement::Admit TyTCodeplug::ChannelElement::admitCriterion() const { return TyTCodeplug::ChannelElement::Admit(getUInt2(Offset::admitCriterion())); } void TyTCodeplug::ChannelElement::setAdmitCriterion(TyTCodeplug::ChannelElement::Admit admit) { setUInt2(Offset::admitCriterion(), uint8_t(admit)); } uint16_t TyTCodeplug::ChannelElement::contactIndex() const { return getUInt16_le(Offset::contactIndex()); } void TyTCodeplug::ChannelElement::setContactIndex(uint16_t idx) { setUInt16_le(Offset::contactIndex(), idx); } bool TyTCodeplug::ChannelElement::txTimeOutDisabled() const { return 0 == getUInt6(Offset::txTimeOut()); } Interval TyTCodeplug::ChannelElement::txTimeOut() const { return Interval::fromSeconds(getUInt6(Offset::txTimeOut())*15); } void TyTCodeplug::ChannelElement::setTXTimeOut(const Interval &tot) { return setUInt6(Offset::txTimeOut(), tot.seconds()/15); } void TyTCodeplug::ChannelElement::resetTXTimeOut() { setUInt6(Offset::txTimeOut(), 0); } uint8_t TyTCodeplug::ChannelElement::txTimeOutRekeyDelay() const { return getUInt8(Offset::txTimeOutRekeyDelay()); } void TyTCodeplug::ChannelElement::setTXTimeOutRekeyDelay(uint8_t delay) { return setUInt8(Offset::txTimeOutRekeyDelay(), delay); } uint8_t TyTCodeplug::ChannelElement::emergencySystemIndex() const { return getUInt8(Offset::emergencySystemIndex()); } void TyTCodeplug::ChannelElement::setEmergencySystemIndex(uint8_t delay) { return setUInt8(Offset::emergencySystemIndex(), delay); } uint8_t TyTCodeplug::ChannelElement::scanListIndex() const { return getUInt8(Offset::scanListIndex()); } void TyTCodeplug::ChannelElement::setScanListIndex(uint8_t idx) { return setUInt8(Offset::scanListIndex(), idx); } uint8_t TyTCodeplug::ChannelElement::groupListIndex() const { return getUInt8(Offset::groupListIndex()); } void TyTCodeplug::ChannelElement::setGroupListIndex(uint8_t idx) { return setUInt8(Offset::groupListIndex(), idx); } uint8_t TyTCodeplug::ChannelElement::positioningSystemIndex() const { return getUInt8(Offset::positioningSystemIndex()); } void TyTCodeplug::ChannelElement::setPositioningSystemIndex(uint8_t idx) { return setUInt8(Offset::positioningSystemIndex(), idx); } bool TyTCodeplug::ChannelElement::dtmfDecode(uint8_t idx) const { return getBit(Offset::dtmfDecode(), idx); } void TyTCodeplug::ChannelElement::setDTMFDecode(uint8_t idx, bool enable) { setBit(Offset::dtmfDecode(), idx, enable); } Frequency TyTCodeplug::ChannelElement::rxFrequency() const { return Frequency::fromHz(getBCD8_le(Offset::rxFrequency())*10); } void TyTCodeplug::ChannelElement::setRXFrequency(const Frequency &freq_Hz) { return setBCD8_le(Offset::rxFrequency(), freq_Hz.inHz()/10); } Frequency TyTCodeplug::ChannelElement::txFrequency() const { return Frequency::fromHz(getBCD8_le(Offset::txFrequency())*10); } void TyTCodeplug::ChannelElement::setTXFrequency(const Frequency &freq_Hz) { return setBCD8_le(Offset::txFrequency(), freq_Hz.inHz()/10); } SelectiveCall TyTCodeplug::ChannelElement::rxSignaling() const { return decode_ctcss_tone_table(getUInt16_le(24)); } void TyTCodeplug::ChannelElement::setRXSignaling(const SelectiveCall &code) { setUInt16_le(24, encode_ctcss_tone_table(code)); } SelectiveCall TyTCodeplug::ChannelElement::txSignaling() const { return decode_ctcss_tone_table(getUInt16_le(26)); } void TyTCodeplug::ChannelElement::setTXSignaling(const SelectiveCall &code) { setUInt16_le(26, encode_ctcss_tone_table(code)); } uint8_t TyTCodeplug::ChannelElement::rxSignalingSystemIndex() const { return getUInt8(Offset::rxSignalingSystemIndex()); } void TyTCodeplug::ChannelElement::setRXSignalingSystemIndex(uint8_t idx) { setUInt8(Offset::rxSignalingSystemIndex(), idx); } uint8_t TyTCodeplug::ChannelElement::txSignalingSystemIndex() const { return getUInt8(Offset::txSignalingSystemIndex()); } void TyTCodeplug::ChannelElement::setTXSignalingSystemIndex(uint8_t idx) { setUInt8(Offset::txSignalingSystemIndex(), idx); } bool TyTCodeplug::ChannelElement::txGPSInfo() const { return ! getBit(Offset::txGPSInfo()); } void TyTCodeplug::ChannelElement::enableTXGPSInfo(bool enable) { setBit(Offset::txGPSInfo(), !enable); } bool TyTCodeplug::ChannelElement::rxGPSInfo() const { return !getBit(Offset::rxGPSInfo()); } void TyTCodeplug::ChannelElement::enableRXGPSInfo(bool enable) { setBit(Offset::rxGPSInfo(), !enable); } QString TyTCodeplug::ChannelElement::name() const { return readUnicode(Offset::name(), Limit::nameLength(), 0x0000); } void TyTCodeplug::ChannelElement::setName(const QString &name) { return writeUnicode(Offset::name(), name, Limit::nameLength(), 0x0000); } Channel * TyTCodeplug::ChannelElement::toChannelObj(const ErrorStack &err) const { if (! isValid()) { errMsg(err) << "Cannot decode invalid channel."; return nullptr; } Channel *ch = nullptr; TyTChannelExtension *ex = new TyTChannelExtension(); // decode power setting if (MODE_ANALOG == mode()) { FMChannel::Admit admit_crit; switch(admitCriterion()) { case ADMIT_ALWAYS: admit_crit = FMChannel::Admit::Always; break; case ADMIT_TONE: admit_crit = FMChannel::Admit::Tone; break; case ADMIT_CH_FREE: admit_crit = FMChannel::Admit::Free; break; default: admit_crit = FMChannel::Admit::Free; break; } FMChannel *ach = new FMChannel(); ach->setAdmit(admit_crit); ach->setSquelchDefault(); ach->setRXTone(rxSignaling()); ach->setTXTone(txSignaling()); ach->setBandwidth(bandwidth()); ach->extended()->enableTalkaround(talkaround()); // Apply analog channel extension settings ex->enableDisplayPTTId(displayPTTId()); ch = ach; } else if (MODE_DIGITAL == mode()) { DMRChannel::Admit admit_crit; switch(admitCriterion()) { case ADMIT_ALWAYS: admit_crit = DMRChannel::Admit::Always; break; case ADMIT_CH_FREE: admit_crit = DMRChannel::Admit::Free; break; case ADMIT_COLOR: admit_crit = DMRChannel::Admit::ColorCode; break; default: admit_crit = DMRChannel::Admit::Free; break; } DMRChannel *dch = new DMRChannel(); dch->setAdmit(admit_crit); dch->setColorCode(colorCode()); dch->setTimeSlot(timeSlot()); dch->extended()->enablePrivateCallConfirm(privateCallConfirm()); dch->extended()->enableDataConfirm(dataCallConfirm()); dch->extended()->enableLoneWorker(loneWorker()); dch->extended()->enableTalkaround(talkaround()); // Apply digital channel extension settings ex->enableEmergencyAlarmConfirmed(emergencyAlarmACK()); // If encryption is enabled, Add commercial extension to channel if needed // the key will be linked later if ((PRIV_NONE != privacyType()) && (nullptr == dch->commercialExtension())) dch->setCommercialExtension(new CommercialChannelExtension()); // done ch = dch; } else { errMsg(err) << "Cannot decode channel. Channel type " << mode() << " unknown!"; delete ex; return nullptr; } // Common settings ch->setName(name()); ch->setRXFrequency(rxFrequency()); ch->setTXFrequency(txFrequency()); ch->setTimeout(txTimeOut()); ch->setRXOnly(rxOnly()); // Power setting must be overridden by specialized class ch->setDefaultPower(); if (vox()) ch->setVOXDefault(); else ch->disableVOX(); // Apply common channel settings ex->enableAutoScan(autoScan()); ex->setRXRefFrequency(rxRefFrequency()); ex->setTXRefFrequency(txRefFrequency()); ch->setTyTChannelExtension(ex); return ch; } bool TyTCodeplug::ChannelElement::linkChannelObj(Channel *c, Context &ctx, const ErrorStack &err) const { if (! isValid()) { errMsg(err) << "Cannot link an invalid channel."; return false; } if (scanListIndex() && ctx.has(scanListIndex())) { c->setScanList(ctx.get(scanListIndex())); } if (MODE_ANALOG == mode()) { // Nothing further to link for analog channels. return true; } else if ((MODE_DIGITAL == mode()) && (c->is())){ DMRChannel *dc = c->as(); if (contactIndex() && ctx.has(contactIndex())) { dc->setContact(ctx.get(contactIndex())); } if (groupListIndex() && ctx.has(groupListIndex())) { dc->setGroupList(ctx.get(groupListIndex())); } if (positioningSystemIndex() && ctx.has(positioningSystemIndex())) { dc->setAPRS(ctx.get(positioningSystemIndex())); } // Link encryption key if defined if (PRIV_NONE != privacyType()) { if (nullptr == dc->commercialExtension()) { errMsg(err) << "Cannot link encryption key: No commercial extension set."; return false; } if (PRIV_BASIC == privacyType()) { if (! ctx.has(privacyIndex())) { errMsg(err) << "Cannot link encryption key: No basic key with index " << privacyIndex() << " defined."; return false; } dc->commercialExtension()->setEncryptionKey(ctx.get(privacyIndex())); } else if (PRIV_ENHANCED == privacyType()) { if (! ctx.has(privacyIndex())) { errMsg(err) << "Cannot link encryption key: No AES (enhances) key with index " << privacyIndex() << " defined."; return false; } dc->commercialExtension()->setEncryptionKey(ctx.get(privacyIndex())); } else { errMsg(err) << "Unknown encryption key type " << privacyType() << "."; return false; } } return true; } errMsg(err) << "Cannot link channel '" << c->name() << "' invalid channel type " << mode() << "."; return false; } void TyTCodeplug::ChannelElement::fromChannelObj(const Channel *chan, Context &ctx) { setName(chan->name()); setRXFrequency(chan->rxFrequency()); setTXFrequency(chan->txFrequency()); enableRXOnly(chan->rxOnly()); if (chan->defaultTimeout()) setTXTimeOut(ctx.config()->settings()->tot()); else setTXTimeOut(chan->timeout()); if (chan->scanList()) setScanListIndex(ctx.index(chan->scanList())); else setScanListIndex(0); // Enable vox bool defaultVOXEnabled = (chan->defaultVOX() && ctx.config()->settings()->audio()->voxEnabled()); bool channelVOXEnabled = (! (chan->voxDisabled()||chan->defaultVOX())); enableVOX(defaultVOXEnabled || channelVOXEnabled); // power setting must be set by specialized element if (chan->is()) { const DMRChannel *dchan = chan->as(); setMode(MODE_DIGITAL); switch (dchan->admit()) { case DMRChannel::Admit::Always: setAdmitCriterion(ADMIT_ALWAYS); break; case DMRChannel::Admit::Free: setAdmitCriterion(ADMIT_CH_FREE); break; case DMRChannel::Admit::ColorCode: setAdmitCriterion(ADMIT_COLOR); break; } setColorCode(dchan->colorCode()); setTimeSlot(dchan->timeSlot()); if (dchan->groupList()) setGroupListIndex(ctx.index(dchan->groupList())); else setGroupListIndex(0); if (dchan->contact()) setContactIndex(ctx.index(dchan->contact())); setBandwidth(FMChannel::Bandwidth::Narrow); setRXSignaling(SelectiveCall()); setTXSignaling(SelectiveCall()); enablePrivateCallConfirm(dchan->extended()->privateCallConfirm()); enableDataCallConfirm(dchan->extended()->dataConfirm()); enableLoneWorker(dchan->extended()->loneWorker()); enableTalkaround(dchan->extended()->talkaround()); if (dchan->aprs() && dchan->aprs()->is()) { setPositioningSystemIndex(ctx.index(dchan->aprs()->as())); enableTXGPSInfo(true); enableRXGPSInfo(false); } if (chan->tytChannelExtension()) { enableEmergencyAlarmACK(chan->tytChannelExtension()->emergencyAlarmConfirmed()); } // Link encryption key if set if (dchan->commercialExtension() && dchan->commercialExtension()->encryptionKey()) { // Check for index if (0 > ctx.index(dchan->commercialExtension()->encryptionKey())) { logError() << "Cannot encode encryption key '" << dchan->commercialExtension()->encryptionKey()->name() << "': Not indexed."; } else if (dchan->commercialExtension()->encryptionKey()->is()) { setPrivacyType(PRIV_BASIC); setPrivacyIndex(ctx.index(dchan->commercialExtension()->encryptionKey())); } else if (dchan->commercialExtension()->encryptionKey()->is()) { setPrivacyType(PRIV_ENHANCED); setPrivacyIndex(ctx.index(dchan->commercialExtension()->encryptionKey())); } else { logInfo() << "Ignore unknown encryption key type " << dchan->commercialExtension()->encryptionKey()->metaObject()->className() << " for DMR channel."; } } } else if (chan->is()) { const FMChannel *achan = chan->as(); setMode(MODE_ANALOG); setBandwidth(achan->bandwidth()); // Squelch must be set by specialized element switch (achan->admit()) { case FMChannel::Admit::Always: setAdmitCriterion(ADMIT_ALWAYS); break; case FMChannel::Admit::Free: setAdmitCriterion(ADMIT_CH_FREE); break; case FMChannel::Admit::Tone: setAdmitCriterion(ADMIT_TONE); break; } setRXSignaling(achan->rxTone()); setTXSignaling(achan->txTone()); setGroupListIndex(0); setContactIndex(0); enableTalkaround(achan->extended()->talkaround()); if (chan->tytChannelExtension()) { enableDisplayPTTId(chan->tytChannelExtension()->displayPTTId()); } } // Apply channel extension (if set) if (chan->tytChannelExtension()) { enableAutoScan(chan->tytChannelExtension()->autoScan()); setRXRefFrequency(chan->tytChannelExtension()->rxRefFrequency()); setTXRefFrequency(chan->tytChannelExtension()->txRefFrequency()); } } /* ******************************************************************************************** * * Implementation of TyTCodeplug::ContactElement * ******************************************************************************************** */ TyTCodeplug::ContactElement::ContactElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCodeplug::ContactElement::ContactElement(uint8_t *ptr) : Codeplug::Element(ptr, size()) { // pass... } TyTCodeplug::ContactElement::~ContactElement() { // pass... } bool TyTCodeplug::ContactElement::isValid() const { return Element::isValid() && (0 != getUInt2(Offset::callType())) && (0x0000 != getUInt16_be(Offset::name())) && (0xffff != getUInt16_be(Offset::name())); } void TyTCodeplug::ContactElement::clear() { memset(_data, 0xff, 3); // clear DMR ID setUInt2(Offset::callType(), 0); // type=0 setBit({3,2}, 0); setBit({3,3}, 0); setBit({3,4}, 0); // unused = 0 enableRingTone(false); setBit({3,6}, 1); setBit({3,7}, 1); // unknown = 1 memset(_data+Offset::name(), 0x00, 2*Limit::nameLength()); } uint32_t TyTCodeplug::ContactElement::dmrId() const { return getUInt24_le(Offset::dmrId()); } void TyTCodeplug::ContactElement::setDMRId(uint32_t id) { setUInt24_le(Offset::dmrId(), id); } bool TyTCodeplug::ContactElement::ringTone() const { return getBit(Offset::ringTone()); } void TyTCodeplug::ContactElement::enableRingTone(bool enable) { setBit(Offset::ringTone(), enable); } DMRContact::Type TyTCodeplug::ContactElement::callType() const { switch((CallType)getUInt2(Offset::callType())) { case CallType::GroupCall: return DMRContact::GroupCall; case CallType::PrivateCall: return DMRContact::PrivateCall; case CallType::AllCall: return DMRContact::AllCall; default: break; } return DMRContact::PrivateCall; } void TyTCodeplug::ContactElement::setCallType(DMRContact::Type type) { switch (type) { case DMRContact::GroupCall: setUInt2(Offset::callType(), (unsigned int)CallType::GroupCall); break; case DMRContact::PrivateCall: setUInt2(Offset::callType(), (unsigned int)CallType::PrivateCall); break; case DMRContact::AllCall: setUInt2(Offset::callType(), (unsigned int)CallType::AllCall); break; } } QString TyTCodeplug::ContactElement::name() const { return readUnicode(Offset::name(), Limit::nameLength(), 0x0000); } void TyTCodeplug::ContactElement::setName(const QString &nm) { writeUnicode(Offset::name(), nm, Limit::nameLength(), 0x0000); } DMRContact * TyTCodeplug::ContactElement::toContactObj() const { return new DMRContact(callType(), name(), dmrId(), ringTone()); } bool TyTCodeplug::ContactElement::fromContactObj(const DMRContact *cont) { if (nullptr == cont) return false; setDMRId(cont->number()); setName(cont->name()); setCallType(cont->type()); enableRingTone(cont->ring()); return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::ZoneElement * ******************************************************************************************** */ TyTCodeplug::ZoneElement::ZoneElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCodeplug::ZoneElement::ZoneElement(uint8_t *ptr) : Codeplug::Element(ptr, 0x0040) { // pass... } TyTCodeplug::ZoneElement::~ZoneElement() { // pass... } bool TyTCodeplug::ZoneElement::isValid() const { return Element::isValid() && (0x0000 != getUInt16_be(0)) && (0xffff != getUInt16_be(0)); } void TyTCodeplug::ZoneElement::clear() { memset(_data, 0x00, 0x40); } QString TyTCodeplug::ZoneElement::name() const { return readUnicode(0, 16); } void TyTCodeplug::ZoneElement::setName(const QString &name) { writeUnicode(0, name, 16); } bool TyTCodeplug::ZoneElement::hasMemberIndex(unsigned n) const { return 0 != memberIndex(n); } uint16_t TyTCodeplug::ZoneElement::memberIndex(unsigned n) const { return getUInt16_le(0x20 + n*sizeof(uint16_t)); } void TyTCodeplug::ZoneElement::setMemberIndex(unsigned n, uint16_t idx) { setUInt16_le(0x20 + n*sizeof(uint16_t), idx); } bool TyTCodeplug::ZoneElement::fromZoneObj(const Zone *zone, Context &ctx) { setName(zone->name()); for (int i=0; i<16; i++) { if (i < zone->A()->count()) { Channel *ch = zone->A()->get(i)->as(); int idx = ctx.index(ch); setMemberIndex(i, idx); } else setMemberIndex(i, 0); } return true; } Zone * TyTCodeplug::ZoneElement::toZoneObj() const { if (!isValid()) return nullptr; return new Zone(name()); } bool TyTCodeplug::ZoneElement::linkZone(Zone *zone, Context &ctx) const { if (! isValid()) return false; for (int i=0; ((i<16) && hasMemberIndex(i)); i++) { if (! ctx.has(memberIndex(i))) { logWarn() << "Cannot link channel with index " << memberIndex(i) << " to zone '" << zone->name() << "': channel not defined."; continue; } zone->A()->add(ctx.get(memberIndex(i))); } return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::GroupListElement * ******************************************************************************************** */ TyTCodeplug::GroupListElement::GroupListElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCodeplug::GroupListElement::GroupListElement(uint8_t *ptr) : Codeplug::Element(ptr, 0x0060) { // pass... } TyTCodeplug::GroupListElement::~GroupListElement() { // pass... } bool TyTCodeplug::GroupListElement::isValid() const { return Element::isValid() && (0x0000 != getUInt16_be(0)); } void TyTCodeplug::GroupListElement::clear() { memset(_data, 0x00, 0x60); } QString TyTCodeplug::GroupListElement::name() const { return readUnicode(0, 16); } void TyTCodeplug::GroupListElement::setName(const QString &nm) { writeUnicode(0, nm, 16); } uint16_t TyTCodeplug::GroupListElement::memberIndex(unsigned n) const { return getUInt16_le(0x20 + n*2); } void TyTCodeplug::GroupListElement::setMemberIndex(unsigned n, uint16_t idx) { setUInt16_le(0x20 + 2*n, idx); } bool TyTCodeplug::GroupListElement::fromGroupListObj(const RXGroupList *lst, Context &ctx) { setName(lst->name()); int j=0; // Iterate over all 32 entries in the codeplug for (int i=0; i<32; i++) { // Skip non-private-call entries while((lst->count() > j) && (DMRContact::GroupCall != lst->contact(j)->type())) { logWarn() << "Contact '" << lst->contact(i)->name() << "' in group list '" << lst->name() << "' is not a group call. Skip entry."; j++; } if (lst->count() > j) { setMemberIndex(i, ctx.index(lst->contact(j))); j++; } else { // clear entry setMemberIndex(i, 0); } } return true; } RXGroupList * TyTCodeplug::GroupListElement::toGroupListObj(Context &ctx) { Q_UNUSED(ctx) if (! isValid()) return nullptr; return new RXGroupList(name()); } bool TyTCodeplug::GroupListElement::linkGroupListObj(RXGroupList *lst, Context &ctx) { if (! isValid()) return false; for (int i=0; (i<32) && memberIndex(i); i++) { if (! ctx.has(memberIndex(i))) { logWarn() << "Cannot link contact " << memberIndex(i) << " to group list '" << name() << "': Invalid contact index. Ignored."; continue; } //logDebug() << "Add contact idx=" << memberIndex(i) << " to group list " << lst->name() << "."; if (0 > lst->addContact(ctx.get(memberIndex(i)))) { logWarn() << "Cannot add contact '" << ctx.get(memberIndex(i))->name() << "' at idx=" << memberIndex(i) << "."; continue; } } return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::ScanListElement * ******************************************************************************************** */ TyTCodeplug::ScanListElement::ScanListElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::ScanListElement::ScanListElement(uint8_t *ptr) : Element(ptr, 0x0068) { // pass... } TyTCodeplug::ScanListElement::~ScanListElement() { // pass... } bool TyTCodeplug::ScanListElement::isValid() const { return Element::isValid() && (0x0000 != getUInt16_be(0)); } void TyTCodeplug::ScanListElement::clear() { memset(_data, 0, 2*16); setPriorityChannel1Index(0xffff); setPriorityChannel2Index(0xffff); setTXChannelIndex(0xffff); setUInt8(0x26, 0xf1); setHoldTime(500); setPrioritySampleTime(2000); setUInt8(0x29, 0xff); memset(_data+0x2a, 0, 2*31); } QString TyTCodeplug::ScanListElement::name() const { return readUnicode(0, 16); } void TyTCodeplug::ScanListElement::setName(const QString &name) { writeUnicode(0, name, 16); } uint16_t TyTCodeplug::ScanListElement::priorityChannel1Index() const { return getUInt16_le(0x20); } void TyTCodeplug::ScanListElement::setPriorityChannel1Index(uint16_t idx) { setUInt16_le(0x20, idx); } uint16_t TyTCodeplug::ScanListElement::priorityChannel2Index() const { return getUInt16_le(0x22); } void TyTCodeplug::ScanListElement::setPriorityChannel2Index(uint16_t idx) { setUInt16_le(0x22, idx); } uint16_t TyTCodeplug::ScanListElement::txChannelIndex() const { return getUInt16_le(0x24); } void TyTCodeplug::ScanListElement::setTXChannelIndex(uint16_t idx) { setUInt16_le(0x24, idx); } unsigned TyTCodeplug::ScanListElement::holdTime() const { return unsigned(getUInt8(0x27))*25; } void TyTCodeplug::ScanListElement::setHoldTime(unsigned time) { setUInt8(0x27, time/25); } unsigned TyTCodeplug::ScanListElement::prioritySampleTime() const { return unsigned(getUInt8(0x28))*250; } void TyTCodeplug::ScanListElement::setPrioritySampleTime(unsigned time) { setUInt8(0x28, time/250); } uint16_t TyTCodeplug::ScanListElement::memberIndex(unsigned n) const { return getUInt16_le(0x2a + 2*n); } void TyTCodeplug::ScanListElement::setMemberIndex(unsigned n, uint16_t idx) { setUInt16_le(0x2a + 2*n, idx); } bool TyTCodeplug::ScanListElement::fromScanListObj(const ScanList *lst, Context &ctx) { // Set name setName(lst->name()); // Set priority channel 1 if (lst->primaryChannel() && (SelectedChannel::get() == lst->primaryChannel())) setPriorityChannel1Index(0); else if (lst->primaryChannel()) setPriorityChannel1Index(ctx.index(lst->primaryChannel())); else setPriorityChannel1Index(0xffff); // Set priority channel 2 if (lst->secondaryChannel() && (SelectedChannel::get() == lst->secondaryChannel())) setPriorityChannel2Index(0); else if (lst->secondaryChannel()) setPriorityChannel2Index(ctx.index(lst->secondaryChannel())); else setPriorityChannel2Index(0xffff); // Set transmit channel if (lst->revertChannel() && (SelectedChannel::get() == lst->revertChannel())) setTXChannelIndex(0); else if (lst->revertChannel()) setTXChannelIndex(ctx.index(lst->revertChannel())); else setTXChannelIndex(0xffff); for (int i=0, j=0; i<31;) { if (j >= lst->count()) { setMemberIndex(i++, 0); } else if (SelectedChannel::get() == lst->channel(j)) { logInfo() << "Cannot encode '" << lst->channel(j) << "' for UV390: skip."; j++; } else { setMemberIndex(i++, ctx.index(lst->channel(j++))); } } if (TyTScanListExtension *ex = lst->tytScanListExtension()) { setHoldTime(ex->holdTime()); setPrioritySampleTime(ex->prioritySampleTime()); } return true; } ScanList * TyTCodeplug::ScanListElement::toScanListObj(Context &ctx) { Q_UNUSED(ctx) if (! isValid()) return nullptr; ScanList *lst = new ScanList(name()); TyTScanListExtension *ex = new TyTScanListExtension(); lst->setTyTScanListExtension(ex); ex->setHoldTime(holdTime()); ex->setPrioritySampleTime(prioritySampleTime()); return lst; } bool TyTCodeplug::ScanListElement::linkScanListObj(ScanList *lst, Context &ctx, const ErrorStack &err) { if (! isValid()) { errMsg(err) << "Cannot link invalid scanlist."; return false; } if (0 == priorityChannel1Index()) lst->setPrimaryChannel(SelectedChannel::get()); else if (ctx.has(priorityChannel1Index())) lst->setPrimaryChannel(ctx.get(priorityChannel1Index())); else if (0xffff == priorityChannel1Index()) lst->setPrimaryChannel(nullptr); else logWarn() << "Cannot decode reference to priority channel index " << priorityChannel1Index() << " in scan list '" << name() << "'."; if (0 == priorityChannel2Index()) lst->setSecondaryChannel(SelectedChannel::get()); else if (ctx.has(priorityChannel2Index())) lst->setSecondaryChannel(ctx.get(priorityChannel2Index())); else if (0xffff == priorityChannel2Index()) lst->setSecondaryChannel(nullptr); else logWarn() << "Cannot decode reference to secondary priority channel index " << priorityChannel2Index() << " in scan list '" << name() << "'."; if (0 == txChannelIndex()) lst->setRevertChannel(SelectedChannel::get()); else if (ctx.has(txChannelIndex())) lst->setRevertChannel(ctx.get(txChannelIndex())); else if (0xffff == txChannelIndex()) lst->setRevertChannel(nullptr); else logWarn() << "Cannot decode reference to transmit channel index " << txChannelIndex() << " in scan list '" << name() << "'."; for (int i=0; ((i<31) && memberIndex(i)); i++) { if (! ctx.has(memberIndex(i))) { errMsg(err) << "Cannot link scanlist to channel idx " << memberIndex(i) << ". Unknown channel index."; return false; } lst->addChannel(ctx.get(memberIndex(i))); } return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::GeneralSettingsElement * ******************************************************************************************** */ TyTCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : Element(ptr, SETTINGS_SIZE) { // pass... } TyTCodeplug::GeneralSettingsElement::~GeneralSettingsElement() { // pass... } void TyTCodeplug::GeneralSettingsElement::clear() { setIntroLine1(""); setIntroLine2(""); memset(_data+0x26, 0xff, 0x1a); setBit(0x40,0, 0); setBit(0,1, 1); disableAllLEDs(false); setBit(0x40,3, 1); setMonitorType(TyTSettingsExtension::MonitorType::Open); setUInt3(0x40, 5, 7); setSavePreamble(true); setSaveModeRX(true); disableAllTones(false); setBit(0x41,3, 1); setChFreeIndicationTone(true); enablePasswdAndLock(false); enableTalkPermitToneDigital(false); enableTalkPermitToneAnalog(false); setBit(0x42,0, 0); setBit(0x41,1, 1); setBit(0x42,2, 0); setBit(0x42,3, 1); setBootDisplay(BootSettings::BootDisplay::Logo); setBit(0x42,5, 1); setBit(0x42,6, 1); setBit(0x42,7, 1); setUInt8(0x43, 0xff); setDMRId(0); setUInt8(0x47,0); setTXPreambleDuration(Interval::fromMilliseconds(600)); setGroupCallHangTime(Interval::fromMilliseconds(3000)); setPrivateCallHangTime(Interval::fromMilliseconds(4000)); setVOXSesitivity(Level::fromValue(3)); setUInt8(0x4c, 0x00); setUInt8(0x4d, 0x00); setLowBatteryInterval(120); setCallAlertToneDuration(0); setLoneWorkerResponseTime(1); setLoneWorkerReminderTime(10); setUInt8(0x52, 0x00); setScanDigitalHangTime(1000); setScanAnalogHangTime(1000); setBacklightTime(10); setUInt6(0x55, 2, 0); keypadLockTimeSetManual(); setUInt8(0x57, 0xff); setPowerOnPassword(0); radioProgPasswordDisable(); pcProgPasswordDisable(); setUInt32_le(0x68, 0xffffffff); setUInt32_le(0x6c, 0xffffffff); setRadioName(""); } QString TyTCodeplug::GeneralSettingsElement::introLine1() const { return readUnicode(0, 10); } void TyTCodeplug::GeneralSettingsElement::setIntroLine1(const QString line) { writeUnicode(0, line, 10); } QString TyTCodeplug::GeneralSettingsElement::introLine2() const { return readUnicode(0x14, 10); } void TyTCodeplug::GeneralSettingsElement::setIntroLine2(const QString line) { writeUnicode(0x14, line, 10); } TyTSettingsExtension::MonitorType TyTCodeplug::GeneralSettingsElement::monitorType() const { return getBit(0x40,4) ? TyTSettingsExtension::MonitorType::Open : TyTSettingsExtension::MonitorType::Silent; } void TyTCodeplug::GeneralSettingsElement::setMonitorType(TyTSettingsExtension::MonitorType type) { setBit(0x40,4, TyTSettingsExtension::MonitorType::Open==type); } bool TyTCodeplug::GeneralSettingsElement::allLEDsDisabled() const { return ! getBit(0x40,2); } void TyTCodeplug::GeneralSettingsElement::disableAllLEDs(bool disable) { setBit(0x40,2, !disable); } bool TyTCodeplug::GeneralSettingsElement::savePreamble() const { return getBit(0x41,0); } void TyTCodeplug::GeneralSettingsElement::setSavePreamble(bool enable) { setBit(0x41,0, enable); } bool TyTCodeplug::GeneralSettingsElement::saveModeRX() const { return getBit(0x41,1); } void TyTCodeplug::GeneralSettingsElement::setSaveModeRX(bool enable) { setBit(0x41,1, enable); } bool TyTCodeplug::GeneralSettingsElement::allTonesDisabled() const { return ! getBit(0x41,2); } void TyTCodeplug::GeneralSettingsElement::disableAllTones(bool disable) { setBit(0x41,2, !disable); } bool TyTCodeplug::GeneralSettingsElement::chFreeIndicationTone() const { return !getBit(0x41,4); } void TyTCodeplug::GeneralSettingsElement::setChFreeIndicationTone(bool enable) { setBit(0x41,4, !enable); } bool TyTCodeplug::GeneralSettingsElement::passwdAndLock() const { return ! getBit(0x41,5); } void TyTCodeplug::GeneralSettingsElement::enablePasswdAndLock(bool enable) { setBit(0x41,5, !enable); } bool TyTCodeplug::GeneralSettingsElement::talkPermitToneDigital() const { return getBit(0x41,6); } void TyTCodeplug::GeneralSettingsElement::enableTalkPermitToneDigital(bool enable) { setBit(0x41,6, enable); } bool TyTCodeplug::GeneralSettingsElement::talkPermitToneAnalog() const { return getBit(0x41,7); } void TyTCodeplug::GeneralSettingsElement::enableTalkPermitToneAnalog(bool enable) { setBit(0x41,7, enable); } BootSettings::BootDisplay TyTCodeplug::GeneralSettingsElement::bootDisplay() const { return getBit(Offset::bootImageEnabled()) ? BootSettings::BootDisplay::Logo : BootSettings::BootDisplay::Text; } void TyTCodeplug::GeneralSettingsElement::setBootDisplay(BootSettings::BootDisplay mode) { switch (mode) { case BootSettings::BootDisplay::Text: setBit(Offset::bootImageEnabled(), false); break; case BootSettings::BootDisplay::Logo: case BootSettings::BootDisplay::Image: setBit(Offset::bootImageEnabled(), true); break; } } uint32_t TyTCodeplug::GeneralSettingsElement::dmrId() const { return getUInt24_le(0x44); } void TyTCodeplug::GeneralSettingsElement::setDMRId(uint32_t id) { setUInt24_le(0x44, id); } Interval TyTCodeplug::GeneralSettingsElement::txPreambleDuration() const { return Interval::fromMilliseconds(unsigned(getUInt8(0x48))*60); } void TyTCodeplug::GeneralSettingsElement::setTXPreambleDuration(const Interval &dur) { auto ms = std::min(8640ULL, dur.milliseconds()); setUInt8(0x48, ms/60); } Interval TyTCodeplug::GeneralSettingsElement::groupCallHangTime() const { return Interval::fromMilliseconds(unsigned(getUInt8(0x49))*100); } void TyTCodeplug::GeneralSettingsElement::setGroupCallHangTime(const Interval &dur) { auto ms = std::min(7000ULL, dur.milliseconds()); setUInt8(0x49, ms/100); } Interval TyTCodeplug::GeneralSettingsElement::privateCallHangTime() const { return Interval::fromMilliseconds(unsigned(getUInt8(0x4a))*100); } void TyTCodeplug::GeneralSettingsElement::setPrivateCallHangTime(const Interval &dur) { auto ms = std::min(7000ULL, dur.milliseconds()); setUInt8(0x4a, ms/100); } Level TyTCodeplug::GeneralSettingsElement::voxSesitivity() const { return Level::fromValue(getUInt8(0x4b)); } void TyTCodeplug::GeneralSettingsElement::setVOXSesitivity(Level level) { setUInt8(0x4b, level.mapTo(Limit::vox())); } unsigned TyTCodeplug::GeneralSettingsElement::lowBatteryInterval() const { return unsigned(getUInt8(0x4e))*5; } void TyTCodeplug::GeneralSettingsElement::setLowBatteryInterval(unsigned dur) { dur = std::min(635U, dur); setUInt8(0x4e, dur/5); } bool TyTCodeplug::GeneralSettingsElement::callAlertToneIsContinuous() const { return 0==getUInt8(0x4f); } unsigned TyTCodeplug::GeneralSettingsElement::callAlertToneDuration() const { return unsigned(getUInt8(0x4f))*5; } void TyTCodeplug::GeneralSettingsElement::setCallAlertToneDuration(unsigned dur) { dur = std::min(1200U, dur); setUInt8(0x4f, dur/5); } void TyTCodeplug::GeneralSettingsElement::setCallAlertToneContinuous() { setUInt8(0x4f, 0); } unsigned TyTCodeplug::GeneralSettingsElement::loneWorkerResponseTime() const { return getUInt8(0x50); } void TyTCodeplug::GeneralSettingsElement::setLoneWorkerResponseTime(unsigned dur) { setUInt8(0x50, dur); } unsigned TyTCodeplug::GeneralSettingsElement::loneWorkerReminderTime() const { return getUInt8(0x51); } void TyTCodeplug::GeneralSettingsElement::setLoneWorkerReminderTime(unsigned dur) { setUInt8(0x51, dur); } unsigned TyTCodeplug::GeneralSettingsElement::scanDigitalHangTime() const { return unsigned(getUInt8(0x53))*100; } void TyTCodeplug::GeneralSettingsElement::setScanDigitalHangTime(unsigned dur) { dur = std::min(10000U, dur); setUInt8(0x53, dur/100); } unsigned TyTCodeplug::GeneralSettingsElement::scanAnalogHangTime() const { return unsigned(getUInt8(0x54))*100; } void TyTCodeplug::GeneralSettingsElement::setScanAnalogHangTime(unsigned dur) { dur = std::min(10000U, dur); setUInt8(0x54, dur/100); } bool TyTCodeplug::GeneralSettingsElement::backlightIsAlways() const { return 0 == getUInt2(0x55, 0); } unsigned TyTCodeplug::GeneralSettingsElement::backlightTime() const { return getUInt2(0x55, 0)*5; } void TyTCodeplug::GeneralSettingsElement::setBacklightTime(unsigned sec) { sec = std::min(15U, sec); setUInt2(0x55, 0, sec/5); } void TyTCodeplug::GeneralSettingsElement::backlightTimeSetAlways() { setUInt2(0x55, 0, 0); } bool TyTCodeplug::GeneralSettingsElement::keypadLockIsManual() const { return 0xff == getUInt8(0x56); } unsigned TyTCodeplug::GeneralSettingsElement::keypadLockTime() const { return unsigned(getUInt8(0x56))*5; } void TyTCodeplug::GeneralSettingsElement::setKeypadLockTime(unsigned sec) { sec = std::max(1U, std::min(15U, sec)); setUInt8(0x56, sec/5); } void TyTCodeplug::GeneralSettingsElement::keypadLockTimeSetManual() { setUInt8(0x56, 0xff); } uint32_t TyTCodeplug::GeneralSettingsElement::powerOnPassword() const { return getBCD8_le(0x58); } void TyTCodeplug::GeneralSettingsElement::setPowerOnPassword(uint32_t pass) { setBCD8_le(0x58, pass); } bool TyTCodeplug::GeneralSettingsElement::radioProgPasswordEnabled() const { return 0xffffffff != getUInt32_le(0x5c); } uint32_t TyTCodeplug::GeneralSettingsElement::radioProgPassword() const { return getBCD8_le(0x5c); } void TyTCodeplug::GeneralSettingsElement::setRadioProgPassword(uint32_t passwd) { setBCD8_le(0x5c, passwd); } void TyTCodeplug::GeneralSettingsElement::radioProgPasswordDisable() { setUInt32_le(0x5c, 0xffffffff); } bool TyTCodeplug::GeneralSettingsElement::pcProgPasswordEnabled() const { return (0xff != getUInt8(0x60)); } QString TyTCodeplug::GeneralSettingsElement::pcProgPassword() const { return readASCII(0x60, 8, 0xff); } void TyTCodeplug::GeneralSettingsElement::setPCProgPassword(const QString &pass) { writeASCII(0x60, pass, 8, 0xff); } void TyTCodeplug::GeneralSettingsElement::pcProgPasswordDisable() { memset(_data+0x60, 0xff, 8); } QString TyTCodeplug::GeneralSettingsElement::radioName() const { return readUnicode(0x70, 16); } void TyTCodeplug::GeneralSettingsElement::setRadioName(const QString &name) { writeUnicode(0x70, name, 16); } bool TyTCodeplug::GeneralSettingsElement::fromConfig(const Config *config) { if (config->settings()->defaultIdRef()->isNull()) return false; setRadioName(config->settings()->defaultIdRef()->as()->name()); setDMRId(config->settings()->defaultIdRef()->as()->number()); setIntroLine1(config->settings()->boot()->message1()); setIntroLine2(config->settings()->boot()->message2()); setVOXSesitivity(config->settings()->audio()->vox()); setBootDisplay(config->settings()->boot()->bootDisplay()); if (! config->settings()->boot()->bootPasswordEnabled()) setPowerOnPassword(0); else setPowerOnPassword(config->settings()->boot()->bootPassword().toUInt()); // apply tone settings disableAllTones(config->settings()->tone()->silent()); enableTalkPermitToneDigital(config->settings()->tone()->talkPermit().testFlag(Channel::Type::DMR)); enableTalkPermitToneAnalog(config->settings()->tone()->talkPermit().testFlag(Channel::Type::FM)); setChFreeIndicationTone(config->settings()->tone()->channelIdle().testFlags(Channel::Type::DMR|Channel::Type::FM)); setPrivateCallHangTime(config->settings()->dmr()->privateCallHangTime()); setGroupCallHangTime(config->settings()->dmr()->groupCallHangTime()); setTXPreambleDuration(config->settings()->dmr()->preamble()); if (TyTSettingsExtension *ex = config->settings()->tytExtension()) { setMonitorType(ex->monitorType()); disableAllLEDs(ex->allLEDsDisabled()); enablePasswdAndLock(ex->passwordAndLock()); setSaveModeRX(ex->powerSaveMode()); setSavePreamble(ex->wakeupPreamble()); setLowBatteryInterval(ex->lowBatteryWarnInterval()); if (ex->callAlertToneContinuous()) setCallAlertToneContinuous(); else setCallAlertToneDuration(ex->callAlertToneDuration()); setLoneWorkerResponseTime(ex->loneWorkerResponseTime()); setLoneWorkerReminderTime(ex->loneWorkerReminderTime()); setScanDigitalHangTime(ex->digitalScanHangTime()); setScanAnalogHangTime(ex->analogScanHangTime()); if (ex->backlightAlwaysOn()) backlightTimeSetAlways(); else setBacklightTime(ex->backlightDuration()); if (ex->keypadLockManual()) keypadLockTimeSetManual(); else setKeypadLockTime(ex->keypadLockTime()); if (! ex->radioProgPasswordEnabled()) radioProgPasswordDisable(); else setRadioProgPassword(ex->radioProgPassword()); setPCProgPassword(ex->pcProgPassword()); } return true; } bool TyTCodeplug::GeneralSettingsElement::updateConfig(Config *config) { int idx = config->radioIDs()->add(new DMRRadioID(radioName(), dmrId())); if (0 <= idx) { config->settings()->defaultIdRef()->set( config->radioIDs()->get(idx)->as()); } else { logError() << "Cannot add radio DMR ID & cannot set default ID."; return false; } // Store boot settings config->settings()->boot()->setBootDisplay(bootDisplay()); config->settings()->boot()->setMessage1(introLine1()); config->settings()->boot()->setMessage2(introLine2()); config->settings()->boot()->enableBootPassword(0 != powerOnPassword()); config->settings()->boot()->setBootPassword(QString::number(powerOnPassword())); // Store audio settings config->settings()->audio()->setVox(voxSesitivity()); // Store tone settings config->settings()->tone()->enableSilent(allTonesDisabled()); config->settings()->tone()->setTalkPermit( (talkPermitToneDigital() ? Channel::Type::DMR : Channel::Type::None) | (talkPermitToneAnalog() ? Channel::Type::FM : Channel::Type::None)); config->settings()->tone()->setChannelIdle( chFreeIndicationTone() ? (Channel::Type::DMR|Channel::Type::FM) : Channel::Type::None); config->settings()->dmr()->setPrivateCallHangTime(privateCallHangTime()); config->settings()->dmr()->setGroupCallHangTime(groupCallHangTime()); config->settings()->dmr()->setPreamble(txPreambleDuration()); // apply extension TyTSettingsExtension *ex = new TyTSettingsExtension(); config->settings()->setTyTExtension(ex); ex->setMonitorType(monitorType()); ex->disableAllLEDs(allLEDsDisabled()); ex->enablePasswordAndLock(passwdAndLock()); ex->enablePowerSaveMode(saveModeRX()); ex->enableWakeupPreamble(savePreamble()); ex->setLowBatteryWarnInterval(lowBatteryInterval()); ex->enableCallAlertToneContinuous(callAlertToneIsContinuous()); if (! callAlertToneIsContinuous()) ex->setCallAlertToneDuration(callAlertToneDuration()); ex->setLoneWorkerResponseTime(loneWorkerResponseTime()); ex->setLoneWorkerReminderTime(loneWorkerReminderTime()); ex->setDigitalScanHangTime(scanDigitalHangTime()); ex->setAnalogScanHangTime(scanAnalogHangTime()); ex->enableBacklightAlwaysOn(backlightIsAlways()); if (! backlightIsAlways()) ex->setBacklightDuration(backlightTime()); ex->enableKeypadLockManual(keypadLockIsManual()); if (! keypadLockIsManual()) ex->setKeypadLockTime(keypadLockTime()); ex->enableRadioProgPassword(radioProgPasswordEnabled()); if (radioProgPasswordEnabled()) ex->setRadioProgPassword(ex->radioProgPassword()); ex->setPCProgPassword(pcProgPassword()); return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::TimestampElement * ******************************************************************************************** */ TyTCodeplug::TimestampElement::TimestampElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCodeplug::TimestampElement::TimestampElement(uint8_t *ptr) : Codeplug::Element(ptr, 0x0c) { // pass... } TyTCodeplug::TimestampElement::~TimestampElement() { // pass... } void TyTCodeplug::TimestampElement::clear() { setUInt8(0x00, 0); setTimestamp(QDateTime::currentDateTime()); setUInt32_be(0x08, 0x00010300); } QDateTime TyTCodeplug::TimestampElement::timestamp() const { return QDateTime(QDate(getBCD4_le(0x01), getBCD2(0x03), getBCD2(0x04)), QTime(getBCD2(0x05), getBCD2(0x06), getBCD2(0x07))); } void TyTCodeplug::TimestampElement::setTimestamp(const QDateTime &ts) { setBCD4_le(0x01, ts.date().year()); setBCD2(0x03, ts.date().month()); setBCD2(0x04, ts.date().day()); setBCD2(0x05, ts.time().hour()); setBCD2(0x06, ts.time().minute()); setBCD2(0x07, ts.time().second()); } QString TyTCodeplug::TimestampElement::cpsVersion() const { const char table[] = "0123456789:;<=>?"; QString v; v.append(table[std::min(uint8_t(15), getUInt8(0x08))]); v.append(table[std::min(uint8_t(15), getUInt8(0x09))]); v.append("."); v.append(table[std::min(uint8_t(15), getUInt8(0x0a))]); v.append(table[std::min(uint8_t(15), getUInt8(0x0b))]); return v; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::GPSSystemElement * ******************************************************************************************** */ TyTCodeplug::GPSSystemElement::GPSSystemElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } TyTCodeplug::GPSSystemElement::GPSSystemElement(uint8_t *ptr) : Codeplug::Element(ptr, 0x10) { // pass... } TyTCodeplug::GPSSystemElement::~GPSSystemElement() { // pass... } bool TyTCodeplug::GPSSystemElement::isValid() const { return Element::isValid() && (! repeatIntervalDisabled()) && (!destinationContactDisabled()); } void TyTCodeplug::GPSSystemElement::clear() { setRevertChannelIndex(0xffff); disableRepeatInterval(); setUInt8(0x03, 0xff); disableDestinationContact(); memset(_data + 0x06, 0xff, 10); } bool TyTCodeplug::GPSSystemElement::revertChannelIsSelected() const { return 0 == getUInt16_le(0x00); } uint16_t TyTCodeplug::GPSSystemElement::revertChannelIndex() const { return getUInt16_le(0x00); } void TyTCodeplug::GPSSystemElement::setRevertChannelIndex(uint16_t idx) { setUInt16_le(0x00, idx); } void TyTCodeplug::GPSSystemElement::setRevertChannelSelected() { setUInt16_le(0x00, 0); } bool TyTCodeplug::GPSSystemElement::repeatIntervalDisabled() const { return 0 == getUInt8(0x02); } Interval TyTCodeplug::GPSSystemElement::repeatInterval() const { return Interval::fromSeconds(unsigned(getUInt8(0x02))*30); } void TyTCodeplug::GPSSystemElement::setRepeatInterval(const Interval &dur) { if (! dur.isFinite()) disableRepeatInterval(); else setUInt8(0x02, dur.seconds()/30); } void TyTCodeplug::GPSSystemElement::disableRepeatInterval() { setUInt8(0x02, 0); } bool TyTCodeplug::GPSSystemElement::destinationContactDisabled() const { return 0 == getUInt16_le(0x04); } uint16_t TyTCodeplug::GPSSystemElement::destinationContactIndex() const { return getUInt16_le(0x04); } void TyTCodeplug::GPSSystemElement::setDestinationContactIndex(uint16_t idx) { setUInt16_le(0x04, idx); } void TyTCodeplug::GPSSystemElement::disableDestinationContact() { setUInt16_le(0x04, 0); } bool TyTCodeplug::GPSSystemElement::fromGPSSystemObj(DMRAPRSSystem *sys, Context &ctx) { clear(); if (sys->hasContact()) setDestinationContactIndex(ctx.index(sys->contact())); if (sys->hasRevertChannel()) setRevertChannelIndex(ctx.index(sys->revertChannel())); else setRevertChannelSelected(); setRepeatInterval(sys->period()); return true; } DMRAPRSSystem * TyTCodeplug::GPSSystemElement::toGPSSystemObj() { return new DMRAPRSSystem("GPS System", nullptr, nullptr, repeatInterval()); } bool TyTCodeplug::GPSSystemElement::linkGPSSystemObj(DMRAPRSSystem *sys, Context &ctx) { if (! isValid()) return false; if ((! destinationContactDisabled()) && (ctx.has(destinationContactIndex()))) sys->setContact(ctx.get(destinationContactIndex())); if (revertChannelIsSelected()) sys->resetRevertChannel(); else if (ctx.has(revertChannelIndex()) && ctx.get(revertChannelIndex())->is()) sys->setRevertChannel(ctx.get(revertChannelIndex())->as()); return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::MenuSettingsElement * ******************************************************************************************** */ TyTCodeplug::MenuSettingsElement::MenuSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::MenuSettingsElement::MenuSettingsElement(uint8_t *ptr) : Element(ptr, MENUSETTINGS_SIZE) { // pass... } TyTCodeplug::MenuSettingsElement::~MenuSettingsElement() { // pass... } void TyTCodeplug::MenuSettingsElement::clear() { setMenuHangtime(0); enableTextMessage(true); enableCallAlert(true); enableContactEditing(true); enableManualDial(true); enableRemoteRadioCheck(false); enableRemoteMonitor(false); enableRemoteRadioEnable(false); enableRemoteRadioDisable(false); setBit(0x02, 0, false); enableScan(true); enableScanListEditing(true); enableCallLogMissed(true); enableCallLogAnswered(true); enableCallLogOutgoing(true); enableTalkaround(false); enableAlertTone(true); enablePower(true); enableBacklight(true); enableBootScreen(true); enableKeypadLock(true); enableLEDIndicator(true); enableSquelch(true); setBit(0x03, 6, false); enableVOX(true); enablePassword(false); enableDisplayMode(true); enableRadioProgramming(true); setBit(0x04, 3, true); setBit(0x04, 1, true); setBit(0x04, 5, true); setBit(0x04, 6, true); setBit(0x04, 7, true); setUInt8(0x05, 0xfb); setUInt8(0x06, 0xff); memset(_data+0x07, 0xff, 9); } bool TyTCodeplug::MenuSettingsElement::menuHangtimeIsInfinite() const { return 0 == menuHangtime(); } unsigned TyTCodeplug::MenuSettingsElement::menuHangtime() const { return getUInt8(0x00); } void TyTCodeplug::MenuSettingsElement::setMenuHangtime(unsigned sec) { setUInt8(0x00, sec); } void TyTCodeplug::MenuSettingsElement::infiniteMenuHangtime() { setUInt8(0x00, 0x00); } bool TyTCodeplug::MenuSettingsElement::textMessage() const { return getBit(0x01, 0); } void TyTCodeplug::MenuSettingsElement::enableTextMessage(bool enable) { setBit(0x01, 0, enable); } bool TyTCodeplug::MenuSettingsElement::callAlert() const { return getBit(0x01, 1); } void TyTCodeplug::MenuSettingsElement::enableCallAlert(bool enable) { setBit(0x01, 1, enable); } bool TyTCodeplug::MenuSettingsElement::contactEditing() const { return getBit(0x01, 2); } void TyTCodeplug::MenuSettingsElement::enableContactEditing(bool enable) { setBit(0x01, 2, enable); } bool TyTCodeplug::MenuSettingsElement::manualDial() const { return getBit(0x01, 3); } void TyTCodeplug::MenuSettingsElement::enableManualDial(bool enable) { setBit(0x01, 3, enable); } bool TyTCodeplug::MenuSettingsElement::remoteRadioCheck() const { return getBit(0x01, 4); } void TyTCodeplug::MenuSettingsElement::enableRemoteRadioCheck(bool enable) { setBit(0x01, 4, enable); } bool TyTCodeplug::MenuSettingsElement::remoteMonitor() const { return getBit(0x01, 5); } void TyTCodeplug::MenuSettingsElement::enableRemoteMonitor(bool enable) { setBit(0x01, 5, enable); } bool TyTCodeplug::MenuSettingsElement::remoteRadioEnable() const { return getBit(0x01, 6); } void TyTCodeplug::MenuSettingsElement::enableRemoteRadioEnable(bool enable) { setBit(0x01, 6, enable); } bool TyTCodeplug::MenuSettingsElement::remoteRadioDisable() const { return getBit(0x01, 7); } void TyTCodeplug::MenuSettingsElement::enableRemoteRadioDisable(bool enable) { setBit(0x01, 7, enable); } bool TyTCodeplug::MenuSettingsElement::scan() const { return getBit(0x02, 1); } void TyTCodeplug::MenuSettingsElement::enableScan(bool enable) { setBit(0x02, 1, enable); } bool TyTCodeplug::MenuSettingsElement::scanListEditing() const { return getBit(0x02, 2); } void TyTCodeplug::MenuSettingsElement::enableScanListEditing(bool enable) { setBit(0x02, 2, enable); } bool TyTCodeplug::MenuSettingsElement::callLogMissed() const { return getBit(0x02, 3); } void TyTCodeplug::MenuSettingsElement::enableCallLogMissed(bool enable) { setBit(0x02, 3, enable); } bool TyTCodeplug::MenuSettingsElement::callLogAnswered() const { return getBit(0x02, 4); } void TyTCodeplug::MenuSettingsElement::enableCallLogAnswered(bool enable) { setBit(0x02, 4, enable); } bool TyTCodeplug::MenuSettingsElement::callLogOutgoing() const { return getBit(0x02, 5); } void TyTCodeplug::MenuSettingsElement::enableCallLogOutgoing(bool enable) { setBit(0x02, 5, enable); } bool TyTCodeplug::MenuSettingsElement::talkaround() const { return getBit(0x02, 6); } void TyTCodeplug::MenuSettingsElement::enableTalkaround(bool enable) { setBit(0x02, 6, enable); } bool TyTCodeplug::MenuSettingsElement::alertTone() const { return getBit(0x02, 7); } void TyTCodeplug::MenuSettingsElement::enableAlertTone(bool enable) { setBit(0x02, 7, enable); } bool TyTCodeplug::MenuSettingsElement::power() const { return getBit(0x03, 0); } void TyTCodeplug::MenuSettingsElement::enablePower(bool enable) { setBit(0x03, 0, enable); } bool TyTCodeplug::MenuSettingsElement::backlight() const { return getBit(0x03, 1); } void TyTCodeplug::MenuSettingsElement::enableBacklight(bool enable) { setBit(0x03, 1, enable); } bool TyTCodeplug::MenuSettingsElement::bootScreen() const { return getBit(0x03, 2); } void TyTCodeplug::MenuSettingsElement::enableBootScreen(bool enable) { setBit(0x03, 2, enable); } bool TyTCodeplug::MenuSettingsElement::keypadLock() const { return getBit(0x03, 3); } void TyTCodeplug::MenuSettingsElement::enableKeypadLock(bool enable) { setBit(0x03, 3, enable); } bool TyTCodeplug::MenuSettingsElement::ledIndicator() const { return getBit(0x03, 4); } void TyTCodeplug::MenuSettingsElement::enableLEDIndicator(bool enable) { setBit(0x03, 4, enable); } bool TyTCodeplug::MenuSettingsElement::squelch() const { return getBit(0x03, 5); } void TyTCodeplug::MenuSettingsElement::enableSquelch(bool enable) { setBit(0x03, 5, enable); } bool TyTCodeplug::MenuSettingsElement::vox() const { return getBit(0x03, 7); } void TyTCodeplug::MenuSettingsElement::enableVOX(bool enable) { setBit(0x03, 7, enable); } bool TyTCodeplug::MenuSettingsElement::password() const { return getBit(0x04, 0); } void TyTCodeplug::MenuSettingsElement::enablePassword(bool enable) { setBit(0x04, 0, enable); } bool TyTCodeplug::MenuSettingsElement::displayMode() const { return getBit(0x04, 1); } void TyTCodeplug::MenuSettingsElement::enableDisplayMode(bool enable) { setBit(0x04, 1, enable); } bool TyTCodeplug::MenuSettingsElement::radioProgramming() const { return ! getBit(0x04, 2); } void TyTCodeplug::MenuSettingsElement::enableRadioProgramming(bool enable) { setBit(0x04, 2, !enable); } bool TyTCodeplug::MenuSettingsElement::fromConfig(const Config *config) { if (nullptr == config->tytExtension()) return true; TyTMenuSettings *ex = config->tytExtension()->menuSettings(); if (ex->hangtimeIsInfinite()) infiniteMenuHangtime(); else setMenuHangtime(ex->hangTime()); enableTextMessage(ex->textMessage()); enableCallAlert(ex->callAlert()); enableContactEditing(ex->contactEditing()); enableManualDial(ex->manualDial()); enableRemoteRadioCheck(ex->remoteRadioCheck()); enableRemoteMonitor(ex->remoteMonitor()); enableRemoteRadioEnable(ex->remoteRadioEnable()); enableRemoteRadioDisable(ex->remoteRadioDisable()); enableScan(ex->scan()); enableScanListEditing(ex->scanListEditing()); enableCallLogMissed(ex->callLogMissed()); enableCallLogAnswered(ex->callLogAnswered()); enableCallLogOutgoing(ex->callLogOutgoing()); enableTalkaround(ex->talkaround()); enableAlertTone(ex->alertTone()); enablePower(ex->power()); enableBacklight(ex->backlight()); enableBootScreen(ex->bootScreen()); enableKeypadLock(ex->keypadLock()); enableLEDIndicator(ex->ledIndicator()); enableSquelch(ex->squelch()); enableVOX(ex->vox()); enablePassword(ex->password()); enableDisplayMode(ex->displayMode()); enableRadioProgramming(ex->radioProgramming()); return true; } bool TyTCodeplug::MenuSettingsElement::updateConfig(Config *config) { TyTConfigExtension *ext = config->tytExtension(); if (nullptr == ext) { ext = new TyTConfigExtension(config); config->setTyTExtension(ext); } TyTMenuSettings *ex = ext->menuSettings(); if (menuHangtimeIsInfinite()) ex->setHangtimeInfinite(true); else ex->setHangTime(menuHangtime()); ex->enableTextMessage(textMessage()); ex->enableCallAlert(callAlert()); ex->enableContactEditing(contactEditing()); ex->enableManualDial(manualDial()); ex->enableRemoteRadioCheck(remoteRadioCheck()); ex->enableRemoteMonitor(remoteMonitor()); ex->enableRemoteRadioEnable(remoteRadioEnable()); ex->enableRemoteRadioDisable(remoteRadioDisable()); ex->enableScan(scan()); ex->enableScanListEditing(scanListEditing()); ex->enableCallLogMissed(callLogMissed()); ex->enableCallLogAnswered(callLogAnswered()); ex->enableCallLogOutgoing(callLogOutgoing()); ex->enableTalkaround(talkaround()); ex->enableAlertTone(alertTone()); ex->enablePower(power()); ex->enableBacklight(backlight()); ex->enableBootScreen(bootScreen()); ex->enableKeypadLock(keypadLock()); ex->enableLEDIndicator(ledIndicator()); ex->enableSquelch(squelch()); ex->enableVOX(vox()); ex->enablePassword(password()); ex->enableDisplayMode(displayMode()); ex->enableRadioProgramming(radioProgramming()); return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::ButtonSettingsElement * ******************************************************************************************** */ TyTCodeplug::ButtonSettingsElement::ButtonSettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::ButtonSettingsElement::ButtonSettingsElement(uint8_t *ptr) : Element(ptr, 0x14) // <- size excluding one-touch settings { // pass... } TyTCodeplug::ButtonSettingsElement::~ButtonSettingsElement() { // pass... } void TyTCodeplug::ButtonSettingsElement::clear() { setUInt16_le(0x00, 0); setSideButton1Short(ButtonAction::Disabled); setSideButton1Long(ButtonAction::Tone1750Hz); setSideButton2Short(ButtonAction::MonitorToggle); setSideButton2Long(ButtonAction::Disabled); memset(_data+0x06, 0x00, 10); setUInt8(0x10, 1); setLongPressDuration(1000); setUInt16_le(0x12, 0xffff); } TyTCodeplug::ButtonSettingsElement::ButtonAction TyTCodeplug::ButtonSettingsElement::sideButton1Short() const { return ButtonAction(getUInt8(0x02)); } void TyTCodeplug::ButtonSettingsElement::setSideButton1Short(ButtonAction action) { setUInt8(0x02, action); } TyTCodeplug::ButtonSettingsElement::ButtonAction TyTCodeplug::ButtonSettingsElement::sideButton1Long() const { return ButtonAction(getUInt8(0x03)); } void TyTCodeplug::ButtonSettingsElement::setSideButton1Long(ButtonAction action) { setUInt8(0x03, action); } TyTCodeplug::ButtonSettingsElement::ButtonAction TyTCodeplug::ButtonSettingsElement::sideButton2Short() const { return ButtonAction(getUInt8(0x04)); } void TyTCodeplug::ButtonSettingsElement::setSideButton2Short(ButtonAction action) { setUInt8(0x04, action); } TyTCodeplug::ButtonSettingsElement::ButtonAction TyTCodeplug::ButtonSettingsElement::sideButton2Long() const { return ButtonAction(getUInt8(0x05)); } void TyTCodeplug::ButtonSettingsElement::setSideButton2Long(ButtonAction action) { setUInt8(0x05, action); } unsigned TyTCodeplug::ButtonSettingsElement::longPressDuration() const { return unsigned(getUInt8(0x11))*250; } void TyTCodeplug::ButtonSettingsElement::setLongPressDuration(unsigned ms) { setUInt8(0x11, ms/250); } bool TyTCodeplug::ButtonSettingsElement::fromConfig(const Config *config) { // Skip if not defined if (nullptr == config->tytExtension()) return true; setSideButton1Short(config->tytExtension()->buttonSettings()->sideButton1Short()); setSideButton1Long(config->tytExtension()->buttonSettings()->sideButton1Long()); setSideButton2Short(config->tytExtension()->buttonSettings()->sideButton2Short()); setSideButton2Long(config->tytExtension()->buttonSettings()->sideButton2Long()); setLongPressDuration(config->tytExtension()->buttonSettings()->longPressDuration()); return true; } bool TyTCodeplug::ButtonSettingsElement::updateConfig(Config *config) { TyTConfigExtension *ext = config->tytExtension(); if (nullptr == ext) { ext = new TyTConfigExtension(config); config->setTyTExtension(ext); } ext->buttonSettings()->setSideButton1Short(sideButton1Short()); ext->buttonSettings()->setSideButton1Long(sideButton1Long()); ext->buttonSettings()->setSideButton2Short(sideButton2Short()); ext->buttonSettings()->setSideButton2Long(sideButton2Long()); ext->buttonSettings()->setLongPressDuration(longPressDuration()); return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::OneTouchSettingElement * ******************************************************************************************** */ TyTCodeplug::OneTouchSettingElement::OneTouchSettingElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::OneTouchSettingElement::OneTouchSettingElement(uint8_t *ptr) : Element(ptr, 0x04) { // pass.... } TyTCodeplug::OneTouchSettingElement::~OneTouchSettingElement() { // pass... } bool TyTCodeplug::OneTouchSettingElement::isValid() const { return Element::isValid() && (Disabled != actionType()); } void TyTCodeplug::OneTouchSettingElement::clear() { setAction(CALL); setActionType(Disabled); setUInt2(0x00, 6, 3); setMessageIndex(0); setContactIndex(0); } TyTCodeplug::OneTouchSettingElement::Action TyTCodeplug::OneTouchSettingElement::action() const { return Action(getUInt4(0x00, 0)); } void TyTCodeplug::OneTouchSettingElement::setAction(Action action) { setUInt4(0x00, 0, action); } TyTCodeplug::OneTouchSettingElement::Type TyTCodeplug::OneTouchSettingElement::actionType() const { return Type(getUInt2(0x00, 4)); } void TyTCodeplug::OneTouchSettingElement::setActionType(Type type) { setUInt2(0x00, 4, type); } uint8_t TyTCodeplug::OneTouchSettingElement::messageIndex() const { return getUInt8(0x01); } void TyTCodeplug::OneTouchSettingElement::setMessageIndex(uint8_t idx) { setUInt8(0x01, idx); } uint16_t TyTCodeplug::OneTouchSettingElement::contactIndex() const { return getUInt16_le(0x02); } void TyTCodeplug::OneTouchSettingElement::setContactIndex(uint16_t idx) { setUInt16_le(0x02, idx); } /* ******************************************************************************************** * * Implementation of TyTCodeplug::EmergencySettingsElement * ******************************************************************************************** */ TyTCodeplug::EmergencySettingsElement::EmergencySettingsElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::EmergencySettingsElement::EmergencySettingsElement(uint8_t *ptr) : Element(ptr, 0x10) { // pass... } TyTCodeplug::EmergencySettingsElement::~EmergencySettingsElement() { // pass... } void TyTCodeplug::EmergencySettingsElement::clear() { setUInt5(0x00, 3, 0b11111); enableRadioDisable(true); enableRemoteMonitor(false); enableEmergencyRemoteMonitor(true); setRemoteMonitorDuration(10); setTXTimeOut(125); setMessageLimit(2); memset(_data+0x04, 0xff, 12); } bool TyTCodeplug::EmergencySettingsElement::emergencyRemoteMonitor() const { return getBit(0x00, 2); } void TyTCodeplug::EmergencySettingsElement::enableEmergencyRemoteMonitor(bool enable) { setBit(0x00, 2, enable); } bool TyTCodeplug::EmergencySettingsElement::remoteMonitor() const { return getBit(0x00, 1); } void TyTCodeplug::EmergencySettingsElement::enableRemoteMonitor(bool enable) { setBit(0x00, 1, enable); } bool TyTCodeplug::EmergencySettingsElement::radioDisable() const { return getBit(0x00, 0); } void TyTCodeplug::EmergencySettingsElement::enableRadioDisable(bool enable) { setBit(0x00, 0, enable); } unsigned TyTCodeplug::EmergencySettingsElement::remoteMonitorDuration() const { return unsigned(getUInt8(0x01))*10; } void TyTCodeplug::EmergencySettingsElement::setRemoteMonitorDuration(unsigned sec) { setUInt8(0x01, sec/10); } unsigned TyTCodeplug::EmergencySettingsElement::txTimeOut() const { return unsigned(getUInt8(0x02))*25; } void TyTCodeplug::EmergencySettingsElement::setTXTimeOut(unsigned ms) { setUInt8(0x02, ms/25); } unsigned TyTCodeplug::EmergencySettingsElement::messageLimit() const { return unsigned(getUInt8(0x03)); } void TyTCodeplug::EmergencySettingsElement::setMessageLimit(unsigned limit) { setUInt8(0x03, limit); } /* ******************************************************************************************** * * Implementation of TyTCodeplug::EmergencySystemElement * ******************************************************************************************** */ TyTCodeplug::EmergencySystemElement::EmergencySystemElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::EmergencySystemElement::EmergencySystemElement(uint8_t *ptr) : Element(ptr, 0x28) { // pass... } TyTCodeplug::EmergencySystemElement::~EmergencySystemElement() { // pass... } bool TyTCodeplug::EmergencySystemElement::isValid() const { return Element::isValid() && (0 != revertChannelIndex()); } void TyTCodeplug::EmergencySystemElement::clear() { setName(""); setAlarmType(DISABLED); setUInt2(0x00, 2, 3); setAlarmMode(ALARM); setUInt2(0x00, 6, 1); setImpoliteRetries(15); setPoliteRetries(5); setHotMICDuration(100); setRevertChannelIndex(0); setUInt16_le(0x26, 0xffff); } QString TyTCodeplug::EmergencySystemElement::name() const { return readUnicode(0x00, 16); } void TyTCodeplug::EmergencySystemElement::setName(const QString &nm) { writeUnicode(0x00, nm, 16); } TyTCodeplug::EmergencySystemElement::AlarmType TyTCodeplug::EmergencySystemElement::alarmType() const { return AlarmType(getUInt2(0x20, 0)); } void TyTCodeplug::EmergencySystemElement::setAlarmType(AlarmType type) { setUInt2(0x20, 0, type); } TyTCodeplug::EmergencySystemElement::AlarmMode TyTCodeplug::EmergencySystemElement::alarmMode() const { return AlarmMode(getUInt2(0x20, 4)); } void TyTCodeplug::EmergencySystemElement::setAlarmMode(AlarmMode mode) { setUInt2(0x20, 4, mode); } unsigned TyTCodeplug::EmergencySystemElement::impoliteRetries() const { return getUInt8(0x21); } void TyTCodeplug::EmergencySystemElement::setImpoliteRetries(unsigned num) { setUInt8(0x21, num); } unsigned TyTCodeplug::EmergencySystemElement::politeRetries() const { return getUInt8(0x22); } void TyTCodeplug::EmergencySystemElement::setPoliteRetries(unsigned num) { setUInt8(0x22, num); } unsigned TyTCodeplug::EmergencySystemElement::hotMICDuration() const { return unsigned(getUInt8(0x23))*10; } void TyTCodeplug::EmergencySystemElement::setHotMICDuration(unsigned dur) { setUInt8(0x23, dur/10); } bool TyTCodeplug::EmergencySystemElement::revertChannelIsSelected() const { return 0xffff == getUInt16_le(0x24); } uint16_t TyTCodeplug::EmergencySystemElement::revertChannelIndex() const { return getUInt16_le(0x24); } void TyTCodeplug::EmergencySystemElement::setRevertChannelIndex(uint16_t idx) { setUInt16_le(0x24, idx); } void TyTCodeplug::EmergencySystemElement::revertChannelSelected() { setUInt16_le(0x24, 0xffff); } /* ******************************************************************************************** * * Implementation of TyTCodeplug::EncryptionElement * ******************************************************************************************** */ TyTCodeplug::EncryptionElement::EncryptionElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::EncryptionElement::EncryptionElement(uint8_t *ptr) : Element(ptr, size()) { // pass... } TyTCodeplug::EncryptionElement::~EncryptionElement() { // pass... } void TyTCodeplug::EncryptionElement::clear() { memset(_data, 0xff, size()); } bool TyTCodeplug::EncryptionElement::isEnhancedKeySet(unsigned n) const { QByteArray key = enhancedKey(n); for (unsigned int i=1; i() && i(i)->key()); for (unsigned int i=0; i() && i(i)->key()); return true; } bool TyTCodeplug::EncryptionElement::updateCommercialExt(Context &ctx) { CommercialExtension *ext = ctx.config()->commercialExtension(); for (unsigned i=0; isetName(QString("Enhanced Key %1").arg(i)); ctx.add(key,i); // 0-based key indices key->fromHex(enhancedKey(i).toHex()); ext->encryptionKeys()->add(key); } for (unsigned i=0; isetName(QString("Basic Key %1").arg(i)); ctx.add(key,i); // 0-based key indices key->fromHex(basicKey(i).toHex()); ext->encryptionKeys()->add(key); } return ext; } bool TyTCodeplug::EncryptionElement::linkCommercialExt(CommercialExtension *ext, Context &ctx) { Q_UNUSED(ext); Q_UNUSED(ctx); return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::MessageElement * ******************************************************************************************** */ TyTCodeplug::MessageElement::MessageElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::MessageElement::MessageElement(uint8_t *ptr) : Element(ptr, MessageElement::size()) { // pass... } void TyTCodeplug::MessageElement::clear() { memset(_data, 0xffff, size()); } bool TyTCodeplug::MessageElement::isValid() const { return Element::isValid() && (0xffff != getUInt16_le(0)) && (0x0000 != getUInt16_le(0)); } QString TyTCodeplug::MessageElement::text() const { return readUnicode(0, Limit::length(), 0x0000); } void TyTCodeplug::MessageElement::setText(const QString &text) { writeUnicode(0, text, Limit::length(), 0x0000); } bool TyTCodeplug::MessageElement::encode(SMSTemplate *sms, const ErrorStack &err) { Q_UNUSED(err); setText(sms->message()); return true; } SMSTemplate * TyTCodeplug::MessageElement::decode(const ErrorStack &err) { if (! isValid()) { errMsg(err) << "Cannot decode invalid SMS message."; return nullptr; } SMSTemplate *sms = new SMSTemplate(); sms->setName("Message"); sms->setMessage(text()); return sms; } /* ******************************************************************************************** * * Implementation of TyTCodeplug::MessageBankElement * ******************************************************************************************** */ TyTCodeplug::MessageBankElement::MessageBankElement(uint8_t *ptr, size_t size) : Element(ptr, size) { // pass... } TyTCodeplug::MessageBankElement::MessageBankElement(uint8_t *ptr) : Element(ptr, MessageBankElement::size()) { // pass... } void TyTCodeplug::MessageBankElement::clear() { for (unsigned int i=0; ismsExtension()->smsTemplates()->count(); count = std::min(count, Limit::messages()); for (unsigned int i=0; ismsExtension()->smsTemplates()->message(i), err)) { errMsg(err) << "Cannot encode" << i+1 << "-th preset message."; return false; } } return true; } bool TyTCodeplug::MessageBankElement::decode(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; ismsExtension()->smsTemplates()->add(sms); } return true; } /* ******************************************************************************************** * * Implementation of TyTCodeplug * ******************************************************************************************** */ TyTCodeplug::TyTCodeplug(QObject *parent) : Codeplug(parent) { // pass... } TyTCodeplug::~TyTCodeplug() { // pass... } bool TyTCodeplug::index(Config *config, Context &ctx, const ErrorStack &err) const { Q_UNUSED(err) // All indices as 1-based. That is, the first channel gets index 1. // Map radio IDs for (int i=0; iradioIDs()->count(); i++) { if (config->radioIDs()->get(i)->is()) ctx.add(config->radioIDs()->get(i)->as(), i+1); } // Map digital and DTMF contacts for (int i=0, d=0, a=0; icontacts()->count(); i++) { Contact *contact = config->contacts()->contact(i); if (contact->is()) { ctx.add(contact->as(), d+1); d++; } else if (contact->is()) { ctx.add(contact->as(), a+1); a++; } else { logInfo() << "Cannot index contact '" << contact->name() << "'. Contact type '" << contact->metaObject()->className() << "' not supported by or implemented for TyT devices."; } } // Map rx group lists for (int i=0; irxGroupLists()->count(); i++) ctx.add(config->rxGroupLists()->list(i), i+1); // Map channels for (int i=0, c=0; ichannelList()->count(); i++) { Channel *channel = config->channelList()->channel(i); if (channel->is() || channel->is()) { ctx.add(channel, c+1); c++; } else { logInfo() << "Cannot index channel '" << channel->name() << "'. Channel type '" << channel->metaObject()->className() << "' not supported by or implemented for TyT devices."; } } // Map zones for (int i=0; izones()->count(); i++) ctx.add(config->zones()->zone(i), i+1); // Map scan lists for (int i=0; iscanlists()->count(); i++) ctx.add(config->scanlists()->scanlist(i), i+1); // Map DMR APRS systems for (int i=0,a=0,d=0; iposSystems()->count(); i++) { if (config->posSystems()->system(i)->is()) { ctx.add(config->posSystems()->system(i)->as(), d+1); d++; } else if (config->posSystems()->system(i)->is()) { ctx.add(config->posSystems()->system(i)->as(), a+1); a++; } } // Map roaming for (int i=0; iroamingZones()->count(); i++) ctx.add(config->roamingZones()->zone(i), i+1); // Index basic (DMR) and AES keys if (CommercialExtension *ext = config->commercialExtension()) { unsigned int basicIndex = 0, aesIndex = 0; for (int i=0; iencryptionKeys()->count(); i++) { if (ext->encryptionKeys()->key(i)->is()) { ctx.add(ext->encryptionKeys()->key(i)->as(), basicIndex++); } else if (ext->encryptionKeys()->key(i)->is()) { ctx.add(ext->encryptionKeys()->key(i)->as(), aesIndex++); } } } return true; } void TyTCodeplug::clear() { // Clear timestamp this->clearTimestamp(); // Clear general config this->clearGeneralSettings(); // Clear menu settings this->clearMenuSettings(); // Clear button settings this->clearButtonSettings(); // Clear text messages this->clearTextMessages(); // Clear privacy keys this->clearPrivacyKeys(); // Clear emergency systems this->clearEmergencySystems(); // Clear RX group lists this->clearGroupLists(); // Clear zones & zone extensions this->clearZones(); // Clear scan lists; this->clearScanLists(); // Clear GPS systems this->clearPositioningSystems(); // Clear channels this->clearChannels(); // Clear contacts this->clearContacts(); } Config * TyTCodeplug::preprocess(Config *config, const ErrorStack &err) const { Config *intermediate = Codeplug::preprocess(config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot pre-process codeplug for TyT device."; return nullptr; } // Remove all AM & M17 channels ObjectFilterVisitor amFilter{AMChannel::staticMetaObject, M17Channel::staticMetaObject}; if (! amFilter.process(intermediate, err)) { errMsg(err) << "Remove AM & M17 channels."; delete intermediate; return nullptr; } return intermediate; } bool TyTCodeplug::encode(Config *config, const Flags &flags, const ErrorStack &err) { // Check if default DMR id is set. if (config->settings()->defaultIdRef()->isNull()) { errMsg(err) << "Cannot encode TyT codeplug: No default radio ID specified."; return false; } // Create index<->object table. Context ctx(config); if (! index(config, ctx)) return false; return this->encodeElements(flags, ctx); } bool TyTCodeplug::decode(Config *config, const ErrorStack &err) { // Create index<->object table. Context ctx(config); // Clear config object config->clear(); return this->decodeElements(ctx, err); } bool TyTCodeplug::encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err) { // Set timestamp if (! this->encodeTimestamp()) { errMsg(err) << "Cannot encode time-stamp."; return false; } // General config if (! this->encodeGeneralSettings(flags, ctx)) { errMsg(err) << "Cannot encode general settings."; return false; } // Define Contacts if (! this->encodeContacts(flags, ctx)) { errMsg(err) << "Cannot encode contacts."; return false; } // Define RX GroupLists if (! this->encodeGroupLists(flags, ctx)) { errMsg(err) << "Cannot encode group lists."; return false; } // Define encryption keys if (! this->encodePrivacyKeys(flags, ctx)) { errMsg(err) << "Cannot encode encryption keys."; return false; } // Define Channels if (! this->encodeChannels(flags, ctx)) { errMsg(err) << "Cannot encode channels."; return false; } // Define Zones if (! this->encodeZones(flags, ctx)) { errMsg(err) << "Cannot encode zones."; return false; } // Define Scanlists if (! this->encodeScanLists(flags, ctx)) { errMsg(err) << "Cannot encode scan lists."; return false; } // Define GPS systems if (! this->encodePositioningSystems(flags, ctx)) { errMsg(err) << "Cannot encode positioning systems."; return false; } // Encode button settings if (! this->encodeButtonSettings(flags, ctx)) { errMsg(err) << "Cannot encode button settings."; return false; } // Encode text messages if (! this->encodeTextMessages(ctx, flags, err)) { errMsg(err) << "Cannot encode text messages."; return false; } return true; } bool TyTCodeplug::decodeElements(Context &ctx, const ErrorStack &err) { // General config if (! this->decodeGeneralSettings(ctx, err)) { errMsg(err) << "Cannot decode general settings."; return false; } // Define Contacts if (! this->createContacts(ctx, err)) { errMsg(err) << "Cannot create contacts."; return false; } // Define RX GroupLists if (! this->createGroupLists(ctx, err)) { errMsg(err) << "Cannot create group lists."; return false; } // Define Channels if (! this->createChannels(ctx, err)) { errMsg(err) << "Cannot create channels."; return false; } // Define Zones if (! this->createZones(ctx, err)) { errMsg(err) << "Cannot create zones."; return false; } // Define Scanlists if (! this->createScanLists(ctx, err)) { errMsg(err) << "Cannot create scan lists."; return false; } // Define GPS systems if (! this->createPositioningSystems(ctx, err)) { errMsg(err) << "Cannot create positioning systems."; return false; } // Decode button settings if (! this->decodeButtonSetttings(ctx, err)) { errMsg(err) << "Cannot decode button settings."; return false; } // Decode text messages if (! this->decodeTextMessages(ctx, err)) { errMsg(err) << "Cannot decode text messages."; return false; } // Decode encryption settings if (! this->decodePrivacyKeys(ctx, err)) { errMsg(err) << "Cannot decode encryption settings."; return false; } // Link RX GroupLists if (! this->linkGroupLists(ctx, err)) { errMsg(err) << "Cannot link group lists."; return false; } // Link Channels if (! this->linkChannels(ctx, err)) { errMsg(err) << "Cannot link channels."; return false; } // Link Zones if (! this->linkZones(ctx, err)) { errMsg(err) << "Cannot link zones."; return false; } // Link Scanlists if (! this->linkScanLists(ctx, err)) { errMsg(err) << "Cannot link scan lists."; return false; } // Link GPS systems if (! this->linkPositioningSystems(ctx, err)) { errMsg(err) << "Cannot link positioning systems."; return false; } return true; } ================================================ FILE: lib/tyt_codeplug.hh ================================================ #ifndef TYT_CODEPLUG_HH #define TYT_CODEPLUG_HH #include #include "codeplug.hh" #include "signaling.hh" #include "channel.hh" #include "contact.hh" #include "bootsettings.hh" #include "tyt_extensions.hh" class DMRContact; class Zone; class RXGroupList; class ScanList; class DMRAPRSSystem; class SMSExtension; class SMSTemplate; /** Base class of all TyT codeplugs. This class implements the majority of all codeplug elements * present in all TyT codeplugs. This eases the support of several TyT radios, as only the * differences in the codeplug to this base class must be implemented. * * @ingroup tyt */ class TyTCodeplug : public Codeplug { Q_OBJECT public: /** Represents a single channel (analog or digital) within the TyT codeplug. * * Memory layout of encoded channel: * @verbinclude tyt_channel.txt */ class ChannelElement: public Codeplug::Element { public: /** Possible modes for the channel, i.e. analog and digital. */ enum Mode { MODE_ANALOG = 1, ///< Analog channel. MODE_DIGITAL = 2 ///< Digital channel. }; /** Bandwidth of the channel. */ enum Bandwidth { BW_12_5_KHZ = 0, ///< 12.5 kHz narrow, (default for binary channels). BW_20_KHZ = 1, ///< 20 kHz (really?) BW_25_KHZ = 2 ///< 25kHz wide. }; /** Possible privacy types. */ enum PrivacyType { PRIV_NONE = 0, ///< No privacy. PRIV_BASIC = 1, ///< Basic privacy. PRIV_ENHANCED = 2 ///< Enhanced privacy. }; /** TX Admit criterion. */ enum Admit { ADMIT_ALWAYS = 0, ///< Always allow TX. ADMIT_CH_FREE = 1, ///< Allow TX if channel is free. ADMIT_TONE = 2, ///< Allow TX if CTCSS tone matches. ADMIT_COLOR = 3, ///< Allow TX if color-code matches. }; protected: /** Constructs a channel from the given memory. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructs a channel from the given memory. */ ChannelElement(uint8_t *ptr); /** Destructor. */ virtual ~ChannelElement(); /** Returns @c true if channel is valid/enabled. */ bool isValid() const; /** Clears/resets the channel and therefore disables it. */ void clear(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x0040; } /** Returns the mode of the channel. */ virtual Mode mode() const; /** Sets the mode of the channel. */ virtual void setMode(Mode setMode); /** Returns the bandwidth of the (analog) channel. */ virtual FMChannel::Bandwidth bandwidth() const; /** Sets the bandwidth of the (analog) channel. */ virtual void setBandwidth(FMChannel::Bandwidth bw); /** Returns @c true if the channel has auto scan enabled. */ virtual bool autoScan() const; /** Enables/disables auto scan for this channel. */ virtual void enableAutoScan(bool enable); /** Returns @c true if the channel has lone worker enabled. */ virtual bool loneWorker() const; /** Enables/disables lone worker for this channel. */ virtual void enableLoneWorker(bool enable); /** Returns @c true if the channel has talkaround enabled. */ virtual bool talkaround() const; /** Enables/disables talkaround for this channel. */ virtual void enableTalkaround(bool enable); /** Returns @c true if the channel has rx only enabled. */ virtual bool rxOnly() const; /** Enables/disables rx only for this channel. */ virtual void enableRXOnly(bool enable); /** Returns the time slot of this channel. */ virtual DMRChannel::TimeSlot timeSlot() const; /** Sets the time slot of this channel. */ virtual void setTimeSlot(DMRChannel::TimeSlot ts); /** Returns the color code of this channel. */ virtual uint8_t colorCode() const; /** Sets the color code of this channel. */ virtual void setColorCode(uint8_t ts); /** Returns the index of the privacy system (key). */ virtual uint8_t privacyIndex() const; /** Sets the index of the privacy system (key). */ virtual void setPrivacyIndex(uint8_t ts); /** Returns the type of the privacy system. */ virtual PrivacyType privacyType() const; /** Sets the type of the privacy system. */ virtual void setPrivacyType(PrivacyType type); /** Returns @c true if the channel has private call confirmation enabled. */ virtual bool privateCallConfirm() const; /** Enables/disables private call confirmation for this channel. */ virtual void enablePrivateCallConfirm(bool enable); /** Returns @c true if the channel has data call confirmation enabled. */ virtual bool dataCallConfirm() const; /** Enables/disables data call confirmation for this channel. */ virtual void enableDataCallConfirm(bool enable); /** Returns some weird reference frequency setting for reception. */ virtual TyTChannelExtension::RefFrequency rxRefFrequency() const; /** Sets some weird reference frequency setting for reception. */ virtual void setRXRefFrequency(TyTChannelExtension::RefFrequency ref); /** Returns some weird reference frequency setting for transmission. */ virtual TyTChannelExtension::RefFrequency txRefFrequency() const; /** Sets some weird reference frequency setting for transmission. */ virtual void setTXRefFrequency(TyTChannelExtension::RefFrequency ref); /** Returns @c true if the channel has alarm confirmation enabled. */ virtual bool emergencyAlarmACK() const; /** Enables/disables alarm confirmation for this channel. */ virtual void enableEmergencyAlarmACK(bool enable); /** Returns @c true if the channel has display PTT ID enabled. */ virtual bool displayPTTId() const; /** Enables/disables PTT ID display for this channel. */ virtual void enableDisplayPTTId(bool enable); /** Returns @c true if the channel has VOX enabled. */ virtual bool vox() const; /** Enables/disables VOX for this channel. */ virtual void enableVOX(bool enable); /** Returns the admit criterion for this channel. */ virtual Admit admitCriterion() const; /** Sets the admit criterion for this channel. */ virtual void setAdmitCriterion(Admit admit); /** Returns the transmit contact index (+1) for this channel. */ virtual uint16_t contactIndex() const; /** Sets the transmit contact index (+1) for this channel. */ virtual void setContactIndex(uint16_t idx); /** Returns @c true, if the transmit time out is disabled. */ virtual bool txTimeOutDisabled() const; /** Returns the transmit time-out in seconds. */ virtual Interval txTimeOut() const; /** Sets the transmit time-out in seconds. */ virtual void setTXTimeOut(const Interval &tot); /** Disables the transmit timeout. */ virtual void resetTXTimeOut(); /** Returns the transmit time-out re-key delay in seconds. */ virtual uint8_t txTimeOutRekeyDelay() const; /** Sets the transmit time-out re-key delay in seconds. */ virtual void setTXTimeOutRekeyDelay(uint8_t delay); /** Returns the emergency system index (+1) for this channel. */ virtual uint8_t emergencySystemIndex() const; /** Sets the emergency system index (+1) for this channel. */ virtual void setEmergencySystemIndex(uint8_t idx); /** Returns the scan-list index (+1) for this channel. */ virtual uint8_t scanListIndex() const; /** Sets the scan-list index (+1) for this channel. */ virtual void setScanListIndex(uint8_t idx); /** Returns the RX group list index (+1) for this channel. */ virtual uint8_t groupListIndex() const; /** Sets the RX group list index (+1) for this channel. */ virtual void setGroupListIndex(uint8_t idx); /** Returns the positioning system index (+1) for this channel. */ virtual uint8_t positioningSystemIndex() const; /** Sets the positioning system index (+1) for this channel. */ virtual void setPositioningSystemIndex(uint8_t idx); /** Returns @c true if the channel has DTMF decoding enabled. */ virtual bool dtmfDecode(uint8_t idx) const; /** Enables/disables DTMF decoding this channel. */ virtual void setDTMFDecode(uint8_t idx, bool enable); /** Returns the RX frequency in Hz. */ virtual Frequency rxFrequency() const; /** Sets the RX frequency in Hz. */ virtual void setRXFrequency(const Frequency &Hz); /** Returns the TX frequency in Hz. */ virtual Frequency txFrequency() const; /** Sets the TX frequency in Hz. */ virtual void setTXFrequency(const Frequency &Hz); /** Returns the CTCSS/DSC signaling for RX. */ virtual SelectiveCall rxSignaling() const; /** Sets the CTCSS/DSC signaling for RX. */ virtual void setRXSignaling(const SelectiveCall &code); /** Returns the CTCSS/DSC signaling for TX. */ virtual SelectiveCall txSignaling() const; /** Sets the CTCSS/DSC signaling for TX. */ virtual void setTXSignaling(const SelectiveCall &code); /** Returns the signaling system index (+1) for RX. */ virtual uint8_t rxSignalingSystemIndex() const; /** Sets the signaling system index (+1) for RX. */ virtual void setRXSignalingSystemIndex(uint8_t idx); /** Returns the signaling system index (+1) for TX. */ virtual uint8_t txSignalingSystemIndex() const; /** Sets the signaling system index (+1) for TX. */ virtual void setTXSignalingSystemIndex(uint8_t idx); /** Returns @c true if the channel transmits GPS information enabled. */ virtual bool txGPSInfo() const; /** Enables/disables transmission of GPS information for this channel. */ virtual void enableTXGPSInfo(bool enable); /** Returns @c true if the channel receives GPS information enabled. */ virtual bool rxGPSInfo() const; /** Enables/disables reception of GPS information for this channel. */ virtual void enableRXGPSInfo(bool enable); /** Returns the name of this channel. */ virtual QString name() const; /** Sets the name of this channel. */ virtual void setName(const QString &setName); /** Constructs a generic @c Channel object from the codeplug channel. */ virtual Channel *toChannelObj(const ErrorStack &err=ErrorStack()) const; /** Links a previously constructed channel to the rest of the configuration. */ virtual bool linkChannelObj(Channel *c, Context &ctx, const ErrorStack &err=ErrorStack()) const; /** Initializes this codeplug channel from the given generic configuration. */ virtual void fromChannelObj(const Channel *c, Context &ctx); public: /** Some limits of the element. */ struct Limit: public Element::Limit { /** Maximum length of the name. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some internal offsets within the element. */ struct Offset: public Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr Offset::Bit mode() { return {0x0000, 0}; } static constexpr Offset::Bit bandwidth() { return {0x0000, 2}; } static constexpr Offset::Bit autoscan() { return {0x0000, 4}; } static constexpr Offset::Bit loneworker() { return {0x0000, 8}; } static constexpr Offset::Bit talkaround() { return {0x0001, 0}; } static constexpr Offset::Bit rxonly() { return {0x0001, 1}; } static constexpr Offset::Bit timeslot() { return {0x0001, 2}; } static constexpr Offset::Bit colorcode() { return {0x0001, 4}; } static constexpr Offset::Bit privacyIndex() { return {0x0002, 0}; } static constexpr Offset::Bit privacyType() { return {0x0002, 4}; } static constexpr Offset::Bit privateCallConfirm() { return {0x0002, 6}; } static constexpr Offset::Bit dataCallConfirm() { return {0x0002, 7}; } static constexpr Offset::Bit rxRefFrequency() { return {0x0003, 0}; } static constexpr Offset::Bit emergencyAlarmACK() { return {0x0003, 3}; } static constexpr Offset::Bit displayPTTId() { return {0x0003, 7}; } static constexpr Offset::Bit txRefFrequency() { return {0x0004, 0}; } static constexpr Offset::Bit vox() { return {0x0004, 4}; } static constexpr Offset::Bit admitCriterion() { return {0x0004, 6}; } static constexpr unsigned int contactIndex() { return 0x0006; } static constexpr Offset::Bit txTimeOut() { return {0x0008, 0}; } static constexpr unsigned int txTimeOutRekeyDelay() { return 0x0009; } static constexpr unsigned int emergencySystemIndex() { return 0x000a; } static constexpr unsigned int scanListIndex() { return 0x000b; } static constexpr unsigned int groupListIndex() { return 0x000c; } static constexpr unsigned int positioningSystemIndex() { return 0x000d; } static constexpr unsigned int dtmfDecode() { return 0x000e; } static constexpr unsigned int rxFrequency() { return 0x0010; } static constexpr unsigned int txFrequency() { return 0x0014; } static constexpr unsigned int rxSignaling() { return 0x018; } static constexpr unsigned int txSignaling() { return 0x01a; } static constexpr unsigned int rxSignalingSystemIndex() { return 0x01c; } static constexpr unsigned int txSignalingSystemIndex() { return 0x01d; } static constexpr Offset::Bit txGPSInfo() { return {0x001f, 0}; } static constexpr Offset::Bit rxGPSInfo() { return {0x001f, 1}; } static constexpr unsigned int name() { return 0x0020; } /// @endcond }; }; /** Represents a digital (DMR) contact within the codeplug. * * Memory layout of encoded contact: * @verbinclude tyt_contact.txt */ class ContactElement: public Codeplug::Element { protected: /** Encoded call types. */ enum class CallType { GroupCall=1, PrivateCall=2, AllCall=3 }; protected: /** Constructor. */ ContactElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ContactElement(uint8_t *ptr); /** Destructor. */ virtual ~ContactElement(); /** Size of the element. */ static constexpr unsigned int size() { return 0x000024; } void clear(); bool isValid() const; /** Returns the DMR ID of the contact. */ virtual uint32_t dmrId() const; /** Sets the DMR ID of the contact. */ virtual void setDMRId(uint32_t id); /** Returns the call-type of the contact. */ virtual DMRContact::Type callType() const; /** Sets the call-type of the contact. */ virtual void setCallType(DMRContact::Type type); /** Returns @c true if the ring-tone is enabled for this contact. */ virtual bool ringTone() const; /** Enables/disables the ring-tone for this contact. */ virtual void enableRingTone(bool enable); /** Returns the name of the contact. */ virtual QString name() const; /** Sets the name of the contact. */ virtual void setName(const QString &nm); /** Encodes the give contact. */ virtual bool fromContactObj(const DMRContact *contact); /** Creates a contact. */ virtual DMRContact *toContactObj() const; public: /** Some limits. */ struct Limit: Element::Limit { /** Maximum name length. */ static constexpr unsigned int nameLength() { return 16; } }; protected: /** Some offsets within the element. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int dmrId() { return 0x0000; } static constexpr Bit callType() { return {0x0003, 0}; } static constexpr Bit ringTone() { return {0x0003, 5}; } static constexpr unsigned int name() { return 0x0004; } /// @endcond DO_NOT_DOCUMENT }; }; /** Represents a zone within the codeplug. * Please note that a zone consists of two elements the @c ZoneElement and the @c ZoneExtElement. * The latter adds additional channels for VFO A and the channels for VFO B. * * Memory layout of encoded zone: * @verbinclude tyt_zone.txt */ class ZoneElement: public Codeplug::Element { protected: /** Constructor. */ ZoneElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ZoneElement(uint8_t *ptr); /** Desturctor. */ virtual ~ZoneElement(); void clear(); bool isValid() const; /** Returns the name of the zone. */ virtual QString name() const; /** Sets the name of the zone. */ virtual void setName(const QString &setName); /** Returns @c true if the n-th member index is set. */ virtual bool hasMemberIndex(unsigned n) const; /** Returns the index (+1) of the @c n-th member. */ virtual uint16_t memberIndex(unsigned n) const; /** Sets the index (+1) of the @c n-th member. */ virtual void setMemberIndex(unsigned n, uint16_t idx); /** Encodes a given zone object. */ virtual bool fromZoneObj(const Zone *zone, Context &ctx); /** Creates a zone. */ virtual Zone *toZoneObj() const; /** Links the created zone to channels. */ virtual bool linkZone(Zone *zone, Context &ctx) const; }; /** Representation of an RX group list within the codeplug. * * Memory layout of encoded RX group list: * @verbinclude tyt_grouplist.txt */ class GroupListElement: public Codeplug::Element { protected: /** Constructor. */ GroupListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GroupListElement(uint8_t *ptr); /** Destructor. */ virtual ~GroupListElement(); void clear(); bool isValid() const; /** Returns the name of the group list. */ virtual QString name() const; /** Sets the name of the group list. */ virtual void setName(const QString &nm); /** Returns the n-th member index. */ virtual uint16_t memberIndex(unsigned n) const; /** Sets the n-th member index. */ virtual void setMemberIndex(unsigned n, uint16_t idx); /** Encodes the given group list. */ virtual bool fromGroupListObj(const RXGroupList *lst, Context &ctx); /** Creates a group list object. */ virtual RXGroupList *toGroupListObj(Context &ctx); /** Links the given group list. */ virtual bool linkGroupListObj(RXGroupList *lst, Context &ctx); }; /** Represents a scan list within the codeplug. * * Memory layout of encoded scan list (0x0068 bytes): * @verbinclude tyt_scanlist.txt */ class ScanListElement: public Codeplug::Element { protected: /** Constructor. */ ScanListElement(uint8_t *ptr, size_t size); public: /** Constructor. */ ScanListElement(uint8_t *ptr); /** Destructor. */ virtual ~ScanListElement(); bool isValid() const; void clear(); /** Returns the name of the scan list. */ virtual QString name() const; /** Sets the name of the scan list. */ virtual void setName(const QString &nm); /** Returns the index (+1) of the first priority channel. */ virtual uint16_t priorityChannel1Index() const; /** Set the index (+1) of the first priority channel. */ virtual void setPriorityChannel1Index(uint16_t idx); /** Returns the index (+1) of the second priority channel. */ virtual uint16_t priorityChannel2Index() const; /** Set the index (+1) of the second priority channel. */ virtual void setPriorityChannel2Index(uint16_t idx); /** Returns the index (+1) of the TX channel. 0=current, 0xffff=none. */ virtual uint16_t txChannelIndex() const; /** Sets the index (+1) of the TX channel. 0=current, 0xffff=none. */ virtual void setTXChannelIndex(uint16_t idx); /** Returns the hold time in ms. */ virtual unsigned holdTime() const; /** Sets the hold time in ms. */ virtual void setHoldTime(unsigned time); /** Returns the priority sample time in ms. */ virtual unsigned prioritySampleTime() const; /** Sets the priority sample time in ms. */ virtual void setPrioritySampleTime(unsigned time); /** Returns the n-th member index. */ virtual uint16_t memberIndex(unsigned n) const; /** Sets the n-th member index. */ virtual void setMemberIndex(unsigned n, uint16_t idx); /** Encodes the given scan list. */ virtual bool fromScanListObj(const ScanList *lst, Context &ctx); /** Creates a scan list. */ virtual ScanList *toScanListObj(Context &ctx); /** Links the scan list object. */ virtual bool linkScanListObj(ScanList *lst, Context &ctx, const ErrorStack &err=ErrorStack()); }; /** Codeplug representation of the general settings. * * Memory layout of encoded settings: * @verbinclude tyt_settings.txt */ class GeneralSettingsElement: public Codeplug::Element { protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit GeneralSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~GeneralSettingsElement(); void clear(); /** Returns the first intro line. */ virtual QString introLine1() const; /** Sets the first intro line. */ virtual void setIntroLine1(const QString line); /** Returns the second intro line. */ virtual QString introLine2() const; /** Sets the second intro line. */ virtual void setIntroLine2(const QString line); /** Returns the monitor type. */ virtual TyTSettingsExtension::MonitorType monitorType() const; /** Sets the monitor type. */ virtual void setMonitorType(TyTSettingsExtension::MonitorType type); /** Returns @c true if all LEDs are disabled. */ virtual bool allLEDsDisabled() const; /** Enables/disables all LEDs. */ virtual void disableAllLEDs(bool disable); /** Returns @c true, if save preamble is enabled. */ virtual bool savePreamble() const; /** Enables/disables save preamble. */ virtual void setSavePreamble(bool enable); /** Returns @c true, if save RX mode is enabled. */ virtual bool saveModeRX() const; /** Enables/disables save mode RX. */ virtual void setSaveModeRX(bool enable); /** Returns @c true, if all tones are disabled. */ virtual bool allTonesDisabled() const; /** Enables/disables all tones. */ virtual void disableAllTones(bool disable); /** Returns @c true, if the channel free indication tone is enabled. */ virtual bool chFreeIndicationTone() const; /** Enables/disables the channel free indication tone. */ virtual void setChFreeIndicationTone(bool enable); /** Returns @c true, if password and lock is enabled. */ virtual bool passwdAndLock() const; /** Enables/disables password and lock. */ virtual void enablePasswdAndLock(bool enable); /** Returns @c true, if the talk permit tone is enabled for DMR channels. */ virtual bool talkPermitToneDigital() const; /** Enables/disables talk permit tone for DMR channels. */ virtual void enableTalkPermitToneDigital(bool enable); /** Returns @c true, if the talk permit tone is enabled for analog channels. */ virtual bool talkPermitToneAnalog() const; /** Enables/disables talk permit tone for analog channels. */ virtual void enableTalkPermitToneAnalog(bool enable); /** Returns boot display mode. */ virtual BootSettings::BootDisplay bootDisplay() const; /** Sets boot display mode. */ virtual void setBootDisplay(BootSettings::BootDisplay mode); /** Returns the default DMR ID of the radio. */ virtual uint32_t dmrId() const; /** Sets the default DMR ID of the radio. */ virtual void setDMRId(uint32_t id); /** Returns the TX preamble duration. */ virtual Interval txPreambleDuration () const; /** Sets the TX preamble duration. */ virtual void setTXPreambleDuration(const Interval &ms); /** Returns the group call hang time. */ virtual Interval groupCallHangTime() const; /** Sets the group call hang time. */ virtual void setGroupCallHangTime(const Interval &ms); /** Returns the private call hang time. */ virtual Interval privateCallHangTime() const; /** Sets the private call hang time. */ virtual void setPrivateCallHangTime(const Interval &ms); /** Returns the VOX sensitivity. */ virtual Level voxSesitivity() const; /** Sets the group call hang time. */ virtual void setVOXSesitivity(Level level); /** Returns the low-battery warning interval. */ virtual unsigned lowBatteryInterval() const; /** Sets the low-battery warning interval. */ virtual void setLowBatteryInterval(unsigned sec); /** Returns @c true if the call-alert is continuous. */ virtual bool callAlertToneIsContinuous() const; /** Returns the call-alert tone duration. */ virtual unsigned callAlertToneDuration() const; /** Sets the call-alert tone duration. */ virtual void setCallAlertToneDuration(unsigned sec); /** Sets the call-alert tone continuous. */ virtual void setCallAlertToneContinuous(); /** Returns the lone-worker response time. */ virtual unsigned loneWorkerResponseTime() const; /** Sets the lone-worker response time. */ virtual void setLoneWorkerResponseTime(unsigned min); /** Returns the lone-worker reminder time. */ virtual unsigned loneWorkerReminderTime() const; /** Sets the lone-worker reminder time. */ virtual void setLoneWorkerReminderTime(unsigned min); /** Returns the scan digital hang time. */ virtual unsigned scanDigitalHangTime() const; /** Sets the scan digital hang time. */ virtual void setScanDigitalHangTime(unsigned ms); /** Returns the scan analog hang time. */ virtual unsigned scanAnalogHangTime() const; /** Sets the scan analog hang time. */ virtual void setScanAnalogHangTime(unsigned ms); /** Returns @c true if the backlight is always on. */ virtual bool backlightIsAlways() const; /** Returns the backlight time. */ virtual unsigned backlightTime() const; /** Sets the backlight time. */ virtual void setBacklightTime(unsigned sec); /** Turns the backlight always on. */ virtual void backlightTimeSetAlways(); /** Returns @c true if the keypad lock is manual. */ virtual bool keypadLockIsManual() const; /** Returns the keypad lock time. */ virtual unsigned keypadLockTime() const; /** Sets the keypad lock time. */ virtual void setKeypadLockTime(unsigned sec); /** Set keypad lock to manual. */ virtual void keypadLockTimeSetManual(); /** Returns the 8-digit power-on password. */ virtual uint32_t powerOnPassword() const; /** Sets the 8-digit power-on password. */ virtual void setPowerOnPassword(uint32_t passwd); /** Returns @c true, if the radio programming password is enabled. */ virtual bool radioProgPasswordEnabled() const; /** Returns the 8-digit radio programming password. */ virtual uint32_t radioProgPassword() const; /** Sets the 8-digit radio programming password. */ virtual void setRadioProgPassword(uint32_t passwd); /** Disables the radio programming password. */ virtual void radioProgPasswordDisable(); /** Returns @c true, if the PC programming password is enabled. */ virtual bool pcProgPasswordEnabled() const; /** Returns the PC programming password. */ virtual QString pcProgPassword() const; /** Sets the PC programming password. */ virtual void setPCProgPassword(const QString &pass); /** Disables the PC programming password. */ virtual void pcProgPasswordDisable(); /** Returns the radio name. */ virtual QString radioName() const; /** Sets the radio name. */ virtual void setRadioName(const QString &name); /** Encodes the general settings. */ virtual bool fromConfig(const Config *config); /** Updates config from general settings. */ virtual bool updateConfig(Config *config); public: /** Some limits for the settings. */ struct Limit: Element::Limit { // Valid VOX sensitivity range. static constexpr Range vox() { return {1,10}; } static constexpr unsigned bootPasswordLength() { return 8; } }; protected: /** Internal offsets. */ struct Offset: Element::Offset { /// @cond DO_NOT_DOCUMENT static constexpr Bit bootImageEnabled() { return {0x0042,4}; } /// @endcond }; }; /** Codeplug representation of programming time-stamp and CPS version. * * Memory layout of encoded timestamp: * @verbinclude tyt_timestamp.txt */ class TimestampElement: public Codeplug::Element { protected: /** Hidden constructor. */ TimestampElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit TimestampElement(uint8_t *ptr); /** Destructor. */ virtual ~TimestampElement(); void clear(); /** Returns the time stamp. */ virtual QDateTime timestamp() const; /** Sets the time stamp. */ virtual void setTimestamp(const QDateTime &ts); /** Returns the CPS version. */ virtual QString cpsVersion() const; }; /** Represents a single GPS system within the codeplug. * * Memory layout of encoded GPS system (size 0x0010 bytes): * @verbinclude tyt_gpssystem.txt */ class GPSSystemElement: public Codeplug::Element { protected: /** Hidden constructor. */ GPSSystemElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit GPSSystemElement(uint8_t *ptr); /** Destructor. */ virtual ~GPSSystemElement(); bool isValid() const; void clear(); /** Returns @c true if the revert channel is the current one. */ virtual bool revertChannelIsSelected() const; /** Returns the revert channel index (+1). */ virtual uint16_t revertChannelIndex() const; /** Sets the revert channel index (+1). */ virtual void setRevertChannelIndex(uint16_t idx); /** Sets the revert channel to the current one. */ virtual void setRevertChannelSelected(); /** Returns @c true if the repeat interval is disabled. */ virtual bool repeatIntervalDisabled() const; /** Returns the repeat interval. */ virtual Interval repeatInterval() const; /** Sets the repeat interval in seconds. */ virtual void setRepeatInterval(const Interval &sec); /** Disables the GPS repeat interval. */ virtual void disableRepeatInterval(); /** Returns @c true if the destination contact is disabled. */ virtual bool destinationContactDisabled() const; /** Returns the destination contact index (+1). */ virtual uint16_t destinationContactIndex() const; /** Sets the destination contact index (+1). */ virtual void setDestinationContactIndex(uint16_t idx); /** Disables the destination contact. */ virtual void disableDestinationContact(); /** Encodes the given GPS system. */ virtual bool fromGPSSystemObj(DMRAPRSSystem *sys, Context &ctx); /** Constructs a GPS system. */ virtual DMRAPRSSystem *toGPSSystemObj(); /** Links the given GPS system. */ virtual bool linkGPSSystemObj(DMRAPRSSystem *sys, Context &ctx); }; /** Represents all menu settings within the codeplug on the radio. * * Memory representation of the menu settings: * @verbinclude tyt_menusettings.txt */ class MenuSettingsElement: public Codeplug::Element { protected: /** Hidden constructor. */ MenuSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit MenuSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~MenuSettingsElement(); void clear(); /** Returns @c true if the menu hang time is infinite. */ virtual bool menuHangtimeIsInfinite() const; /** Returns the menu hang time in seconds. */ virtual unsigned menuHangtime() const; /** Sets the menu hang time in seconds. */ virtual void setMenuHangtime(unsigned sec); /** Sets the menu hang time to be infinite. */ virtual void infiniteMenuHangtime(); /** Returns @c true if text message menu is enabled. */ virtual bool textMessage() const; /** Enables/disables text message menu. */ virtual void enableTextMessage(bool enable); /** Returns @c true if call alert menu is enabled. */ virtual bool callAlert() const; /** Enables/disables call alert menu. */ virtual void enableCallAlert(bool enable); /** Returns @c true if contact editing is enabled. */ virtual bool contactEditing() const; /** Enables/disables contact editing. */ virtual void enableContactEditing(bool enable); /** Returns @c true if manual dial is enabled. */ virtual bool manualDial() const; /** Enables/disables manual dial. */ virtual void enableManualDial(bool enable); /** Returns @c true if contact radio-check menu is enabled. */ virtual bool remoteRadioCheck() const; /** Enables/disables contact radio-check menu. */ virtual void enableRemoteRadioCheck(bool enable); /** Returns @c true if remote monitor menu is enabled. */ virtual bool remoteMonitor() const; /** Enables/disables remote monitor menu. */ virtual void enableRemoteMonitor(bool enable); /** Returns @c true if radio enable menu is enabled. */ virtual bool remoteRadioEnable() const; /** Enables/disables radio enable menu. */ virtual void enableRemoteRadioEnable(bool enable); /** Returns @c true if radio disable menu is enabled. */ virtual bool remoteRadioDisable() const; /** Enables/disables radio disable menu. */ virtual void enableRemoteRadioDisable(bool enable); /** Returns @c true if scan menu is enabled. */ virtual bool scan() const; /** Enables/disables scan menu. */ virtual void enableScan(bool enable); /** Returns @c true if edit scan-list menu is enabled. */ virtual bool scanListEditing() const; /** Enables/disables edit scan-list menu. */ virtual void enableScanListEditing(bool enable); /** Returns @c true if call-log missed menu is enabled. */ virtual bool callLogMissed() const; /** Enables/disables call-log missed menu. */ virtual void enableCallLogMissed(bool enable); /** Returns @c true if call-log answered menu is enabled. */ virtual bool callLogAnswered() const; /** Enables/disables call-log answered menu. */ virtual void enableCallLogAnswered(bool enable); /** Returns @c true if call-log outgoing menu is enabled. */ virtual bool callLogOutgoing() const; /** Enables/disables call-log outgoing menu. */ virtual void enableCallLogOutgoing(bool enable); /** Returns @c true if talkaround menu is enabled. */ virtual bool talkaround() const; /** Enables/disables talkaround menu. */ virtual void enableTalkaround(bool enable); /** Returns @c true if tone/alert menu is enabled. */ virtual bool alertTone() const; /** Enables/disables tone/alert menu. */ virtual void enableAlertTone(bool enable); /** Returns @c true if power menu is enabled. */ virtual bool power() const; /** Enables/disables power menu. */ virtual void enablePower(bool enable); /** Returns @c true if backlight menu is enabled. */ virtual bool backlight() const; /** Enables/disables backlight menu. */ virtual void enableBacklight(bool enable); /** Returns @c true if intro screen menu is enabled. */ virtual bool bootScreen() const; /** Enables/disables intro screen menu. */ virtual void enableBootScreen(bool enable); /** Returns @c true if keypad lock menu is enabled. */ virtual bool keypadLock() const; /** Enables/disables keypad lock menu. */ virtual void enableKeypadLock(bool enable); /** Returns @c true if LED indicator menu is enabled. */ virtual bool ledIndicator() const; /** Enables/disables LED indicator menu. */ virtual void enableLEDIndicator(bool enable); /** Returns @c true if squelch menu is enabled. */ virtual bool squelch() const; /** Enables/disables squelch menu. */ virtual void enableSquelch(bool enable); /** Returns @c true if VOX menu is enabled. */ virtual bool vox() const; /** Enables/disables VOX menu. */ virtual void enableVOX(bool enable); /** Returns @c true if password menu is enabled. */ virtual bool password() const; /** Enables/disables password menu. */ virtual void enablePassword(bool enable); /** Returns @c true if display mode menu is enabled. */ virtual bool displayMode() const; /** Enables/disables display mode menu. */ virtual void enableDisplayMode(bool enable); /** Returns @c true if program radio menu is enabled. */ virtual bool radioProgramming() const; /** Enables/disables program radio menu. */ virtual void enableRadioProgramming(bool enable); /** Encodes the menu settings. */ virtual bool fromConfig(const Config *config); /** Updates config from menu settings. */ virtual bool updateConfig(Config *config); }; /** Represents all button settings within the codeplug on the radio. * * Memory representation of the button settings: * @verbinclude tyt_buttonsettings.txt */ class ButtonSettingsElement: public Codeplug::Element { public: /** The possible button actions. */ typedef enum TyTButtonSettings::ButtonAction ButtonAction; protected: /** Hidden constructor. */ ButtonSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit ButtonSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~ButtonSettingsElement(); void clear(); /** Returns the action for a short press on side button 1. */ virtual ButtonAction sideButton1Short() const; /** Sets the action for a short press on side button 1. */ virtual void setSideButton1Short(ButtonAction action); /** Returns the action for a long press on side button 1. */ virtual ButtonAction sideButton1Long() const; /** Sets the action for a short press on side button 1. */ virtual void setSideButton1Long(ButtonAction action); /** Returns the action for a short press on side button 2. */ virtual ButtonAction sideButton2Short() const; /** Sets the action for a short press on side button 2. */ virtual void setSideButton2Short(ButtonAction action); /** Returns the action for a long press on side button 2. */ virtual ButtonAction sideButton2Long() const; /** Sets the action for a short press on side button 2. */ virtual void setSideButton2Long(ButtonAction action); /** Returns the long-press duration in ms. */ virtual unsigned longPressDuration() const; /** Sets the long-press duration in ms. */ virtual void setLongPressDuration(unsigned ms); /** Encodes the button settings. */ virtual bool fromConfig(const Config *config); /** Updates config from button settings. */ virtual bool updateConfig(Config *config); }; /** Represents a single one-touch setting within the codeplug on the radio. * * Memory representation of a one-touch setting: * @verbinclude tyt_onetouchsettings.txt */ class OneTouchSettingElement: public Codeplug::Element { public: /** Possible one-touch actions. */ enum Action { CALL = 0b0000, ///< Call someone, see @c contact. MESSAGE = 0b0001, ///< Send a message, see @c message. DTMF1 = 0b1000, ///< Analog call DTMF system 1. DTMF2 = 0b1001, ///< Analog call DTMF system 2. DTMF3 = 0b1010, ///< Analog call DTMF system 3. DTMF4 = 0b1011 ///< Analog call DTMF system 4. }; /** Possible one-touch action types. */ enum Type { Disabled = 0b00, ///< Disabled one-touch. Digital = 0b01, ///< Digital call/message. Analog = 0b10 ///< Analog call. }; protected: /** Hidden constructor. */ OneTouchSettingElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit OneTouchSettingElement(uint8_t *ptr); /** Destructor. */ virtual ~OneTouchSettingElement(); bool isValid() const; void clear(); /** Returns the action to perform. */ virtual Action action() const; /** Sets the action to perform. */ virtual void setAction(Action action); /** Returns the type of the action. */ virtual Type actionType() const; /** Sets the type of the action. */ virtual void setActionType(Type action); /** Returns the message index +1. */ virtual uint8_t messageIndex() const; /** Sets the message index +1. */ virtual void setMessageIndex(uint8_t idx); /** Returns the contact index +1. */ virtual uint16_t contactIndex() const; /** Sets the contact index +1. */ virtual void setContactIndex(uint16_t idx); }; /** Represents the emergency settings within the codeplug on the radio. * * Memory representation of the emergency settings (size 0x0010 bytes): * @verbinclude tyt_emergencysettings.txt */ class EmergencySettingsElement: public Codeplug::Element { protected: /** Hidden constructor. */ EmergencySettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit EmergencySettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~EmergencySettingsElement(); virtual void clear(); /** Returns @c true if emergency remote monitor is enabled. */ virtual bool emergencyRemoteMonitor() const; /** Enables/disables emergency remote monitor. */ virtual void enableEmergencyRemoteMonitor(bool enable); /** Returns @c true if remote monitor is enabled. */ virtual bool remoteMonitor() const; /** Enables/disables remote monitor. */ virtual void enableRemoteMonitor(bool enable); /** Returns @c true if radio disable is enabled. */ virtual bool radioDisable() const; /** Enables/disables radio disable. */ virtual void enableRadioDisable(bool enable); /** Returns the remote monitor duration in seconds. */ virtual unsigned remoteMonitorDuration() const; /** Sets the remote monitor duration in seconds. */ virtual void setRemoteMonitorDuration(unsigned sec); /** Returns the TX time-out in ms. */ virtual unsigned txTimeOut() const; /** Sets the TX time-out in ms. */ virtual void setTXTimeOut(unsigned ms); /** Returns the message limit. */ virtual unsigned messageLimit() const; /** Sets the message limit. */ virtual void setMessageLimit(unsigned limit); }; /** Represents a single emergency system within the radio. * * Memory representation of emergency system (size 0x0028 bytes): * @verbinclude tyt_emergencysystem.txt */ class EmergencySystemElement: public Codeplug::Element { public: /** Possible alarm type for the system. */ enum AlarmType { DISABLED = 0, ///< No alarm at all REGULAR = 1, ///< Regular alarm sound. SILENT = 2, ///< Silent alarm. SILENT_W_VOICE = 3 ///< silent alarm with voice. }; /** Possible alarm modes for the system. */ enum AlarmMode { ALARM = 0, ///< Just alarm. ALARM_W_CALL = 1, ///< Alarm + call. ALARM_W_VOICE = 2 ///< Alarm + call + voice? }; protected: /** Hidden constructor. */ EmergencySystemElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit EmergencySystemElement(uint8_t *ptr); /** Destructor. */ virtual ~EmergencySystemElement(); bool isValid() const; void clear(); /** Returns the name of the system. */ virtual QString name() const; /** Sets the name of the system. */ virtual void setName(const QString &name); /** Returns the alarm type of the system. */ virtual AlarmType alarmType() const; /** Sets the alarm type of the system. */ virtual void setAlarmType(AlarmType type); /** Returns the alarm mode of the system. */ virtual AlarmMode alarmMode() const; /** Sets the alarm mode of the system. */ virtual void setAlarmMode(AlarmMode mode); /** Returns the number of impolite retries. */ virtual unsigned impoliteRetries() const; /** Sets the number of impolite retries. */ virtual void setImpoliteRetries(unsigned num); /** Returns the number of polite retries. */ virtual unsigned politeRetries() const; /** Sets the number of polite retries. */ virtual void setPoliteRetries(unsigned num); /** Returns the hot MIC duration in seconds. */ virtual unsigned hotMICDuration() const; /** Sets the hot MIC duration in seconds. */ virtual void setHotMICDuration(unsigned sec); /** Returns @c true if the revert channel is the selected one. */ virtual bool revertChannelIsSelected() const; /** Returns the index of the revert channel. */ virtual uint16_t revertChannelIndex() const; /** Sets the revert channel index. */ virtual void setRevertChannelIndex(uint16_t idx); /** Sets revert channel to selected channel. */ virtual void revertChannelSelected(); }; /** Represents all encryption keys and settings within the codeplug on the device. * * Memory representation of encryption settings: * @verbinclude tyt_privacy.txt */ class EncryptionElement: public Codeplug::Element { protected: /** Hidden constructor. */ EncryptionElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit EncryptionElement(uint8_t *ptr); /** Destructor. */ virtual ~EncryptionElement(); void clear(); /** Returns the size of the element. */ static constexpr unsigned int size() { return 0x00b0; } /** Returns @c true if the n-th "enhanced" key (128bit) is set. * That is, if it is not filled with 0xff. */ virtual bool isEnhancedKeySet(unsigned n) const; /** Returns the n-th "enhanced" key (128bit). */ virtual QByteArray enhancedKey(unsigned n) const; /** Sets the n-th "enhanced" key (128bit). */ virtual void setEnhancedKey(unsigned n, const QByteArray &key); /** Returns @c true if the n-th "basic" key (16bit) is set. * That is, if it is not filled with 0xff. */ virtual bool isBasicKeySet(unsigned n) const; /** Returns the n-th "basic" key (16bit). */ virtual QByteArray basicKey(unsigned n) const; /** Sets the n-th "basic" key (16bit). */ virtual void setBasicKey(unsigned n, const QByteArray &key); /** Encodes given commercial extension. */ virtual bool fromCommercialExt(CommercialExtension *encr, Context &ctx); /** Updates the commercial extension. */ virtual bool updateCommercialExt(Context &ctx); /** Links the given encryption extension. */ virtual bool linkCommercialExt(CommercialExtension *ext, Context &ctx); public: /** Some limits for the element. */ struct Limit { /** Specifies the maximum number of basic (DMR) encryption keys (16bit). */ static constexpr unsigned int basicKeys() { return 16; } /** Specifies the maximum number of advanced (AES) encryption keys (128bit). */ static constexpr unsigned int advancedKeys() { return 8; } }; protected: /** Some internal offsets. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int advancedKeys() { return 0x0000; } static constexpr unsigned int betweenAdvancedKeys() { return 0x0010; } static constexpr unsigned int basicKeys() { return 0x0090; } static constexpr unsigned int betweenBasicKeys() { return 0x0002; } /// @endcond }; }; /** Basic pre-defined SMS text message. */ class MessageElement: public Element { protected: /** Hidden constructor. */ MessageElement(uint8_t *ptr, size_t size); public: /** Constructor. */ MessageElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return 0x00120; } void clear(); bool isValid() const; /** Returns the text of the message. */ virtual QString text() const; /** Sets the text of the message. */ virtual void setText(const QString &text); /** Encodes the given SMS template. */ virtual bool encode(SMSTemplate *sms, const ErrorStack &err=ErrorStack()); /** Decodes the given SMS template. */ virtual SMSTemplate *decode(const ErrorStack &err=ErrorStack()); public: /** Some limits. */ struct Limit { static constexpr unsigned int length() { return 144; } ///< Maximum message length. }; }; /** Bank of pre-defined SMS text messages. */ class MessageBankElement: public Element { protected: /** Hidden constructor. */ MessageBankElement(uint8_t *ptr, size_t size); public: /** Constructor. */ MessageBankElement(uint8_t *ptr); /** The size of the element. */ static constexpr unsigned int size() { return MessageElement::size()*Limit::messages(); } void clear(); /** Returns the i-th message. */ virtual MessageElement message(unsigned int i) const; /** Encodes all messages. */ virtual bool encode(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()); /** Decodes all messages. */ virtual bool decode(Context &ctx, const ErrorStack &err=ErrorStack()); public: /** Some Limits. */ struct Limit { /** The maximum number of messages in a bank. */ static constexpr unsigned int messages() { return 50; } }; }; protected: /** Empty constructor. */ explicit TyTCodeplug(QObject *parent = nullptr); public: /** Destructor. */ virtual ~TyTCodeplug(); /** Clears and resets the complete codeplug to some default values. */ virtual void clear(); bool index(Config *config, Context &ctx, const ErrorStack &err=ErrorStack()) const; Config *preprocess(Config *config, const ErrorStack &err=ErrorStack()) const; /** Decodes the binary codeplug and stores its content in the given generic configuration. */ bool decode(Config *config, const ErrorStack &err=ErrorStack()); /** Encodes the given generic configuration as a binary codeplug. */ bool encode(Config *config, const Flags &flags = Flags(), const ErrorStack &err=ErrorStack()); public: /** Decodes the binary codeplug and stores its content in the given generic configuration using * the given context. */ virtual bool decodeElements(Context &ctx, const ErrorStack &err=ErrorStack()); /** Encodes the given generic configuration as a binary codeplug using the given context. */ virtual bool encodeElements(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); /** Clears the time-stamp in the codeplug. */ virtual void clearTimestamp() = 0; /** Sets the time-stamp. */ virtual bool encodeTimestamp() = 0; /** Clears the general settings in the codeplug. */ virtual void clearGeneralSettings() = 0; /** Updates the general settings from the given configuration. */ virtual bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Updates the given configuration from the general settings. */ virtual bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all contacts in the codeplug. */ virtual void clearContacts() = 0; /** Encodes all digital contacts in the configuration into the codeplug. */ virtual bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds a digital contact to the configuration for each one in the codeplug. */ virtual bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all RX group lists in the codeplug. */ virtual void clearGroupLists() = 0; /** Encodes all group lists in the configuration into the codeplug. */ virtual bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds a RX group list to the configuration for each one in the codeplug. */ virtual bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all added RX group lists within the configuration. */ virtual bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all channels in the codeplug. */ virtual void clearChannels() = 0; /** Encodes all channels in the configuration into the codeplug. */ virtual bool encodeChannels(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds a channel to the configuration for each one in the codeplug. */ virtual bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all added channels within the configuration. */ virtual bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all zones in the codeplug. */ virtual void clearZones() = 0; /** Encodes all zones in the configuration into the codeplug. */ virtual bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds a zone to the configuration for each one in the codeplug. */ virtual bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all added zones within the configuration. */ virtual bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all scan lists in the codeplug. */ virtual void clearScanLists() = 0; /** Encodes all scan lists in the configuration into the codeplug. */ virtual bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds a scan list to the configuration for each one in the codeplug. */ virtual bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all added scan lists within the configuration. */ virtual bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all positioning systems in the codeplug. */ virtual void clearPositioningSystems() = 0; /** Encodes all DMR positioning systems in the configuration into the codeplug. */ virtual bool encodePositioningSystems(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Adds a GPS positioning system to the configuration for each one in the codeplug. */ virtual bool createPositioningSystems(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Links all added positioning systems within the configuration. */ virtual bool linkPositioningSystems(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears the button settings in the codeplug. */ virtual void clearButtonSettings() = 0; /** Encodes the button settings. */ virtual bool encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Decodes the button settings. */ virtual bool decodeButtonSetttings(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all encryption keys in the codeplug. */ virtual void clearPrivacyKeys() = 0; /** Encodes the encryption keys. */ virtual bool encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Decodes the encryption keys. */ virtual bool decodePrivacyKeys(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears the menu settings in the codeplug. */ virtual void clearMenuSettings() = 0; /** Clears all text messages in the codeplug. */ virtual void clearTextMessages() = 0; /** Encodes text messages. */ virtual bool encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err=ErrorStack()) = 0; /** Decodes text messages. */ virtual bool decodeTextMessages(Context &ctx, const ErrorStack &err=ErrorStack()) = 0; /** Clears all emergency systems in the codeplug. */ virtual void clearEmergencySystems() = 0; }; #endif // TYT_CODEPLUG_HH ================================================ FILE: lib/tyt_extensions.cc ================================================ #include "tyt_extensions.hh" #include "level.hh" /* ******************************************************************************************** * * Implementation of TyTChannelExtension * ******************************************************************************************** */ TyTChannelExtension::TyTChannelExtension(QObject *parent) : ConfigExtension(parent), _autoScan(false), _emergencyAlarmConfirmed(false), _displayPTTId(true), _rxRefFrequency(RefFrequency::Low), _txRefFrequency(RefFrequency::Low), _tightSquelch(false), _compressedUDPHeader(false), _killTone(KillTone::Off), _inCallCriterion(InCallCriterion::Always), _allowInterrupt(false), _dcdmLeader(false), _dmrSquelch(Level::fromValue(1)) { // pass... } ConfigItem * TyTChannelExtension::clone() const { TyTChannelExtension *ex = new TyTChannelExtension(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } bool TyTChannelExtension::autoScan() const { return _autoScan; } void TyTChannelExtension::enableAutoScan(bool enable) { if (_autoScan == enable) return; _autoScan = enable; emit modified(this); } bool TyTChannelExtension::emergencyAlarmConfirmed() const { return _emergencyAlarmConfirmed; } void TyTChannelExtension::enableEmergencyAlarmConfirmed(bool enable) { if (_emergencyAlarmConfirmed == enable) return; _emergencyAlarmConfirmed = enable; emit modified(this); } bool TyTChannelExtension::displayPTTId() const { return _displayPTTId; } void TyTChannelExtension::enableDisplayPTTId(bool enable) { if (_displayPTTId == enable) return; _displayPTTId = enable; emit modified(this); } TyTChannelExtension::RefFrequency TyTChannelExtension::rxRefFrequency() const { return _rxRefFrequency; } void TyTChannelExtension::setRXRefFrequency(RefFrequency ref) { if (_rxRefFrequency == ref) return; _rxRefFrequency = ref; emit modified(this); } TyTChannelExtension::RefFrequency TyTChannelExtension::txRefFrequency() const { return _txRefFrequency; } void TyTChannelExtension::setTXRefFrequency(RefFrequency ref) { if (_txRefFrequency == ref) return; _txRefFrequency = ref; emit modified(this); } Level TyTChannelExtension::dmrSquelch() const { return _dmrSquelch; } void TyTChannelExtension::setDMRSquelch(Level sq) { if (_dmrSquelch == sq) return; _dmrSquelch = sq; emit modified(this); } bool TyTChannelExtension::tightSquelch() const { return _tightSquelch; } void TyTChannelExtension::enableTightSquelch(bool enable) { if (_tightSquelch == enable) return; _tightSquelch = enable; emit modified(this); } bool TyTChannelExtension::compressedUDPHeader() const { return _compressedUDPHeader; } void TyTChannelExtension::enableCompressedUDPHeader(bool enable) { if (_compressedUDPHeader == enable) return; _compressedUDPHeader = enable; emit modified(this); } TyTChannelExtension::KillTone TyTChannelExtension::killTone() const { return _killTone; } void TyTChannelExtension::setKillTone(KillTone tone) { if (_killTone == tone) return; _killTone = tone; emit modified(this); } TyTChannelExtension::InCallCriterion TyTChannelExtension::inCallCriterion() const { return _inCallCriterion; } void TyTChannelExtension::setInCallCriterion(InCallCriterion crit) { if (_inCallCriterion == crit) return; _inCallCriterion = crit; emit modified(this); } bool TyTChannelExtension::allowInterrupt() const { return _allowInterrupt; } void TyTChannelExtension::enableAllowInterrupt(bool enable) { if (_allowInterrupt == enable) return; _allowInterrupt = enable; emit modified(this); } bool TyTChannelExtension::dcdmLeader() const { return _dcdmLeader; } void TyTChannelExtension::enableDCDMLeader(bool enable) { if (_dcdmLeader == enable) return; _dcdmLeader = enable; emit modified(this); } /* ******************************************************************************************** * * Implementation of TyTScanListExtension * ******************************************************************************************** */ TyTScanListExtension::TyTScanListExtension(QObject *parent) : ConfigExtension(parent), _holdTime(500), _prioritySampleTime(2000) { // pass... } ConfigItem * TyTScanListExtension::clone() const { TyTScanListExtension *ex = new TyTScanListExtension(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } unsigned TyTScanListExtension::holdTime() const { return _holdTime; } void TyTScanListExtension::setHoldTime(unsigned ms) { if (_holdTime == ms) return; _holdTime = ms; emit modified(this); } unsigned TyTScanListExtension::prioritySampleTime() const { return _prioritySampleTime; } void TyTScanListExtension::setPrioritySampleTime(unsigned ms) { if (_prioritySampleTime == ms) return; _prioritySampleTime = ms; emit modified(this); } /*ConfigItem * TyTScanListExtension::allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err) { Q_UNUSED(prop); Q_UNUSED(node); Q_UNUSED(ctx); Q_UNUSED(err) // There are no further extension/children to TyTScanListExtension. return nullptr; }*/ /* ******************************************************************************************** * * Implementation of TyTButtonSettings * ******************************************************************************************** */ TyTButtonSettings::TyTButtonSettings(QObject *parent) : ConfigExtension(parent) { _sideButton1Short = Disabled; _sideButton1Long = Tone1750Hz; _sideButton2Short = MonitorToggle; _sideButton2Long = Disabled; _sideButton3Short = Disabled; _sideButton3Long = Disabled; _progButton1Short = Disabled; _progButton1Long = Disabled; _progButton2Short = Disabled; _progButton2Long = Disabled; _longPressDuration = 1000; } ConfigItem * TyTButtonSettings::clone() const { TyTButtonSettings *ex = new TyTButtonSettings(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } TyTButtonSettings::ButtonAction TyTButtonSettings::sideButton1Short() const { return _sideButton1Short; } void TyTButtonSettings::setSideButton1Short(ButtonAction action) { if (_sideButton1Short == action) return; _sideButton1Short = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::sideButton1Long() const { return _sideButton1Long; } void TyTButtonSettings::setSideButton1Long(ButtonAction action) { if (_sideButton1Long == action) return; _sideButton1Long = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::sideButton2Short() const { return _sideButton2Short; } void TyTButtonSettings::setSideButton2Short(ButtonAction action) { if (_sideButton2Short == action) return; _sideButton2Short = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::sideButton2Long() const { return _sideButton2Long; } void TyTButtonSettings::setSideButton2Long(ButtonAction action) { if (_sideButton2Long == action) return; _sideButton2Long = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::sideButton3Short() const { return _sideButton3Short; } void TyTButtonSettings::setSideButton3Short(ButtonAction action) { if (_sideButton3Short == action) return; _sideButton3Short = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::sideButton3Long() const { return _sideButton3Long; } void TyTButtonSettings::setSideButton3Long(ButtonAction action) { if (_sideButton3Long == action) return; _sideButton3Long = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::progButton1Short() const { return _progButton1Short; } void TyTButtonSettings::setProgButton1Short(ButtonAction action) { if (_progButton1Short == action) return; _progButton1Short = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::progButton1Long() const { return _progButton1Long; } void TyTButtonSettings::setProgButton1Long(ButtonAction action) { if (_progButton1Long == action) return; _progButton1Long = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::progButton2Short() const { return _progButton2Short; } void TyTButtonSettings::setProgButton2Short(ButtonAction action) { if (_progButton2Short == action) return; _progButton2Short = action; emit modified(this); } TyTButtonSettings::ButtonAction TyTButtonSettings::progButton2Long() const { return _progButton2Long; } void TyTButtonSettings::setProgButton2Long(ButtonAction action) { if (_progButton2Long == action) return; _progButton2Long = action; emit modified(this); } unsigned TyTButtonSettings::longPressDuration() const { return _longPressDuration; } void TyTButtonSettings::setLongPressDuration(unsigned dur) { _longPressDuration = dur; } /* ******************************************************************************************** * * Implementation of TyTMenuSettings * ******************************************************************************************** */ TyTMenuSettings::TyTMenuSettings(QObject *parent) : ConfigExtension(parent), _inifiniteHangTime(false), _hangTime(10), _textMessage(true), _callAlert(true), _contactEditing(true), _manualDial(true), _remoteRadioCheck(true), _remoteMonitor(true), _remoteRadioEnable(true), _remoteRadioDisable(true), _scan(true), _scanListEditing(true), _callLogMissed(true), _callLogAnswered(true), _callLogOutgoing(true), _talkaround(true), _alertTone(true), _power(true), _backlight(true), _bootScreen(true), _keypadLock(true), _ledIndicator(true), _squelch(true), _vox(true), _password(true), _displayMode(true), _radioProgramming(true), _gpsInformation(true) { // pass... } ConfigItem * TyTMenuSettings::clone() const { TyTMenuSettings *ex = new TyTMenuSettings(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } /*ConfigItem * TyTMenuSettings::allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err) { Q_UNUSED(prop); Q_UNUSED(node); Q_UNUSED(ctx); Q_UNUSED(err) // There are no further extension/children to TyTButtonSettings. return nullptr; }*/ bool TyTMenuSettings::hangtimeIsInfinite() const { return _inifiniteHangTime; } void TyTMenuSettings::setHangtimeInfinite(bool infinite) { if (_inifiniteHangTime == infinite) return; _inifiniteHangTime = infinite; if (_inifiniteHangTime) _hangTime = 0; emit modified(this); } unsigned TyTMenuSettings::hangTime() const { return _hangTime; } void TyTMenuSettings::setHangTime(unsigned sec) { if (_hangTime == sec) return; _hangTime = sec; _inifiniteHangTime = (0 == _hangTime); emit modified(this); } bool TyTMenuSettings::textMessage() const { return _textMessage; } void TyTMenuSettings::enableTextMessage(bool enable) { if (_textMessage == enable) return; _textMessage = enable; emit modified(this); } bool TyTMenuSettings::callAlert() const { return _callAlert; } void TyTMenuSettings::enableCallAlert(bool enable) { if (_callAlert == enable) return; _callAlert = enable; emit modified(this); } bool TyTMenuSettings::contactEditing() const { return _contactEditing; } void TyTMenuSettings::enableContactEditing(bool enable) { if (_contactEditing == enable) return; _contactEditing = enable; emit modified(this); } bool TyTMenuSettings::manualDial() const { return _manualDial; } void TyTMenuSettings::enableManualDial(bool enable) { if (_manualDial == enable) return; _manualDial = enable; emit modified(this); } bool TyTMenuSettings::remoteRadioCheck() const { return _remoteRadioCheck; } void TyTMenuSettings::enableRemoteRadioCheck(bool enable) { if (_remoteRadioCheck == enable) return; _remoteRadioCheck = enable; emit modified(this); } bool TyTMenuSettings::remoteMonitor() const { return _remoteMonitor; } void TyTMenuSettings::enableRemoteMonitor(bool enable) { if (_remoteMonitor == enable) return; _remoteMonitor = enable; emit modified(this); } bool TyTMenuSettings::remoteRadioEnable() const { return _remoteRadioEnable; } void TyTMenuSettings::enableRemoteRadioEnable(bool enable) { if (_remoteRadioEnable == enable) return; _remoteRadioEnable = enable; emit modified(this); } bool TyTMenuSettings::remoteRadioDisable() const { return _remoteRadioDisable; } void TyTMenuSettings::enableRemoteRadioDisable(bool enable) { if (_remoteRadioDisable == enable) return; _remoteRadioDisable = enable; emit modified(this); } bool TyTMenuSettings::scan() const { return _scan; } void TyTMenuSettings::enableScan(bool enable) { if (_scan == enable) return; _scan = enable; emit modified(this); } bool TyTMenuSettings::scanListEditing() const { return _scanListEditing; } void TyTMenuSettings::enableScanListEditing(bool enable) { if (_scanListEditing == enable) return; _scanListEditing = enable; emit modified(this); } bool TyTMenuSettings::callLogMissed() const { return _callLogMissed; } void TyTMenuSettings::enableCallLogMissed(bool enable) { if (_callLogMissed == enable) return; _callLogMissed = enable; emit modified(this); } bool TyTMenuSettings::callLogAnswered() const { return _callLogAnswered; } void TyTMenuSettings::enableCallLogAnswered(bool enable) { if (_callLogAnswered == enable) return; _callLogAnswered = enable; emit modified(this); } bool TyTMenuSettings::callLogOutgoing() const { return _callLogOutgoing; } void TyTMenuSettings::enableCallLogOutgoing(bool enable) { if (_callLogOutgoing == enable) return; _callLogOutgoing = enable; emit modified(this); } bool TyTMenuSettings::talkaround() const { return _talkaround; } void TyTMenuSettings::enableTalkaround(bool enable) { if (_talkaround == enable) return; _talkaround = enable; emit modified(this); } bool TyTMenuSettings::alertTone() const { return _alertTone; } void TyTMenuSettings::enableAlertTone(bool enable) { if (_alertTone == enable) return; _alertTone = enable; emit modified(this); } bool TyTMenuSettings::power() const { return _power; } void TyTMenuSettings::enablePower(bool enable) { if (_power == enable) return; _power = enable; emit modified(this); } bool TyTMenuSettings::backlight() const { return _backlight; } void TyTMenuSettings::enableBacklight(bool enable) { if (_backlight == enable) return; _backlight = enable; emit modified(this); } bool TyTMenuSettings::bootScreen() const { return _bootScreen; } void TyTMenuSettings::enableBootScreen(bool enable) { if (_bootScreen == enable) return; _bootScreen = enable; emit modified(this); } bool TyTMenuSettings::keypadLock() const { return _keypadLock; } void TyTMenuSettings::enableKeypadLock(bool enable) { if (_keypadLock == enable) return; _keypadLock = enable; emit modified(this); } bool TyTMenuSettings::ledIndicator() const { return _ledIndicator; } void TyTMenuSettings::enableLEDIndicator(bool enable) { if (_ledIndicator == enable) return; _ledIndicator = enable; emit modified(this); } bool TyTMenuSettings::squelch() const { return _squelch; } void TyTMenuSettings::enableSquelch(bool enable) { if (_squelch == enable) return; _squelch = enable; emit modified(this); } bool TyTMenuSettings::vox() const { return _vox; } void TyTMenuSettings::enableVOX(bool enable) { if (_vox == enable) return; _vox = enable; emit modified(this); } bool TyTMenuSettings::password() const { return _password; } void TyTMenuSettings::enablePassword(bool enable) { if (_password == enable) return; _password = enable; emit modified(this); } bool TyTMenuSettings::displayMode() const { return _displayMode; } void TyTMenuSettings::enableDisplayMode(bool enable) { if (_displayMode == enable) return; _displayMode = enable; emit modified(this); } bool TyTMenuSettings::radioProgramming() const { return _radioProgramming; } void TyTMenuSettings::enableRadioProgramming(bool enable) { if (_radioProgramming == enable) return; _radioProgramming = enable; emit modified(this); } bool TyTMenuSettings::gpsInformation() const { return _gpsInformation; } void TyTMenuSettings::enableGPSInformation(bool enable) { if (_gpsInformation == enable) return; _gpsInformation = enable; emit modified(this); } /* ******************************************************************************************** * * Implementation of TyTSettingsExtension * ******************************************************************************************** */ TyTSettingsExtension::TyTSettingsExtension(QObject *parent) : ConfigExtension(parent), _monitorType(MonitorType::Open), _allLEDsDisabled(false), _passwdAndLock(false), _powerSaveMode(true), _wakeupPreamble(true), _channelModeA(true), _channelModeB(true), _channelMode(true), _txPreambleDuration(600), _groupCallHangTime(3000), _privateCallHangTime(3000), _lowBatteryWarnInterval(120), _callAlertToneContinuous(false), _callAlertToneDuration(0), _loneWorkerResponseTime(1), _loneWorkerReminderTime(10), _digitalScanHangTime(1000), _analogScanHangTime(1000), _backlightAlwaysOn(false), _backlightDuration(10), _keypadLockManual(true), _keypadLockTime(5*0xff), _pcProgPasswordEnabled(false), _pcProgPassword(), _radioProgPasswordEnabled(false), _radioProgPassword(0), _privateCallMatch(true), _groupCallMatch(true), _channelHangTime(3000) { // pass... } ConfigItem * TyTSettingsExtension::clone() const { TyTSettingsExtension *ex = new TyTSettingsExtension(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } TyTSettingsExtension::MonitorType TyTSettingsExtension::monitorType() const { return _monitorType; } void TyTSettingsExtension::setMonitorType(MonitorType type) { if (_monitorType == type) return; _monitorType = type; emit modified(this); } bool TyTSettingsExtension::allLEDsDisabled() const { return _allLEDsDisabled; } void TyTSettingsExtension::disableAllLEDs(bool disable) { if (_allLEDsDisabled == disable) return; _allLEDsDisabled = disable; emit modified(this); } bool TyTSettingsExtension::passwordAndLock() const { return _passwdAndLock; } void TyTSettingsExtension::enablePasswordAndLock(bool enable) { if (_passwdAndLock == enable) return; _passwdAndLock = enable; emit modified(this); } bool TyTSettingsExtension::powerSaveMode() const { return _powerSaveMode; } void TyTSettingsExtension::enablePowerSaveMode(bool enable) { if (_powerSaveMode == enable) return; _powerSaveMode = enable; emit modified(this); } bool TyTSettingsExtension::wakeupPreamble() const { return _wakeupPreamble; } void TyTSettingsExtension::enableWakeupPreamble(bool enable) { if (_wakeupPreamble == enable) return; _wakeupPreamble = enable; emit modified(this); } bool TyTSettingsExtension::channelMode() const { return _channelMode; } void TyTSettingsExtension::enableChannelMode(bool enable) { if (_channelMode == enable) return; _channelMode = enable; emit modified(this); } bool TyTSettingsExtension::channelModeA() const { return _channelModeA; } void TyTSettingsExtension::enableChannelModeA(bool enable) { if (_channelModeA == enable) return; _channelModeA = enable; emit modified(this); } bool TyTSettingsExtension::channelModeB() const { return _channelModeB; } void TyTSettingsExtension::enableChannelModeB(bool enable) { if (_channelModeB == enable) return; _channelModeB = enable; emit modified(this); } unsigned TyTSettingsExtension::lowBatteryWarnInterval() const { return _lowBatteryWarnInterval; } void TyTSettingsExtension::setLowBatteryWarnInterval(unsigned sec) { if (_lowBatteryWarnInterval == sec) return; _lowBatteryWarnInterval = sec; emit modified(this); } bool TyTSettingsExtension::callAlertToneContinuous() const { return _callAlertToneContinuous; } void TyTSettingsExtension::enableCallAlertToneContinuous(bool enable) { if (_callAlertToneContinuous == enable) return; _callAlertToneContinuous = enable; emit modified(this); } unsigned TyTSettingsExtension::callAlertToneDuration() const { return _callAlertToneDuration; } void TyTSettingsExtension::setCallAlertToneDuration(unsigned sec) { if (_callAlertToneDuration == sec) return; _callAlertToneDuration = sec; emit modified(this); } unsigned TyTSettingsExtension::loneWorkerResponseTime() const { return _loneWorkerResponseTime; } void TyTSettingsExtension::setLoneWorkerResponseTime(unsigned min) { if (_loneWorkerResponseTime == min) return; _loneWorkerResponseTime = min; emit modified(this); } unsigned TyTSettingsExtension::loneWorkerReminderTime() const { return _loneWorkerReminderTime; } void TyTSettingsExtension::setLoneWorkerReminderTime(unsigned sec) { if (_loneWorkerReminderTime == sec) return; _loneWorkerReminderTime = sec; emit modified(this); } unsigned TyTSettingsExtension::digitalScanHangTime() const { return _digitalScanHangTime; } void TyTSettingsExtension::setDigitalScanHangTime(unsigned ms) { if (_digitalScanHangTime == ms) return; _digitalScanHangTime = ms; emit modified(this); } unsigned TyTSettingsExtension::analogScanHangTime() const { return _analogScanHangTime; } void TyTSettingsExtension::setAnalogScanHangTime(unsigned ms) { if (_analogScanHangTime == ms) return; _analogScanHangTime = ms; emit modified(this); } bool TyTSettingsExtension::backlightAlwaysOn() const { return _backlightAlwaysOn; } void TyTSettingsExtension::enableBacklightAlwaysOn(bool enable) { if (_backlightAlwaysOn == enable) return; _backlightAlwaysOn = enable; emit modified(this); } unsigned TyTSettingsExtension::backlightDuration() const { return _backlightDuration; } void TyTSettingsExtension::setBacklightDuration(unsigned sec) { if (_backlightDuration == sec) return; _backlightDuration = sec; emit modified(this); } bool TyTSettingsExtension::keypadLockManual() const { return _keypadLockManual; } void TyTSettingsExtension::enableKeypadLockManual(bool enable) { if (_keypadLockManual == enable) return; _keypadLockManual = enable; emit modified(this); } unsigned TyTSettingsExtension::keypadLockTime() const { return _keypadLockTime; } void TyTSettingsExtension::setKeypadLockTime(unsigned sec) { if (_keypadLockTime == sec) return; _keypadLockTime = sec; emit modified(this); } bool TyTSettingsExtension::radioProgPasswordEnabled() const { return _radioProgPassword; } void TyTSettingsExtension::enableRadioProgPassword(bool enable) { if (_radioProgPasswordEnabled == enable) return; _radioProgPasswordEnabled = enable; emit modified(this); } unsigned TyTSettingsExtension::radioProgPassword() const { return _radioProgPassword; } void TyTSettingsExtension::setRadioProgPassword(unsigned passwd) { if (_radioProgPassword == passwd) return; _radioProgPassword = passwd; emit modified(this); } const QString & TyTSettingsExtension::pcProgPassword() const { return _pcProgPassword; } void TyTSettingsExtension::setPCProgPassword(const QString &passwd) { if (_pcProgPassword == passwd) return; _pcProgPassword = passwd; emit modified(this); } unsigned TyTSettingsExtension::channelHangTime() const { return _channelHangTime; } void TyTSettingsExtension::setChannelHangTime(unsigned ms) { if (_channelHangTime == ms) return; _channelHangTime = ms; emit modified(this); } /*ConfigItem * TyTSettingsExtension::allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err) { Q_UNUSED(prop); Q_UNUSED(node); Q_UNUSED(ctx); Q_UNUSED(err) // No extensions to this extension. return nullptr; }*/ /* ******************************************************************************************** * * Implementation of TyTButtonSettings * ******************************************************************************************** */ TyTConfigExtension::TyTConfigExtension(QObject *parent) : ConfigExtension(parent), _buttonSettings(new TyTButtonSettings(this)), _menuSettings(new TyTMenuSettings(this)) { // Pass... } ConfigItem * TyTConfigExtension::clone() const { TyTConfigExtension *ex = new TyTConfigExtension(); if (! ex->copy(*this)) { ex->deleteLater(); return nullptr; } return ex; } TyTButtonSettings * TyTConfigExtension::buttonSettings() const { return _buttonSettings; } TyTMenuSettings * TyTConfigExtension::menuSettings() const { return _menuSettings; } /*ConfigItem * TyTConfigExtension::allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err) { Q_UNUSED(prop); Q_UNUSED(node); Q_UNUSED(ctx); Q_UNUSED(err) // All extensions are pre-allocated. So nothing to do here. return nullptr; }*/ ================================================ FILE: lib/tyt_extensions.hh ================================================ #ifndef TYTEXTENSION_HH #define TYTEXTENSION_HH #include "configobject.hh" #include "level.hh" /** Represents the TyT channel extension. * * That is, all device specific settings for TyT devices, that are not represented though the common * codeplug. * * @ingroup tyt */ class TyTChannelExtension: public ConfigExtension { Q_OBJECT Q_CLASSINFO("description", "Settings for MD-390, RT8, MD-UV390, RT3S, MD-2017, RT82, DM-1701, RT84.") Q_CLASSINFO("longDescription", "Device specific channel settings for TyT and Retevis devices." "Including TyT MD-390, MD-UV390, MD-2017, Retevis RT8, RT3S and RT82" " as well as Baofeng DM-1701.") /** The auto scan feature. */ Q_PROPERTY(bool autoScan READ autoScan WRITE enableAutoScan) /** If @c true, emergency calls are confirmed. */ Q_PROPERTY(bool emergencyAlarmConfirmed READ emergencyAlarmConfirmed WRITE enableEmergencyAlarmConfirmed) /** If @c true, displays analog PTT IDs. */ Q_PROPERTY(bool displayPTTId READ displayPTTId WRITE enableDisplayPTTId) /** Holds the reference frequency setting for RX. */ Q_PROPERTY(RefFrequency rxRefFrequency READ rxRefFrequency WRITE setRXRefFrequency) /** Holds the reference frequency setting for TX. */ Q_PROPERTY(RefFrequency txRefFrequency READ txRefFrequency WRITE setTXRefFrequency) /** The tight-squelch feature. */ Q_PROPERTY(bool tightSquelch READ tightSquelch WRITE enableTightSquelch) /** The compressed UDP header feature. */ Q_PROPERTY(bool compressedUDPHeader READ compressedUDPHeader WRITE enableCompressedUDPHeader) /** Holds the kill tone frequency. */ Q_PROPERTY(KillTone killTone READ killTone WRITE setKillTone) /** Holds the in-call criterion. */ Q_PROPERTY(InCallCriterion inCallCriterion READ inCallCriterion WRITE setInCallCriterion) /** Holds the allow-interrupt flag. */ Q_PROPERTY(bool allowInterrupt READ allowInterrupt WRITE enableAllowInterrupt) /** If @c true, and dcdm is enabled, this radio is the leader, specifying the clock. */ Q_PROPERTY(bool dcdmLeader READ dcdmLeader WRITE enableDCDMLeader) /** The squelch level for DMR channels. */ Q_PROPERTY(Level dmrSquelch READ dmrSquelch WRITE setDMRSquelch) Q_CLASSINFO("dmrSquelchDescription", "Sets the squelch level for DMR channels. " "Only applicable for MD-UV390 and MD-2017") public: /** Possible reference frequency settings for RX & TX. */ enum class RefFrequency { Low=0, Medium=1, High=2 }; Q_ENUM(RefFrequency) /** Possible kill-tone settings. */ enum class KillTone { Tone259_2Hz=0, Tone55_2Hz=1, Off=3 }; Q_ENUM(KillTone) /** Possible in-call criterions. */ enum class InCallCriterion { Always = 0, AdmitCriterion=1, TXInterrupt=2 }; Q_ENUM(InCallCriterion) public: /** Default constructor. */ Q_INVOKABLE explicit TyTChannelExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true if the auto scan feature is enabled. */ bool autoScan() const; /** Enables/disables the auto-scan feature. */ void enableAutoScan(bool enable); /** Returns @c true if emergency calls are confirmed. */ bool emergencyAlarmConfirmed() const; /** Enables/disables emergency-call confirmation. */ void enableEmergencyAlarmConfirmed(bool enable); /** Returns @c true if analog PTT IDs are shown. */ bool displayPTTId() const; /** Enables/disables analog PTT ID display. */ void enableDisplayPTTId(bool enable); /** Returns the reference frequency setting for RX. */ RefFrequency rxRefFrequency() const; /** Sets the reference frequency setting for RX. */ void setRXRefFrequency(RefFrequency ref); /** Returns the reference frequency setting for TX. */ RefFrequency txRefFrequency() const; /** Sets the reference frequency setting for TX. */ void setTXRefFrequency(RefFrequency ref); /** Returns @c true if the tight squelch is enabled. */ bool tightSquelch() const; /** Enables/disables the tight squelch. */ void enableTightSquelch(bool enable); /** Returns @c true if the compressed UDP header is enabled. */ bool compressedUDPHeader() const; /** Enables/disables the compressed UDP header. */ void enableCompressedUDPHeader(bool enable); /** Returns the kill tone frequency. */ KillTone killTone() const; /** Sets the kill-tone frequency. */ void setKillTone(KillTone tone); /** Returns the in-call criterion. */ InCallCriterion inCallCriterion() const; /** Sets the in-call criterion. */ void setInCallCriterion(InCallCriterion crit); /** Returns @c true if interrupt is allowed. */ bool allowInterrupt() const; /** Enables/disables interrupt. */ void enableAllowInterrupt(bool enable); /** Returns @c true if this radio is the leader for a DCDM simplex channel. */ bool dcdmLeader() const; /** Enables/disables this radio to be the leader on a DCDM simplex channel. */ void enableDCDMLeader(bool enable); /** Squelch level for DMR channels. */ Level dmrSquelch() const; /** Sets the squelch-level for DMR channels. */ void setDMRSquelch(Level sq); public: /*ConfigItem *allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack());*/ protected: // Common properties /** Holds the auto-scan flag. */ bool _autoScan; /** Holds the emergency-call confirmation flag. */ bool _emergencyAlarmConfirmed; /** Holds the display PTT ID flag. */ bool _displayPTTId; /** Holds the reference frequency setting for RX. */ RefFrequency _rxRefFrequency; /** Holds the reference frequency setting for TX. */ RefFrequency _txRefFrequency; // MD-390 properties /** Holds the tightSquelch flag. */ bool _tightSquelch; /** Holds the compressed UDP header flag. */ bool _compressedUDPHeader; // MD-UV390, MD-2017 properties /** Holds the kill tone setting. */ KillTone _killTone; /** Holds the in-call criterion. */ InCallCriterion _inCallCriterion; /** Holds the interrupt flag. */ bool _allowInterrupt; /** Holds the DCDM-leader flag. */ bool _dcdmLeader; /** The squelch level [0-10] for DMR channels. */ Level _dmrSquelch; }; /** Represents device specific scan-list settings for TyT devices. * @ingroup tyt */ class TyTScanListExtension: public ConfigExtension { Q_OBJECT /** Holds the hold time in ms. */ Q_PROPERTY(unsigned holdTime READ holdTime WRITE setHoldTime) /** Holds the sample time in ms for priority channels. */ Q_PROPERTY(unsigned prioritySampleTime READ prioritySampleTime WRITE setPrioritySampleTime) public: /** Default constructor. */ Q_INVOKABLE explicit TyTScanListExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the hold time in ms. */ unsigned holdTime() const; /** Sets the hold time im ms. */ void setHoldTime(unsigned ms); /** Returns the sample time for priority channels in ms. */ unsigned prioritySampleTime() const; /** Sets the sample time for priority channels in ms. */ void setPrioritySampleTime(unsigned ms); public: /*ConfigItem *allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack());*/ protected: /** The hold time in ms. */ unsigned _holdTime; /** The sample time for priority channels in ms. */ unsigned _prioritySampleTime; }; /** Represents the TyT button settings extension. * @ingroup tyt */ class TyTButtonSettings : public ConfigExtension { Q_OBJECT /** The action to perform on a short press on side button 1. */ Q_PROPERTY(ButtonAction sideButton1Short READ sideButton1Short WRITE setSideButton1Short) /** The action to perform on a long press on side button 1. */ Q_PROPERTY(ButtonAction sideButton1Long READ sideButton1Long WRITE setSideButton1Long) /** The action to perform on a short press on side button 2. */ Q_PROPERTY(ButtonAction sideButton2Short READ sideButton2Short WRITE setSideButton2Short) /** The action to perform on a long press on side button 2. */ Q_PROPERTY(ButtonAction sideButton2Long READ sideButton2Long WRITE setSideButton2Long) /** The action to perform on a short press on side button 3. */ Q_PROPERTY(ButtonAction sideButton3Short READ sideButton3Short WRITE setSideButton3Short) /** The action to perform on a long press on side button 3. */ Q_PROPERTY(ButtonAction sideButton3Long READ sideButton3Long WRITE setSideButton3Long) /** The action to perform on a short press on programmable button 1. */ Q_PROPERTY(ButtonAction progButton1Short READ progButton1Short WRITE setProgButton1Short) /** The action to perform on a long press on programmable button 1. */ Q_PROPERTY(ButtonAction progButton1Long READ progButton1Long WRITE setProgButton1Long) /** The action to perform on a short press on programmable button 2. */ Q_PROPERTY(ButtonAction progButton2Short READ progButton2Short WRITE setProgButton2Short) /** The action to perform on a long press on programmable button 2. */ Q_PROPERTY(ButtonAction progButton2Long READ progButton2Long WRITE setProgButton2Long) /** The duration of a long press in msec. */ Q_PROPERTY(unsigned longPressDuration READ longPressDuration WRITE setLongPressDuration) public: /** Possible actions for the side-buttons. */ enum ButtonAction { Disabled = 0, ///< Disabled side-button action. ToggleAllAlertTones = 1, ///< Toggle all alert tones. EmergencyOn = 2, ///< Enable emergency. EmergencyOff = 3, ///< Disable emergency. PowerSelect = 4, ///< Select TX power. MonitorToggle = 5, ///< Toggle monitor (promiscuous mode on digital channel, open squelch on analog channel). NuisanceDelete = 6, ///< Nuisance delete. OneTouch1 = 7, ///< Perform one-touch action 1. OneTouch2 = 8, ///< Perform one-touch action 2. OneTouch3 = 9, ///< Perform one-touch action 3. OneTouch4 = 10, ///< Perform one-touch action 4. OneTouch5 = 11, ///< Perform one-touch action 5. OneTouch6 = 12, ///< Perform one-touch action 6. RepeaterTalkaroundToggle = 13, ///< Toggle repater mode / talkaround. ScanToggle = 14, ///< Start/stop scan. SquelchToggle = 21, ///< Enable/disable squelch. PrivacyToggle = 22, ///< Enable/disable privacy system. VoxToggle = 23, ///< Enable/disable VOX. ZoneIncrement = 24, ///< Switch to next zone. BatteryIndicator = 26, ///< Show battery charge. ManualDialForPrivate = 30, ///< Manual dial for private. LoneWorkerToggle = 31, ///< Toggle lone-worker. RecordToggle = 34, ///< Enable/disable recording (dep. on firmware). RecordPlayback = 35, ///< Start/stop playback. RecordDeleteAll = 36, ///< Delete all recordings. Tone1750Hz = 38, ///< Send 1750Hz tone. SwitchUpDown = 47, ///< Switch Channel A/B. RightKey = 48, ///< Who knows? LeftKey = 49, ///< Who knows? ZoneDecrement = 55, ///< Switch to previous zone. SetTalkgroup = 81, ///< md380tools: set temp. TG PromiscuousToggle = 86 ///< md380tools: enable/disable promiscuous mode }; Q_ENUM(ButtonAction) public: /** Constructor. */ Q_INVOKABLE explicit TyTButtonSettings(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the action for the side button 1 short-press. */ ButtonAction sideButton1Short() const; /** Sets the action for the side button 1 short-press. */ void setSideButton1Short(ButtonAction action); /** Returns the action for the side button 1 long-press. */ ButtonAction sideButton1Long() const; /** Sets the action for the side button 1 long-press. */ void setSideButton1Long(ButtonAction action); /** Returns the action for the side button 2 short-press. */ ButtonAction sideButton2Short() const; /** Sets the action for the side button 2 short-press. */ void setSideButton2Short(ButtonAction action); /** Returns the action for the side button 2 long-press. */ ButtonAction sideButton2Long() const; /** Sets the action for the side button 2 long-press. */ void setSideButton2Long(ButtonAction action); /** Returns the action for the side button 3 short-press (Baofeng DM-1701). */ ButtonAction sideButton3Short() const; /** Sets the action for the side button 3 short-press (Baofeng DM-1701). */ void setSideButton3Short(ButtonAction action); /** Returns the action for the side button 3 long-press (Baofeng DM-1701). */ ButtonAction sideButton3Long() const; /** Sets the action for the side button 3 long-press (Baofeng DM-1701). */ void setSideButton3Long(ButtonAction action); /** Returns the action for the programmable button 1 short-press (Baofeng DM-1701). */ ButtonAction progButton1Short() const; /** Sets the action for the programmable button 1 short-press (Baofeng DM-1701). */ void setProgButton1Short(ButtonAction action); /** Returns the action for the programmable button 1 long-press (Baofeng DM-1701). */ ButtonAction progButton1Long() const; /** Sets the action for the programmable button 1 long-press (Baofeng DM-1701). */ void setProgButton1Long(ButtonAction action); /** Returns the action for the programmable button 2 short-press (Baofeng DM-1701). */ ButtonAction progButton2Short() const; /** Sets the action for the programmable button 2 short-press (Baofeng DM-1701). */ void setProgButton2Short(ButtonAction action); /** Returns the action for the programmable button 2 long-press (Baofeng DM-1701). */ ButtonAction progButton2Long() const; /** Sets the action for the programmable button 2 long-press (Baofeng DM-1701). */ void setProgButton2Long(ButtonAction action); /** Returns the long-press duration in msec. */ unsigned longPressDuration() const; /** Sets the long-press duration in msec. */ void setLongPressDuration(unsigned dur); protected: /** Holds the side button 1 short-press action. */ ButtonAction _sideButton1Short; /** Holds the side button 1 long-press action. */ ButtonAction _sideButton1Long; /** Holds the side button 2 short-press action. */ ButtonAction _sideButton2Short; /** Holds the side button 2 long-press action. */ ButtonAction _sideButton2Long; /** Holds the side button 3 short-press action. */ ButtonAction _sideButton3Short; /** Holds the side button 3 long-press action. */ ButtonAction _sideButton3Long; /** Holds the prog button 1 short-press action. */ ButtonAction _progButton1Short; /** Holds the prog button 1 long-press action. */ ButtonAction _progButton1Long; /** Holds the prog button 2 short-press action. */ ButtonAction _progButton2Short; /** Holds the prog button 2 long-press action. */ ButtonAction _progButton2Long; /** Holds the long-press duration in ms. */ unsigned _longPressDuration; }; /** Represents the TyT menu settings extension. * @ingroup tyt */ class TyTMenuSettings : public ConfigExtension { Q_OBJECT /** If @c true, the menu hang time is infinite. */ Q_PROPERTY(bool hangtimeIsInfinite READ hangtimeIsInfinite WRITE setHangtimeInfinite) /** The menu hang time in seconds. */ Q_PROPERTY(unsigned hangTime READ hangTime WRITE setHangTime) /** If @c true, the text message menu is shown. */ Q_PROPERTY(bool textMessage READ textMessage WRITE enableTextMessage) /** If @c true, the call-alert menu item is shown. */ Q_PROPERTY(bool callAlert READ callAlert WRITE enableCallAlert) /** If @c true, the contact editing menu is shown. */ Q_PROPERTY(bool contactEditing READ contactEditing WRITE enableContactEditing) /** If @c true, the manual dial menu item is shown. */ Q_PROPERTY(bool manualDial READ manualDial WRITE enableManualDial) /** If @c true, the remote radio check menu item is shown. */ Q_PROPERTY(bool remoteRadioCheck READ remoteRadioCheck WRITE enableRemoteRadioCheck) /** If @c true, the remote monitor menu item is shown. */ Q_PROPERTY(bool remoteMonitor READ remoteMonitor WRITE enableRemoteMonitor) /** If @c true, the remote radio enable menu item is shown. */ Q_PROPERTY(bool remoteRadioEnable READ remoteRadioEnable WRITE enableRemoteRadioEnable) /** If @c true, the remote radio disable menu item is shown. */ Q_PROPERTY(bool remoteRadioDisable READ remoteRadioDisable WRITE enableRemoteRadioDisable) /** If @c true, the scan menu item is shown. */ Q_PROPERTY(bool scan READ scan WRITE enableScan) /** If @c true, the scan list editing is enabled. */ Q_PROPERTY(bool scanListEditing READ scanListEditing WRITE enableScanListEditing) /** If @c true, the list of missed calls is shown. */ Q_PROPERTY(bool callLogMissed READ callLogMissed WRITE enableCallLogMissed) /** If @c true, the list of answered calls is shown. */ Q_PROPERTY(bool callLogAnswered READ callLogAnswered WRITE enableCallLogAnswered) /** If @c true, the list of outgoing calls is shown. */ Q_PROPERTY(bool callLogOutgoing READ callLogOutgoing WRITE enableCallLogOutgoing) /** If @c true, the talkaround menu item is shown. */ Q_PROPERTY(bool talkaround READ talkaround WRITE enableTalkaround) /** If @c true, the alert-tone menu item is shown. */ Q_PROPERTY(bool alertTone READ alertTone WRITE enableAlertTone) /** If @c true, the power settings menu item is shown. */ Q_PROPERTY(bool power READ power WRITE enablePower) /** If @c true, the backlight menu item is shown. */ Q_PROPERTY(bool backlight READ backlight WRITE enableBacklight) /** If @c true, the boot-screen settings menu item is shown. */ Q_PROPERTY(bool bootScreen READ bootScreen WRITE enableBootScreen) /** If @c true, the keypad-lock settings menu item is shown. */ Q_PROPERTY(bool keypadLock READ keypadLock WRITE enableKeypadLock) /** If @c true, the LED indicator settings menu item is shown. */ Q_PROPERTY(bool ledIndicator READ ledIndicator WRITE enableLEDIndicator) /** If @c true, the squelch settings menu item is shown. */ Q_PROPERTY(bool squelch READ squelch WRITE enableSquelch) /** If @c true, the VOX settings menu item is shown. */ Q_PROPERTY(bool vox READ vox WRITE enableVOX) /** If @c true, the password menu item is shown. */ Q_PROPERTY(bool password READ password WRITE enablePassword) /** If @c true, the display-mode settings menu item is shown. */ Q_PROPERTY(bool displayMode READ displayMode WRITE enableDisplayMode) /** If @c true, radio programming on the radio is enabled. */ Q_PROPERTY(bool radioProgramming READ radioProgramming WRITE enableRadioProgramming) /** If @c true, the positioning settings menu item is shown. */ Q_PROPERTY(bool gpsInformation READ gpsInformation WRITE enableGPSInformation) public: /** Constructor. */ Q_INVOKABLE explicit TyTMenuSettings(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns @c true if the hang time is infinite. */ bool hangtimeIsInfinite() const; /** Enables/disables infinite hang time. */ void setHangtimeInfinite(bool infinite); /** Returns the menu hang time in seconds. */ unsigned hangTime() const; /** Sets the menu hang time in seconds. */ void setHangTime(unsigned sec); /** Returns @c true if the text message menu item is enabled. */ bool textMessage() const; /** Enables/disables the text message menu item. */ void enableTextMessage(bool enable); /** Returns @c true if the call alert menu item is enabled. */ bool callAlert() const; /** Enables/disables the call alert menu item. */ void enableCallAlert(bool enable); /** Returns @c true if contact editing is enabled. */ bool contactEditing() const; /** Enables/disables contact editing. */ void enableContactEditing(bool enable); /** Returns @c true if the manual dial menu item is enabled. */ bool manualDial() const; /** Enables/disables the manual dial menu item. */ void enableManualDial(bool enable); /** Returns @c true if the remote radio check menu item is enabled. */ bool remoteRadioCheck() const; /** Enables/disables the remote radio check menu item. */ void enableRemoteRadioCheck(bool enable); /** Returns @c true if the remote monitor menu item is enabled. */ bool remoteMonitor() const; /** Enables/disables the remote monitor menu item. */ void enableRemoteMonitor(bool enable); /** Returns @c true if the remote radio enable menu item is enabled. */ bool remoteRadioEnable() const; /** Enables/disables the remote radio enable menu item. */ void enableRemoteRadioEnable(bool enable); /** Returns @c true if the remote radio disable menu item is enabled. */ bool remoteRadioDisable() const; /** Enables/disables the remote radio disable menu item. */ void enableRemoteRadioDisable(bool enable); /** Returns @c true if the scan menu item is enabled. */ bool scan() const; /** Enables/disables the scan menu item. */ void enableScan(bool enable); /** Returns @c true if the scan list editing menu item is enabled. */ bool scanListEditing() const; /** Enables/disables the scan list editing menu item. */ void enableScanListEditing(bool enable); /** Returns @c true if the list of missed calls menu item is enabled. */ bool callLogMissed() const; /** Enables/disables the list of missed calls menu item. */ void enableCallLogMissed(bool enable); /** Returns @c true if the list of answered calls menu item is enabled. */ bool callLogAnswered() const; /** Enables/disables the list of answered calls menu item. */ void enableCallLogAnswered(bool enable); /** Returns @c true if the list of outgoing calls menu item is enabled. */ bool callLogOutgoing() const; /** Enables/disables the list of outgoing calls menu item. */ void enableCallLogOutgoing(bool enable); /** Returns @c true if the talkaround menu item is enabled. */ bool talkaround() const; /** Enables/disables the talkaround menu item. */ void enableTalkaround(bool enable); /** Returns @c true if the alert tone menu item is enabled. */ bool alertTone() const; /** Enables/disables the alert tone menu item. */ void enableAlertTone(bool enable); /** Returns @c true if the power menu item is enabled. */ bool power() const; /** Enables/disables the power menu item. */ void enablePower(bool enable); /** Returns @c true if the backlight menu item is enabled. */ bool backlight() const; /** Enables/disables the backlight menu item. */ void enableBacklight(bool enable); /** Returns @c true if the boot screen menu item is enabled. */ bool bootScreen() const; /** Enables/disables the boot screen menu item. */ void enableBootScreen(bool enable); /** Returns @c true if the keypad lock menu item is enabled. */ bool keypadLock() const; /** Enables/disables the keypad lock menu item. */ void enableKeypadLock(bool enable); /** Returns @c true if the LED indicator menu item is enabled. */ bool ledIndicator() const; /** Enables/disables the LED indicator menu item. */ void enableLEDIndicator(bool enable); /** Returns @c true if the squelch menu item is enabled. */ bool squelch() const; /** Enables/disables the squelch menu item. */ void enableSquelch(bool enable); /** Returns @c true if the VOX menu item is enabled. */ bool vox() const; /** Enables/disables the VOX menu item. */ void enableVOX(bool enable); /** Returns @c true if the password menu item is enabled. */ bool password() const; /** Enables/disables the password menu item. */ void enablePassword(bool enable); /** Returns @c true if the display mode menu item is enabled. */ bool displayMode() const; /** Enables/disables the display mode menu item. */ void enableDisplayMode(bool enable); /** Returns @c true if the radio programming menu item is enabled. */ bool radioProgramming() const; /** Enables/disables the radio programming menu item. */ void enableRadioProgramming(bool enable); /** Returns @c true if the GPS information menu item is enabled. */ bool gpsInformation() const; /** Enables/disables the GPS information menu item. */ void enableGPSInformation(bool enable); public: /*ConfigItem *allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack());*/ protected: /** If @c true, the menu hang time is infinite. */ bool _inifiniteHangTime; /** The menu hang time in seconds. */ unsigned _hangTime; /** If @c true, the text message menu is shown. */ bool _textMessage; /** If @c true, the call-alert menu item is shown. */ bool _callAlert; /** If @c true, the contact editing menu is shown. */ bool _contactEditing; /** If @c true, the manual dial menu item is shown. */ bool _manualDial; /** If @c true, the remote radio check menu item is shown. */ bool _remoteRadioCheck; /** If @c true, the remote monitor menu item is shown. */ bool _remoteMonitor; /** If @c true, the remote radio enable menu item is shown. */ bool _remoteRadioEnable; /** If @c true, the remote radio disable menu item is shown. */ bool _remoteRadioDisable; /** If @c true, the scan menu item is shown. */ bool _scan; /** If @c true, the scan list editing is enabled. */ bool _scanListEditing; /** If @c true, the list of missed calls is shown. */ bool _callLogMissed; /** If @c true, the list of answered calls is shown. */ bool _callLogAnswered; /** If @c true, the list of outgoing calls is shown. */ bool _callLogOutgoing; /** If @c true, the talkaround menu item is shown. */ bool _talkaround; /** If @c true, the alert-tone menu item is shown. */ bool _alertTone; /** If @c true, the power settings menu item is shown. */ bool _power; /** If @c true, the backlight menu item is shown. */ bool _backlight; /** If @c true, the boot-screen settings menu item is shown. */ bool _bootScreen; /** If @c true, the keypad-lock settings menu item is shown. */ bool _keypadLock; /** If @c true, the LED indicator settings menu item is shown. */ bool _ledIndicator; /** If @c true, the squelch settings menu item is shown. */ bool _squelch; /** If @c true, the VOX settings menu item is shown. */ bool _vox; /** If @c true, the password menu item is shown. */ bool _password; /** If @c true, the display-mode settings menu item is shown. */ bool _displayMode; /** If @c true, radio programming on the radio is enabled. */ bool _radioProgramming; /** If @c true, the positioning settings menu item is shown. */ bool _gpsInformation; }; /** Represents the TyT general settings extension. * @ingroup tyt */ class TyTSettingsExtension: public ConfigExtension { Q_OBJECT /** The monitor type setting. */ Q_PROPERTY(MonitorType monitorType READ monitorType WRITE setMonitorType) /** If @c true, all LEDs are disabled. */ Q_PROPERTY(bool allLEDsDisabled READ allLEDsDisabled WRITE disableAllLEDs) /** If @c true, the password and lock is enabled. */ Q_PROPERTY(bool passwordAndLock READ passwordAndLock WRITE enablePasswordAndLock) /** If @c true, the power save mode is enabled. */ Q_PROPERTY(bool powerSaveMode READ powerSaveMode WRITE enablePowerSaveMode) Q_CLASSINFO("powerSaveModeDescription", "Puts the radio into sleep-mode when idle.") Q_CLASSINFO("powerSaveModeLongDescription", "When enabled, the radio enters a sleep mode when idle. That is, when on receive and " "there is no activity on the current channel. However, the radio may need some time " "to wake up from this mode. Hence, the 'wakeupPreamble' need to be enabled by all " "radios in the network to provide this wake-up delay.") /** If @c true, a wakeup preamble is sent. */ Q_PROPERTY(bool wakeupPreamble READ wakeupPreamble WRITE enableWakeupPreamble) Q_CLASSINFO("wakeupPreambleDescription", "If enabled, the radio will transmit a short wake-up " "preamble before each call.") /** If @c true, the radio is in channel mode. */ Q_PROPERTY(bool channelMode READ channelMode WRITE enableChannelMode) /** If @c true or channelMode is true, the VFO A is in channel mode. */ Q_PROPERTY(bool channelModeA READ channelModeA WRITE enableChannelModeA) /** If @c true or channelMode is true, the VFO B is in channel mode. */ Q_PROPERTY(bool channelModeB READ channelModeB WRITE enableChannelModeB) /** The low battery warn interval in seconds. */ Q_PROPERTY(unsigned lowBatteryWarnInterval READ lowBatteryWarnInterval WRITE setLowBatteryWarnInterval) /** If @c true, the call alert-tone is continuous. */ Q_PROPERTY(bool callAlertToneContinuous READ callAlertToneContinuous WRITE enableCallAlertToneContinuous) /** The call alert duration in seconds. */ Q_PROPERTY(unsigned callAlertToneDuration READ callAlertToneDuration WRITE setCallAlertToneDuration) /** The lone-worker response time in minutes. */ Q_PROPERTY(unsigned loneWorkerResponseTime READ loneWorkerResponseTime WRITE setLoneWorkerResponseTime) /** The lone-worker reminder time in seconds. */ Q_PROPERTY(unsigned loneWorkerReminderTime READ loneWorkerReminderTime WRITE setLoneWorkerReminderTime) /** The digital channel scan hang time in ms. */ Q_PROPERTY(unsigned digitalScanHangTime READ digitalScanHangTime WRITE setDigitalScanHangTime) /** The analog channel scan hang time in ms. */ Q_PROPERTY(unsigned analogScanHangTime READ analogScanHangTime WRITE setAnalogScanHangTime) /** If @c true, the backlight is always on. */ Q_PROPERTY(bool backlightAlwaysOn READ backlightAlwaysOn WRITE enableBacklightAlwaysOn) /** If @c backlightAlwaysOn is @c false, specifies the backlight duration in seconds. */ Q_PROPERTY(unsigned backlightDuration READ backlightDuration WRITE setBacklightDuration) /** If @c true, the keypad is locked manually. */ Q_PROPERTY(bool keypadLockManual READ keypadLockManual WRITE enableKeypadLockManual) /** If @c keypadLockManual is @c false, specifies the keypad lock time. */ Q_PROPERTY(unsigned keypadLockTime READ keypadLockTime WRITE setKeypadLockTime) /** If @c true the radio programming password is enabled. */ Q_PROPERTY(bool radioProgPasswordEnabled READ radioProgPasswordEnabled WRITE enableRadioProgPassword) /** If @c radioProgPasswordEnabled is @c true, specifies the radio programming password. */ Q_PROPERTY(unsigned radioProgPassword READ radioProgPassword WRITE setRadioProgPassword) /** Specifies the PC programming password. */ Q_PROPERTY(QString pcProgPassword READ pcProgPassword WRITE setPCProgPassword) /** Holds the channel hang time in ms. */ Q_PROPERTY(unsigned channelHangTime READ channelHangTime WRITE setChannelHangTime) Q_CLASSINFO("description", "Settings for MD-390, RT8, MD-UV390, RT3S, MD-2017, RT82.") Q_CLASSINFO("longDescription", "Device specific radio settings for TyT and Retevis devices." "Including TyT MD-390, MD-UV390, MD-2017 as well as Retevis RT8, " "RT3S and RT82.") public: /** Possible monitor types. */ enum class MonitorType { Silent=0, Open=1 }; Q_ENUM(MonitorType) public: /** Default constructor. */ Q_INVOKABLE explicit TyTSettingsExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the monitor type. */ MonitorType monitorType() const; /** Sets the monitor type. */ void setMonitorType(MonitorType type); /** Returns @c true if all LEDs are disabled. */ bool allLEDsDisabled() const; /** Disables all LEDs. */ void disableAllLEDs(bool disable); /** Returns @c true if the password and lock is enabled. */ bool passwordAndLock() const; /** Enables the password and lock. */ void enablePasswordAndLock(bool enable); /** Returns @c true if the power save mode is enabled. */ bool powerSaveMode() const; /** Enables the power save mode. */ void enablePowerSaveMode(bool enable); /** Returns @c true if the wake-up preamble is sent. */ bool wakeupPreamble() const; /** Enables transmission of wakeup preamble. */ void enableWakeupPreamble(bool enable); /** Returns @c true if the radio is in channel mode. Overrides @c channelModeA and @c channelModeB. */ bool channelMode() const; /** Enables/disables channel mode for the radio. */ void enableChannelMode(bool enable); /** Returns @c true if VFO A is in channel mode. Overridden by @c channelMode. */ bool channelModeA() const; /** Enables/disables channel mode for the VFO A. */ void enableChannelModeA(bool enable); /** Returns @c true if VFO B is in channel mode. Overridden by @c channelMode. */ bool channelModeB() const; /** Enables/disables channel mode for the VFO B. */ void enableChannelModeB(bool enable); /** Returns the low-battery warn interval in seconds. */ unsigned lowBatteryWarnInterval() const; /** Sets the low-battery warn interval in seconds. */ void setLowBatteryWarnInterval(unsigned sec); /** Returns @c true if the call alert-tone is continuous. */ bool callAlertToneContinuous() const; /** Sets the call alert-tone continuous. */ void enableCallAlertToneContinuous(bool enable); /** Returns the call alert-tone duration in seconds. */ unsigned callAlertToneDuration() const; /** Sets the call alert-tone duration in seconds. */ void setCallAlertToneDuration(unsigned sec); /** Returns the lone worker response time in minutes. */ unsigned loneWorkerResponseTime() const; /** Sets the lone-worker response time in minutes. */ void setLoneWorkerResponseTime(unsigned min); /** Returns the lone-worker reminder time in seconds. */ unsigned loneWorkerReminderTime() const; /** Sets the lone-worker reminder timer in seconds. */ void setLoneWorkerReminderTime(unsigned sec); /** Returns the hang time scanning for digital channels. */ unsigned digitalScanHangTime() const; /** Sets the scan hang-time for digital channels. */ void setDigitalScanHangTime(unsigned ms); /** Returns the hang time scanning for analog channels. */ unsigned analogScanHangTime() const; /** Sets the scan hang-time for analog channels. */ void setAnalogScanHangTime(unsigned ms); /** Returns @c true if the backlight is always on. */ bool backlightAlwaysOn() const; /** Enables the backlight continuously. */ void enableBacklightAlwaysOn(bool enable); /** Returns the backlight duration in seconds. */ unsigned backlightDuration() const; /** Sets the backlight duration in seconds. */ void setBacklightDuration(unsigned sec); /** Returns @c true if the keypad lock is manual. */ bool keypadLockManual() const; /** Sets the keypad lock to manual. */ void enableKeypadLockManual(bool enable); /** Returns the keypad lock time in seconds. */ unsigned keypadLockTime() const; /** Sets the keypad lock time in seconds. */ void setKeypadLockTime(unsigned sec); /** Returns @c true if radio programming password is enabled. */ bool radioProgPasswordEnabled() const; /** Enables the radio programming password. */ void enableRadioProgPassword(bool enable); /** Returns the radio programming password. */ unsigned radioProgPassword() const; /** Sets the radio programming password. */ void setRadioProgPassword(unsigned passwd); /** Returns the PC programming password. */ const QString &pcProgPassword() const; /** Sets PC programming password. */ void setPCProgPassword(const QString &passwd); /** Returns the channel hang time in ms. */ unsigned channelHangTime() const; /** Sets the channel hang time in ms. */ void setChannelHangTime(unsigned ms); public: /*ConfigItem *allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack());*/ protected: /** Holds the monitor type. */ MonitorType _monitorType; /** If @c true all LEDs are disabled. */ bool _allLEDsDisabled; /** If @c true the password and lock is enabled. */ bool _passwdAndLock; /** If @c true, the power save mode is enabled. */ bool _powerSaveMode; /** If @c true, the wake-up preamble is sent. */ bool _wakeupPreamble; /** If @c true or channelMode is true, the VFO A is in channel (memory) mode. */ bool _channelModeA; /** If @c true or channelMode is true, the VFO B is in channel (memory) mode. */ bool _channelModeB; /** If @c true, the radio is in channel (memory) mode. Overrides channelModeA and channelModeB. */ bool _channelMode; /** Holds the TX preamble duration. */ unsigned _txPreambleDuration; /** Holds the group-call hang time. */ unsigned _groupCallHangTime; /** Holds the private-call hang time. */ unsigned _privateCallHangTime; /** Holds the low-battery warn interval. */ unsigned _lowBatteryWarnInterval; /** If @c true, the call alert-tone is continuous. */ bool _callAlertToneContinuous; /** Holds the call alert-tone duration. */ unsigned _callAlertToneDuration; /** Holds the lone-worker response time. */ unsigned _loneWorkerResponseTime; /** Holds the lone-worker reminder time. */ unsigned _loneWorkerReminderTime; /** Holds the scan hang-time for digital channels. */ unsigned _digitalScanHangTime; /** Holds the scan hang-time for analog channels. */ unsigned _analogScanHangTime; /** If @c true, the backlight is always on. */ bool _backlightAlwaysOn; /** Holds the backlight duration. */ unsigned _backlightDuration; /** If @c true, the keypad lock is manual. */ bool _keypadLockManual; /** Holds the keypad lock time. */ unsigned _keypadLockTime; /** If @c true, the programming password is enabled. */ bool _pcProgPasswordEnabled; /** Holds the programming password. */ QString _pcProgPassword; /** If @c true, the radio programming password is enabled. */ bool _radioProgPasswordEnabled; /** Holds the radio programming password. */ unsigned _radioProgPassword; /** If @c true, the private call IDs must match. */ bool _privateCallMatch; /** If @c true, the group call IDs must match. */ bool _groupCallMatch; /** Holds the channel hang time in ms. */ unsigned _channelHangTime; }; /** Groups several extension for TyT devices. * @ingroup tyt */ class TyTConfigExtension: public ConfigExtension { Q_OBJECT /** The button settings for TyT devices. */ Q_PROPERTY(TyTButtonSettings* buttonSettings READ buttonSettings) /** The menu settings for TyT devices. */ Q_PROPERTY(TyTMenuSettings* menuSettings READ menuSettings) public: /** Constructor. Also allocates all associates extensions. */ Q_INVOKABLE explicit TyTConfigExtension(QObject *parent=nullptr); ConfigItem *clone() const; /** Returns the button settings extension for TyT devices. */ TyTButtonSettings *buttonSettings() const; /** Returns the menu settings extension for TyT devices. */ TyTMenuSettings *menuSettings() const; public: /*ConfigItem *allocateChild(QMetaProperty &prop, const YAML::Node &node, const Context &ctx, const ErrorStack &err=ErrorStack());*/ protected: /** Owns the button settings extension. */ TyTButtonSettings *_buttonSettings; /** Owns the menu settings extension. */ TyTMenuSettings *_menuSettings; }; #endif // TYTBUTTONSETTINGSEXTENSION_HH ================================================ FILE: lib/tyt_interface.cc ================================================ #include "tyt_interface.hh" #include "logger.hh" #include #include "utils.hh" #include "errorstack.hh" #define USB_VID 0x0483 #define USB_PID 0xdf11 TyTInterface::TyTInterface(const USBDeviceDescriptor &descr, const ErrorStack &err, QObject *parent) : DFUSEDevice(descr, err, 16, parent), RadioInterface() { if (! DFUDevice::isOpen()) { errMsg(err) << "Cannot open TyTInterface."; return; } // Enter Programming Mode. if (wait_idle()) { errMsg(err) << "Device not ready. Close device."; close(); return; } if (md380_command(0x91, 0x01, err)) { errMsg(err) << "Cannot enter programming mode. Close device."; reboot(err); close(); return; } // Get device identifier in a static buffer. const char *idstr = identify(err); if (idstr && (0==strcmp("DR780", idstr))) { _ident = RadioInfo::byID(RadioInfo::MD380); } else if (idstr && (0==strcmp("MD390", idstr))) { _ident = RadioInfo::byID(RadioInfo::MD390); } else if (idstr && (0==strcmp("MD-UV380", idstr))) { _ident = RadioInfo::byID(RadioInfo::UV380); } else if (idstr && (0==strcmp("MD-UV390", idstr))) { _ident = RadioInfo::byID(RadioInfo::UV390); } else if (idstr && (0==strcmp("2017", idstr))) { _ident = RadioInfo::byID(RadioInfo::MD2017); } else if (idstr && (0==strcmp("DM-1701", idstr))) { _ident = RadioInfo::byID(RadioInfo::DM1701); } else if (idstr) { errMsg(err) << "Unknown TyT device '" << idstr << "'."; close(); return; } // Zero address. if(set_address(0x00000000, err)) { errMsg(err) << "Cannot set device address to 0x00000000."; close(); return; } logDebug() << "Found device " << _ident.manufacturer() << " "<< _ident.name() << " at " << descr.description() << "."; } TyTInterface::~TyTInterface() { if (isOpen()) close(); } USBDeviceInfo TyTInterface::interfaceInfo() { return USBDeviceInfo(USBDeviceInfo::Class::DFU, USB_VID, USB_PID); } QList TyTInterface::detect(bool saveOnly) { Q_UNUSED(saveOnly); return DFUDevice::detect(USB_VID, USB_PID); } void TyTInterface::close() { if (isOpen()) { _ident = RadioInfo(); } DFUSEDevice::close(); } bool TyTInterface::isOpen() const { return DFUSEDevice::isOpen() && _ident.isValid(); } RadioInfo TyTInterface::identifier(const ErrorStack &err) { Q_UNUSED(err); return _ident; } int TyTInterface::md380_command(uint8_t a, uint8_t b, const ErrorStack &err) { unsigned char cmd[2] = { a, b }; if (int error = download(0, cmd, 2, err)) return error; usleep(100000); return wait_idle(); } int TyTInterface::set_address(uint32_t address, const ErrorStack &err) { unsigned char cmd[5] = { 0x21, (uint8_t)address, (uint8_t)(address >> 8), (uint8_t)(address >> 16), (uint8_t)(address >> 24), }; if (int error = download(0, cmd, 5, err)) return error; return wait_idle(); } int TyTInterface::erase_block(uint32_t address, const ErrorStack &err) { unsigned char cmd[5] = { 0x41, (uint8_t)address, (uint8_t)(address >> 8), (uint8_t)(address >> 16), (uint8_t)(address >> 24), }; if (int error = download(0, cmd, 5, err)) return error; wait_idle(); return 0; } const char * TyTInterface::identify(const ErrorStack &err) { static uint8_t data[64]; md380_command(0xa2, 0x01, err); if (upload(0, data, 64, err)) return nullptr; return (const char*) data; } bool TyTInterface::erase(unsigned start, unsigned size, void(*progress)(unsigned, void *), void *ctx, const ErrorStack &err) { int error; // Enter Programming Mode. if ((error = get_status(err))) return false; if ((error = wait_idle())) return false; if ((error = md380_command(0x91, 0x01, err))) return false; usleep(100000); unsigned end = start+size; start = align_addr(start, 0x10000); end = align_size(end, 0x10000); size = end-start; for (unsigned i=0; i * WhatCode Resp. Len. Description> * 0x01 32 The radio identifier as a string + some unknown * information. * 0x02 4 Unknown * 0x03 24 Unknown * 0x04 8 Unknown * 0x07 16 Unknown * * * @ingroup tyt */ class TyTInterface : public DFUSEDevice, public RadioInterface { Q_OBJECT public: /** Constructor. Opens an interface to the specified interface. */ TyTInterface(const USBDeviceDescriptor &descr, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); /** Destructor. */ ~TyTInterface(); bool isOpen() const; RadioInfo identifier(const ErrorStack &err=ErrorStack()); void close(); bool read_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool read(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool read_finish(const ErrorStack &err=ErrorStack()); bool write_start(uint32_t bank, uint32_t addr, const ErrorStack &err=ErrorStack()); bool write(uint32_t bank, uint32_t addr, uint8_t *data, int nbytes, const ErrorStack &err=ErrorStack()); bool write_finish(const ErrorStack &err=ErrorStack()); bool reboot(const ErrorStack &err=ErrorStack()); /** Erases a memory section at @c start of size @c size. */ bool erase(unsigned start, unsigned size, void (*progress)(unsigned, void *)=nullptr, void *ctx=nullptr, const ErrorStack &err=ErrorStack()); public: /** Returns some information about the interface. */ static USBDeviceInfo interfaceInfo(); /** Tries to find all interfaces connected TyT radios. */ static QList detect(bool saveOnly=true); protected: /** Internal used function to send a control command to the device. */ int md380_command(uint8_t a, uint8_t b, const ErrorStack &err=ErrorStack()); /** Internal used function to set the current I/O address. */ int set_address(uint32_t address, const ErrorStack &err=ErrorStack()); /** Internal used function to erase a specific block. */ int erase_block(uint32_t address, const ErrorStack &err=ErrorStack()); /** Internal used function to read the device identifier. */ const char *identify(const ErrorStack &err=ErrorStack()); protected: /** Read identifier. */ RadioInfo _ident; }; #endif // TYTINTERFACE_HH ================================================ FILE: lib/tyt_radio.cc ================================================ #include "tyt_radio.hh" #include "config.hh" #include "logger.hh" #include "utils.hh" #define BSIZE 1024 TyTRadio::TyTRadio(TyTInterface *device, QObject *parent) : Radio(parent), _dev(device), _codeplugFlags(), _config(nullptr) { // pass... } TyTRadio::~TyTRadio() { if (_dev && _dev->isOpen()) { logDebug() << "Reboot TyT device."; _dev->reboot(); logDebug() << "Close connection to TyT device."; _dev->close(); } if (_dev) { _dev->deleteLater(); _dev = nullptr; } logDebug() << "Destructed TyT radio."; } bool TyTRadio::startDownload(const TransferFlags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; _task = StatusDownload; _errorStack = err; if (flags.blocking()) { run(); return (StatusIdle == _task); } start(); return true; } bool TyTRadio::startUpload(Config *config, const Codeplug::Flags &flags, const ErrorStack &err) { if (StatusIdle != _task) return false; if (_config) delete _config; if (! (_config = config)) return false; _config->setParent(this); _task = StatusUpload; _errorStack = err; _codeplugFlags = flags; if (flags.blocking()) { this->run(); return (StatusIdle == _task); } this->start(); return true; } bool TyTRadio::startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection, const ErrorStack &err) { if (StatusIdle != _task) return false; logDebug() << "Encode call-sign DB."; if (nullptr == callsignDB()) { errMsg(err) << "Cannot upload callsign DB. DB not created."; return false; } callsignDB()->encode(db, selection); _task = StatusUploadCallsigns; _errorStack = err; if (selection.blocking()) { this->run(); return (StatusIdle == _task); } this->start(); return true; } bool TyTRadio::startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err) { Q_UNUSED(db); Q_UNUSED(flags); errMsg(err) << "Satellite config upload is not implemented yet."; return false; } void TyTRadio::run() { if (StatusDownload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit downloadError(this); return; } if (! download()) { _dev->reboot(); _dev->close(); _task = StatusError; emit downloadError(this); return; } _task = StatusIdle; _dev->reboot(); _dev->close(); emit downloadFinished(this, &codeplug()); _config = nullptr; } else if (StatusUpload == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if (! upload()) { _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _dev->reboot(); _dev->close(); _task = StatusIdle; emit uploadComplete(this); } else if (StatusUploadCallsigns == _task) { if ((nullptr==_dev) || (! _dev->isOpen())) { emit uploadError(this); return; } if(! uploadCallsigns()) { _dev->reboot(); _dev->close(); _task = StatusError; emit uploadError(this); return; } _task = StatusIdle; _dev->reboot(); _dev->close(); emit uploadComplete(this); } } bool TyTRadio::download() { emit downloadStarted(); logDebug() << "Download of " << codeplug().image(0).numElements() << " elements."; // Check every segment in the codeplug size_t totb = 0; for (int n=0; nread(0, (b0+b)*BSIZE, codeplug().data((b0+b)*BSIZE), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot download codeplug."; return false; } emit downloadProgress(float(bcount*100)/totb); } } return true; } bool TyTRadio::upload() { emit uploadStarted(); // Check every segment in the codeplug if (! codeplug().isAligned(BSIZE)) { errMsg(_errorStack) << "Cannot upload codeplug: Codeplug is not aligned with blocksize " << BSIZE << "."; return false; } size_t totb = codeplug().memSize(); size_t bcount = 0; // If codeplug gets updated, download codeplug from device first: if (_codeplugFlags.updateCodeplug()) { for (int n=0; nread(0, (b0+b)*BSIZE, codeplug().data((b0+b)*BSIZE), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot upload codeplug."; return false; } emit uploadProgress(float(bcount*50)/totb); } } } // Encode config into codeplug logDebug() << "Encode codeplug."; codeplug().encode(_config, _codeplugFlags); // then erase memory for (int i=0; ierase(codeplug().image(0).element(i).address(), codeplug().image(0).element(i).memSize(), nullptr, nullptr, _errorStack); logDebug() << "Upload " << codeplug().image(0).numElements() << " elements."; // then, upload modified codeplug bcount = 0; for (int n=0; nwrite(0, (b0+b)*BSIZE, codeplug().data((b0+b)*BSIZE), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot upload codeplug."; return false; } emit uploadProgress(50+float(bcount*50)/totb); } } return true; } bool TyTRadio::uploadCallsigns() { emit uploadStarted(); logDebug() << "Check alignment."; // Check alignment in the codeplug if (! callsignDB()->isAligned(BSIZE)) { errMsg(_errorStack) << "Cannot upload callsign db: Callsign DB is not aligned with blocksize " << BSIZE << "."; return false; } // then erase memory logDebug() << "Erase memory section for call-sign DB."; _dev->erase(callsignDB()->image(0).element(0).address(), callsignDB()->image(0).element(0).memSize(), [](unsigned percent, void *ctx) { emit ((TyTRadio *)ctx)->uploadProgress(percent/2); }, this, _errorStack); logDebug() << "Upload " << callsignDB()->image(0).numElements() << " elements."; // Total amount of data to transfer size_t totb = callsignDB()->memSize(); // Upload callsign DB unsigned addr = callsignDB()->image(0).element(0).address(); unsigned size = callsignDB()->image(0).element(0).memSize(); unsigned b0 = addr/BSIZE, nb = size/BSIZE; for (size_t b=0, bcount=0; bwrite(0, (b0+b)*BSIZE, callsignDB()->data((b0+b)*BSIZE), BSIZE, _errorStack)) { errMsg(_errorStack) << "Cannot upload codeplug."; return false; } emit uploadProgress(50+float(bcount*50)/totb); } return true; } ================================================ FILE: lib/tyt_radio.hh ================================================ /** @defgroup tyt TYT/Retevis Radios * Abstract classes for TYT and Retevis radios. * * @ingroup dsc */ #ifndef TYT_RADIO_HH #define TYT_RADIO_HH #include "radio.hh" #include "tyt_interface.hh" /** Implements an USB interface to TYT & Retevis radios. * * @ingroup tyt */ class TyTRadio: public Radio { Q_OBJECT public: /** Do not construct this class directly, rather use @c Radio::detect. */ explicit TyTRadio(TyTInterface *device=nullptr, QObject *parent=nullptr); virtual ~TyTRadio(); public slots: /** Starts the download of the codeplug and derives the generic configuration from it. */ bool startDownload(const TransferFlags &flags, const ErrorStack &err=ErrorStack()); /** Derives the device-specific codeplug from the generic configuration and uploads that * codeplug to the radio. */ bool startUpload(Config *config, const Codeplug::Flags &flags = Codeplug::Flags(), const ErrorStack &err=ErrorStack()); /** Encodes the given user-database and uploads it to the device. */ bool startUploadCallsignDB(UserDatabase *db, const CallsignDB::Flags &selection=CallsignDB::Flags(), const ErrorStack &err=ErrorStack()); bool startUploadSatelliteConfig(SatelliteDatabase *db, const TransferFlags &flags, const ErrorStack &err); protected: /** Thread main routine, performs all blocking IO operations for codeplug up- and download. */ void run(); private: virtual bool download(); virtual bool upload(); virtual bool uploadCallsigns(); protected: /** The interface to the radio. */ TyTInterface *_dev; /** Holds the flags to control assembly and upload of code-plugs. */ Codeplug::Flags _codeplugFlags; /** The generic configuration. */ Config *_config; /** A weak reference to the user-database. */ UserDatabase *_userDB; }; #endif // UV390_HH ================================================ FILE: lib/usbdevice.cc ================================================ #include "usbdevice.hh" #include #include #include #include "logger.hh" #include "radioinfo.hh" #include "anytone_interface.hh" #include "radioddity_interface.hh" #include "opengd77_interface.hh" #include "tyt_interface.hh" #include "dr1801uv_interface.hh" #include "c7000device.hh" /* ********************************************************************************************* * * Implementation of USBDeviceHandle * ********************************************************************************************* */ USBDeviceHandle::USBDeviceHandle() : bus(0xff), device(0xff) { // pass... } USBDeviceHandle::USBDeviceHandle(uint8_t busno, uint8_t deviceno, uint32_t locid) : bus(busno), device(deviceno), locationId(locid) { // pass... } bool USBDeviceHandle::operator==(const USBDeviceHandle &other) { return (bus == other.bus) && (device == other.device); } /* ********************************************************************************************* * * Implementation of USBDeviceInfo * ********************************************************************************************* */ USBDeviceInfo::USBDeviceInfo() : _class(Class::None), _vid(0), _pid(0) { // pass... } USBDeviceInfo::USBDeviceInfo(Class cls, uint16_t vid, uint16_t pid, bool save) : _class(cls), _vid(vid), _pid(pid), _save(save) { // pass... } USBDeviceInfo::USBDeviceInfo(const USBDeviceInfo &other) : _class(other._class), _vid(other._vid), _pid(other._pid), _save(other._save) { // pass... } USBDeviceInfo & USBDeviceInfo::operator =(const USBDeviceInfo &other) { _class = other._class; _vid = other._vid; _pid = other._pid; _save = other._save; return *this; } bool USBDeviceInfo::operator ==(const USBDeviceInfo &other) const { // Class must match, VID/PID only need to match, if both are != 0. return (other._class == _class) && ((! other.hasVendorID()) || (! hasVendorID()) || (other._vid == _vid)) && ((! other.hasProductID()) || (! hasProductID()) || (other._pid == _pid)); } bool USBDeviceInfo::operator !=(const USBDeviceInfo &other) const { return !(*this == other); } USBDeviceInfo::~USBDeviceInfo() { // pass... } bool USBDeviceInfo::isValid() const { return Class::None != _class; } USBDeviceInfo::Class USBDeviceInfo::interfaceClass() const { return _class; } bool USBDeviceInfo::hasVendorID() const { return 0 != vendorId(); } uint16_t USBDeviceInfo::vendorId() const { return _vid; } bool USBDeviceInfo::hasProductID() const { return 0 != productId(); } uint16_t USBDeviceInfo::productId() const { return _pid; } bool USBDeviceInfo::isSave() const { return _save; } QString USBDeviceInfo::description() const { QString res; QTextStream stream(&res); switch (_class) { case Class::None: stream << "Invalid"; break; case Class::Serial: stream << "USB-serial interface " << QString::number(_vid,16) << ":" << QString::number(_pid,16); break; case Class::DFU: stream << "USB device in DFU mode " << QString::number(_vid,16) << ":" << QString::number(_pid,16); break; case Class::HID: stream << "HID " << QString::number(_vid,16) << ":" << QString::number(_pid,16); break; case Class::C7K: stream << "C7000 " << QString::number(_vid,16) << ":" << QString::number(_pid,16); break; } return res; } QString USBDeviceInfo::longDescription() const { QStringList radios; foreach (RadioInfo radio, RadioInfo::allRadios(*this, true)) { radios.append(QString("%1 %2").arg(radio.manufacturer(), radio.name())); } // This should not happen if (radios.isEmpty()) return QString("Unknown interface."); return QString("Possibly interfacing %1").arg(radios.join(", ")); } /* ********************************************************************************************* * * Implementation of USBDeviceDescriptor * ********************************************************************************************* */ USBDeviceDescriptor::USBDeviceDescriptor() : USBDeviceInfo(), _device() { // pass... } USBDeviceDescriptor::USBDeviceDescriptor(const USBDeviceInfo &info, const QString &device) : USBDeviceInfo(info), _device(device) { // pass... } USBDeviceDescriptor::USBDeviceDescriptor(const USBDeviceInfo &info, const USBDeviceHandle &device) : USBDeviceInfo(info), _device(QVariant::fromValue(device)) { // pass... } USBDeviceDescriptor::USBDeviceDescriptor(const USBDeviceDescriptor &other) : USBDeviceInfo(other), _device(other._device) { // pass... } USBDeviceDescriptor & USBDeviceDescriptor::operator =(const USBDeviceDescriptor &other) { USBDeviceInfo::operator =(other); _device = other._device; return *this; } bool USBDeviceDescriptor::isValid() const { if (! USBDeviceInfo::isValid()) return false; // dispatch by device class switch (_class) { case Class::None: return false; case Class::Serial: return validSerial(); case Class::DFU: case Class::HID: case Class::C7K: return validRawUSB(); } return false; } bool USBDeviceDescriptor::validRawUSB() const { int error, num; libusb_context *ctx; if (0 > (error = libusb_init(&ctx))) { logError() << "Libusb init failed (" << error << "): " << libusb_strerror((enum libusb_error) error) << "."; return false; } libusb_device **lst; if (0 == (num = libusb_get_device_list(ctx, &lst))) { logDebug() << "No USB devices found at all."; // unref devices and free list libusb_free_device_list(lst, 1); libusb_exit(ctx); return false; } USBDeviceHandle addr = _device.value(); logDebug() << "Search for a device matching VID:PID " << QString::number(_vid, 16) << ":" << QString::number(_pid, 16) << " at bus " << addr.bus << ", device " << addr.device << "."; bool found = false; for (int i=0; (i(); return QString("USB device in DFU mode: bus %1, device %2").arg(addr.bus).arg(addr.device); } else if (USBDeviceInfo::Class::HID == _class) { USBDeviceHandle addr = _device.value(); return QString("USB HID: bus %1, device %2").arg(addr.bus).arg(addr.device); } else if (USBDeviceInfo::Class::C7K == _class) { USBDeviceHandle addr = _device.value(); return QString("USB C7000 HT: bus %1, device %2").arg(addr.bus).arg(addr.device); } return "Invalid"; } const QVariant & USBDeviceDescriptor::device() const { return _device; } QString USBDeviceDescriptor::deviceHandle() const { switch (_class) { case Class::None: break; case Class::DFU: case Class::HID: case Class::C7K: return QString("%1:%2").arg(_device.value().bus) .arg(_device.value().device); case Class::Serial: return _device.toString(); } return "[invalid]"; } QList USBDeviceDescriptor::detect(bool saveOnly) { QList res; res.append(AnytoneGD32Interface::detect(saveOnly)); res.append(AnytoneSTM32Interface::detect(saveOnly)); res.append(OpenGD77Interface::detect(saveOnly)); res.append(RadioddityInterface::detect(saveOnly)); res.append(TyTInterface::detect(saveOnly)); res.append(DR1801UVInterface::detect(saveOnly)); res.append(C7000Device::detect(saveOnly)); return res; } ================================================ FILE: lib/usbdevice.hh ================================================ /** @defgroup detect Device detection and enumeration. * This module collects classes and functions to discover and select interfaces to connected radios. * * With an increasing number of supported devices, the issue arises, that auto detection of * radios may fail or may even harmful. Some manufacturers simply use generic USB-serial chips * within the cable to talk to them over USB. These chips may also be used in other devices that may * react harmful to qdmrs attempts to identify them. Moreover, the assumption of the first detected * device with a specific VID:PID combination may not be valid. To this end, some means of * discovering possible radios and selecting a specific one by the user are needed. Also some radios * do not identify themselves before any action is performed (i.e., reading, writing the codeplug * or callsign db). Hence, for some devices, the user must specify the type of the radio. The latter * concerns the Kydera CDR-300UV, Retevis RT73 and similar devices. * * This module specifies the classes and functions to discover possible radios and to address each * one uniquely. They allow for an implementation of a semi-automatic device detection. That is, * for the majority of radios, if a single matching VID:PID combination is found, it can be assumed * that this device is a radio and it can then be identified by sending commands to it. Some radios, * however, like the Kydera CDR-300UV, cannot be identified before the actual codeplug read or * write operation. They simply do not provide a command for identification. * * For a save semi-automatic detection the following steps are performed: * @dotfile autodetect.dot "Semi-automatic radio detection" * * -# Search for all USB devices with known VID:PID combinations. This can be done using the * @c USBDeviceDescriptor::detect method. It returns a list of matching device descriptors * found. If only one device is found, one may continue with that one if it is safe to assume * that the device detected is a DMR radio. The latter is not true for radios using generic * USB CDC-ACM chips, as other serial devices may be connected. If several devices are found or * it is not save to assume a DMR radio, the user must select a device. * -# Once the USB device is selected, one needs to identify the connected radio. Unfortunately, * not all radios can be identified easily by simply sending a command to it. To this end, * one first needs to check if the selected device is identifiable. That is, if the protocol * provides commands to identify the connected radio. The @c USBDeviceInfo provides this * information. If a device is not identifiable, the user must specify the specific connected * radio. This can be done by obtaining all known radios matching the selected USB device * (VID:PID) by calling RadioInfo::allRadios, passing the @c USBDeviceDescriptor. * * @section detectExample A example for AnyTone devices * This example tries to detect an AnyTone device and reads the binary codeplug from it. Once the * codeplug is read, it is decoded into its generic device independent representation (@c Config). * * @code * #include "libdmrconf/usbdevice.hh" * #include "libdmrconf/anytone_radio.hh" * * int main(void) * { * // First, search matching devices (only AnyTones) * // to find all supported devices, call @c USBDescriptor::detect(); * QList devices = AnytoneInterface::detect(); * if (1 != devices.count()) { * // Either none or more than one device found... * return -1; * } * * // A place to put error messages * ErrorStack err; * // Dedetect the specific radio and get radio descriptor. * // To detect any radio based on the selected descriptor, call @c Radio::detect(). * Radio *radio = AnytoneRadio::detect(devices.first(), RadioInfo(), err); * if (nullptr == radio) { * // There went something wrong, check err. * return -1; * } * * // Read codeplug from device blocking. * if (! radio->startDownload(true, err)) { * // Some read error, check err. * delete radio; * return -1; * } * * // Decode codeplug into generic representation * Config genericCodeplug; * if (! radio->codeplug().decode(&genericCodeplug, err)) { * // Some decoding error, check err. * delete radio; * return -1; * } * * // Do whatever you like with the codeplug. * * return 0; * } * @endcode * * @ingroup rif */ #ifndef USBDEVICE_HH #define USBDEVICE_HH #include #include /** Combines the USB bus and device number, to address a USB device uniquely. * * @ingroup detect */ struct USBDeviceHandle { uint8_t bus; ///< Holds the bus number. uint8_t device; ///< Holds the device address. uint32_t locationId; ///< On MacOS, holds the location ID. /** Empty constructor. */ USBDeviceHandle(); /** Constructor from bus and device number. */ USBDeviceHandle(uint8_t busno, uint8_t deviceno, uint32_t locid=0); /** Compares only wrt bus and device number. */ bool operator==(const USBDeviceHandle &other); }; Q_DECLARE_METATYPE(USBDeviceHandle) /** Generic information about a possible radio interface. * * This class combines the USB vendor, product ID and some meta information about the interface. * In particular if it is safe to access the device without user ineraction and if the protocol * implements means for identifying the specific radio. * * @ingroup detect */ class USBDeviceInfo { public: /** Possible interface types. */ enum class Class { None, ///< Class for invalid interface info. Serial, ///< Serial port interface class. DFU, ///< DFU interface class. HID, ///< HID (human-interface device) interface class. C7K ///< Raw USB access to C7000 devices. }; public: /** Empty constructor. */ USBDeviceInfo(); /** Constructor from class, VID and PID. */ USBDeviceInfo(Class cls, uint16_t vid, uint16_t pid, bool save=true); /** Destructor. */ virtual ~USBDeviceInfo(); /** Copy constructor. */ USBDeviceInfo(const USBDeviceInfo &other); /** Assignment. */ USBDeviceInfo &operator =(const USBDeviceInfo &other); /** Comparison. */ bool operator ==(const USBDeviceInfo &other) const; /** Comparison. */ bool operator !=(const USBDeviceInfo &other) const; /** Returns @c true if the interface info is valid. */ bool isValid() const; /** Returns the interface class. */ Class interfaceClass() const; /** Returns @c true, if a vendor ID is set. */ bool hasVendorID() const; /** Returns the vendor ID or 0 if not set. */ uint16_t vendorId() const; /** Returns @c true, if a product ID is set. */ bool hasProductID() const; /** Returns the product ID or 0 if not set. */ uint16_t productId() const; /** Returns a brief human readable description of the interface. */ QString description() const; /** Returns a more extensive human readable description of the interface. */ QString longDescription() const; /** Returns @c true if it is safe to send commands to this device without user approval. * This is true for radios which use somewhat unique VID:PIDs. Radios with generic USB-serial * chips are not save, as other devices may use the same chip and sending data to these devices * may be harmful. */ bool isSave() const; protected: /** The class of the interface. */ Class _class; /** The USB vid. */ uint16_t _vid; /** The USB pid. */ uint16_t _pid; /** If @c true, it is safe to send commands to the device without user approval. */ bool _save; }; /** Base class for all radio interface descriptors representing a unique interface to a * connected radio. * * This class extends the @c USBDeviceInfo by some information to identify a USB uniquely. This is * either the bus and device number or the path to the serial port. * * @ingroup detect */ class USBDeviceDescriptor: public USBDeviceInfo { protected: /** Hidden constructor from info and path string. */ USBDeviceDescriptor(const USBDeviceInfo &info, const QString &device); /** Hidden constructor from info and USB device address. */ USBDeviceDescriptor(const USBDeviceInfo &info, const USBDeviceHandle &device); public: /** Empty constructor. */ USBDeviceDescriptor(); /** Copy constructor. */ USBDeviceDescriptor(const USBDeviceDescriptor &other); /** Assignment */ USBDeviceDescriptor &operator =(const USBDeviceDescriptor &other); /** Returns @c true if the descriptor is still valid. That is, if the described device is still * connected. */ bool isValid() const; /** Returns a human readable description of the device. */ QString description() const; /** Returns the device information identifying the interface uniquely. */ const QVariant &device() const; /** Returns a unique string representation of the device information. */ QString deviceHandle() const; public: /** Searches for all connected radios (may contain false positives). */ static QList detect(bool saveOnly=true); protected: /** Checks a serial port. */ bool validSerial() const; /** Checks a raw USB device. */ bool validRawUSB() const; protected: /** Holds some information to identify the radio interface uniquely. */ QVariant _device; }; namespace std { template <> struct hash { // seed is optional inline size_t operator()(const USBDeviceInfo &key, size_t seed = 0) const { return qHash((unsigned)key.interfaceClass(), qHash(key.vendorId(), qHash(key.productId(), qHash(key.isSave(), seed)))); } }; } #endif // USBDEVICE_HH ================================================ FILE: lib/usbserial.cc ================================================ #include "usbserial.hh" #include "logger.hh" #include #include #include /* ******************************************************************************************** * * Implementation of USBSerial::Info * ******************************************************************************************** */ USBSerial::Descriptor::Descriptor(uint16_t vid, uint16_t pid, const QString &device, bool isSave) : USBDeviceDescriptor(USBDeviceInfo(Class::Serial, vid, pid, isSave), device) { // pass... } /* ******************************************************************************************** * * Implementation of USBSerial * ******************************************************************************************** */ USBSerial::USBSerial(const USBDeviceDescriptor &descriptor, BaudRate rate, const ErrorStack &err, QObject *parent) : QSerialPort(parent), RadioInterface() { if (USBDeviceInfo::Class::Serial != descriptor.interfaceClass()) { errMsg(err) << "Cannot open serial port for a non-serial descriptor: " << descriptor.description(); } logDebug() << "Try to open " << descriptor.description() << "."; QSerialPortInfo port(descriptor.device().toString()); this->setPort(port); if (! setParity(QSerialPort::NoParity)) { logWarn() << "Cannot set parity of the serial port to none."; } if (! setStopBits(QSerialPort::OneStop)) { logWarn() << "Cannot set stop bit."; } if (! setBaudRate(rate)) { logWarn() << "Cannot set speed to " << rate << " baud."; } if (! setFlowControl(QSerialPort::HardwareControl)) { logWarn() << "Cannot enable hardware flow control."; } if (! this->open(QIODevice::ReadWrite)) { #ifdef Q_OS_UNIX QFileInfo portFileInfo(port.systemLocation()); if (portFileInfo.exists() && ((! portFileInfo.isReadable()) || (! portFileInfo.isWritable()))) { QString owner = QString(portFileInfo.permission(QFileDevice::ReadOwner) ? "r" : "-") + (portFileInfo.permission(QFile::WriteOwner) ? "w" : "-") + (portFileInfo.permission(QFile::ExeOwner) ? "x" : "-"); QString group = QString(portFileInfo.permission(QFileDevice::ReadGroup) ? "r" : "-") + (portFileInfo.permission(QFile::WriteGroup) ? "w" : "-") + (portFileInfo.permission(QFile::ExeGroup) ? "x" : "-"); QString other = QString(portFileInfo.permission(QFileDevice::ReadOther) ? "r" : "-") + (portFileInfo.permission(QFile::WriteOther) ? "w" : "-") + (portFileInfo.permission(QFile::ExeOther) ? "x" : "-"); errMsg(err) << "Insufficient rights to read or write '" << port.systemLocation() << "' (" << port.description() << "): " << portFileInfo.owner() << ": " << owner << ", " << portFileInfo.group() << ": " << group << ", other: " << other << "."; if (portFileInfo.permission(QFile::ReadGroup | QFile::WriteGroup)) errMsg(err) << "A membership in the group " << portFileInfo.group() << " would grant access."; } #endif errMsg(err) << "Cannot open serial port '" << port.portName() << "': " << this->errorString() << "."; return; } logDebug() << "Opened serial port " << this->portName() << " with " << this->baudRate() << "baud."; connect(this, SIGNAL(aboutToClose()), this, SLOT(onClose())); connect(this, SIGNAL(errorOccurred(QSerialPort::SerialPortError)), this, SLOT(onError(QSerialPort::SerialPortError))); connect(this, SIGNAL(dataTerminalReadyChanged(bool)), this, SLOT(signalingChanged())); connect(this, SIGNAL(requestToSendChanged(bool)), this, SLOT(signalingChanged())); } USBSerial::~USBSerial() { if (isOpen()) close(); } bool USBSerial::isOpen() const { return QSerialPort::isOpen(); } void USBSerial::close() { if (isOpen()) QSerialPort::close(); } void USBSerial::onError(QSerialPort::SerialPortError err) { logError() << "Serial port error: (" << err << ") " << errorString() << "."; } void USBSerial::onClose() { logDebug() << "Serial port will close now."; } void USBSerial::signalingChanged() { logDebug() << "Pinout signals changed to " << formatPinoutSignals() << "."; } QList USBSerial::detect(uint16_t vid, uint16_t pid, bool isSave) { QList interfaces; // Find matching serial port by VID/PID. logDebug() << "Search for serial port with matching VID:PID " << QString::number(vid, 16) << ":" << QString::number(pid, 16) << "."; QList ports = QSerialPortInfo::availablePorts(); foreach (QSerialPortInfo port, ports) { if (port.hasProductIdentifier() && (pid == port.productIdentifier()) && port.hasVendorIdentifier() && (vid == port.vendorIdentifier())) { interfaces.append(Descriptor(vid, pid, port.portName(), isSave)); logDebug() << "Found " << port.portName() << " (USB " << QString::number(vid, 16) << ":" << QString::number(pid, 16) << ")."; } } return interfaces; } QList USBSerial::detect() { QList interfaces; logDebug() << "Search for serial ports."; QList ports = QSerialPortInfo::availablePorts(); foreach (QSerialPortInfo port, ports) { if (port.hasProductIdentifier() && port.hasVendorIdentifier()) { interfaces.append(Descriptor(port.vendorIdentifier(), port.productIdentifier(), port.portName(), false)); logDebug() << "Found " << port.portName() << " (USB " << QString::number(port.vendorIdentifier(), 16) << ":" << QString::number(port.productIdentifier(), 16) << ")."; } } return interfaces; } QString USBSerial::formatPinoutSignals() { if (QSerialPort::NoSignal == pinoutSignals()) return "None"; QStringList res; if (QSerialPort::DataTerminalReadySignal & pinoutSignals()) res.append("Data Terminal Ready"); if (QSerialPort::DataCarrierDetectSignal & pinoutSignals()) res.append("Data Carrier Detect"); if (QSerialPort::DataSetReadySignal & pinoutSignals()) res.append("Data Set Ready"); if (QSerialPort::RingIndicatorSignal & pinoutSignals()) res.append("Ring Indicator"); if (QSerialPort::RequestToSendSignal & pinoutSignals()) res.append("Request To Send"); if (QSerialPort::ClearToSendSignal & pinoutSignals()) res.append("Clear To Send"); if (QSerialPort::SecondaryTransmittedDataSignal & pinoutSignals()) res.append("Secondary Transmitted Data"); if (QSerialPort::SecondaryReceivedDataSignal & pinoutSignals()) res.append("Secondary Received Data"); return res.join(", "); } ================================================ FILE: lib/usbserial.hh ================================================ #ifndef USBSERIAL_HH #define USBSERIAL_HH #include #include #include "radiointerface.hh" #include "errorstack.hh" /** Implements a serial connection to a radio via USB. * * The correct serial port is selected by the given VID and PID to the constructor. * * @ingroup rif */ class USBSerial : public QSerialPort, public RadioInterface { Q_OBJECT public: /** Specialization of radio interface info for serial ports. */ class Descriptor: public USBDeviceDescriptor { public: /** Constructor from VID, PID and device path. */ Descriptor(uint16_t vid, uint16_t pid, const QString &device, bool isSave=false); }; protected: /** Constructs an opens new serial interface to the devices identified by the given vendor and * product IDs. * @param descriptor Specifies the device to open. * @param rate Specifies the transferrate in baud. * @param err The error stack, messages are put onto. * @param parent Specifies the parent object. */ explicit USBSerial(const USBDeviceDescriptor &descriptor, QSerialPort::BaudRate rate=QSerialPort::Baud115200, const ErrorStack &err=ErrorStack(), QObject *parent=nullptr); public: /** Destructor. */ virtual ~USBSerial(); /** If @c true, the device has been found and is open. */ bool isOpen() const override; /** Closes the interface to the device. */ void close() override; public: /** Searches for all USB serial ports with the specified VID/PID. */ static QList detect(uint16_t vid, uint16_t pid, bool isSave=true); /** Searches for all USB serial ports */ static QList detect(); protected slots: /** Callback for serial interface errors. */ void onError(QSerialPort::SerialPortError error_t); /** Callback when closing interface. */ void onClose(); /** Signaling callback. */ void signalingChanged(); protected: /** Serializes the pinout signals. */ QString formatPinoutSignals(); }; #endif // USBSERIAL_HH ================================================ FILE: lib/userdatabase.cc ================================================ #include "userdatabase.hh" #include #include #include #include #include #include #include #include #include "logger.hh" #include /* ********************************************************************************************* * * Implementation of User * ********************************************************************************************* */ UserDatabase::User::User() : id(0) { // pass... } UserDatabase::User::User(const QJsonObject &obj) : id(obj.value("id").toInt()), call(obj.value("callsign").toString()), name(obj.value("fname").toString()), surname(obj.value("surname").toString()), city(obj.value("city").toString()), state(obj.value("state").toString()), country(obj.value("country").toString()), comment(obj.value("remarks").toString()) { // pass... } unsigned UserDatabase::User::distance(unsigned id) const { // Fix number of digits int a = this->id, b = id; int ad = std::ceil(std::log10(a)); int bd = std::ceil(std::log10(b)); if (ad > bd) b *= std::pow(10u, (ad-bd)); else if (bd > ad) a *= std::pow(10u, (bd-ad)); // Distance is just the difference between these two numbers // this ensures a small distance between two numbers with the same // prefix. return std::abs(a-b); } /* ********************************************************************************************* * * Implementation of UserDatabase * ********************************************************************************************* */ UserDatabase::UserDatabase(bool parallel, unsigned updatePeriodDays, QObject *parent) : QAbstractTableModel(parent), _user(), _network() { connect(&_network, SIGNAL(finished(QNetworkReply*)), this, SLOT(downloadFinished(QNetworkReply*))); if ((! exists()) || (updatePeriodDays < dbAge())) download(); else { if (parallel) { _parsing = QtConcurrent::run([this]() { QList users; this->parse(users); return users;}) .then(this, [this](const QList &users){ return this->load(users); }); } else { load(); } } } qint64 UserDatabase::count() const { return _user.size(); } bool UserDatabase::exists() const { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QFileInfo info(path + "/user.json"); return info.isFile() && info.isReadable(); } bool UserDatabase::ready() const { return ! _user.empty(); } bool UserDatabase::load() { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); return load(path+"/user.json"); } const UserDatabase::User & UserDatabase::user(int idx) const { return _user[idx]; } bool UserDatabase::load(const QString &filename) { QList users; if (! parse(filename, users)) return false; auto res = load(users); logDebug() << "Loaded user database with " << _user.size() << " entries from " << filename << "."; return res; } bool UserDatabase::load(const QList &users) { beginResetModel(); _user.clear(); emit readyChanged(false); _user.reserve(users.size()); for (auto user: users) _user.append(user); endResetModel(); if (ready()) emit readyChanged(ready()); emit loaded(); return true; } bool UserDatabase::parse(QList &users) { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); return parse(path+"/user.json", users); } bool UserDatabase::parse(const QString &filename, QList &users) { QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { QString msg = QString("Cannot open user list '%1': %2").arg(filename).arg(file.errorString()); logError() << msg; emit error(msg); return false; } QByteArray data = file.readAll(); file.close(); QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (doc.isEmpty()) { QString msg = "Failed to load user DB: " + err.errorString(); logError() << msg; emit error(msg); return false; } if (! doc.isObject()) { QString msg = "Failed to load user DB: JSON document is not an object!"; logError() << msg; emit error(msg); return false; } if (! doc.object().contains("users")) { QString msg = "Failed to load user DB: JSON object does not contain 'users' item."; logError() << msg; emit error(msg); return false; } if (! doc.object()["users"].isArray()) { QString msg = "Failed to load user DB: 'users' item is not an array."; logError() << msg; emit error(msg); return false; } users.clear(); QJsonArray array = doc.object()["users"].toArray(); users.reserve(array.size()); for (int i=0; i &ids) { if (0 == ids.count()) return; // Sort repeater w.r.t. distance to each ID std::stable_sort(_user.begin(), _user.end(), [ids](const User &a, const User &b){ QSet::const_iterator id=ids.begin(); unsigned min_a = a.distance(*id), min_b = b.distance(*id); id++; for (; id!=ids.end(); id++) { min_a = std::min(min_a, a.distance(*id)); min_b = std::min(min_b, b.distance(*id)); } return min_a < min_b; }); } void UserDatabase::download() { QUrl url("https://database.radioid.net/static/users.json"); QNetworkRequest request(url); _network.get(request); } void UserDatabase::downloadFinished(QNetworkReply *reply) { if (reply->error()) { QString msg = QString("Cannot download user database: %1").arg(reply->errorString()); logError() << msg; emit error(msg); return; } QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QFile file(path+"/user.json"); QDir directory; if ((! directory.exists(path)) && (!directory.mkpath(path))) { QString msg = QString("Cannot create path '%1'.").arg(path); logError() << msg; emit error(msg); return; } if (! file.open(QIODevice::WriteOnly)) { QString msg = QString("Cannot save user database at '%1'.").arg(path+"/user.json"); logError() << msg; emit error(msg); return; } file.write(reply->readAll()); file.flush(); file.close(); load(); reply->deleteLater(); } unsigned UserDatabase::dbAge() const { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation) + "/user.json"; QFileInfo info(path); if (! info.exists()) return -1; return info.lastModified().daysTo(QDateTime::currentDateTime()); } int UserDatabase::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return _user.size(); } int UserDatabase::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent); return 3; } QVariant UserDatabase::data(const QModelIndex &index, int role) const { if ((Qt::EditRole != role) && ((Qt::DisplayRole != role))) return QVariant(); if (index.row() >= _user.size()) return QVariant(); if (0 == index.column()) { // Call if (Qt::DisplayRole == role) { if (_user[index.row()].surname.isEmpty()) { if (_user[index.row()].name.isEmpty()) { return _user[index.row()].call; } else { return tr("%1 (%2)") .arg(_user[index.row()].call) .arg(_user[index.row()].name); } } else { return tr("%1 (%2, %3)") .arg(_user[index.row()].call) .arg(_user[index.row()].name) .arg(_user[index.row()].surname); } } else { return _user[index.row()].call; } } else if (1 == index.column()) { // ID return _user[index.row()].id; } else if (2 == index.column()) { // Country return _user[index.column()].country; } return QVariant(); } ================================================ FILE: lib/userdatabase.hh ================================================ #ifndef USERDATABASE_HH #define USERDATABASE_HH #include #include #include #include #include #include #include #include #include /** Auto-updating DMR user database. * * This class represents the complete DMR user database. The user database gets downloaded from * https://www.radioid.net/static/users.json and kept up-to-date by re-downloading it * periodically (by default every 30 days). This user database gets used in the GUI application * to help assemble private call contacts and to assemble so-called CSV callsign databases, that * are programmable to some DMR radios to resolve the DMR ID to callsigns and names. * * @ingroup util */ class UserDatabase : public QAbstractTableModel { Q_OBJECT /** Get notification, once the database has been loaded. */ Q_PROPERTY(bool ready READ ready NOTIFY readyChanged FINAL) public: /** Represents the user information within the @c UserDatabase. */ class User { public: /** Empty constructor. */ User(); /** Constructs entry from JSON object. */ User(const QJsonObject &obj); /** Returns @c true if the entry is valid. */ inline bool isValid() const { return 0 != id; } /** Returns the "distance" between this user and the given ID. */ unsigned distance(unsigned id) const; /** The DMR ID of the user. */ unsigned id; /** The callsign of the user. */ QString call; /** The name of the user. */ QString name; /** The surname of the user. */ QString surname; /** The city of the user. */ QString city; /** The state of the user. */ QString state; /** The country of the user. */ QString country; /** Some arbitrary comment or text. */ QString comment; }; public: /** Constructs the user-database. * The constructor will download the current user database if it was not downloaded yet or * if the downloaded version is older than @c updatePeriodDays days. */ explicit UserDatabase(bool parallel, unsigned updatePeriodDays=30, QObject *parent=nullptr); /** Returns the number of users. */ qint64 count() const; /** Retruns @c true, if the user database file exists. */ bool exists() const; /** Loads all entries from the downloaded user database. */ bool load(); /** Loads all entries from the downloaded user database at the specified location. */ bool load(const QString &filename); /** Returns @c true, if the database has been loaded. */ bool ready() const; /** Sorts users with respect to the distance to the given ID. */ void sortUsers(unsigned id); /** Sorts users with respect to the minimum distance to the given IDs. */ void sortUsers(const QSet &ids); /** Returns the user with index @c idx. */ const User &user(int idx) const; /** Returns the age of the database in days. */ unsigned dbAge() const; /** Implements the QAbstractTableModel interface, returns the number of rows (number of entries). */ int rowCount(const QModelIndex &parent=QModelIndex()) const; /** Implements the QAbstractTableModel interface, returns the number of columns. */ int columnCount(const QModelIndex &parent=QModelIndex()) const; /** Implements the QAbstractTableModel interface, return the entry data. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; signals: /** Gets emitted once the call-sign database has been loaded. */ void loaded(); /** Gets emitted if the loading of the call-sign database fails. */ void error(const QString &msg); /** Gets emitted, once the database has been loaded or cleard. * @warning This event might be emitted by another thread. */ void readyChanged(bool ready); public slots: /** Starts the download of the user database. */ void download(); private slots: /** Gets called whenever the download is complete. */ void downloadFinished(QNetworkReply *reply); /** Parses cache and stores all users in given result list. */ bool parse(QList &users); /** Parses the given file and stores all users in given result list. */ bool parse(const QString &filename, QList &users); /** Loads all entries from the parsed file. */ bool load(const QList &users); private: /** Holds all users sorted by their ID. */ QVector _user; /** The network access used for downloading. */ QNetworkAccessManager _network; /** The current parallel task of parsing the database. */ QFuture _parsing; }; #endif // USERDATABASE_HH ================================================ FILE: lib/utils.cc ================================================ #include "utils.hh" #include #include #include #include #include #include // Maps APRS icon number to code-char static QVector aprsIconCodeTable{ '!','"','#','$','%','&','\'','(',')','*','+',',','-','.','/','0', '1','2','3','4','5','6', '7','8','9',':',';','<','=','>','?','@', 'A','B','C','D','E','F', 'G','H','I','J','K','L','M','N','O','P', 'Q','R','S','T','U','V', 'W','X','Y','Z','[','/',']','^','_','`', 'a','b','c','d','e','f', 'g','h','i','j','k','l','m','n','o','p', 'q','r','s','t','u','v','w','x','y','z','{','|','}','~'}; static QHash aprsIconNameTable{ {(unsigned)FMAPRSSystem::Icon::None, ""}, {(unsigned)FMAPRSSystem::Icon::PoliceStation, "Police station"}, {(unsigned)FMAPRSSystem::Icon::Digipeater, "Digipeater"}, {(unsigned)FMAPRSSystem::Icon::Phone, "Phone"}, {(unsigned)FMAPRSSystem::Icon::DXCluster, "DX cluster"}, {(unsigned)FMAPRSSystem::Icon::HFGateway, "HF gateway"}, {(unsigned)FMAPRSSystem::Icon::SmallPlane, "Small plane"}, {(unsigned)FMAPRSSystem::Icon::MobileSatelliteStation, "Mobile Satellite station"}, {(unsigned)FMAPRSSystem::Icon::WheelChair, "Wheel chair"}, {(unsigned)FMAPRSSystem::Icon::Snowmobile, "Snowmobile"}, {(unsigned)FMAPRSSystem::Icon::RedCross, "Red cross"}, {(unsigned)FMAPRSSystem::Icon::BoyScout, "Boy scout"}, {(unsigned)FMAPRSSystem::Icon::Home, "Home"}, {(unsigned)FMAPRSSystem::Icon::X, "X"}, {(unsigned)FMAPRSSystem::Icon::RedDot, "Red dot"}, {(unsigned)FMAPRSSystem::Icon::Circle0, "Circle 0"}, {(unsigned)FMAPRSSystem::Icon::Circle1, "Circle 1"}, {(unsigned)FMAPRSSystem::Icon::Circle2, "Circle 2"}, {(unsigned)FMAPRSSystem::Icon::Circle3, "Circle 3"}, {(unsigned)FMAPRSSystem::Icon::Circle4, "Circle 4"}, {(unsigned)FMAPRSSystem::Icon::Circle5, "Circle 5"}, {(unsigned)FMAPRSSystem::Icon::Circle6, "Circle 6"}, {(unsigned)FMAPRSSystem::Icon::Circle7, "Circle 7"}, {(unsigned)FMAPRSSystem::Icon::Circle8, "Circle 8"}, {(unsigned)FMAPRSSystem::Icon::Circle9, "Circle 9"}, {(unsigned)FMAPRSSystem::Icon::Fire, "Fire"}, {(unsigned)FMAPRSSystem::Icon::Campground, "Campground"}, {(unsigned)FMAPRSSystem::Icon::Motorcycle, "Motorcycle"}, {(unsigned)FMAPRSSystem::Icon::RailEngine, "Rail engine"}, {(unsigned)FMAPRSSystem::Icon::Car, "Car"}, {(unsigned)FMAPRSSystem::Icon::FileServer, "File server"}, {(unsigned)FMAPRSSystem::Icon::HCFuture, "HC future"}, {(unsigned)FMAPRSSystem::Icon::AidStation, "Aid station"}, {(unsigned)FMAPRSSystem::Icon::BBS, "BBS"}, {(unsigned)FMAPRSSystem::Icon::Canoe, "Canoe"}, {(unsigned)FMAPRSSystem::Icon::Eyeball, "Eyeball"}, {(unsigned)FMAPRSSystem::Icon::Tractor, "Tractor"}, {(unsigned)FMAPRSSystem::Icon::GridSquare, "Grid square"}, {(unsigned)FMAPRSSystem::Icon::Hotel, "Hotel"}, {(unsigned)FMAPRSSystem::Icon::TCPIP, "TCP/IP"}, {(unsigned)FMAPRSSystem::Icon::School, "School"}, {(unsigned)FMAPRSSystem::Icon::Logon, "Logon"}, {(unsigned)FMAPRSSystem::Icon::MacOS, "MacOS"}, {(unsigned)FMAPRSSystem::Icon::NTSStation, "NTS station"}, {(unsigned)FMAPRSSystem::Icon::Balloon, "Balloon"}, {(unsigned)FMAPRSSystem::Icon::PoliceCar, "Police car"}, {(unsigned)FMAPRSSystem::Icon::TBD, "TBD"}, {(unsigned)FMAPRSSystem::Icon::RV, "RV"}, {(unsigned)FMAPRSSystem::Icon::Shuttle, "Shuttle"}, {(unsigned)FMAPRSSystem::Icon::SSTV, "SSTV"}, {(unsigned)FMAPRSSystem::Icon::Bus, "Bus"}, {(unsigned)FMAPRSSystem::Icon::ATV, "ATV"}, {(unsigned)FMAPRSSystem::Icon::WXService, "WX service"}, {(unsigned)FMAPRSSystem::Icon::Helo, "Helo"}, {(unsigned)FMAPRSSystem::Icon::Yacht, "Yacht"}, {(unsigned)FMAPRSSystem::Icon::Windows, "Windows"}, {(unsigned)FMAPRSSystem::Icon::Jogger, "Jogger"}, {(unsigned)FMAPRSSystem::Icon::Triangle, "Triangle"}, {(unsigned)FMAPRSSystem::Icon::PBBS, "PBBS"}, {(unsigned)FMAPRSSystem::Icon::LargePlane, "Large plane"}, {(unsigned)FMAPRSSystem::Icon::WXStation, "WX station"}, {(unsigned)FMAPRSSystem::Icon::DishAntenna, "Dish antenna"}, {(unsigned)FMAPRSSystem::Icon::Ambulance, "Ambulance"}, {(unsigned)FMAPRSSystem::Icon::Bike, "Bike"}, {(unsigned)FMAPRSSystem::Icon::ICP, "ICP"}, {(unsigned)FMAPRSSystem::Icon::FireStation, "Fire station"}, {(unsigned)FMAPRSSystem::Icon::Horse, "Horse"}, {(unsigned)FMAPRSSystem::Icon::FireTruck, "Fire truck"}, {(unsigned)FMAPRSSystem::Icon::Glider, "Glider"}, {(unsigned)FMAPRSSystem::Icon::Hospital, "Hospital"}, {(unsigned)FMAPRSSystem::Icon::IOTA, "IOTA"}, {(unsigned)FMAPRSSystem::Icon::Jeep, "Jeep"}, {(unsigned)FMAPRSSystem::Icon::SmallTruck, "Small truck"}, {(unsigned)FMAPRSSystem::Icon::Laptop, "Laptop"}, {(unsigned)FMAPRSSystem::Icon::MicE, "Mic-E"}, {(unsigned)FMAPRSSystem::Icon::Node, "Node"}, {(unsigned)FMAPRSSystem::Icon::EOC, "EOC"}, {(unsigned)FMAPRSSystem::Icon::Rover, "Rover"}, {(unsigned)FMAPRSSystem::Icon::Grid, "Grid"}, {(unsigned)FMAPRSSystem::Icon::Antenna, "Antenna"}, {(unsigned)FMAPRSSystem::Icon::PowerBoat, "Power boat"}, {(unsigned)FMAPRSSystem::Icon::TruckStop, "Truck stop"}, {(unsigned)FMAPRSSystem::Icon::TruckLarge, "Truck large"}, {(unsigned)FMAPRSSystem::Icon::Van, "Van"}, {(unsigned)FMAPRSSystem::Icon::Water, "Water"}, {(unsigned)FMAPRSSystem::Icon::XAPRS, "XAPRS"}, {(unsigned)FMAPRSSystem::Icon::Yagi, "Yagi"}, {(unsigned)FMAPRSSystem::Icon::Shelter, "Shelter"}}; QString decode_unicode(const uint16_t *data, size_t size, uint16_t fill) { QString res; res.reserve(size); for (size_t i=0; (i> 28) & 0xf) + 1e1 * ((bcd >> 24) & 0xf) + 1.0 * ((bcd >> 20) & 0xf) + 1e-1 * ((bcd >> 16) & 0xf) + 1e-2 * ((bcd >> 12) & 0xf) + 1e-3 * ((bcd >> 8) & 0xf) + 1e-4 * ((bcd >> 4) & 0xf) + 1e-5 * ((bcd >> 0) & 0xf); return freq; } uint32_t encode_frequency(double freq) { uint32_t hz = std::round(freq * 1e6); uint32_t a = (hz / 100000000) % 10; uint32_t b = (hz / 10000000) % 10; uint32_t c = (hz / 1000000) % 10; uint32_t d = (hz / 100000) % 10; uint32_t e = (hz / 10000) % 10; uint32_t f = (hz / 1000) % 10; uint32_t g = (hz / 100) % 10; uint32_t h = (hz / 10) % 10; return (a << 28) + (b << 24) + (c << 20) + (d << 16) + (e << 12) + (f << 8) + (g << 4) + h; } uint32_t decode_dmr_id_bin(const uint8_t *id) { return ( (id[0]) | (id[1] << 8) | (id[2] << 16) ); } void encode_dmr_id_bin(uint8_t *id, uint32_t no) { id[0] = no; id[1] = no >> 8; id[2] = no >> 16; } uint32_t decode_dmr_id_bcd(const uint8_t *id) { return ((id[0] >> 4) * 10000000 + (id[0] & 15) * 1000000 + (id[1] >> 4) * 100000 + (id[1] & 15) * 10000 + (id[2] >> 4) * 1000 + (id[2] & 15) * 100 + (id[3] >> 4) * 10 + (id[3] & 15)); } uint32_t decode_dmr_id_bcd_le(const uint8_t *id) { return ((id[3] >> 4) * 10000000 + (id[3] & 15) * 1000000 + (id[2] >> 4) * 100000 + (id[2] & 15) * 10000 + (id[1] >> 4) * 1000 + (id[1] & 15) * 100 + (id[0] >> 4) * 10 + (id[0] & 15)); } void encode_dmr_id_bcd(uint8_t *id, uint32_t no) { id[0] = ((no / 10000000) << 4) | ((no / 1000000) % 10); id[1] = ((no / 100000 % 10) << 4) | ((no / 10000) % 10); id[2] = ((no / 1000 % 10) << 4) | ((no / 100) % 10); id[3] = ((no / 10 % 10) << 4) | (no % 10); } void encode_dmr_id_bcd_le(uint8_t *id, uint32_t no) { id[3] = ((no / 10000000) << 4) | ((no / 1000000) % 10); id[2] = ((no / 100000 % 10) << 4) | ((no / 10000) % 10); id[1] = ((no / 1000 % 10) << 4) | ((no / 100) % 10); id[0] = ((no / 10 % 10) << 4) | (no % 10); } QVector bin_dtmf_tab = {'0','1','2','3','4','5','6','7','8','9','A','B','C','D','*','#'}; QString decode_dtmf_bin(const uint8_t *num, int size, uint8_t fill) { Q_UNUSED(fill); QString number; for (int i=0; (i=tmp.size()) continue; int idx = bin_dtmf_tab.indexOf(number.at(i).toLatin1()); if (idx<0) continue; num[i] = idx; } return true; } QString decode_dtmf_bcd_be(const uint8_t *num, int digits) { QString number; for (int i=0; i>4)&0xf) : ((num[i/2])&0xf); number.append(bin_dtmf_tab[d]); } return number; } bool encode_dtmf_bcd_be(const QString &number, uint8_t *num, int size, uint8_t fill) { memset(num, fill, size); QString tmp = number.simplified().toUpper(); for (int i=0; i> 14; unsigned a = (data >> 12) & 3; unsigned b = (data >> 8) & 15; unsigned c = (data >> 4) & 15; unsigned d = data & 15; switch (tag) { case 2: // DCS Normal return SelectiveCall(100*b+10*c+1*d, false); case 3: // DCS Inverted return SelectiveCall(100*b+10*c+1*d, true); default: break; } // CTCSS return SelectiveCall(100.0*a+10.0*b+1.0*c+0.1*d); } uint16_t encode_ctcss_tone_table(const SelectiveCall &code) { unsigned tag=0xff, a=0xf, b=0xf, c=0xf, d=0xf; // Disabled if (code.isInvalid()) return 0xffff; if (code.isCTCSS()) { // CTCSS tone tag = 0; unsigned val = code.Hz() * 10.0 + 0.5; a = val / 1000; b = (val / 100) % 10; c = (val / 10) % 10; d = val % 10; } else if (code.isDCS()) { // DCS normal if (code.isInverted()) tag = 3; else tag = 2; unsigned val = code.octalCode(); a = 0; b = (val / 100) % 10; c = (val / 10) % 10; d = val % 10; } return (a << 12) | (b << 8) | (c << 4) | d | (tag << 14); } bool validDMRNumber(const QString &text) { return QRegularExpression("^[0-9]+$").match(text).hasMatch(); } bool validDTMFNumber(const QString &text) { return QRegularExpression("^[0-9a-dA-D\\*#]+$").match(text).hasMatch(); } QString aprsicon2config(FMAPRSSystem::Icon icon) { if ((FMAPRSSystem::Icon::None == icon) || (! aprsIconCodeTable.contains(unsigned(icon)))) return "-"; return QString("\"%1\"").arg(aprsIconNameTable.value((unsigned)icon)); } QString aprsicon2name(FMAPRSSystem::Icon icon) { if ((FMAPRSSystem::Icon::None == icon) || (! aprsIconCodeTable.contains(unsigned(icon)))) return ""; return aprsIconNameTable.value((unsigned)icon); } FMAPRSSystem::Icon name2aprsicon(const QString &name) { if (name.isEmpty()) return FMAPRSSystem::Icon::None; FMAPRSSystem::Icon icon = FMAPRSSystem::Icon::None; int best = levDist(name, ""); QHash::const_iterator item=aprsIconNameTable.constBegin(); for(; item != aprsIconNameTable.constEnd(); item++) { int dist = levDist(name, item.value()); if (dist < best) { icon = (FMAPRSSystem::Icon)item.key(); best = dist; } } return icon; } char aprsicon2iconcode(FMAPRSSystem::Icon icon) { unsigned num = unsigned(FMAPRSSystem::ICON_MASK & unsigned(icon)); if (num >= unsigned(aprsIconCodeTable.size())) return '"'; return aprsIconCodeTable[num]; } char aprsicon2tablecode(FMAPRSSystem::Icon icon) { unsigned tab = (FMAPRSSystem::TABLE_MASK & unsigned(icon)); switch (tab) { case FMAPRSSystem::SECONDARY_TABLE: return '\\'; case FMAPRSSystem::PRIMARY_TABLE: return '/'; } return '/'; } FMAPRSSystem::Icon code2aprsicon(char table, char icon) { unsigned num = (FMAPRSSystem::ICON_MASK & unsigned(FMAPRSSystem::Icon::None)); if (aprsIconCodeTable.contains(icon)) num = aprsIconCodeTable.indexOf(icon); if ('/' == table) num = num | FMAPRSSystem::PRIMARY_TABLE; else if ('\\' == table) num = num | FMAPRSSystem::PRIMARY_TABLE; return FMAPRSSystem::Icon(num); } int levDist(const QString &source, const QString &target, Qt::CaseSensitivity cs) { // Mostly stolen from https://qgis.org/api/2.14/qgsstringutils_8cpp_source.html if (0 == QString::compare(source,target, cs)) { return 0; } const int sourceCount = source.size(); const int targetCount = target.size(); if (source.isEmpty()) return targetCount; if (target.isEmpty()) return sourceCount; if (sourceCount > targetCount) return levDist(target, source, cs); QVector column; column.fill(0, targetCount + 1); QVector previousColumn; previousColumn.reserve(targetCount + 1); for (int i = 0; i < targetCount + 1; i++) previousColumn.append(i); for (int i = 0; i < sourceCount; i++) { column[0] = i + 1; for (int j = 0; j < targetCount; j++) { column[j + 1] = std::min( { 1 + column.at(j), 1 + previousColumn.at(1 + j), previousColumn.at(j) + (QString::compare(source.at(i),target.at(j), cs) ? 1 : 0) }); } column.swap(previousColumn); } return previousColumn.at(targetCount); } uint32_t align_size(uint32_t size, uint32_t block) { if (0 == (size % block)) return size; return (size + (block - (size%block))); } uint32_t align_addr(uint32_t addr, uint32_t block) { if (0 == (addr % block)) return addr; return (addr - (addr%block)); } QGeoCoordinate loc2deg(const QString &loc) { double lon = 0, lat = 0, dlon = 20, dlat = 10; if (2 > loc.size()) return QGeoCoordinate(); QChar l = loc[0].toUpper(); QChar c = loc[1].toUpper(); lon += double(int(l.toLatin1())-'A')*dlon; lat += double(int(c.toLatin1())-'A')*dlat; if (4 > loc.size()) { lon = lon - 180; lat = lat - 90; // Offset places coordinate in the middle of the square return QGeoCoordinate(lat+dlat/2, lon+dlon/2); } dlon /= 10; dlat /= 10; l = loc[2].toUpper(); c = loc[3].toUpper(); lon += double(int(l.toLatin1())-'0')*dlon; lat += double(int(c.toLatin1())-'0')*dlat; if (6 > loc.size()){ lon = lon - 180; lat = lat - 90; // Offset places coordinate in the middle of the square return QGeoCoordinate(lat+dlat/2, lon+dlon/2,0); } dlon /= 24; dlat /= 24; l = loc[4].toUpper(); c = loc[5].toUpper(); lon += double(int(l.toLatin1())-'A')*dlon; lat += double(int(c.toLatin1())-'A')*dlat; if (8 > loc.size()) { lon = lon - 180; lat = lat - 90; // Offset places coordinate in the middle of the square return QGeoCoordinate(lat+dlat/2, lon+dlon/2); } dlon /= 10; dlat /= 10; l = loc[6].toUpper(); c = loc[7].toUpper(); lon += double(int(l.toLatin1())-'0')*dlon; lat += double(int(c.toLatin1())-'0')*dlat; if (10 > loc.size()) { lon = lon - 180; lat = lat - 90; // Offset places coordinate in the middle of the square return QGeoCoordinate(lat+dlat/2, lon+dlon/2); } dlon /= 24; dlat /= 24; l = loc[8].toUpper(); c = loc[9].toUpper(); lon += double(int(l.toLatin1())-'A')*dlon; lat += double(int(c.toLatin1())-'A')*dlat; lon = lon - 180; lat = lat - 90; // Offset places coordinate in the middle of the square return QGeoCoordinate(lat+dlat/2, lon+dlon/2); } QString deg2loc(const QGeoCoordinate &coor, unsigned int size) { if (!coor.isValid()) return QString(); QString loc; double lon = (coor.longitude()+180)/360; double lat = (coor.latitude()+90)/180; size += (size % 2); if (2 > size) return loc; lon *= 18; lat *= 18; char l = lon; lon -= l; char c = lat; lat -= c; loc.append(QChar::fromLatin1(l+'A')); loc.append(QChar::fromLatin1(c+'A')); if (4 > size) return loc; lon *= 10; lat *= 10; l = lon; lon -= l; c = lat; lat -= c; loc.append(QChar::fromLatin1(l+'0')); loc.append(QChar::fromLatin1(c+'0')); if (6 > size) return loc; lon *= 24; lat *= 24; l = lon; lon -= l; c = lat; lat -= c; loc.append(QChar::fromLatin1(l+'a')); loc.append(QChar::fromLatin1(c+'a')); if (8 > size) return loc; lon *= 10; lat *= 10; l = lon; lon -= l; c = lat; lat -= c; loc.append(QChar::fromLatin1(l+'0')); loc.append(QChar::fromLatin1(c+'0')); if (10 > size) return loc; lon *= 24; lat *= 24; l = lon; //lon -= l; c = lat; //lat -= c; loc.append(QChar::fromLatin1(l+'a')); loc.append(QChar::fromLatin1(c+'a')); return loc; } ================================================ FILE: lib/utils.hh ================================================ /** @defgroup util Utility functions and classes. * This module collects all utility functions and classes. That is, functions to encode some * data and also classes implementing the DFU file format. */ #ifndef UTILS_HH #define UTILS_HH #include #include #include "signaling.hh" #include "gpssystem.hh" #include /** Decodes the unicode string stored in @c data of size @c size. The @c fill code also defines the * end-of-string symbol. * @returns The decoded string. */ QString decode_unicode(const uint16_t *data, size_t size, uint16_t fill=0x0000); /** Encodes the string @c text as unicode and stores the result into @c data using up-to @c size * 16bit words in data. The @c fill word specifies the fill and end-of-string word. */ void encode_unicode(uint16_t *data, const QString &text, size_t size, uint16_t fill=0x0000); /** Decodes the ascii string in @c data into a @c QString of up-to size length. The @c fill word * specifies the fill and end-of-string word. */ QString decode_ascii(const uint8_t *data, size_t size, uint16_t fill=0x00); /** Encodes the given QString @c text of up-to size length as ASCII into @c data using the * @c fill word as fill and end-of-string word. */ void encode_ascii(uint8_t *data, const QString &text, size_t size, uint16_t fill=0x00); /** Decodes the UTF-8 string in @c data into a @c QString of up-to size length. The @c fill word * specifies the fill and end-of-string word. */ QString decode_utf8(const uint8_t *data, size_t size, uint16_t fill=0x00); /** Encodes the given QString @c text of up-to size length as UTF-8 into @c data using the * @c fill word as fill and end-of-string word. */ void encode_utf8(uint8_t *data, const QString &text, size_t size, uint16_t fill=0x00); /** Decodes an 8 digit BCD encoded frequency (in MHz). */ double decode_frequency(uint32_t bcd); /** Eecodes an 8 digit BCD encoded frequency (in MHz). */ uint32_t encode_frequency(double freq); /** Decodes binary (24bit) encoded DMR ID. */ uint32_t decode_dmr_id_bin(const uint8_t *id); /** Encodes binary (24bit) encoded DMR ID. */ void encode_dmr_id_bin(uint8_t *id, uint32_t num); /** Decodes bcd (32bit) encoded DMR ID, little endian. */ uint32_t decode_dmr_id_bcd(const uint8_t *id); /** Decodes bcd (32bit) encoded DMR ID, big endian. */ uint32_t decode_dmr_id_bcd_le(const uint8_t *id); /** Encodes bcd (32bit) encoded DMR ID, little endian. */ void encode_dmr_id_bcd(uint8_t *id, uint32_t num); /** Encodes bcd (32bit) encoded DMR ID, big endian. */ void encode_dmr_id_bcd_le(uint8_t *id, uint32_t num); QString decode_dtmf_bin(const uint8_t *num, int size=16, uint8_t fill=0xff); bool encode_dtmf_bin(const QString &number, uint8_t *num, int size=16, uint8_t fill=0xff); QString decode_dtmf_bcd_be(const uint8_t *num, int digits); bool encode_dtmf_bcd_be(const QString &number, uint8_t *num, int size, uint8_t fill); /** Decodes the CTCSS tone or DCS code to @c Signaling::Code. * @todo TyT specific, move to TyT codeplug. */ SelectiveCall decode_ctcss_tone_table(uint16_t data); /** Encodes the CTCSS tone or DCS code from @c Signaling::Code. * @todo TyT specific, move to TyT codeplug. */ uint16_t encode_ctcss_tone_table(const SelectiveCall &code); /** Validates a DMR ID number. */ bool validDMRNumber(const QString &text); /** Validates a DTMF number. */ bool validDTMFNumber(const QString &text); QString aprsicon2config(FMAPRSSystem::Icon icon); QString aprsicon2name(FMAPRSSystem::Icon icon); FMAPRSSystem::Icon name2aprsicon(const QString &name); char aprsicon2iconcode(FMAPRSSystem::Icon icon); char aprsicon2tablecode(FMAPRSSystem::Icon icon); FMAPRSSystem::Icon code2aprsicon(char table, char icon); /** Implements the Levenshtein distance between two strings. * That is, the number of edits (insert, delete or replace operations) needed to turn source * into target. */ int levDist(const QString &source, const QString &target, Qt::CaseSensitivity cs=Qt::CaseInsensitive); /** Increases the given size to be aligned with the given block size. */ uint32_t align_size(uint32_t size, uint32_t block); /** Decreases the address to be aligned with the given block size. */ uint32_t align_addr(uint32_t addr, uint32_t block); QGeoCoordinate loc2deg(const QString &loc); QString deg2loc(const QGeoCoordinate &coor, unsigned int size=6); #endif // UTILS_HH ================================================ FILE: lib/uv390.cc ================================================ #include "uv390.hh" #include "uv390_limits.hh" RadioLimits *UV390::_limits = nullptr; UV390::UV390(TyTInterface *device, QObject *parent) : TyTRadio(device, parent), _name("TyT MD-UV390") { // pass... } UV390::~UV390() { // pass... } const QString & UV390::name() const { return _name; } const RadioLimits & UV390::limits() const { if (nullptr == _limits) _limits = new UV390Limits(); return *_limits; } const Codeplug & UV390::codeplug() const { return _codeplug; } Codeplug & UV390::codeplug() { return _codeplug; } const CallsignDB * UV390::callsignDB() const { return &_callsigndb; } CallsignDB * UV390::callsignDB() { return &_callsigndb; } RadioInfo UV390::defaultRadioInfo() { return RadioInfo( RadioInfo::UV390, "uv390", "MD-UV390", "TyT", {TyTInterface::interfaceInfo()}, QList{ RadioInfo(RadioInfo::UV380, "MD-UV380", "TyT", {TyTInterface::interfaceInfo()}), RadioInfo(RadioInfo::RT3S, "RT3S", "Retevis", {TyTInterface::interfaceInfo()}) }); } ================================================ FILE: lib/uv390.hh ================================================ /** @defgroup uv390 TYT MD-UV390, Retevis RT3S * Device specific classes for TyT MD-UV390 and Retevis RT3S. * * \image html uv390.jpg "MD-UV390" width=200px * \image latex uv390.jpg "MD-UV390" width=200px * * The TYT MD-UV390 and the identical Retevis RT3S are decent VHF/UHF FM and DMR handheld radios. * Both radios are available with and without an GPS option. @c libdmrconf will support that * feature. Non-GPS variants of that radio will simply ignore any GPS system settings. * * These radios support up to 3000 channels organized in 250 zones. Each zone may hold up to 64 * channels for each VFO (64 for VFO A and 64 for VFO B). There are also up to 250 scanlists * holding up to 31(?) channels each. * * The radio can hold up to 3000 contacts (DMR contacts) and 250 RX group lists as well as up to 50 * pre-programmed messages. Depending on the firmware programmed on the radio, it may also hold a * callsign database of up to 100000 entries. This can be used to resolve amlost any DMR ID assigned * (at the time of this writing, there are about 140k IDs assigned) to name and callsign. * * @ingroup tyt */ #ifndef UV390_HH #define UV390_HH #include "tyt_radio.hh" #include "uv390_codeplug.hh" #include "uv390_callsigndb.hh" class RadioLimits; /** Implements an USB interface to the TYT MD-UV390 & Retevis RT3S VHF/UHF 5W DMR (Tier I&II) radios. * * The TYT MD-UV390 and Retevis RT3S radios use the TyT typical DFU-style communication protocol * to read and write codeplugs onto the radio (see @c TyTRadio). * * @ingroup uv390 */ class UV390 : public TyTRadio { Q_OBJECT public: /** Constructor. * @param device Specifies the DFU device to use for communication with the device. * @param parent The QObject parent. */ UV390(TyTInterface *device=nullptr, QObject *parent=nullptr); /** Desturctor. */ virtual ~UV390(); const QString &name() const; const RadioLimits &limits() const; const Codeplug &codeplug() const; Codeplug &codeplug(); const CallsignDB *callsignDB() const; CallsignDB *callsignDB(); /** Returns the default radio information. The actual instance may have different properties * due to variants of the same radio. */ static RadioInfo defaultRadioInfo(); private: /** Holds the name of the device. */ QString _name; /** The codeplug object. */ UV390Codeplug _codeplug; /** The callsign DB object. */ UV390CallsignDB _callsigndb; private: /** Holds the singleton instance of the radio limits. */ static RadioLimits *_limits; }; #endif // MD2017_HH ================================================ FILE: lib/uv390_callsigndb.cc ================================================ #include "uv390_callsigndb.hh" UV390CallsignDB::UV390CallsignDB(QObject *parent) : TyTCallsignDB(parent) { image(0).setName("TYT MD-UV390 Callsign database."); } UV390CallsignDB::~UV390CallsignDB() { // pass... } ================================================ FILE: lib/uv390_callsigndb.hh ================================================ #ifndef UV390_CALLSIGNDB_HH #define UV390_CALLSIGNDB_HH #include "tyt_callsigndb.hh" /** Device specific implementation of the call-sign DB for the TyT MD-UV390. * * In fact this callsign DB is identical to the generic @c TyTCallsignDB. * * @ingroup uv390 */ class UV390CallsignDB : public TyTCallsignDB { Q_OBJECT public: /** Constructor. */ explicit UV390CallsignDB(QObject *parent=nullptr); /** Destructor. */ virtual ~UV390CallsignDB(); }; #endif // UV390_CALLSIGNDB_HH ================================================ FILE: lib/uv390_codeplug.cc ================================================ #include "uv390_codeplug.hh" #include "logger.hh" #include "config.hh" #include "tyt_extensions.hh" #include #define NUM_CONTACTS 10000 #define ADDR_CONTACTS 0x140000 #define CONTACT_SIZE 0x000024 #define NUM_ZONES 250 #define ADDR_ZONES 0x0149e0 #define ZONE_SIZE 0x000040 #define ADDR_ZONEEXTS 0x031000 #define ZONEEXT_SIZE 0x0000e0 #define NUM_GROUPLISTS 250 #define ADDR_GROUPLISTS 0x00ec20 #define GROUPLIST_SIZE 0x000060 #define NUM_SCANLISTS 250 #define ADDR_SCANLISTS 0x018860 #define SCANLIST_SIZE 0x000068 #define ADDR_TIMESTAMP 0x002000 #define ADDR_SETTINGS 0x002040 #define SETTINGS_SIZE 0x0000b0 #define ADDR_BOOTSETTINGS 0x02f000 #define ADDR_MENUSETTINGS 0x0020f0 #define ADDR_BUTTONSETTINGS 0x002100 #define ADDR_PRIVACY_KEYS 0x0059c0 #define NUM_GPSSYSTEMS 16 #define ADDR_GPSSYSTEMS 0x03ec40 #define GPSSYSTEM_SIZE 0x000010 #define ADDR_EMERGENCY_SETTINGS 0x005a50 #define NUM_EMERGENCY_SYSTEMS 32 #define ADDR_EMERGENCY_SYSTEMS 0x005a60 #define EMERGENCY_SYSTEM_SIZE 0x000028 #define ADDR_VFO_CHANNEL_A 0x02ef00 #define ADDR_VFO_CHANNEL_B 0x02ef40 /* ******************************************************************************************** * * Implementation of UV390Codeplug::ChannelElement * ******************************************************************************************** */ UV390Codeplug::ChannelElement::ChannelElement(uint8_t *ptr, size_t size) : TyTCodeplug::ChannelElement(ptr, size) { // pass... } UV390Codeplug::ChannelElement::ChannelElement(uint8_t *ptr) : TyTCodeplug::ChannelElement(ptr, size()) { // pass... } void UV390Codeplug::ChannelElement::clear() { TyTCodeplug::ChannelElement::clear(); enableTalkaround(false); clearBit(5,0); setInCallCriteria(TyTChannelExtension::InCallCriterion::Always); setTurnOffFreq(TyTChannelExtension::KillTone::Off); setSquelch(Level::fromValue(1)); setPower(Channel::Power::High); enableAllowInterrupt(true); enableDualCapacityDirectMode(false); enableDCDMLeader(true); enablePrivacySwitch(false); } bool UV390Codeplug::ChannelElement::talkaround() const { return getBit(Offset::talkaround()); } void UV390Codeplug::ChannelElement::enableTalkaround(bool enable) { setBit(Offset::talkaround(), enable); } bool UV390Codeplug::ChannelElement::privacySwitch() const { return getBit(Offset::privacySwitch()); } void UV390Codeplug::ChannelElement::enablePrivacySwitch(bool enable) { setBit(Offset::privacySwitch(), enable); } void UV390Codeplug::ChannelElement::setPrivacyType(PrivacyType type) { enablePrivacySwitch(PrivacyType::PRIV_NONE != type); TyTCodeplug::ChannelElement::setPrivacyType(type); } TyTChannelExtension::InCallCriterion UV390Codeplug::ChannelElement::inCallCriteria() const { return TyTChannelExtension::InCallCriterion(getUInt2(Offset::inCallCriteria())); } void UV390Codeplug::ChannelElement::setInCallCriteria(TyTChannelExtension::InCallCriterion crit) { setUInt2(Offset::inCallCriteria(), uint8_t(crit)); } TyTChannelExtension::KillTone UV390Codeplug::ChannelElement::turnOffFreq() const { return TyTChannelExtension::KillTone(getUInt2(Offset::turnOffFreq())); } void UV390Codeplug::ChannelElement::setTurnOffFreq(TyTChannelExtension::KillTone freq) { setUInt2(Offset::turnOffFreq(), uint8_t(freq)); } Level UV390Codeplug::ChannelElement::squelch() const { return Level::fromValue(getUInt8(Offset::squelch())); } void UV390Codeplug::ChannelElement::setSquelch(Level value) { if (value.isInvalid()) setUInt8(Offset::squelch(), value.value()); } Channel::Power UV390Codeplug::ChannelElement::power() const { switch (getUInt2(Offset::power())) { case 0: return Channel::Power::Low; case 2: return Channel::Power::Mid; case 3: return Channel::Power::High; default: break; } return Channel::Power::Low; } void UV390Codeplug::ChannelElement::setPower(Channel::Power pwr) { switch (pwr) { case Channel::Power::Min: case Channel::Power::Low: setUInt2(Offset::power(), 0); break; case Channel::Power::Mid: setUInt2(Offset::power(), 2); break; case Channel::Power::High: case Channel::Power::Max: setUInt2(Offset::power(), 3); } } bool UV390Codeplug::ChannelElement::allowInterrupt() const { return !getBit(Offset::allowInterrupt()); } void UV390Codeplug::ChannelElement::enableAllowInterrupt(bool enable) { setBit(Offset::allowInterrupt(), !enable); } bool UV390Codeplug::ChannelElement::dualCapacityDirectMode() const { return !getBit(Offset::dualCapacityDirectMode()); } void UV390Codeplug::ChannelElement::enableDualCapacityDirectMode(bool enable) { setBit(Offset::dualCapacityDirectMode(), !enable); } bool UV390Codeplug::ChannelElement::dcdmLeader() const { return !getBit(Offset::dcdmLeader()); } void UV390Codeplug::ChannelElement::enableDCDMLeader(bool enable) { setBit(Offset::dcdmLeader(), !enable); } Channel * UV390Codeplug::ChannelElement::toChannelObj(const ErrorStack &err) const { if (! isValid()) { errMsg(err) << "Cannot decode invalid channel."; return nullptr; } Channel *ch = TyTCodeplug::ChannelElement::toChannelObj(err); if (nullptr == ch) { errMsg(err) << "Cannot decode base TyT channel element."; return nullptr; } // decode squelch setting if (auto ach = ch->as()) { ach->setSquelch(squelch()); } else if (auto dch = ch->as()) { dch->extended()->enableDCDM(dualCapacityDirectMode()); } // Common settings ch->setPower(power()); // assemble extension if (TyTChannelExtension *ex = ch->tytChannelExtension()) { ex->setKillTone(turnOffFreq()); ex->setInCallCriterion(inCallCriteria()); ex->enableAllowInterrupt(allowInterrupt()); ex->enableDCDMLeader(dcdmLeader()); if (ch->is()) ex->setDMRSquelch(squelch()); } return ch; } void UV390Codeplug::ChannelElement::fromChannelObj(const Channel *chan, Context &ctx) { TyTCodeplug::ChannelElement::fromChannelObj(chan, ctx); // encode power setting if (chan->defaultPower()) setPower(ctx.config()->settings()->power()); else setPower(chan->power()); // By default, set to global default value. setSquelch(ctx.config()->settings()->audio()->squelch()); if (auto achan = chan->as()) { if (! achan->defaultSquelch()) setSquelch(achan->squelch()); } else if (auto dch = chan->as()) { enableDualCapacityDirectMode(dch->extended()->dcdm()); } // apply extensions if (TyTChannelExtension *ex = chan->tytChannelExtension()) { setTurnOffFreq(ex->killTone()); setInCallCriteria(ex->inCallCriterion()); enableAllowInterrupt(ex->allowInterrupt()); enableDCDMLeader(ex->dcdmLeader()); if (chan->is()) setSquelch(ex->dmrSquelch()); } } /* ******************************************************************************************** * * Implementation of UV390Codeplug::VFOChannelElement * ******************************************************************************************** */ UV390Codeplug::VFOChannelElement::VFOChannelElement(uint8_t *ptr, size_t size) : ChannelElement(ptr, size) { // pass... } UV390Codeplug::VFOChannelElement::VFOChannelElement(uint8_t *ptr) : ChannelElement(ptr, size()) { // pass... } UV390Codeplug::VFOChannelElement::~VFOChannelElement() { // pass... } QString UV390Codeplug::VFOChannelElement::name() const { return ""; } void UV390Codeplug::VFOChannelElement::setName(const QString &txt) { Q_UNUSED(txt) // pass... } unsigned UV390Codeplug::VFOChannelElement::stepSize() const { return (getUInt8(32)+1)*2500; } void UV390Codeplug::VFOChannelElement::setStepSize(unsigned ss_Hz) { ss_Hz = std::min(50000U, std::max(ss_Hz, 2500U)); setUInt8(32, ss_Hz/2500-1); setUInt8(33, 0xff); } /* ******************************************************************************************** * * Implementation of UV390Codeplug::GeneralSettingsElement * ******************************************************************************************** */ UV390Codeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr, size_t size) : DM1701Codeplug::GeneralSettingsElement(ptr, size) { // pass... } UV390Codeplug::GeneralSettingsElement::GeneralSettingsElement(uint8_t *ptr) : DM1701Codeplug::GeneralSettingsElement(ptr, SETTINGS_SIZE) { // pass... } void UV390Codeplug::GeneralSettingsElement::clear() { TyTCodeplug::GeneralSettingsElement::clear(); setTransmitMode(DESIGNED_AND_HAND_CH); enableChannelVoiceAnnounce(false); setBit(0x43,0, 1); setBit(0x43,1, 1); setUInt4(0x43,3, 0xf); setBit(0x6b, 2, 1); setUInt8(0x91, 0xff); setUInt2(0x92, 0, 0x03); enablePublicZone(true); setUInt5(0x92, 3, 0x1f); setUInt8(0x93, 0xff); setAdditionalDMRId(0, 1); setUInt8(0x97, 0); setAdditionalDMRId(1, 2); setUInt8(0x9b, 0); setAdditionalDMRId(2, 3); setUInt8(0x9f, 0); setUInt3(0xa0, 0, 0b111); setMICLevel(Level::fromValue(2)); enableEditRadioID(true); setBit(0xa0, 7, true); memset(_data+0xa1, 0xff, 15); } UV390Codeplug::GeneralSettingsElement::TransmitMode UV390Codeplug::GeneralSettingsElement::transmitMode() const { return TransmitMode(getUInt2(0x40,6)); } void UV390Codeplug::GeneralSettingsElement::setTransmitMode(TransmitMode mode) { setUInt2(0x40,6, mode); } bool UV390Codeplug::GeneralSettingsElement::channelVoiceAnnounce() const { return getBit(0x42,1); } void UV390Codeplug::GeneralSettingsElement::enableChannelVoiceAnnounce(bool enable) { setBit(0x42,1, enable); } bool UV390Codeplug::GeneralSettingsElement::keypadTones() const { return getBit(0x42,5); } void UV390Codeplug::GeneralSettingsElement::enableKeypadTones(bool enable) { setBit(0x42,5, enable); } bool UV390Codeplug::GeneralSettingsElement::publicZone() const { return getBit(0x92, 2); } void UV390Codeplug::GeneralSettingsElement::enablePublicZone(bool enable) { setBit(0x92, 2, enable); } uint32_t UV390Codeplug::GeneralSettingsElement::additionalDMRId(unsigned n) const { return getUInt24_le(0x94+4*n); } void UV390Codeplug::GeneralSettingsElement::setAdditionalDMRId(unsigned n, uint32_t id) { setUInt24_le(0x94+4*n, id); } Level UV390Codeplug::GeneralSettingsElement::micLevel() const { return Level::fromValue(getUInt3(Offset::micGain()), Limit::micGain()); } void UV390Codeplug::GeneralSettingsElement::setMICLevel(Level level) { setUInt3(Offset::micGain(), level.mapTo(Limit::micGain())); } bool UV390Codeplug::GeneralSettingsElement::editRadioID() const { return !getBit(0xa0, 6); } void UV390Codeplug::GeneralSettingsElement::enableEditRadioID(bool enable) { setBit(0xa0,6, !enable); } bool UV390Codeplug::GeneralSettingsElement::fromConfig(const Config *config) { if (! DM1701Codeplug::GeneralSettingsElement::fromConfig(config)) return false; setTimeZone(QTimeZone::systemTimeZone()); setMICLevel(config->settings()->audio()->micGain()); enableChannelVoiceAnnounce(config->settings()->audio()->speechSynthesisEnabled()); return true; } bool UV390Codeplug::GeneralSettingsElement::updateConfig(Config *config) { if (! DM1701Codeplug::GeneralSettingsElement::updateConfig(config)) return false; config->settings()->audio()->setMicGain(micLevel()); config->settings()->audio()->enableSpeechSynthesis(channelVoiceAnnounce()); return true; } /* ******************************************************************************************** * * Implementation of UV390Codeplug::BootSettingsElement * ******************************************************************************************** */ UV390Codeplug::BootSettingsElement::BootSettingsElement(uint8_t *ptr, size_t size) : Codeplug::Element(ptr, size) { // pass... } UV390Codeplug::BootSettingsElement::BootSettingsElement(uint8_t *ptr) : Codeplug::Element(ptr, 0x0010) { // pass... } UV390Codeplug::BootSettingsElement::~BootSettingsElement() { // pass... } void UV390Codeplug::BootSettingsElement::clear() { setUInt24_le(0, 0xffffff); setZoneIndex(1); setChannelIndexA(1); setUInt8(0x05, 0xff); setChannelIndexB(1); setUInt16_le(0x07, 0xffff); setUInt16_le(0x09, 0x0001); setUInt8(0x0b, 0xff); setUInt32_le(0x0c, 0xffffffff); } unsigned UV390Codeplug::BootSettingsElement::zoneIndex() const { return getUInt8(0x03); } void UV390Codeplug::BootSettingsElement::setZoneIndex(unsigned idx) { setUInt8(0x03, idx); } unsigned UV390Codeplug::BootSettingsElement::channelIndexA() const { return getUInt8(0x04); } void UV390Codeplug::BootSettingsElement::setChannelIndexA(unsigned idx) { setUInt8(0x04, idx); } unsigned UV390Codeplug::BootSettingsElement::channelIndexB() const { return getUInt8(0x06); } void UV390Codeplug::BootSettingsElement::setChannelIndexB(unsigned idx) { setUInt8(0x06, idx); } /* ******************************************************************************************** * * Implementation of UV390Codeplug::MenuSettingsElement * ******************************************************************************************** */ UV390Codeplug::MenuSettingsElement::MenuSettingsElement(uint8_t *ptr, size_t size) : TyTCodeplug::MenuSettingsElement(ptr, size) { // pass... } UV390Codeplug::MenuSettingsElement::MenuSettingsElement(uint8_t *ptr) : TyTCodeplug::MenuSettingsElement(ptr) { // pass... } void UV390Codeplug::MenuSettingsElement::clear() { TyTCodeplug::MenuSettingsElement::clear(); enableGPSSettings(true); enableRecording(true); enableGroupCallMatch(true); enablePrivateCallMatch(true); enableMenuHangtimeItem(true); enableTXMode(true); enableZoneSettings(true); enableNewZone(true); enableEditZone(true); enableNewScanList(true); setBit(0x05, 0, true); setBit(0x05, 1, true); enableGroupCallMatch(true); enablePrivateCallMatch(true); enableMenuHangtimeItem(true); enableTXMode(true); enableZoneSettings(true); enableNewZone(true); enableEditZone(true); enableNewScanList(true); } bool UV390Codeplug::MenuSettingsElement::gpsSettings() const { return !getBit(0x04, 3); } void UV390Codeplug::MenuSettingsElement::enableGPSSettings(bool enable) { setBit(0x04, 3, !enable); } bool UV390Codeplug::MenuSettingsElement::recording() const { return getBit(0x04, 5); } void UV390Codeplug::MenuSettingsElement::enableRecording(bool enable) { setBit(0x04, 5, enable); } bool UV390Codeplug::MenuSettingsElement::groupCallMatch() const { return getBit(0x05, 2); } void UV390Codeplug::MenuSettingsElement::enableGroupCallMatch(bool enable) { setBit(0x05, 2, enable); } bool UV390Codeplug::MenuSettingsElement::privateCallMatch() const { return getBit(0x05, 3); } void UV390Codeplug::MenuSettingsElement::enablePrivateCallMatch(bool enable) { setBit(0x05, 3, enable); } bool UV390Codeplug::MenuSettingsElement::menuHangtimeItem() const { return getBit(0x05, 4); } void UV390Codeplug::MenuSettingsElement::enableMenuHangtimeItem(bool enable) { setBit(0x05, 4, enable); } bool UV390Codeplug::MenuSettingsElement::txMode() const { return getBit(0x05, 5); } void UV390Codeplug::MenuSettingsElement::enableTXMode(bool enable) { setBit(0x05, 5, enable); } bool UV390Codeplug::MenuSettingsElement::zoneSettings() const { return getBit(0x05, 6); } void UV390Codeplug::MenuSettingsElement::enableZoneSettings(bool enable) { setBit(0x05, 6, enable); } bool UV390Codeplug::MenuSettingsElement::newZone() const { return getBit(0x05, 7); } void UV390Codeplug::MenuSettingsElement::enableNewZone(bool enable) { setBit(0x05, 7, enable); } bool UV390Codeplug::MenuSettingsElement::editZone() const { return getBit(0x06, 0); } void UV390Codeplug::MenuSettingsElement::enableEditZone(bool enable) { setBit(0x06, 0, enable); } bool UV390Codeplug::MenuSettingsElement::newScanList() const { return getBit(0x06, 1); } void UV390Codeplug::MenuSettingsElement::enableNewScanList(bool enable) { setBit(0x06, 1, enable); } /* ******************************************************************************************** * * Implementation of UV390Codeplug * ******************************************************************************************** */ UV390Codeplug::UV390Codeplug(QObject *parent) : TyTCodeplug(parent) { addImage("TYT MD-UV390 Codeplug"); image(0).addElement(0x002000, 0x3e000); image(0).addElement(0x110000, 0x90000); // Clear entire codeplug UV390Codeplug::clear(); } UV390Codeplug::~UV390Codeplug() { // pass... } void UV390Codeplug::clear() { TyTCodeplug::clear(); UV390Codeplug::clearBootSettings(); UV390Codeplug::clearVFOSettings(); } void UV390Codeplug::clearTimestamp() { TimestampElement(data(ADDR_TIMESTAMP)).clear(); } bool UV390Codeplug::encodeTimestamp() { TimestampElement ts(data(ADDR_TIMESTAMP)); ts.setTimestamp(QDateTime::currentDateTime()); return true; } void UV390Codeplug::clearGeneralSettings() { GeneralSettingsElement(data(ADDR_SETTINGS)).clear(); } bool UV390Codeplug::encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err) return GeneralSettingsElement(data(ADDR_SETTINGS)).fromConfig(ctx.config()); } bool UV390Codeplug::decodeGeneralSettings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return GeneralSettingsElement(data(ADDR_SETTINGS)).updateConfig(ctx.config()); } void UV390Codeplug::clearChannels() { // Clear channels for (unsigned int i=0; ichannelList()->count()) { chan.fromChannelObj(ctx.config()->channelList()->channel(i), ctx); } } return true; } bool UV390Codeplug::createChannels(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; ichannelList()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid channel at index " << i << "."; return false; } } return true; } bool UV390Codeplug::linkChannels(Context &ctx, const ErrorStack &err) { for (unsigned int i=0; i(i+1), ctx, err)) { errMsg(err) << "Cannot link channel at index " << i << "."; return false; } } return true; } void UV390Codeplug::clearContacts() { // Clear contacts for (int i=0; i()) cont.fromContactObj(ctx.get(i+1)); else cont.clear(); } return true; } bool UV390Codeplug::createContacts(Context &ctx, const ErrorStack &err) { for (int i=0; icontacts()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid contact at index " << i << "."; return false; } } return true; } void UV390Codeplug::clearZones() { // Clear zones & zone extensions for (int i=0; izones()->count()) { elm.fromZoneObj(ctx.config()->zones()->zone(i), ctx); if (ctx.config()->zones()->zone(i)->B()->count() || (16 < ctx.config()->zones()->zone(i)->A()->count())) ext.fromZoneObj(ctx.config()->zones()->zone(i), ctx); } } return true; } bool UV390Codeplug::createZones(Context &ctx, const ErrorStack &err) { for (int i=0; izones()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid zone at index " << i << "."; return false; } } return true; } bool UV390Codeplug::linkZones(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link zone at index " << i << "."; return false; } ZoneExtElement zoneext(data(ADDR_ZONEEXTS + i*ZONEEXT_SIZE)); if (! zoneext.linkZoneObj(ctx.get(i+1), ctx)) { errMsg(err) << "Cannot link zone extension at index " << i << "."; return false; } } return true; } void UV390Codeplug::clearGroupLists() { for (int i=0; irxGroupLists()->count()) glist.fromGroupListObj(ctx.config()->rxGroupLists()->list(i), ctx); else glist.clear(); } return true; } bool UV390Codeplug::createGroupLists(Context &ctx, const ErrorStack &err) { for (int i=0; irxGroupLists()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid RX group list at index " << i << "."; return false; } } return true; } bool UV390Codeplug::linkGroupLists(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link group-list at index " << i << "."; return false; } } return true; } void UV390Codeplug::clearScanLists() { // Clear scan lists for (int i=0; iscanlists()->count()) scan.fromScanListObj(ctx.config()->scanlists()->scanlist(i), ctx); else scan.clear(); } return true; } bool UV390Codeplug::createScanLists(Context &ctx, const ErrorStack &err) { for (int i=0; iscanlists()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid scan list at index " << i << "."; return false; } } return true; } bool UV390Codeplug::linkScanLists(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link scan list at index " << i << "."; return false; } } return true; } void UV390Codeplug::clearPositioningSystems() { // Clear GPS systems for (int i=0; i()) { logDebug() << "Encode GPS system #" << i << " '" << ctx.get(i+1)->name() << "'."; gps.fromGPSSystemObj(ctx.get(i+1), ctx); } else { gps.clear(); } } return true; } bool UV390Codeplug::createPositioningSystems(Context &ctx, const ErrorStack &err) { for (int i=0; iposSystems()->add(obj); ctx.add(obj, i+1); } else { errMsg(err) << "Invalid GPS system at index " << i << "."; return false; } } return true; } bool UV390Codeplug::linkPositioningSystems(Context &ctx, const ErrorStack &err) { for (int i=0; i(i+1), ctx)) { errMsg(err) << "Cannot link GPS system at index " << i << "."; return false; } } return true; } void UV390Codeplug::clearButtonSettings() { ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).clear(); } bool UV390Codeplug::encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(ctx); Q_UNUSED(err) // Encode settings return ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).fromConfig(ctx.config()); } bool UV390Codeplug::decodeButtonSetttings(Context &ctx, const ErrorStack &err) { Q_UNUSED(err) return ButtonSettingsElement(data(ADDR_BUTTONSETTINGS)).updateConfig(ctx.config()); } void UV390Codeplug::clearPrivacyKeys() { EncryptionElement(data(ADDR_PRIVACY_KEYS)).clear(); } bool UV390Codeplug::encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err) { Q_UNUSED(flags); Q_UNUSED(err); // First, reset keys clearPrivacyKeys(); // Get keys EncryptionElement keys(data(ADDR_PRIVACY_KEYS)); return keys.fromCommercialExt(ctx.config()->commercialExtension(), ctx); } bool UV390Codeplug::decodePrivacyKeys(Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx.config()) // Get keys EncryptionElement keys(data(ADDR_PRIVACY_KEYS)); // Decode element if (! keys.updateCommercialExt(ctx)) { errMsg(err) << "Cannot create encryption extension."; return false; } return true; } void UV390Codeplug::clearBootSettings() { BootSettingsElement(data(ADDR_BOOTSETTINGS)).clear(); } void UV390Codeplug::clearMenuSettings() { MenuSettingsElement(data(ADDR_MENUSETTINGS)).clear(); } void UV390Codeplug::clearTextMessages() { MessageBankElement(data(Offset::messages())).clear(); } bool UV390Codeplug::encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).encode(ctx, flags, err); } bool UV390Codeplug::decodeTextMessages(Context &ctx, const ErrorStack &err) { return MessageBankElement(data(Offset::messages())).decode(ctx, err); } void UV390Codeplug::clearEmergencySystems() { EmergencySettingsElement(data(ADDR_EMERGENCY_SETTINGS)).clear(); for (int i=0; i * Start End Size Content * First segment 0x002000-0x040000 * 0x002000 0x00200c 0x0000c Timestamp see @c TyTCodeplug::TimestampElement. * 0x00200c 0x002040 0x00034 Reserved, filled with 0xff. * 0x002040 0x0020f0 0x000b0 General settings see @c GeneralSettingsElement. * 0x0020f0 0x002100 0x00010 Menu settings, see @c MenuSettingsElement. * 0x002100 0x002140 0x00040 Button config, see @c TyTCodeplug::ButtonSettingsElement. * 0x002140 0x002180 0x00040 Reserved, filled with 0xff. * 0x002180 0x0059c0 0x03840 50 Text messages @ 0x120 bytes each. * 0x0059c0 0x005a70 0x000b0 Privacy keys, see @c TyTCodeplug::EncryptionElement. * 0x005a70 0x005a80 0x00010 Emergency system settings, see @c TyTCodeplug::EmergencySettingsElement. * 0x005a80 0x005f80 0x00500 Emergency systems, see @c TyTCodeplug::EmergencySystemElement. * 0x005f80 0x00ec20 0x08ca0 Reserved, filled with 0xff. * 0x00ec20 0x0149e0 0x05dc0 250 RX Group lists @ 0x60 bytes each, see @c TyTCodeplug::GroupListElement. * 0x0149e0 0x018860 0x03e80 250 Zones @ 0x40 bytes each, see @c TyTCodeplug::ZoneElement. * 0x018860 0x01edf0 0x06590 250 Scanlists @ 0x68 bytes each, see @c TyTCodeplug::ScanListElement. * 0x01edf0 0x02ef00 0x10110 Reserved, filled with @c 0xff. * 0x02ef00 0x02ef40 0x00040 VFO A channel, see @c VFOChannelElement. * 0x02ef40 0x02ef80 0x00040 VFO B channel, see @c VFOChannelElement. * 0x02ef80 0x02f000 0x00080 Reserved, filled with @c 0xff. * 0x02f000 0x02f010 0x00010 Boot settings, see @c BootSettingsElement. * 0x02f010 0x031000 0x01ff0 Reserved, filled with @c 0xff. * 0x031000 0x03eac0 0x0dac0 250 Zone-extensions @ 0xe0 bytes each, see @c DM1701Codeplug::ZoneExtElement. * 0x03eac0 0x03ec40 0x00180 Reserved, filled with @c 0xff. * 0x03ec40 0x03ed40 0x00100 16 GPS systems @ 0x10 bytes each, see @c TyTCodeplug::GPSSystemElement. * 0x03ed40 0x040000 0x012c0 Reserved, filled with @c 0xff. * Second segment 0x110000-0x1a0000 * 0x110000 0x13ee00 0x2ee00 3000 Channels @ 0x40 bytes each, see @c ChannelElement. * 0x13ee00 0x140000 0x01200 Reserved, filled with @c 0xff. * 0x140000 0x197e40 0x57e40 10000 Contacts @ 0x24 bytes each, see @c TyTCodeplug::ContactElement. * 0x197e40 0x1a0000 0x081c0 Reserved, filled with @c 0xff. * * * @ingroup uv390 */ class UV390Codeplug : public TyTCodeplug { Q_OBJECT public: /** Extends the @c TyTCodeplug::ChannelElement for the TyT MD-UV390 and Retevis RT3S. * * Memory layout of encoded channel: * @verbinclude uv390_channel.txt */ class ChannelElement: public TyTCodeplug::ChannelElement { protected: /** Constructs a channel from the given memory. */ ChannelElement(uint8_t *ptr, size_t size); public: /** Constructs a channel from the given memory. */ explicit ChannelElement(uint8_t *ptr); /** Clears/resets the channel and therefore disables it. */ void clear() override; bool talkaround() const override; void enableTalkaround(bool enable) override; /** Returns @c true, if the privacy switch is enabled. */ virtual bool privacySwitch() const; /** Enables/disables the privacy switch. */ virtual void enablePrivacySwitch(bool enable); /** Overrides TyT basic implementation to enable privacy switch, whenever encryption * is enabled for this channel. */ void setPrivacyType(PrivacyType type) override; /** Returns the in-call criterion for this channel. */ virtual TyTChannelExtension::InCallCriterion inCallCriteria() const; /** Sets the in-call criterion for this channel. */ virtual void setInCallCriteria(TyTChannelExtension::InCallCriterion crit); /** Returns the remote turn-off/kill frequency for this channel. */ virtual TyTChannelExtension::KillTone turnOffFreq() const; /** Sets the remote turn-off/kill frequency for this channel. */ virtual void setTurnOffFreq(TyTChannelExtension::KillTone freq); /** Returns the squelch level [0-10]. */ virtual Level squelch() const; /** Sets the squelch level [0-10]. */ virtual void setSquelch(Level value); /** Returns the power of this channel. */ virtual Channel::Power power() const; /** Sets the power of this channel. */ virtual void setPower(Channel::Power pwr); /** Returns @c true if the channel allows interruption enabled. */ virtual bool allowInterrupt() const; /** Enables/disables interruption for this channel. */ virtual void enableAllowInterrupt(bool enable); /** Returns @c true if the channel has dual-capacity direct mode enabled. */ virtual bool dualCapacityDirectMode() const; /** Enables/disables dual-capacity direct mode for this channel. */ virtual void enableDualCapacityDirectMode(bool enable); /** Returns @c true if the radio acts as the leader for this DCDM channel. */ virtual bool dcdmLeader() const; /** Enables/disables this radio to be the leader for this DCDM channel. */ virtual void enableDCDMLeader(bool enable); /** Constructs a generic @c Channel object from the codeplug channel. */ virtual Channel *toChannelObj(const ErrorStack &err=ErrorStack()) const override; /** Initializes this codeplug channel from the given generic configuration. */ virtual void fromChannelObj(const Channel *c, Context &ctx) override; protected: /** Some internal offsets. */ struct Offset: public TyTCodeplug::ChannelElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr Bit privacySwitch() { return {0x0005, 2}; } static constexpr Bit inCallCriteria() { return {0x0005, 4}; } static constexpr Bit turnOffFreq() { return {0x0005, 6}; } static constexpr unsigned int squelch() { return 0x000f; } static constexpr Bit power() { return {0x001e, 0}; } static constexpr Bit allowInterrupt() { return {0x001f, 2}; } static constexpr Bit dualCapacityDirectMode() { return {0x001f, 3}; } static constexpr Bit dcdmLeader() { return {0x001f, 4}; } /// @endcond }; }; /** Implements a VFO channel for TyT radios. * This class is an extension of the normal ChannelElement that only implements the step-size * feature and encodes it where the name used to be. Thus the memory layout and size is identical * to the normal channel. */ class VFOChannelElement: public ChannelElement { protected: /** Constructor from pointer to memory. */ VFOChannelElement(uint8_t *ptr, size_t size); public: /** Constructor from pointer to memory. */ VFOChannelElement(uint8_t *ptr); /** Destructor. */ virtual ~VFOChannelElement(); QString name() const; void setName(const QString &txt); /** Returns the step-size for the VFO channel. */ virtual unsigned stepSize() const; /** Sets the step-size for the VFO channel in Hz. */ virtual void setStepSize(unsigned ss_hz); }; /** Reuse zone extension from DM1701. */ typedef DM1701Codeplug::ZoneExtElement ZoneExtElement; /** Extends the common @c TyTCodeplug::GeneralSettings to implement the MD-UV390 specific * settings. * * Memory layout of the settings (size 0x00b0 bytes): * @verbinclude uv390_settings.txt */ class GeneralSettingsElement: public DM1701Codeplug::GeneralSettingsElement { protected: /** Hidden constructor. */ GeneralSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ GeneralSettingsElement(uint8_t *ptr); void clear(); /** Defines all possible transmit modes. */ enum TransmitMode { LAST_CALL_CH = 0, LAST_CALL_AND_HAND_CH = 1, DESIGNED_CH = 2, DESIGNED_AND_HAND_CH = 3, }; /** Returns the transmit mode. */ virtual TransmitMode transmitMode() const; /** Sets the transmit mode. */ virtual void setTransmitMode(TransmitMode mode); /** Returns @c true, if the speech synthesis is enabled. */ virtual bool channelVoiceAnnounce() const; /** Enables/disables the speech synthesis. */ virtual void enableChannelVoiceAnnounce(bool enable); /** Returns @c true, if keypad tones are enabled. */ virtual bool keypadTones() const; /** Enables/disables the keypad tones. */ virtual void enableKeypadTones(bool enable); /** Returns @c true, if public zone is enabled. */ virtual bool publicZone() const; /** Enables/disables public zone. */ virtual void enablePublicZone(bool enable); /** Returns the n-th DMR id. */ virtual uint32_t additionalDMRId(unsigned n) const; /** Sets the n-th DMR id. */ virtual void setAdditionalDMRId(unsigned n, uint32_t id); /** Returns the microphone gain. */ virtual Level micLevel() const; /** Sets the microphone gain. */ virtual void setMICLevel(Level val); /** If @c true, radio ID editing is enabled. */ virtual bool editRadioID() const; /** Enable/disable radio ID editing. */ virtual void enableEditRadioID(bool enable); /** Encodes the general settings. */ virtual bool fromConfig(const Config *config); /** Updates config from general settings. */ virtual bool updateConfig(Config *config); public: /** Some limits. */ struct Limit: DM1701Codeplug::GeneralSettingsElement::Limit { /** Specifies the valid range for mic gain. */ static constexpr Range micGain() { return {0,6}; } }; protected: /** Some internal offsets. */ struct Offset: DM1701Codeplug::GeneralSettingsElement::Offset { /// @cond DO_NOT_DOCUMENT static constexpr Bit micGain() { return {0x00a0, 3}; } /// @endcond }; }; /** Represents the boot-time settings (selected zone and channels) within the UV390 code-plug. * * Memory layout of encoded boot settings: * @verbinclude uv390_bootsettings.txt */ class BootSettingsElement: public Codeplug::Element { protected: /** Hidden constructor. */ BootSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit BootSettingsElement(uint8_t *ptr); /** Destructor. */ virtual ~BootSettingsElement(); void clear(); /** Returns the boot zone index. */ virtual unsigned zoneIndex() const; /** Sets the boot zone index. */ virtual void setZoneIndex(unsigned idx); /** Returns the channel index (within zone) for VFO A. */ virtual unsigned channelIndexA() const; /** Sets the channel index (within zone) for VFO A. */ virtual void setChannelIndexA(unsigned idx); /** Returns the channel index (within zone) for VFO B. */ virtual unsigned channelIndexB() const; /** Sets the channel index (within zone) for VFO B. */ virtual void setChannelIndexB(unsigned idx); }; /** Represents the menu settings (selected zone and channels) within the UV390 code-plug. * * Memory layout of encoded boot settings: * @verbinclude uv390_menusettings.txt */ class MenuSettingsElement: public TyTCodeplug::MenuSettingsElement { protected: /** Hidden constructor. */ MenuSettingsElement(uint8_t *ptr, size_t size); public: /** Constructor. */ explicit MenuSettingsElement(uint8_t *ptr); void clear(); /** Returns @c true if GPS settings menu is enabled. */ virtual bool gpsSettings() const; /** Enables/disables GPS settings menu. */ virtual void enableGPSSettings(bool enable); /** Returns @c true if recording menu is enabled. */ virtual bool recording() const; /** Enables/disables recording menu. */ virtual void enableRecording(bool enable); /** Returns @c true if group call match menu is enabled. */ virtual bool groupCallMatch() const; /** Enables/disables group call match menu. */ virtual void enableGroupCallMatch(bool enable); /** Returns @c true if private call match menu is enabled. */ virtual bool privateCallMatch() const; /** Enables/disables private call match menu. */ virtual void enablePrivateCallMatch(bool enable); /** Returns @c true if menu hang time item is enabled. */ virtual bool menuHangtimeItem() const; /** Enables/disables menu hang time item. */ virtual void enableMenuHangtimeItem(bool enable); /** Returns @c true if TX mode menu is enabled. */ virtual bool txMode() const; /** Enables/disables TX mode menu. */ virtual void enableTXMode(bool enable); /** Returns @c true if zone settings menu is enabled. */ virtual bool zoneSettings() const; /** Enables/disables zone settings menu. */ virtual void enableZoneSettings(bool enable); /** Returns @c true if new zone menu is enabled. */ virtual bool newZone() const; /** Enables/disables new zone menu. */ virtual void enableNewZone(bool enable); /** Returns @c true if edit zone menu is enabled. */ virtual bool editZone() const; /** Enables/disables edit zone menu. */ virtual void enableEditZone(bool enable); /** Returns @c true if new scan list menu is enabled. */ virtual bool newScanList() const; /** Enables/disables new scan list menu. */ virtual void enableNewScanList(bool enable); }; public: /** Constructor. */ explicit UV390Codeplug(QObject *parent = nullptr); /** Destructor. */ virtual ~UV390Codeplug(); void clear(); public: void clearTimestamp(); bool encodeTimestamp(); void clearGeneralSettings(); bool encodeGeneralSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeGeneralSettings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearChannels(); bool encodeChannels( const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createChannels(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkChannels(Context &ctx, const ErrorStack &err=ErrorStack()); void clearContacts(); bool encodeContacts(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createContacts(Context &ctx, const ErrorStack &err=ErrorStack()); void clearZones(); bool encodeZones(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createZones(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkZones(Context &ctx, const ErrorStack &err=ErrorStack()); void clearGroupLists(); bool encodeGroupLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkGroupLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearScanLists(); bool encodeScanLists(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkScanLists(Context &ctx, const ErrorStack &err=ErrorStack()); void clearPositioningSystems(); bool encodePositioningSystems(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool createPositioningSystems(Context &ctx, const ErrorStack &err=ErrorStack()); bool linkPositioningSystems(Context &ctx, const ErrorStack &err=ErrorStack()); void clearButtonSettings(); bool encodeButtonSettings(const Flags &flags, Context &ctx, const ErrorStack &err=ErrorStack()); bool decodeButtonSetttings(Context &ctx, const ErrorStack &err=ErrorStack()); void clearPrivacyKeys(); bool encodePrivacyKeys(const Flags &flags, Context &ctx, const ErrorStack &err); bool decodePrivacyKeys(Context &ctx, const ErrorStack &err); void clearTextMessages(); bool encodeTextMessages(Context &ctx, const Flags &flags, const ErrorStack &err); bool decodeTextMessages(Context &ctx, const ErrorStack &err); /** Resets the boot setting, e.g. initial channels and zone at bootup. */ virtual void clearBootSettings(); void clearMenuSettings(); void clearEmergencySystems(); /** Clears the VFO A & B. */ virtual void clearVFOSettings(); public: /** Some limits for the codeplug. */ struct Limit: public Element::Limit { /// Number of channels. static constexpr unsigned int channels() { return 3000; } }; protected: /** Some internal offsets within the codeplug. */ struct Offset { /// @cond DO_NOT_DOCUMENT static constexpr unsigned int messages() { return 0x002180; } static constexpr unsigned int channels() { return 0x110000; } static constexpr unsigned int betweenChannels() { return ChannelElement::size(); } /// @endcond }; }; #endif // UV390CODEPLUG_HH ================================================ FILE: lib/uv390_filereader.cc ================================================ #include "uv390_filereader.hh" #include #include #define SEGMENT0_FILE_ADDR 0x00002225 #define SEGMENT0_TARGET_ADDR 0x00002000 #define SEGMENT0_SIZE 0x0003e000 #define SEGMENT1_FILE_ADDR 0x00040235 #define SEGMENT1_TARGET_ADDR 0x00110000 #define SEGMENT1_SIZE 0x00090000 bool UV390FileReader::read(const QString &filename, UV390Codeplug *codeplug, const ErrorStack &err) { // Check file properties QFileInfo info(filename); if (! info.exists()) { errMsg(err) << "Cannot open file '" << filename << "': File does not exisist."; return false; } if (852533 != info.size()) { errMsg(err) << "Cannot read codeplug file '" << filename << "': File size is not 852533 bytes."; return false; } // Open file QFile file(filename); if (! file.open(QFile::ReadOnly)) { errMsg(err) << "Cannot open file '" << filename << "': " << file.errorString() << "."; return false; } // Read file content if (! file.seek(SEGMENT0_FILE_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } char *ptr = (char *)codeplug->data(SEGMENT0_TARGET_ADDR); size_t n = SEGMENT0_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } if (! file.seek(SEGMENT1_FILE_ADDR)) { errMsg(err) << "Cannot read codeplug file '" << filename << "': Cannot seek within file: " << file.errorString() << "."; file.close(); return false; } ptr = (char *)codeplug->data(SEGMENT1_TARGET_ADDR); n = SEGMENT1_SIZE; while (0 < n) { int nread = file.read(ptr, n); if (0 > nread) { errMsg(err) << "Cannot read codeplug file '" << filename << "': " << file.errorString() << "."; file.close(); return false; } n -= nread; ptr += nread; } return true; } ================================================ FILE: lib/uv390_filereader.hh ================================================ #ifndef UV390FILEREADER_HH #define UV390FILEREADER_HH #include "uv390_codeplug.hh" /** Methods to read manufacturer codeplug files. * * The file format of the stock CPS is still pretty simple. The first part of the file consists of * a mal-formed DFU file. This contains a single image with a single element containing the * first section of the memory written to the device. The second section is then added as-is * to the end of the file. Due to the DFU header/footer, the file and memory offsets differ. * * * * * *
File Start Memory Start Size
0x002225 0x002000 0x03e000
0x040235 0x110000 0x090000
* * @ingroup uv390 */ class UV390FileReader { public: /** Reads manufacturer codeplug file into given codeplug object. * @param filename Specifies the file to read. * @param codeplug Specifies the codeplug object to store read codeplug. * @param err Error stack. * @returns @c true on success and @c false on error. */ static bool read(const QString &filename, UV390Codeplug *codeplug, const ErrorStack &err = ErrorStack()); }; #endif // UV390FILEREADER_HH ================================================ FILE: lib/uv390_limits.cc ================================================ #include "uv390_limits.hh" #include "uv390_codeplug.hh" #include "channel.hh" #include "radioid.hh" #include "contact.hh" #include "rxgrouplist.hh" #include "scanlist.hh" #include "zone.hh" #include "gpssystem.hh" #include "roamingzone.hh" UV390Limits::UV390Limits(QObject *parent) : RadioLimits(false, parent) { // Define limits for call-sign DB _hasCallSignDB = true; _callSignDBImplemented = true; _numCallSignDBEntries = 122197; // Define limits for satellite config _hasSatelliteConfig = false; _satelliteConfigImplemented = false; _numSatellites = 0; add("settings", new RadioLimitItem { { "introLine1", new RadioLimitString(-1, 10, RadioLimitString::Unicode) }, { "introLine2", new RadioLimitString(-1, 10, RadioLimitString::Unicode) }, { "micLevel", new RadioLimitLevel({1, 10}, false) }, { "speech", new RadioLimitIgnoredBool() }, { "power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High) } }, { "squelch", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "vox", new RadioLimitIgnored(RadioLimitIssue::Silent) }, { "tot", new RadioLimitInterval() }, { "boot", new RadioLimitItem { {"passwordEnabled", new RadioLimitIgnored(RadioLimitIssue::Silent) }, {"password", new RadioLimitPin(UV390Codeplug::GeneralSettingsElement::Limit::bootPasswordLength(), RadioLimitIssue::Critical) } } } /// @todo check default radio ID. } ); /* Define limits for radio IDs. */ add("radioIDs", new RadioLimitList{ { DMRRadioID::staticMetaObject, 1, 1, new RadioLimitObject { {"name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, {"number", new RadioLimitDMRId(RadioLimitIssue::Severity::Critical)} } } } ); /* Define limits for contacts. */ add("contacts", new RadioLimitList{ { DMRContact::staticMetaObject, 1, 10000, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "ring", new RadioLimitBool() }, { "type", new RadioLimitEnum { (unsigned) DMRContact::PrivateCall, (unsigned) DMRContact::GroupCall, (unsigned) DMRContact::AllCall }}, { "number", new RadioLimitDMRId(RadioLimitIssue::Severity::Hint) } } }, { DTMFContact::staticMetaObject, -1, -1, new RadioLimitIgnored() } } ); /* Define limits for group lists. */ add("groupLists", new RadioLimitList( RXGroupList::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "contacts", new RadioLimitGroupCallRefList(1, 32) } }) ); /* Define limits for channel list. */ add("channels", new RadioLimitList( Channel::staticMetaObject, 1, UV390Codeplug::Limit::channels(), new RadioLimitObjects { { FMChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, UV390Codeplug::ChannelElement::Limit::nameLength(), RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}}, RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum{unsigned(Channel::Power::Low), unsigned(Channel::Power::High)}}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef({ScanList::staticMetaObject})}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"admit", new RadioLimitEnum{ (unsigned)FMChannel::Admit::Always, (unsigned)FMChannel::Admit::Free, (unsigned)FMChannel::Admit::Tone } }, {"squelch", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"bandwidth", new RadioLimitEnum{ (unsigned)FMChannel::Bandwidth::Narrow, (unsigned)FMChannel::Bandwidth::Wide }}, {"aprs", new RadioLimitObjRefIgnored(nullptr, RadioLimitIssue::Hint)} } }, { DMRChannel::staticMetaObject, new RadioLimitObject { {"name", new RadioLimitString(1, UV390Codeplug::ChannelElement::Limit::nameLength(), RadioLimitString::Unicode)}, {"rxFrequency", new RadioLimitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}},RadioLimitIssue::Severity::Critical)}, {"txFrequency", new RadioLimitTransmitFrequencies({{Frequency::fromMHz(136.), Frequency::fromMHz(174.)}, {Frequency::fromMHz(400.), Frequency::fromMHz(480.)}})}, {"power", new RadioLimitEnum { unsigned(Channel::Power::Low), unsigned(Channel::Power::Mid), unsigned(Channel::Power::High), }}, {"timeout", new RadioLimitInterval()}, {"scanlist", new RadioLimitObjRef(ScanList::staticMetaObject)}, {"vox", new RadioLimitIgnored(RadioLimitIssue::Silent)}, {"rxOnly", new RadioLimitBool()}, {"admit", new RadioLimitEnum { unsigned(DMRChannel::Admit::Always), unsigned(DMRChannel::Admit::Free), unsigned(DMRChannel::Admit::ColorCode) } }, {"colorCode", new RadioLimitUInt(0,16)}, {"timeSlot", new RadioLimitEnum { unsigned(DMRChannel::TimeSlot::TS1), unsigned(DMRChannel::TimeSlot::TS2) } }, {"radioID", new RadioLimitObjRef(RadioID::staticMetaObject, true)}, {"groupList", new RadioLimitObjRef(RXGroupList::staticMetaObject, false)}, {"contact", new RadioLimitObjRef(DMRContact::staticMetaObject, true)}, {"aprs", new RadioLimitObjRefIgnored()}, {"roaming", new RadioLimitObjRefIgnored(DefaultRoamingZone::get())}, {"openGD77", new RadioLimitIgnored(RadioLimitIssue::Hint)}, {"tyt", new RadioLimitIgnored(RadioLimitIssue::Hint)} } } } ) ); /* Define limits for zone list. */ add("zones", new RadioLimitList( Zone::staticMetaObject, 1, 250, new RadioLimitObject { { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, // 16 ASCII chars in name { "A", new RadioLimitRefList(0, 64, Channel::staticMetaObject) }, { "B", new RadioLimitRefList(0, 64, Channel::staticMetaObject) }, { "anytone", new RadioLimitIgnored(RadioLimitIssue::Hint) } // ignore AnyTone extensions } ) ); /* Define limits for scan lists. */ add("scanlists", new RadioLimitList( ScanList::staticMetaObject, 0, 250, new RadioLimitObject{ { "name", new RadioLimitString(1, 16, RadioLimitString::Unicode) }, { "primary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "secondary", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "revert", new RadioLimitObjRef(Channel::staticMetaObject, true) }, { "channels", new RadioLimitRefList(0, 31, Channel::staticMetaObject) } }) ); /* Define limits for positioning systems in case, this is a MD-380G/MD-390G. */ add("positioning", new RadioLimitList({ { DMRAPRSSystem::staticMetaObject, 0, 16, new RadioLimitObject { { "name", new RadioLimitStringIgnored() }, { "period", new RadioLimitInterval({Interval::null(), Interval::fromMinutes(127)}) }, { "contact", new RadioLimitObjRef(DMRContact::staticMetaObject, false) }, { "revert", new RadioLimitObjRef({SelectedChannel::staticMetaObject, DMRChannel::staticMetaObject}, true) } } }, { FMAPRSSystem::staticMetaObject, 0, -1, new RadioLimitIgnored() } }) ); /* Check encryption keys. */ add("commercial", new RadioLimitItem { {"encryptionKeys", new RadioLimitList { {BasicEncryptionKey::staticMetaObject, 0, TyTCodeplug::EncryptionElement::Limit::basicKeys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{4}")} }}, {AESEncryptionKey::staticMetaObject, 0, TyTCodeplug::EncryptionElement::Limit::advancedKeys(), new RadioLimitObject { {"name", new RadioLimitIgnored()}, {"key", new RadioLimitStringRegEx("[0-9a-fA-F]{32}")} }} } } }); /* Ignore roaming zones. */ add("roaming", new RadioLimitList( ConfigObject::staticMetaObject, -1, -1, new RadioLimitIgnored(RadioLimitIssue::Hint) ) ); } ================================================ FILE: lib/uv390_limits.hh ================================================ #ifndef UV390LIMITS_HH #define UV390LIMITS_HH #include "radiolimits.hh" /** Implements the configuration limits for the TyT MD-UV390 and Retevis RT3S. * @ingroup uv390 */ class UV390Limits : public RadioLimits { Q_OBJECT public: /** Constructor. */ explicit UV390Limits(QObject *parent=nullptr); }; #endif // UV390LIMITS_HH ================================================ FILE: lib/visitor.cc ================================================ #include "visitor.hh" #include "config.hh" #include "configobject.hh" #include "configreference.hh" #include "logger.hh" Visitor::Visitor() { // Pass... } Visitor::~Visitor() { // pass... } bool Visitor::process(Config *config, const ErrorStack &err) { return this->processItem(config, err); } bool Visitor::processItem(ConfigItem *item, const ErrorStack &err) { // Process all properties const QMetaObject *meta = item->metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if (! prop.isValid()) { logWarn() << "Found invalid property at index " << p << " in an instance of '" << meta->className() << "'. Skip."; continue; } if (! this->processProperty(item, prop, err)) { errMsg(err) << "While processing property '" << prop.name() << "' of '" << meta->className() << "'."; return false; } } return true; } bool Visitor::processProperty(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err) { if (prop.isFlagType()) { if (! this->processFlags(item, prop, err)) { errMsg(err) << "While processing flags '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (prop.isEnumType()) { if (! this->processEnum(item, prop, err)) { errMsg(err) << "While processing enum '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::Bool == prop.typeId()) { if (! this->processBool(item, prop, err)) { errMsg(err) << "While processing boolean '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::Int == prop.typeId()) { if (! this->processInt(item, prop, err)) { errMsg(err) << "While processing integer '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::UInt == prop.typeId()) { if (! this->processUInt(item, prop, err)) { errMsg(err) << "While processing unsigned integer '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::Double == prop.typeId()) { if (! this->processDouble(item, prop, err)) { errMsg(err) << "While processing double '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::QString == prop.typeId()) { if (! this->processString(item, prop, err)) { errMsg(err) << "While processing string '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::fromType() == prop.metaType()) { if (! this->processFrequency(item, prop, err)) { errMsg(err) << "While processing frequency '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::fromType() == prop.metaType()) { if (! this->processInterval(item, prop, err)) { errMsg(err) << "While processing interval '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::fromType() == prop.metaType()) { if (! this->processLevel(item, prop, err)) { errMsg(err) << "While processing level '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::fromType() == prop.metaType()) { if (! this->processSelectiveCall(item, prop, err)) { errMsg(err) << "While processing selective call '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (QMetaType::fromType() == prop.metaType()) { if (! this->processGeoCoordinate(item, prop, err)) { errMsg(err) << "While processing geo coordinate '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (ConfigObjectReference *ref = prop.read(item).value()) { if (! this->processReference(ref, err)) { errMsg(err) << "While processing reference '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (ConfigObjectRefList *refs = prop.read(item).value()) { if (! this->processList(refs, err)) { errMsg(err) << "While processing reference list '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (propIsInstance(prop)) { ConfigItem *pitem = prop.read(item).value(); // Some items, held as writeable properties might be null (e.g., extensions) if (prop.isWritable() && (nullptr == pitem)) return true; // Go for it if (! this->processItem(pitem, err)) { errMsg(err) << "While processing item '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else if (ConfigObjectList *lst = prop.read(item).value()) { if (! this->processList(lst, err)) { errMsg(err) << "While processing reference list '" << prop.name() << "' of '" << item->metaObject()->className() << "'."; return false; } } else { if (! this->processUnknownType(item, prop, err)) { errMsg(err) << "While processing property '" << prop.name() << "' of '" << item->metaObject()->className() << "' of unknown type."; return false; } } return true; } bool Visitor::processBool(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processFlags(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processEnum(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processInt(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processUInt(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processDouble(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processString(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processFrequency(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processInterval(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processLevel(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processSelectiveCall(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processGeoCoordinate(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { Q_UNUSED(parent); Q_UNUSED(prop); Q_UNUSED(err) // Does nothing, return true; return true; } bool Visitor::processUnknownType(ConfigItem *parent, const QMetaProperty &prop, const ErrorStack &err) { errMsg(err) << "Cannot handle property '" << prop.name() << "' of '" << parent->metaObject()->className() << "': Unknown type '" << prop.typeName() << "'."; return false; } bool Visitor::processReference(ConfigObjectReference* ref, const ErrorStack &err) { Q_UNUSED(ref); Q_UNUSED(err) // Does nothing, returns true; return true; } bool Visitor::processList(AbstractConfigObjectList *list, const ErrorStack &err) { if (ConfigObjectList *objList = qobject_cast(list)) { for (int i=0; icount(); i++) { if (! this->processItem(objList->get(i), err)) { errMsg(err) << "While processing object list."; return false; } } } return true; } ================================================ FILE: lib/visitor.hh ================================================ #ifndef VISITOR_HH #define VISITOR_HH #include #include "errorstack.hh" // Forward declarations class Config; class ConfigItem; class ConfigObjectReference; class AbstractConfigObjectList; /** Base visitor class for the config tree. * * This class can be used to implement a convenient tree taversal for the entry configuration. * * @ingroup config */ class Visitor { protected: /** Hidden constructor. */ Visitor(); public: /** Destructor. */ virtual ~Visitor(); /** Traverses the properties of the configuration recursively. * @returns @c true on success. Error information can be found in the error stack, if passed. */ virtual bool process(Config *config, const ErrorStack &err=ErrorStack()); /** Processes the specified property of the item. * This method dispatches to the type-specific methods like @c processEnum, @c processBool, * @c processInt, @c processUInt, @c processDouble, @c processString, @c processItem, * @c processReference, @c processList, and @c processUnknownType, depending on the type of the * property. Do not override this method unless you need to handle all properties differently. */ virtual bool processProperty(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a flag typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processFlags(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles an enum typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processEnum(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a boolean typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processBool(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles an integer typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processInt(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles an unsigned integer typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processUInt(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a double precision float typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processDouble(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a string typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processString(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a @c Frequency typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processFrequency(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a @c Interval typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processInterval(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a @c Level typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processLevel(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a @c SelectiveCall typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processSelectiveCall(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a @c QGeoCoordinate typed property. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processGeoCoordinate(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Handles a property of unknown type. * Returns always @c false. * @param item Specifies the config item holding this property. * @param prop Specifies the property. * @param err Specifies the error stack to pass on. */ virtual bool processUnknownType(ConfigItem *item, const QMetaProperty &prop, const ErrorStack &err=ErrorStack()); /** Traverses the specified config item. * This method calls @c processProperty on all properties of the item. */ virtual bool processItem(ConfigItem *item, const ErrorStack &err=ErrorStack()); /** Traverses the list of objects or references. * If @c list is a list of @c ConfigItem, the visitor will traverse those by calling * @c processItem on every of these element. If it is a list of @c ConfigObjectRef references, * the visitor stop here and simply return @c true. By default, the visitor does not follow * references. */ virtual bool processList(AbstractConfigObjectList *list, const ErrorStack &err=ErrorStack()); /** Handles references to config objects. * By default, the method will simply return @c true. The visitor does not follow references. */ virtual bool processReference(ConfigObjectReference*, const ErrorStack &err=ErrorStack()); }; #endif // VISITOR_HH ================================================ FILE: lib/xmodem.cc ================================================ #include "xmodem.hh" #include "logger.hh" XModem::XModem(OpenRTXLinkStream *link, QObject *parent) : QObject(parent), _link(link), _state(State::Init), _maxRetry(10) { // pass... } bool XModem::receive(QByteArray &buffer, int timeout, const ErrorStack &err) { if (State::Init != _state) { errMsg(err) << "Cannot start reception of data. Not in INIT state."; return false; } // Clear buffer buffer.clear(); // Init transfer if (! this->txByte(CRC, timeout, err)) { _state = State::Error; errMsg(err) << "Cannot initialize transfer."; return false; } // Expected sequence number uint8_t seqNum = 1; QByteArray payload; unsigned int retry_count = 0; while (true) { uint8_t rsp, seq, seq1c; uint16_t crc; if (! this->rxByte(rsp, timeout, err)) { _state = State::Error; errMsg(err) << "Cannot initialize transfer."; return false; } switch (_state) { case State::Init: case State::Transfer: switch (rsp) { case SOH: case STX: // receive sequence number if ((! rxByte(seq, timeout)) || (! rxByte(seq1c, timeout))) { _state = State::Error; errMsg(err) << "Cannot receive sequence numbers."; return false; } // allocate payload buffer based on type payload.clear(); if (SOH == rsp) payload.resize(128); else payload.resize(1024); // receive payload if (! rxBytes((uint8_t *)payload.data(), payload.size(), timeout)) { _state = State::Error; errMsg(err) << "Cannot receive payload."; return false; } // receive CRC if (! rxBytes((uint8_t *)&crc, 2, timeout)) { _state = State::Error; errMsg(err) << "Cannot receive CRC16."; return false; } // check sequence number. On mismatch -> cancel transfer if ((seq != seqNum) || ((255-seq) != seq1c)) { _state = State::Init; if (! txByte(CAN, timeout, err)) { _state = State::Error; return false; } if (! txByte(CAN, timeout, err)) { _state = State::Error; return false; } return false; } seqNum++; // Check CRC if (crc != crc_ccitt(payload)) { if (! txByte(NAK, timeout, err)) { _state = State::Error; return false; } retry_count++; if (retry_count > _maxRetry) { errMsg(err) << "Maximum retries (" << _maxRetry<<") reached."; _state = State::Error; return false; } } else { if( !txByte(ACK, timeout, err)) { _state = State::Error; return false; } } if (State::Init == _state) _state = State::Transfer; // State updated to transfer -> continue transfer retry_count = 0; break; case EOT: // Once EOT received -> state back to init _state = State::Init; // ACK if (! txByte(ACK, timeout, err)) { _state = State::Error; return false; } // Done. return true; default: // Unknown command: _state = State::Error; errMsg(err) << "Unknown command " << rsp << "."; return false; } case State::Error: return false; } } // Should never be reached. } bool XModem::send(const QByteArray &buffer, int timeout, const ErrorStack &err) { if (State::Init != _state) { errMsg(err) << "Cannot start reception of data. Not in INIT state."; return false; } // Wait for CRC uint8_t cmd; if ((! rxByte(cmd, timeout, err)) || (CRC != cmd)) { _state = State::Error; errMsg(err) << "Cannot start transfer."; return false; } uint8_t seqNum = 1; quint64 offset = 0; quint64 length = buffer.length(); QByteArray payload; payload.resize(1024); while (length) { _state = State::Transfer; // Copy a chunk payload.fill(0); quint64 n_send = std::min(1024ULL, length); memcpy(payload.data(), buffer.constData()+offset, n_send); // Transmit header if ((!txByte(STX, timeout, err)) || (!txByte(seqNum, timeout, err)) || (!txByte(255-seqNum, timeout, err))) { _state = State::Error; errMsg(err) << "Cannot send header."; return false; } // Transmit data if (!txBytes((const uint8_t *)payload.constData(), payload.length(), timeout, err)) { _state = State::Error; errMsg(err) << "Cannot send payload"; return false; } // Transmit CRC uint16_t crc = crc_ccitt(payload); if (!txBytes((const uint8_t *)&crc, 2, timeout, err)) { _state = State::Error; errMsg(err) << "Cannot send CRC."; return false; } // Receive ACK/NACK/CAN if (! rxByte(cmd, timeout, err)) { _state = State::Error; errMsg(err) << "Cannot receive response."; return false; } switch (cmd) { case ACK: // Increment sequence number seqNum++; // Update length and offset length -= n_send; offset += n_send; break; case NAK: // Resend break; case CAN: // Canceled by receiver _state = State::Init; return false; } } return true; } bool XModem::txByte(uint8_t byte, int timeout, const ErrorStack &err) { Q_UNUSED(timeout) if (1 != _link->write((const char *)&byte, 1)) { errMsg(err) << "Cannot send byte: " << _link->errorString(); return false; } return true; } bool XModem::rxByte(uint8_t &byte, int timeout, const ErrorStack &err) { if ((! _link->bytesAvailable()) && (!_link->waitForReadyRead(timeout))) { errMsg(err) << "Read time-out."; return false; } if (! _link->getChar((char *)&byte)) { errMsg(err) << "Cannot receive byte: " << _link->errorString(); return false; } return true; } bool XModem::txBytes(const uint8_t *buffer, unsigned int length, int timeout, const ErrorStack &err) { while (length) { qint64 nb_written = _link->write((const char *)buffer, length); if (0 > nb_written) { errMsg(err) << "Cannot write to interface: " << _link->errorString(); return false; } length -= nb_written; if (! _link->waitForBytesWritten(timeout)) { errMsg(err) << "Read time-out."; return false; } } return true; } bool XModem::rxBytes(uint8_t *buffer, unsigned int length, int timeout, const ErrorStack &err) { while (length) { if ((! _link->bytesAvailable()) && (!_link->waitForReadyRead(timeout))) { errMsg(err) << "Read time-out."; return false; } qint64 nb_read = _link->read((char *)buffer, length); if (0 > nb_read) { errMsg(err) << "Cannot read from interface: " << _link->errorString(); return false; } length -= nb_read; } return true; } uint16_t XModem::crc_ccitt(const QByteArray &data) { uint16_t x = 0; uint16_t crc = 0; const uint8_t *buf = ((const uint8_t *) data.constData()); for (int i = 0; i < data.length(); i++) { x = (crc >> 8) ^ buf[i]; x ^= x >> 4; crc = (crc << 8) ^ (x << 12) ^ (x << 5) ^ x; } return crc ^ 0xFFFF; } ================================================ FILE: lib/xmodem.hh ================================================ #ifndef XMODEM_HH #define XMODEM_HH #include "openrtx_link.hh" /** Implements the XMODEM protocol (1k + crc16 variant) for a packet stream. * * Provides two methods to send and receive an entire "file". * * @ingroup rif */ class XModem : public QObject { Q_OBJECT protected: /** Possible states of the state machine. */ enum class State { Init, Transfer, Error }; /** Possible XMODEM control bytes. */ enum CtrlByte { SOH = 0x01, STX = 0x02, EOT = 0x04, ACK = 0x06, NAK = 0x15, CAN = 0x18, CRC = 0x43 }; public: /** Constructs a xmodem connection via the USB device specified by @c descriptor. */ explicit XModem(OpenRTXLinkStream *transferLayer, QObject *parent=nullptr); /** Receives an entire file from the device. * @param buffer The buffer to store the data in. The contents of the buffer will be cleared. * @param timeout Specifies the time-out in milliseconds. If a negative number is set, no * time-out applies. * @param err Optional stack for error messages. */ bool receive(QByteArray &buffer, int timeout=-1, const ErrorStack &err=ErrorStack()); /** Sends the contents of @c buffer to the device. * @param buffer [in] Specifies the data to send. * @param timeout Specifies the time-out in milliseconds. If a negativ number is passed, no * time-out applies. * @param err Specifies the stack for error messages. */ bool send(const QByteArray& buffer, int timeout=-1, const ErrorStack &err=ErrorStack()); protected: /** Recives a single byte from the device. */ bool rxByte(uint8_t &b, int timeout=-1, const ErrorStack &err=ErrorStack()); /** Sends a single byte to the device. */ bool txByte(uint8_t b, int timeout=-1, const ErrorStack &err=ErrorStack()); /** Receives exactly @c size bytes from the device. */ bool rxBytes(uint8_t *data, unsigned int size, int timeout=-1, const ErrorStack &err=ErrorStack()); /** Sends exactly @c size bytes to the device. */ bool txBytes(const uint8_t *data, unsigned int size, int timeout=-1, const ErrorStack &err=ErrorStack()); /** Computes the CRC16 checksum. */ static uint16_t crc_ccitt(const QByteArray &data); private: /// A weak reference to the transfer layer. OpenRTXLinkStream *_link; /// State of the state machine. State _state; /// Maximum number of retires. unsigned int _maxRetry; }; #endif // XMODEM_HH ================================================ FILE: lib/zone.cc ================================================ #include "zone.hh" #include "channel.hh" #include "config.hh" /* ********************************************************************************************* * * Implementation of Zone * ********************************************************************************************* */ Zone::Zone(QObject *parent) : ConfigObject(parent), _A(), _B(), _anytone(nullptr) { connect(&_A, SIGNAL(elementAdded(int)), this, SIGNAL(modified())); connect(&_A, SIGNAL(elementRemoved(int)), this, SIGNAL(modified())); connect(&_B, SIGNAL(elementAdded(int)), this, SIGNAL(modified())); connect(&_B, SIGNAL(elementRemoved(int)), this, SIGNAL(modified())); } Zone::Zone(const QString &name, QObject *parent) : ConfigObject(name, parent), _A(), _B(), _anytone(nullptr) { connect(&_A, SIGNAL(elementAdded(int)), this, SIGNAL(modified())); connect(&_A, SIGNAL(elementRemoved(int)), this, SIGNAL(modified())); connect(&_B, SIGNAL(elementAdded(int)), this, SIGNAL(modified())); connect(&_B, SIGNAL(elementRemoved(int)), this, SIGNAL(modified())); } Zone & Zone::operator =(const Zone &other) { copy(other); return *this; } ConfigItem * Zone::clone() const { Zone *z = new Zone(); if (! z->copy(*this)) { z->deleteLater(); return nullptr; } return z; } void Zone::clear() { _name.clear(); _A.clear(); _B.clear(); } const ChannelRefList * Zone::A() const { return &_A; } ChannelRefList * Zone::A() { return &_A; } const ChannelRefList * Zone::B() const { return &_B; } ChannelRefList * Zone::B() { return &_B; } bool Zone::contains(Channel *obj) const { return _A.has(obj) || _B.has(obj); } AnytoneZoneExtension * Zone::anytoneExtension() const { return _anytone; } void Zone::setAnytoneExtension(AnytoneZoneExtension *ext) { if (_anytone == ext) return; if (_anytone) { disconnect(_anytone, SIGNAL(modified(ConfigItem*)), this, SIGNAL(modified(ConfigItem*))); _anytone->deleteLater(); } _anytone = ext; if (_anytone) { _anytone->setParent(this); connect(_anytone, SIGNAL(modified(ConfigItem*)), this, SIGNAL(modified(ConfigItem*))); } } /* ********************************************************************************************* * * Implementation of ZoneList * ********************************************************************************************* */ ZoneList::ZoneList(QObject *parent) : ConfigObjectList(Zone::staticMetaObject, parent) { // pass... } Zone * ZoneList::zone(int idx) const { if (ConfigItem *obj = get(idx)) return obj->as(); return nullptr; } int ZoneList::add(ConfigObject *obj, int row, bool unique) { if (obj && obj->is()) return ConfigObjectList::add(obj, row, unique); return -1; } ConfigItem * ZoneList::allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err) { Q_UNUSED(ctx) if (! node) return nullptr; if (! node.IsMap()) { errMsg(err) << node.Mark().line << ":" << node.Mark().column << ": Cannot create zone: Expected object."; return nullptr; } return new Zone(); } ================================================ FILE: lib/zone.hh ================================================ #ifndef ZONE_HH #define ZONE_HH #include #include "configobject.hh" #include "configreference.hh" #include "anytone_extension.hh" class Config; /** Represents a zone within the generic configuration. * @ingroup conf */ class Zone : public ConfigObject { Q_OBJECT Q_CLASSINFO("IdPrefix", "zone") /** The A channels. */ Q_PROPERTY(ChannelRefList* A READ A) /** The B channels. */ Q_PROPERTY(ChannelRefList* B READ B) /** The AnyTone extensions. */ Q_PROPERTY(AnytoneZoneExtension* anytone READ anytoneExtension WRITE setAnytoneExtension) public: /** Default constructor. */ Q_INVOKABLE explicit Zone(QObject *parent=nullptr); /** Constructs an empty Zone with the given name. */ Zone(const QString &name, QObject *parent = nullptr); /** Copies the given zone. */ Zone &operator =(const Zone &other); ConfigItem *clone() const; /** Clears this zone. */ void clear(); /** Returns the list of channels for VFO A in this zone. */ const ChannelRefList *A() const; /** Returns the list of channels for VFO A in this zone. */ ChannelRefList* A(); /** Returns the list of channels for VFO B in this zone. */ const ChannelRefList *B() const; /** Returns the list of channels for VFO B in this zone. */ ChannelRefList* B(); /** Returns @c true, if the zone contains the given channel. */ bool contains(Channel *obj) const; /** Returns the AnyTone extension. */ AnytoneZoneExtension *anytoneExtension() const; /** Sets the AnyTone extension. */ void setAnytoneExtension(AnytoneZoneExtension *ext); signals: /** Gets emitted whenever the zone gets modified. */ void modified(); protected: /** List of channels for VFO A. */ ChannelRefList _A; /** List of channels for VFO B. */ ChannelRefList _B; /** Owns the AnyTone extensions. */ AnytoneZoneExtension *_anytone; }; /** Represents the list of zones within the generic configuration. * @ingroup conf */ class ZoneList : public ConfigObjectList { Q_OBJECT public: /** Constructs an empty list of zones. */ explicit ZoneList(QObject *parent = nullptr); /** Returns the zone at the given index. */ Zone *zone(int idx) const; int add(ConfigObject *obj, int row=-1, bool unique=true); public: ConfigItem *allocateChild(const YAML::Node &node, ConfigItem::Context &ctx, const ErrorStack &err=ErrorStack()); }; #endif // ZONE_HH ================================================ FILE: shared/icons/dark/index.theme ================================================ [Icon Theme] Name = "Light Theme" Comment = "Default light icon theme." Directories = 16x16/emblems 16x16/actions, 16x16/apps, 32x32/emblems 32x32/actions, 32x32/apps 48x48/apps, 64x64/apps, scalable/emblems scalable/actions, scalable/apps [16x16/emblems] Size=16 Context=Emblems [16x16/actions] Size=16 Context=Actions [16x16/apps] Size=16 Context=Applications [32x32/emblems] Size=32 Context=Emblems [32x32/actions] Size=32 Context=Actions [32x32/apps] Size=32 Context=Applications [48x48/apps] Size=46 Context=Applications [64x64/apps] Size=64 Context=Applications [scalable/emblems] Size=8 Type=Scalable MinSize=1 MaxSize=1024 Context=Emblems [scalable/actions] Size=8 Type=Scalable MinSize=1 MaxSize=1024 Context=Actions [scalable/apps] Size=128 Type=Scalable MinSize=1 MaxSize=1024 Context=Applications ================================================ FILE: shared/icons/light/index.theme ================================================ [Icon Theme] Name = "Light Theme" Comment = "Default light icon theme." Directories = 16x16/emblems 16x16/actions, 16x16/apps, 32x32/emblems 32x32/actions, 32x32/apps 48x48/apps, 64x64/apps, scalable/emblems scalable/actions, scalable/apps [16x16/emblems] Size=16 Context=Emblems [16x16/actions] Size=16 Context=Actions [16x16/apps] Size=16 Context=Applications [32x32/emblems] Size=32 Context=Emblems [32x32/actions] Size=32 Context=Actions [32x32/apps] Size=32 Context=Applications [48x48/apps] Size=46 Context=Applications [64x64/apps] Size=64 Context=Applications [scalable/emblems] Size=8 Type=Scalable MinSize=1 MaxSize=1024 Context=Emblems [scalable/actions] Size=8 Type=Scalable MinSize=1 MaxSize=1024 Context=Actions [scalable/apps] Size=128 Type=Scalable MinSize=1 MaxSize=1024 Context=Applications ================================================ FILE: shared/resources.qrc ================================================ logo256.png ui/aboutdialog.ui aprs/aprs-symbols-24-0.png aprs/aprs-symbols-24-1.png aprs/aprs-symbols-24-2.png ================================================ FILE: shared/ui/aboutdialog.ui ================================================ AboutDialog Qt::NonModal 0 0 540 371 Qt::NoFocus About qdmr :/icons/about.png:/icons/about.png true 0 About qdmr 0 0 0 0 About qdmr false <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd"> <html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><title>About qdmr</title><style type="text/css"> p, li { white-space: pre-wrap; } hr { height: 1px; border-width: 0; } li.unchecked::marker { content: "\2610"; } li.checked::marker { content: "\2612"; } </style></head><body style=" font-family:'Noto Sans'; font-size:10pt; font-weight:400; font-style:normal;"> <p align="center" style=" margin-top:18px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:600;">qdmr</span></p> <p align="center" style=" margin-top:14px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:600;">Version %1</span></p> <p align="center" style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:600;">Hannes Matuschek, DM3MAT<br /> dm3mat@darc.de</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.SF NS Text'; font-size:13pt; font-weight:600;">qdmr</span><span style=" font-family:'.SF NS Text'; font-size:13pt;"> – A platform independent configuration and programming tool for codeplugs of cheap DMR radios.</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.SF NS Text'; font-size:13pt;">This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.SF NS Text'; font-size:13pt;">This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.</span></p> <p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><span style=" font-family:'.SF NS Text'; font-size:13pt;">You should have received a copy of the GNU General Public License along with this program. If not, see </span><a href="https://www.gnu.org/licenses/"><span style=" font-family:'.SF NS Text'; font-size:13pt; text-decoration: underline; color:#0000ff;">https://www.gnu.org/licenses/</span></a><span style=" font-family:'.SF NS Text'; font-size:13pt;">.</span></p></body></html> Qt::NoTextInteraction Supported Radios 0 0 0 0 11 false 1 QDialogButtonBox::Close false buttonBox accepted() AboutDialog accept() 248 254 157 274 buttonBox rejected() AboutDialog reject() 316 260 286 274 ================================================ FILE: src/CMakeLists.txt ================================================ qt6_add_resources(qdmr_RCC_SOURCES ../shared/resources.qrc) qt6_add_executable(qdmr WIN32 main.cc configitemwrapper.cc configitemwrapper.hh application.cc application.hh settings.cc settings.hh settingsdialog.ui dmrcontactdialog.cc dmrcontactdialog.hh dmrcontactdialog.ui dtmfcontactdialog.cc dtmfcontactdialog.hh dtmfcontactdialog.ui m17contactdialog.cc m17contactdialog.hh m17contactdialog.ui rxgrouplistdialog.cc rxgrouplistdialog.hh rxgrouplistdialog.ui channeldialog.hh channeldialog.cc channeldialog.ui squelchedit.hh squelchedit.cc squelchedit.ui fmchanneldialog.cc fmchanneldialog.hh amchanneldialog.hh amchanneldialog.cc dmrchanneldialog.cc dmrchanneldialog.hh m17channeldialog.cc m17channeldialog.hh channelvalidator.cc channelvalidator.hh channelcombobox.cc channelcombobox.hh channelselectiondialog.cc channelselectiondialog.hh zonedialog.cc zonedialog.hh zonedialog.ui scanlistdialog.cc scanlistdialog.hh scanlistdialog.ui verifydialog.cc verifydialog.hh verifydialog.ui gpssystemdialog.cc gpssystemdialog.hh gpssystemdialog.ui contactselectiondialog.cc contactselectiondialog.hh searchpopup.cc searchpopup.hh aprssystemdialog.cc aprssystemdialog.hh aprssystemdialog.ui releasenotes.cc releasenotes.hh roamingzonedialog.cc roamingzonedialog.hh roamingzonedialog.ui roamingchannellistview.cc roamingchannellistview.hh roamingchannellistview.ui roamingchanneldialog.cc roamingchanneldialog.hh roamingchanneldialog.ui roamingchannelselectiondialog.cc roamingchannelselectiondialog.hh configobjectlistview.cc configobjectlistview.hh configobjectlistview.ui configobjecttableview.cc configobjecttableview.hh configobjecttableview.ui generalsettingsview.cc generalsettingsview.hh generalsettingsview.ui radioidlistview.cc radioidlistview.hh radioidlistview.ui contactlistview.cc contactlistview.hh contactlistview.ui grouplistsview.cc grouplistsview.hh grouplistsview.ui channellistview.cc channellistview.hh channellistview.ui zonelistview.cc zonelistview.hh zonelistview.ui scanlistsview.cc scanlistsview.hh scanlistsview.ui positioningsystemlistview.cc positioningsystemlistview.hh positioningsystemlistview.ui roamingzonelistview.cc roamingzonelistview.hh roamingzonelistview.ui collapsablewidget.cc collapsablewidget.hh extensionview.cc extensionview.hh extensionview.ui extensionwrapper.cc extensionwrapper.hh propertydelegate.cc propertydelegate.hh errormessageview.cc errormessageview.hh errormessageview.ui deviceselectiondialog.cc deviceselectiondialog.hh deviceselectiondialog.ui radioselectiondialog.cc radioselectiondialog.hh radioselectiondialog.ui dmriddialog.cc dmriddialog.hh dmriddialog.ui configobjecttypeselectiondialog.cc configobjecttypeselectiondialog.hh configobjecttypeselectiondialog.ui configmergedialog.cc configmergedialog.hh configmergedialog.ui satellitedatabasedialog.cc satellitedatabasedialog.hh satellitedatabasedialog.ui satelliteselectiondialog.cc satelliteselectiondialog.hh satelliteselectiondialog.ui repeaterdatabase.cc repeaterdatabase.hh repeatercompleter.cc repeatercompleter.hh repeaterbooksource.cc repeaterbooksource.hh repeatermapsource.cc repeatermapsource.hh hearhamrepeatersource.cc hearhamrepeatersource.hh radioidrepeatersource.cc radioidrepeatersource.hh selectivecallbox.cc selectivecallbox.hh transponderfrequencydelegate.cc transponderfrequencydelegate.hh mainwindow.cc mainwindow.hh mainwindow.ui flageditdialog.hh flageditdialog.cc flageditdialog.ui ${qdmr_RCC_SOURCES} satellitetransponderdialog.hh satellitetransponderdialog.cc satellitetransponderdialog.ui admitselect.hh admitselect.cc bandwidthselect.hh bandwidthselect.cc aprsselect.hh aprsselect.cc idselect.hh idselect.cc timeslotselect.hh timeslotselect.cc melody_edit.hh melody_edit.cc channel_type_edit.cc channel_type_edit.hh melody_player.cc melody_player.hh ) # # Translations # qt6_add_translations(qdmr TS_FILES ${PROJECT_SOURCE_DIR}/i18n/de.ts ${PROJECT_SOURCE_DIR}/i18n/en_US.ts ${PROJECT_SOURCE_DIR}/i18n/sv.ts ${PROJECT_SOURCE_DIR}/i18n/it.ts ${PROJECT_SOURCE_DIR}/i18n/nl.ts ${PROJECT_SOURCE_DIR}/i18n/pl.ts ${PROJECT_SOURCE_DIR}/i18n/fr.ts ${PROJECT_SOURCE_DIR}/i18n/pt_BR.ts ${PROJECT_SOURCE_DIR}/i18n/ru.ts) # # Icon generation # generate_icons( DIRECTORY ${CMAKE_SOURCE_DIR}/shared/icons ICONS application-qdmr THEMES light dark CONTEXT apps SIZES 16 32 48 64) generate_icons( DIRECTORY ${CMAKE_SOURCE_DIR}/shared/icons ICONS application-exit application-settings device-read device-write-callsign device-write device-search device-write-satellites document-new document-open document-verify document-save-as document-download document-import document-export edit-move-10-down edit-move-10-up edit-move-down edit-move-up edit-move-bottom edit-move-top edit-satellites edit-clear item-filter edit-edit media-play help-contents help-about symbol-minus symbol-plus symbol-none THEMES light dark CONTEXT actions SIZES 16 32) generate_icons( DIRECTORY ${CMAKE_SOURCE_DIR}/shared/icons ICONS symbol-info symbol-warning symbol-error THEMES light dark CONTEXT emblems SIZES 16 32) qt_add_resources(qdmr "icons" PREFIX "icons" BASE "${CMAKE_CURRENT_BINARY_DIR}/icons" FILES ${GENERATE_ICONS_OUTPUT_FILES}) qt_add_resources(qdmr "icons-themes" PREFIX "icons" BASE "${CMAKE_SOURCE_DIR}/shared/icons" FILES "${CMAKE_SOURCE_DIR}/shared/icons/light/index.theme" "${CMAKE_SOURCE_DIR}/shared/icons/dark/index.theme") if (UNIX AND APPLE AND INSTALL_BUNDLE) set_target_properties(qdmr PROPERTIES MACOSX_BUNDLE ON MACOSX_BUNDLE_BUNDLE_NAME ${CMAKE_PROJECT_NAME} MACOSX_BUNDLE_BUNDLE_VERSION ${CMAKE_PROJECT_VERSION} MACOSX_BUNDLE_GUI_IDENTIFIER "de.darc.dm3mat.qdmr" MACOSX_BUNDLE_ICON_FILE "Resources/qdmr.icns" MACOSX_BUNDLE_LONG_VERSION_STRING ${CMAKE_PROJECT_VERSION} MACOSX_BUNDLE_SHORT_VERSION_STRING "${CMAKE_PROJECT_VERSION_MAJOR}.${CMAKE_PROJECT_VERSION_MINOR}" RESOURCE "${CMAKE_SOURCE_DIR}/dist/macosx/qdmr.icns" ${qdmr_QM_FILES}) endif(UNIX AND APPLE AND INSTALL_BUNDLE) target_link_libraries(qdmr PRIVATE Qt6::Core Qt6::Widgets Qt6::Network Qt6::Positioning Qt6::UiTools Qt6::Concurrent Qt6::Multimedia ${LIBUSB_1_LIBS} libdmrconf) target_include_directories(qdmr PUBLIC "${CMAKE_CURRENT_BINARY_DIR}") # Install binary only if not a bundle under MacOS X if (UNIX AND APPLE AND INSTALL_BUNDLE) install(TARGETS qdmr BUNDLE DESTINATION ${BUNDLE_PATH}/ RESOURCE DESTINATION "${BUNDLE_PATH}/${CMAKE_PROJECT_NAME}.app/Contents/Resources") else() install(TARGETS qdmr DESTINATION ${CMAKE_INSTALL_FULL_BINDIR}) endif(UNIX AND APPLE AND INSTALL_BUNDLE) ================================================ FILE: src/aboutdialog.ui ================================================ AboutDialog 0 0 400 300 About qdmr Qt::Horizontal QDialogButtonBox::Close true buttonBox accepted() AboutDialog accept() 248 254 157 274 buttonBox rejected() AboutDialog reject() 316 260 286 274 ================================================ FILE: src/admitselect.cc ================================================ #include "admitselect.hh" /* ****************************************************************************************** * * FMAdmitSelect * ****************************************************************************************** */ FMAdmitSelect::FMAdmitSelect(QWidget *parent) : QComboBox(parent) { addItem(tr("Always"), QVariant::fromValue(FMChannel::Admit::Always)); addItem(tr("Channel Free"), QVariant::fromValue(FMChannel::Admit::Free)); addItem(tr("Other Tone"), QVariant::fromValue(FMChannel::Admit::Tone)); } void FMAdmitSelect::setAdmit(FMChannel::Admit admit) { for (int i=0; i() == admit) setCurrentIndex(i); } FMChannel::Admit FMAdmitSelect::admit() const { return currentData().value(); } /* ****************************************************************************************** * * DMRAdmitSelect * ****************************************************************************************** */ DMRAdmitSelect::DMRAdmitSelect(QWidget *parent) : QComboBox(parent) { addItem(tr("Always"), QVariant::fromValue(DMRChannel::Admit::Always)); addItem(tr("Channel Free"), QVariant::fromValue(DMRChannel::Admit::Free)); addItem(tr("Other Color-code"), QVariant::fromValue(DMRChannel::Admit::ColorCode)); } void DMRAdmitSelect::setAdmit(DMRChannel::Admit admit) { for (int i=0; i() == admit) setCurrentIndex(i); } DMRChannel::Admit DMRAdmitSelect::admit() const { return currentData().value(); } ================================================ FILE: src/admitselect.hh ================================================ #ifndef ADMITSELECT_HH #define ADMITSELECT_HH #include #include "channel.hh" class FMAdmitSelect : public QComboBox { Q_OBJECT public: explicit FMAdmitSelect(QWidget *parent=nullptr); void setAdmit(FMChannel::Admit admit); FMChannel::Admit admit() const; }; class DMRAdmitSelect : public QComboBox { Q_OBJECT public: explicit DMRAdmitSelect(QWidget *parent=nullptr); void setAdmit(DMRChannel::Admit admit); DMRChannel::Admit admit() const; }; #endif // ADMITSELECT_HH ================================================ FILE: src/amchanneldialog.cc ================================================ #include "amchanneldialog.hh" #include "channel.hh" #include "ui_channeldialog.h" #include "squelchedit.hh" #include "config.hh" AMChannelDialog::AMChannelDialog(Config *config, QWidget *parent) : ChannelDialog(config, parent), _clone(nullptr), _orig(nullptr), _squelch(nullptr) { ui->rightForm->addRow(tr("Squelch"), _squelch = new ChannelSquelchEdit(_config->settings()->audio()->squelch())); } void AMChannelDialog::setChannel(AMChannel *am) { if (_clone) { delete _clone; _clone = nullptr; } _orig = am; if (_orig.isNull()) return; _clone = _orig->clone()->as(); _clone->setParent(this); ChannelDialog::setChannel(_clone); _squelch->setChannel(_clone); } void AMChannelDialog::accept() { _squelch->accept(); ChannelDialog::accept(); _orig->copy(*_clone); } ================================================ FILE: src/amchanneldialog.hh ================================================ #ifndef AMCHANNELDIALOG_HH #define AMCHANNELDIALOG_HH #include "channeldialog.hh" class Config; class AMChannel; class ChannelSquelchEdit; class AMChannelDialog : public ChannelDialog { Q_OBJECT public: AMChannelDialog(Config *config, QWidget *parent=nullptr); void setChannel(AMChannel *am); public slots: void accept() override; protected: AMChannel *_clone; QPointer _orig; ChannelSquelchEdit *_squelch; }; #endif // AMCHANNELDIALOG_HH ================================================ FILE: src/application.cc ================================================ #include "application.hh" #include #include #include #include #include #include "logger.hh" #include "radio.hh" #include "codeplug.hh" #include "config.h" #include "settings.hh" #include "radiolimits.hh" #include "verifydialog.hh" #include "rxgrouplistdialog.hh" #include "zonedialog.hh" #include "scanlistdialog.hh" #include "roamingzonedialog.hh" #include "repeaterdatabase.hh" #include "repeaterbooksource.hh" #include "repeatermapsource.hh" #include "hearhamrepeatersource.hh" #include "radioidrepeatersource.hh" #include "userdatabase.hh" #include "talkgroupdatabase.hh" #include "satellitedatabase.hh" #include "generalsettingsview.hh" #include "radioidlistview.hh" #include "contactlistview.hh" #include "grouplistsview.hh" #include "channellistview.hh" #include "zonelistview.hh" #include "scanlistsview.hh" #include "positioningsystemlistview.hh" #include "roamingchannellistview.hh" #include "roamingzonelistview.hh" #include "errormessageview.hh" #include "deviceselectiondialog.hh" #include "radioselectiondialog.hh" #include "chirpformat.hh" #include "configmergedialog.hh" #include "configmergevisitor.hh" #include "satellitedatabasedialog.hh" #include "mainwindow.hh" inline QStringList getLanguages() { QStringList languages = {QLocale::system().name()}; if (languages.last().contains("_")) { languages.append(languages.last().split("_").first()); } return languages; } Application::Application(int &argc, char *argv[]) : QApplication(argc, argv), _config(nullptr), _mainWindow(nullptr), _translator(nullptr), _repeater(nullptr), _satellites(nullptr), _lastDevice() { setApplicationName("qdmr"); setOrganizationName("DM3MAT"); setOrganizationDomain("hmatuschek.github.io"); setApplicationVersion(VERSION_STRING); // open logfile QString logdir = QStandardPaths::writableLocation(QStandardPaths::AppLocalDataLocation); // Log all messages to file Logger::get().addHandler(new FileLogHandler(logdir+"/qdmr.log", LogMessage::Level::DEBUG)); // register icon themes QStringList iconPaths = QIcon::themeSearchPaths(); iconPaths.prepend(":/icons"); QIcon::setThemeSearchPaths(iconPaths); onPaletteChanged(palette()); // handle translations _translator = new QTranslator(this); foreach (QString language, getLanguages()) { logDebug() << "Search for translation in ':/i18n/" << language << ".qm'."; if (_translator->load(language, ":/i18n/", "", ".qm")) { this->installTranslator(_translator); logDebug() << "Installed translator for locale '" << QLocale::system().name() << "'."; break; } } // load settings Settings settings; // load databases _repeater = new RepeaterDatabase(this); if (settings.repeaterBookSourceEnabled()) _repeater->addSource(new RepeaterBookSource()); if (settings.repeaterMapSourceEnabled() && !settings.repeaterMapAPIToken().isEmpty()) _repeater->addSource(new RepeaterMapSource(settings.repeaterMapAPIToken(), settings.position(), settings.repeaterSearchRadius())); if (settings.hearhamSourceEnabled()) _repeater->addSource(new HearhamRepeaterSource()); if (settings.radioIdRepeaterSourceEnabled()) _repeater->addSource(new RadioidRepeaterSource()); _users = new UserDatabase(true, 30, this); _talkgroups = new TalkGroupDatabase(30, this); _satellites = new SatelliteDatabase(7, this); // create empty codeplug _config = new Config(this); // load position _currentPosition = settings.position(); _source = QGeoPositionInfoSource::createDefaultSource(this); if (_source) { connect(_source, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(positionUpdated(QGeoPositionInfo))); if (settings.queryPosition()) { _source->startUpdates(); _currentPosition = _source->lastKnownPosition().coordinate(); } } // Check if updated _releaseNotes.checkForUpdate(); logDebug() << "Last known position: " << _currentPosition.toString(); connect(_config, SIGNAL(modified(ConfigItem*)), this, SLOT(onConfigModifed())); } Application::~Application() { if (_mainWindow) delete _mainWindow; _mainWindow = nullptr; } bool Application::isDarkMode() const { return isDarkMode(palette()); } bool Application::isDarkMode(const QPalette &palette) const { int text_hsv_value = palette.color(QPalette::WindowText).value(), bg_hsv_value = palette.color(QPalette::Base).value(); return text_hsv_value > bg_hsv_value; } bool Application::isModified() const { return _config->isModified(); } MainWindow * Application::mainWindow() { if (_mainWindow) return _mainWindow; return (_mainWindow = new MainWindow(_config)); } UserDatabase * Application::user() const { return _users; } TalkGroupDatabase * Application::talkgroup() const { return _talkgroups; } RepeaterDatabase * Application::repeater() const{ return _repeater; } SatelliteDatabase * Application::satellite() const { return _satellites; } void Application::newCodeplug() { if (_config->isModified()) { if (QMessageBox::Ok != QMessageBox::question(0, tr("Unsaved changes to codeplug."), tr("There are unsaved changes to the current codeplug. " "These changes are lost if you proceed."), QMessageBox::Cancel|QMessageBox::Ok)) return; } _config->clear(); _config->setModified(false); } void Application::loadCodeplug() { if (! _mainWindow) return; if (_config->isModified()) { if (QMessageBox::Ok != QMessageBox::question(nullptr, tr("Unsaved changes to codeplug."), tr("There are unsaved changes to the current codeplug. " "These changes are lost if you proceed."), QMessageBox::Cancel|QMessageBox::Ok)) return; } Settings settings; QString filename = QFileDialog::getOpenFileName( nullptr, tr("Open codeplug"), settings.lastDirectory().absolutePath(), tr("Codeplug Files (*.yaml);;Codeplug Files, old format (*.conf *.csv *.txt);;All Files (*)")); if (filename.isEmpty()) return; ErrorStack err; if (! loadCodeplug(filename, err)) { QMessageBox::critical(nullptr, tr("Cannot read codeplug."), tr("Cannot read codeplug from file '%1': %2") .arg(filename).arg(err.format())); return; } processEvents(); _config->setModified(false); } bool Application::loadCodeplug(const QString &filename, const ErrorStack &err) { QFile file(filename); if (!file.open(QIODevice::ReadOnly)) { errMsg(err) << tr("Cannot read codeplug from file '%1': %2").arg(filename).arg(file.errorString()); return false;; } logDebug() << "Load codeplug from '" << filename << "'."; QFileInfo info(filename); Settings().setLastDirectoryDir(info.absoluteDir()); if (("yaml" == info.suffix()) || ("yml" == info.suffix())) { ErrorStack err; if (! _config->readYAML(filename, err)) { _config->clear(); return false; } } else { QString errorMessage; QTextStream stream(&file); if (!_config->readCSV(stream, errorMessage)) { errMsg(err) << tr("Cannot read codeplug from file '%1': %2").arg(filename).arg(errorMessage); _config->clear(); return false; } } return true; } void Application::saveCodeplug() { if (! _mainWindow) return; Settings settings; QString filename = QFileDialog::getSaveFileName( nullptr, tr("Save codeplug"), settings.lastDirectory().absolutePath(), tr("Codeplug Files (*.yaml *.yml)")); if (filename.isEmpty()) return; if (filename.endsWith(".conf") || filename.endsWith("csv")){ QMessageBox::critical(nullptr, tr("Please use new YAML format."), tr("Saving in the old table-based conf format was disabled with 0.9.0. " "Reading these files still works.")); return; } // append .yaml if missing if ((!filename.endsWith(".yaml")) && (!filename.endsWith(".yml"))) filename.append(".yaml"); QFile file(filename); if (! file.open(QIODevice::WriteOnly)) { QMessageBox::critical(nullptr, tr("Cannot open file"), tr("Cannot save codeplug to file '%1': %2").arg(filename).arg(file.errorString())); return; } QTextStream stream(&file); QFileInfo info(filename); if (_config->toYAML(stream)) { _config->setModified(false); } else { QMessageBox::critical(nullptr, tr("Cannot save codeplug"), tr("Cannot save codeplug to file '%1'.").arg(filename)); } file.flush(); file.close(); settings.setLastDirectoryDir(info.absoluteDir()); } void Application::exportCodeplugToChirp() { if (! _mainWindow) return; Settings settings; QString filename = QFileDialog::getSaveFileName( nullptr, tr("Export codeplug"), settings.lastDirectory().absolutePath(), tr("CHIRP CSV Files (*.csv)")); if (filename.isEmpty()) return; QFile file(filename); if (! file.open(QIODevice::WriteOnly)) { QMessageBox::critical(nullptr, tr("Cannot open file"), tr("Cannot save codeplug to file '%1': %2").arg(filename).arg(file.errorString())); return; } QTextStream stream(&file); QFileInfo info(filename); ErrorStack err; if (! ChirpWriter::write(stream, _config, err)) QMessageBox::critical(nullptr, tr("Cannot export codeplug"), tr("Cannot export codeplug to file '%1':\n%2").arg(filename).arg(err.format())); file.flush(); file.close(); settings.setLastDirectoryDir(info.absoluteDir()); } void Application::importCodeplug() { if (! _mainWindow) return; Settings settings; QString filename = QFileDialog::getOpenFileName( nullptr, tr("Import codeplug"), settings.lastDirectory().absolutePath(), tr("CHIRP CSV Files (*.csv);;YAML Files (*.yaml *.yml)")); if (filename.isEmpty()) return; Config merging; ErrorStack err; if (filename.endsWith(".csv")) { // import CHIRP CSV file QFile file(filename); if (! file.open(QIODevice::ReadOnly)) { QMessageBox::critical(nullptr, tr("Cannot open file"), tr("Cannot read codeplug from file '%1': %2").arg(filename).arg(file.errorString())); return; } QTextStream stream(&file); if (! ChirpReader::read(stream, &merging, err)) { QMessageBox::critical(nullptr, tr("Cannot import codeplug"), tr("Cannot import codeplug from '%1': %2") .arg(filename).arg(err.format())); return; } } else if (filename.endsWith(".yaml") || filename.endsWith(".yml")) { // import QDMR YAML codeplug if (! merging.readYAML(filename, err)) { QMessageBox::critical(nullptr, tr("Cannot import codeplug"), tr("Cannot import codeplug from '%1': %2") .arg(filename).arg(err.format())); return; } } else { QMessageBox::critical(nullptr, tr("Cannot import codeplug"), tr("Do not know, how to handle file '%1'.") .arg(filename)); return; } ConfigMergeDialog mergeDialog; if (QDialog::Accepted != mergeDialog.exec()) return; logDebug() << "Merging codeplugs ..."; if (! ConfigMerge::mergeInto(_config, &merging, mergeDialog.itemStrategy(), mergeDialog.setStrategy(), err)) { QMessageBox::critical(nullptr, tr("Cannot import codeplug"), tr("Cannot import codeplug from '%1': %2") .arg(filename).arg(err.format())); return; } } Radio * Application::autoDetect(const ErrorStack &err) { Settings settings; // If the last detected device is still valid // -> skip interface detection and selection if ((! _lastDevice.isValid()) || settings.disableAutoDetect()) { logDebug() << "Last device is invalid, search for new one."; // First get all devices that are known by VID/PID QList interfaces = USBDeviceDescriptor::detect(); if (interfaces.isEmpty()) { logDebug() << "No save device found, continue searching for unsave ones."; interfaces = USBDeviceDescriptor::detect(false); } if (interfaces.isEmpty()) { errMsg(err) << tr("No matching devices found."); return nullptr; } else if ((1 != interfaces.count()) || (! interfaces.first().isSave()) || settings.disableAutoDetect()) { // More than one device found, or device not save -> select by user DeviceSelectionDialog dialog(interfaces); if (QDialog::Accepted != dialog.exec()) { return nullptr; } _lastDevice = dialog.device(); } else { _lastDevice = interfaces.first(); } } // Check if device supports identification RadioInfo radioInfo; if ((! _lastDevice.isSave()) || settings.disableAutoDetect()){ RadioSelectionDialog dialog(_lastDevice); if (QDialog::Accepted != dialog.exec()) { return nullptr; } radioInfo = dialog.radioInfo(); } Radio *radio = Radio::detect(_lastDevice, radioInfo, err); if (nullptr == radio) { QMessageBox::critical(nullptr, tr("Cannot connect to radio"), tr("Cannot connect to radio: %1").arg(err.format())); return nullptr; } return radio; } void Application::detectRadio() { if (Radio *radio = autoDetect()) { QMessageBox::information(nullptr, tr("Radio found"), tr("Found device '%1'.").arg(radio->name())); delete radio; } else { QMessageBox::information(nullptr, tr("No radio found"), tr("No matching device was found.")); } } bool Application::verifyCodeplug(Radio *radio, bool showSuccess) { Radio *myRadio = radio; // If no radio is given -> try to detect the radio if (nullptr == myRadio) myRadio = autoDetect(); if (nullptr == myRadio) { if (showSuccess) { QMessageBox::warning(nullptr, tr("No radio found"), tr("No matching device was found.")); } return false; } ErrorStack err; Config *intermediate = myRadio->codeplug().preprocess(_config, err); if (nullptr == intermediate) { ErrorMessageView(err).exec(); return false; } Settings settings; RadioLimitContext ctx(settings.ignoreFrequencyLimits()); myRadio->limits().verifyConfig(intermediate, ctx); bool verified = true; if ( (settings.ignoreVerificationWarning() && (ctx.maxSeverity()>RadioLimitIssue::Warning)) || ((!settings.ignoreVerificationWarning()) && (ctx.maxSeverity()>=RadioLimitIssue::Warning)) ) { VerifyDialog dialog(ctx, (nullptr != radio)); if (QDialog::Accepted != dialog.exec()) verified = false; } else if (showSuccess) { QMessageBox::information( nullptr, tr("Verification success"), tr("The codeplug was successfully verified with the radio '%1'").arg(myRadio->name())); } // Delete intermediate representation delete intermediate; // If no radio was given -> close connection to radio again if (nullptr == radio) myRadio->deleteLater(); return verified; } void Application::downloadCodeplug() { if (! _mainWindow) return; if (_config->isModified()) { if (QMessageBox::Ok != QMessageBox::question(nullptr, tr("Unsaved changes to codeplug."), tr("There are unsaved changes to the current codeplug. " "These changes are lost if you proceed."), QMessageBox::Cancel|QMessageBox::Ok)) return; } Radio *radio = autoDetect(); if (nullptr == radio) { QMessageBox::warning(nullptr, tr("No radio found"), tr("No matching device was found.")); return; } QProgressBar *progress = _mainWindow->findChild("progress"); progress->setMaximum(0); progress->setVisible(true); connect(radio, &Radio::downloadProgress, this, &Application::onProgress); connect(radio, SIGNAL(downloadError(Radio *)), this, SLOT(onCodeplugDownloadError(Radio *))); connect(radio, SIGNAL(downloadFinished(Radio *, Codeplug *)), this, SLOT(onCodeplugDownloaded(Radio *, Codeplug *))); TransferFlags flags; flags.setBlocking(false); ErrorStack err; if (radio->startDownload(flags, err)) { _mainWindow->statusBar()->showMessage(tr("Read ...")); _mainWindow->setEnabled(false); } else { ErrorMessageView(err).exec(); progress->setVisible(false); } } void Application::onCodeplugDownloadError(Radio *radio) { _mainWindow->statusBar()->showMessage(tr("Read error")); ErrorMessageView(radio->errorStack()).exec(); _mainWindow->findChild("progress")->setVisible(false); _mainWindow->setEnabled(true); if (radio->wait(250)) radio->deleteLater(); _mainWindow->setWindowModified(false); } void Application::onCodeplugDownloaded(Radio *radio, Codeplug *codeplug) { _config->clear(); _mainWindow->setWindowModified(false); ErrorStack err; if (codeplug->decode(_config, err)) { _mainWindow->statusBar()->showMessage(tr("Read complete")); _mainWindow->findChild("progress")->setVisible(false); _config->setModified(false); } else { ErrorMessageView(err).exec(); _config->clear(); } if (! codeplug->postprocess(_config, err)) { ErrorMessageView(err).exec(); _config->clear(); } _mainWindow->setEnabled(true); if (radio->wait(250)) radio->deleteLater(); } void Application::uploadCodeplug() { // Start upload Settings settings; Radio *radio = autoDetect(); if (nullptr == radio) { QMessageBox::warning(nullptr, tr("No radio found"), tr("No matching device was found.")); return; } if (! verifyCodeplug(radio, false)) { radio->deleteLater(); return; } QProgressBar *progress = _mainWindow->findChild("progress"); progress->setValue(0); progress->setMaximum(0); progress->setVisible(true); connect(radio, &Radio::uploadProgress, this, &Application::onProgress); connect(radio, SIGNAL(uploadError(Radio *)), this, SLOT(onCodeplugUploadError(Radio *))); connect(radio, SIGNAL(uploadComplete(Radio *)), this, SLOT(onCodeplugUploaded(Radio *))); ErrorStack err; Config *intermediate = radio->codeplug().preprocess(_config, err); if (nullptr == intermediate) { ErrorMessageView(err).exec(); progress->setVisible(false); return; } Codeplug::Flags flags = settings.codePlugFlags(); flags.setBlocking(false); if (radio->startUpload(intermediate, flags, err)) { _mainWindow->statusBar()->showMessage(tr("Upload ...")); _mainWindow->setEnabled(false); } else { ErrorMessageView(err).exec(); progress->setVisible(false); } } void Application::uploadCallsignDB() { // Start upload Radio *radio = autoDetect(); if (nullptr == radio) { QMessageBox::warning(nullptr, tr("No radio found"), tr("No matching device was found.")); return; } if (! radio->limits().hasCallSignDB()) { logDebug() << "Radio " << radio->name() << " does not support call-sign DB."; QMessageBox::information(nullptr, tr("Cannot write call-sign DB."), tr("The detected radio '%1' does not support " "a call-sign DB.") .arg(radio->name())); radio->deleteLater(); return; } if (! radio->limits().callSignDBImplemented()) { logDebug() << "Radio " << radio->name() << " does support call-sign DB but it is not implemented yet."; QMessageBox::critical(nullptr, tr("Cannot write call-sign DB."), tr("The detected radio '%1' does support a call-sign DB. " "This feature, however, is not implemented yet.").arg(radio->name())); radio->deleteLater(); return; } // Sort call-sign DB w.r.t. the current DMR ID in _config // this is part of the "auto-selection" of calls-signs for upload Settings settings; if (settings.selectUsingUserDMRID()) { if (nullptr == _config->settings()->defaultId()) { QMessageBox::critical(nullptr, tr("Cannot write call-sign DB."), tr("QDMR selects the call-signs to be written based on the default DMR " "ID of the radio. No default ID set.")); radio->deleteLater(); return; } // Sort w.r.t users DMR ID unsigned id = _config->settings()->defaultId()->number(); logDebug() << "Sort call-signs closest to ID=" << id << "."; _users->sortUsers(id); } else { // sort w.r.t. chosen prefixes QSet ids=settings.callSignDBPrefixes(); QStringList prefs; foreach (unsigned pref, ids) prefs.append(QString::number(pref)); logDebug() << "Sort call-signs closest to IDs={" << prefs.join(", ") << "}."; _users->sortUsers(ids); } // Assemble flags for callsign DB encoding CallsignDB::Flags css; css.setUpdateDeviceClock(settings.updateDeviceClock()); if (settings.limitCallSignDBEntries()) { logDebug() << "Limit callsign DB entries to " << settings.maxCallSignDBEntries() << "."; css.setCountLimit(settings.maxCallSignDBEntries()); } QProgressBar *progress = _mainWindow->findChild("progress"); progress->setRange(0, 0); progress->setValue(0); progress->setVisible(true); connect(radio, &Radio::uploadProgress, this, &Application::onProgress); connect(radio, SIGNAL(uploadError(Radio *)), this, SLOT(onCodeplugUploadError(Radio *))); connect(radio, SIGNAL(uploadComplete(Radio *)), this, SLOT(onCodeplugUploaded(Radio *))); ErrorStack err; css.setBlocking(false); if (radio->startUploadCallsignDB(_users, css, err)) { logDebug() << "Start call-sign DB write..."; _mainWindow->statusBar()->showMessage(tr("Write call-sign DB ...")); _mainWindow->setEnabled(false); } else { ErrorMessageView(err).exec(); progress->setVisible(false); } } void Application::uploadSatellites() { // Start upload satellites Radio *radio = autoDetect(); if (nullptr == radio) { QMessageBox::warning(nullptr, tr("No radio found"), tr("No matching device was found.")); return; } if (! radio->limits().hasSatelliteConfig()) { logDebug() << "Radio " << radio->name() << " does not support satellite tracking."; QMessageBox::information(nullptr, tr("Cannot write satellite config."), tr("The detected radio '%1' does not support satellite tracking.") .arg(radio->name())); radio->deleteLater(); return; } if (! radio->limits().satelliteConfigImplemented()) { logDebug() << "Radio " << radio->name() << " does support satellite tracking but it is not implemented yet."; QMessageBox::critical(nullptr, tr("Cannot write satellite config."), tr("The detected radio '%1' does support satellite tracking. " "This feature, however, is not implemented yet.").arg(radio->name())); radio->deleteLater(); return; } Settings settings; TransferFlags flags; flags.setUpdateDeviceClock(settings.updateDeviceClock()); QProgressBar *progress = _mainWindow->findChild("progress"); progress->setRange(0, 100); progress->setValue(0); progress->setVisible(true); connect(radio, SIGNAL(uploadProgress(int)), progress, SLOT(setValue(int))); connect(radio, SIGNAL(uploadError(Radio *)), this, SLOT(onCodeplugUploadError(Radio *))); connect(radio, SIGNAL(uploadComplete(Radio *)), this, SLOT(onCodeplugUploaded(Radio *))); ErrorStack err; flags.setBlocking(false); if (radio->startUploadSatelliteConfig(_satellites, flags, err)) { logDebug() << "Start satellite config write..."; _mainWindow->statusBar()->showMessage(tr("Write satellite config ...")); _mainWindow->setEnabled(false); } else { ErrorMessageView(err).exec(); progress->setVisible(false); } } void Application::onProgress(int value) { QProgressBar *progress = _mainWindow->findChild("progress"); if (value >= 0) progress->setMaximum(100); progress->setValue(value); } void Application::onCodeplugUploadError(Radio *radio) { _mainWindow->statusBar()->showMessage(tr("Write error")); ErrorMessageView(radio->errorStack()).exec(); _mainWindow->findChild("progress")->setVisible(false); _mainWindow->setEnabled(true); if (radio->wait(250)) radio->deleteLater(); } void Application::onCodeplugUploaded(Radio *radio) { _mainWindow->statusBar()->showMessage(tr("Write complete")); _mainWindow->findChild("progress")->setVisible(false); _mainWindow->setEnabled(true); logDebug() << "Write complete."; if (radio->wait(250)) radio->deleteLater(); } void Application::showSettings() { SettingsDialog dialog; if (QDialog::Accepted != dialog.exec()) return; Settings settings; // Handle positioning if (! settings.queryPosition()) { if (_source) _source->stopUpdates(); setSearchRadius(settings.position(), settings.repeaterSearchRadius()); } else { if (_source) _source->startUpdates(); _searchRadius = settings.repeaterSearchRadius(); } } void Application::showAbout() { QUiLoader loader; QFile uiFile("://ui/aboutdialog.ui"); uiFile.open(QIODevice::ReadOnly); auto obj = loader.load(&uiFile); if (nullptr == obj) return; QDialog *dialog = qobject_cast(obj); if (nullptr == dialog) return; QTextEdit *text = dialog->findChild("textEdit"); text->setHtml(text->toHtml().arg(VERSION_STRING)); QTreeWidget *radioTab = dialog->findChild("radioTable"); radioTab->setColumnCount(1); QHash items; foreach (RadioInfo radio, RadioInfo::allRadios(false)) { if (! items.contains(radio.manufacturer())) items.insert(radio.manufacturer(), new QTreeWidgetItem(QStringList(radio.manufacturer()))); items[radio.manufacturer()]->addChild( new QTreeWidgetItem(QStringList(radio.name()))); foreach (RadioInfo alias, radio.alias()) { if (! items.contains(alias.manufacturer())) items.insert(alias.manufacturer(), new QTreeWidgetItem(QStringList(alias.manufacturer()))); items[alias.manufacturer()]->addChild( new QTreeWidgetItem(QStringList(tr("%1 (alias for %2 %3)").arg(alias.name()) .arg(radio.manufacturer()).arg(radio.name())))); } } radioTab->insertTopLevelItems(0, items.values()); radioTab->sortByColumn(0, Qt::AscendingOrder); connect(dialog, &QDialog::finished, dialog, &QObject::deleteLater); dialog->open(); } void Application::showHelp() { QDesktopServices::openUrl(QUrl("https://static.dm3mat.de/qdmr/manual")); } void Application::editSatellites() { SatelliteDatabaseDialog dialog(_satellites); if (QDialog::Accepted == dialog.exec()) { _satellites->save(); } else { _satellites->load(); } } void Application::onConfigModifed() { if (! _mainWindow) return; _mainWindow->setWindowModified(true); } void Application::positionUpdated(const QGeoPositionInfo &info) { if (info.isValid()) setSearchRadius(info.coordinate(), _searchRadius); } bool Application::hasPosition() const { return _currentPosition.isValid(); } QGeoCoordinate Application::position() const { return _currentPosition; } void Application::setSearchRadius(const QGeoCoordinate &position, unsigned int radius) { QGeoCoordinate lastPosition = _currentPosition; _currentPosition = position; _searchRadius = radius; if (_searchRadius && _currentPosition.isValid() && (!lastPosition.isValid() || _currentPosition.distanceTo(lastPosition)>100*_searchRadius)) { _repeater->setSearchRadius(_currentPosition, _searchRadius); } } void Application::onPaletteChanged(const QPalette &palette) { // Set theme based on UI mode (light vs. dark). if (isDarkMode(palette)) { QIcon::setThemeName("dark"); logDebug() << "Set icon theme to 'dark'."; } else { QIcon::setThemeName("light"); logDebug() << "Set icon theme to 'light'."; } } ================================================ FILE: src/application.hh ================================================ #ifndef APPLICATION_HH #define APPLICATION_HH #include #include #include "config.hh" #include #include "releasenotes.hh" #include "radio.hh" #include class MainWindow; class QTranslator; class RepeaterDatabase; class UserDatabase; class TalkGroupDatabase; class SatelliteDatabase; class RadioIDListView; class GeneralSettingsView; class ContactListView; class GroupListsView; class ChannelListView; class ZoneListView; class ScanListsView; class PositioningSystemListView; class RoamingChannelListView; class RoamingZoneListView; class ExtensionView; class Application : public QApplication { Q_OBJECT public: Application(int &argc, char *argv[]); virtual ~Application(); MainWindow *mainWindow(); bool isModified() const; UserDatabase *user() const; RepeaterDatabase *repeater() const; TalkGroupDatabase *talkgroup() const; SatelliteDatabase *satellite() const; bool hasPosition() const; QGeoCoordinate position() const; unsigned int radius() const; void setSearchRadius(const QGeoCoordinate &position, unsigned int radius); Radio *autoDetect(const ErrorStack &err=ErrorStack()); bool isDarkMode() const; bool isDarkMode(const QPalette &palette) const; public slots: void newCodeplug(); void loadCodeplug(); bool loadCodeplug(const QString &filename, const ErrorStack &err); void saveCodeplug(); void exportCodeplugToChirp(); void importCodeplug(); void detectRadio(); bool verifyCodeplug(Radio *radio=nullptr, bool showSuccess=true); void downloadCodeplug(); void uploadCodeplug(); void uploadCallsignDB(); void uploadSatellites(); void showSettings(); void showAbout(); void showHelp(); void editSatellites(); private slots: void onCodeplugDownloadError(Radio *radio); void onCodeplugDownloaded(Radio *radio, Codeplug *codeplug); void onProgress(int val); void onCodeplugUploadError(Radio *radio); void onCodeplugUploaded(Radio *radio); void onConfigModifed(); void positionUpdated(const QGeoPositionInfo &info); void onPaletteChanged(const QPalette &palette); protected: Config *_config; MainWindow *_mainWindow; QTranslator *_translator; RepeaterDatabase *_repeater; UserDatabase *_users; TalkGroupDatabase *_talkgroups; SatelliteDatabase *_satellites; QGeoPositionInfoSource *_source; QGeoCoordinate _currentPosition; unsigned int _searchRadius; ReleaseNotes _releaseNotes; // Last detected device: USBDeviceDescriptor _lastDevice; }; #endif // APPLICATION_HH ================================================ FILE: src/aprsselect.cc ================================================ #include "aprsselect.hh" #include "gpssystem.hh" #include "config.hh" /* ******************************************************************************************* * * FM APRS selection * ******************************************************************************************* */ FMAPRSSelect::FMAPRSSelect(Config *config, QWidget *parent) : QComboBox(parent) { addItem(tr("[None]"), QVariant::fromValue(nullptr)); for (int i=0; iposSystems()->count(); i++) { if (config->posSystems()->system(i)->is()) { auto sys = config->posSystems()->system(i)->as(); addItem(sys->name(), QVariant::fromValue(sys)); } } } void FMAPRSSelect::setAPRSSystem(FMAPRSSystem *sys) { for (int i=0; i() == sys) { setCurrentIndex(i); break; } } } FMAPRSSystem * FMAPRSSelect::aprsSystem() const { return currentData().value(); } /* ******************************************************************************************* * * Any (FM & DMR) APRS selection * ******************************************************************************************* */ APRSSelect::APRSSelect(Config *config, QWidget *parent) : QComboBox(parent) { addItem(tr("[None]"), QVariant::fromValue(nullptr)); for (int i=0; iposSystems()->count(); i++) { if (config->posSystems()->system(i)->is()) { auto sys = config->posSystems()->system(i)->as(); addItem(sys->name(), QVariant::fromValue(sys)); } } } void APRSSelect::setAPRSSystem(PositionReportingSystem *sys) { for (int i=0; i() == sys) { setCurrentIndex(i); break; } } } PositionReportingSystem * APRSSelect::aprsSystem() const { return currentData().value(); } ================================================ FILE: src/aprsselect.hh ================================================ #ifndef APRSSELECT_HH #define APRSSELECT_HH #include class PositionReportingSystem; class FMAPRSSystem; class Config; class APRSSelect : public QComboBox { Q_OBJECT public: APRSSelect(Config *config, QWidget *parent=nullptr); void setAPRSSystem(PositionReportingSystem *sys); PositionReportingSystem *aprsSystem() const; }; class FMAPRSSelect : public QComboBox { Q_OBJECT public: FMAPRSSelect(Config *config, QWidget *parent=nullptr); void setAPRSSystem(FMAPRSSystem *sys); FMAPRSSystem *aprsSystem() const; }; #endif // APRSSELECT_HH ================================================ FILE: src/aprssystemdialog.cc ================================================ #include "aprssystemdialog.hh" #include "ui_aprssystemdialog.h" #include "settings.hh" static QVector> aprsIconTable{ {FMAPRSSystem::Icon::None, FMAPRSSystem::tr("[None]")}, {FMAPRSSystem::Icon::PoliceStation, FMAPRSSystem::tr("Police station")}, {FMAPRSSystem::Icon::Digipeater, FMAPRSSystem::tr("Digipeater")}, {FMAPRSSystem::Icon::Phone, FMAPRSSystem::tr("Phone")}, {FMAPRSSystem::Icon::DXCluster, FMAPRSSystem::tr("DX cluster")}, {FMAPRSSystem::Icon::HFGateway, FMAPRSSystem::tr("HF gateway")}, {FMAPRSSystem::Icon::SmallPlane, FMAPRSSystem::tr("Plane small")}, {FMAPRSSystem::Icon::MobileSatelliteStation, FMAPRSSystem::tr("Mobile Satellite station")}, {FMAPRSSystem::Icon::WheelChair, FMAPRSSystem::tr("Wheel Chair")}, {FMAPRSSystem::Icon::Snowmobile, FMAPRSSystem::tr("Snowmobile")}, {FMAPRSSystem::Icon::RedCross, FMAPRSSystem::tr("Red cross")}, {FMAPRSSystem::Icon::BoyScout, FMAPRSSystem::tr("Boy scout")}, {FMAPRSSystem::Icon::Home, FMAPRSSystem::tr("Home")}, {FMAPRSSystem::Icon::X, FMAPRSSystem::tr("X")}, {FMAPRSSystem::Icon::RedDot, FMAPRSSystem::tr("Red dot")}, {FMAPRSSystem::Icon::Circle0, FMAPRSSystem::tr("Circle 0")}, {FMAPRSSystem::Icon::Circle1, FMAPRSSystem::tr("Circle 1")}, {FMAPRSSystem::Icon::Circle2, FMAPRSSystem::tr("Circle 2")}, {FMAPRSSystem::Icon::Circle3, FMAPRSSystem::tr("Circle 3")}, {FMAPRSSystem::Icon::Circle4, FMAPRSSystem::tr("Circle 4")}, {FMAPRSSystem::Icon::Circle5, FMAPRSSystem::tr("Circle 5")}, {FMAPRSSystem::Icon::Circle6, FMAPRSSystem::tr("Circle 6")}, {FMAPRSSystem::Icon::Circle7, FMAPRSSystem::tr("Circle 7")}, {FMAPRSSystem::Icon::Circle8, FMAPRSSystem::tr("Circle 8")}, {FMAPRSSystem::Icon::Circle9, FMAPRSSystem::tr("Circle 9")}, {FMAPRSSystem::Icon::Fire, FMAPRSSystem::tr("Fire")}, {FMAPRSSystem::Icon::Campground, FMAPRSSystem::tr("Campground")}, {FMAPRSSystem::Icon::Motorcycle, FMAPRSSystem::tr("Motorcycle")}, {FMAPRSSystem::Icon::RailEngine, FMAPRSSystem::tr("Rail engine")}, {FMAPRSSystem::Icon::Car, FMAPRSSystem::tr("Car")}, {FMAPRSSystem::Icon::FileServer, FMAPRSSystem::tr("File server")}, {FMAPRSSystem::Icon::HCFuture, FMAPRSSystem::tr("HC Future")}, {FMAPRSSystem::Icon::AidStation, FMAPRSSystem::tr("Aid station")}, {FMAPRSSystem::Icon::BBS, FMAPRSSystem::tr("BBS")}, {FMAPRSSystem::Icon::Canoe, FMAPRSSystem::tr("Canoe")}, {FMAPRSSystem::Icon::Eyeball, FMAPRSSystem::tr("Eyeball")}, {FMAPRSSystem::Icon::Tractor, FMAPRSSystem::tr("Tractor")}, {FMAPRSSystem::Icon::GridSquare, FMAPRSSystem::tr("Grid Square")}, {FMAPRSSystem::Icon::Hotel, FMAPRSSystem::tr("Hotel")}, {FMAPRSSystem::Icon::TCPIP, FMAPRSSystem::tr("TCP/IP")}, {FMAPRSSystem::Icon::School, FMAPRSSystem::tr("School")}, {FMAPRSSystem::Icon::Logon, FMAPRSSystem::tr("Logon")}, {FMAPRSSystem::Icon::MacOS, FMAPRSSystem::tr("MacOS")}, {FMAPRSSystem::Icon::NTSStation, FMAPRSSystem::tr("NTS station")}, {FMAPRSSystem::Icon::Balloon, FMAPRSSystem::tr("Balloon")}, {FMAPRSSystem::Icon::PoliceCar, FMAPRSSystem::tr("Police car")}, {FMAPRSSystem::Icon::TBD, FMAPRSSystem::tr("TBD")}, {FMAPRSSystem::Icon::RV, FMAPRSSystem::tr("RV")}, {FMAPRSSystem::Icon::Shuttle, FMAPRSSystem::tr("Shuttle")}, {FMAPRSSystem::Icon::SSTV, FMAPRSSystem::tr("SSTV")}, {FMAPRSSystem::Icon::Bus, FMAPRSSystem::tr("Bus")}, {FMAPRSSystem::Icon::ATV, FMAPRSSystem::tr("ATV")}, {FMAPRSSystem::Icon::WXService, FMAPRSSystem::tr("Weather service")}, {FMAPRSSystem::Icon::Helo, FMAPRSSystem::tr("Helo")}, {FMAPRSSystem::Icon::Yacht, FMAPRSSystem::tr("Yacht")}, {FMAPRSSystem::Icon::Windows, FMAPRSSystem::tr("MS Windows")}, {FMAPRSSystem::Icon::Jogger, FMAPRSSystem::tr("Jogger")}, {FMAPRSSystem::Icon::Triangle, FMAPRSSystem::tr("Triangle")}, {FMAPRSSystem::Icon::PBBS, FMAPRSSystem::tr("PBBS")}, {FMAPRSSystem::Icon::LargePlane, FMAPRSSystem::tr("Plane large")}, {FMAPRSSystem::Icon::WXStation, FMAPRSSystem::tr("Weather station")}, {FMAPRSSystem::Icon::DishAntenna, FMAPRSSystem::tr("Dish antenna")}, {FMAPRSSystem::Icon::Ambulance, FMAPRSSystem::tr("Ambulance")}, {FMAPRSSystem::Icon::Bike, FMAPRSSystem::tr("Bike")}, {FMAPRSSystem::Icon::ICP, FMAPRSSystem::tr("ICP")}, {FMAPRSSystem::Icon::FireStation, FMAPRSSystem::tr("Fire station")}, {FMAPRSSystem::Icon::Horse, FMAPRSSystem::tr("Horse")}, {FMAPRSSystem::Icon::FireTruck, FMAPRSSystem::tr("Fire truck")}, {FMAPRSSystem::Icon::Glider, FMAPRSSystem::tr("Glider")}, {FMAPRSSystem::Icon::Hospital, FMAPRSSystem::tr("Hospital")}, {FMAPRSSystem::Icon::IOTA, FMAPRSSystem::tr("IOTA")}, {FMAPRSSystem::Icon::Jeep, FMAPRSSystem::tr("Jeep")}, {FMAPRSSystem::Icon::SmallTruck, FMAPRSSystem::tr("Truck small")}, {FMAPRSSystem::Icon::Laptop, FMAPRSSystem::tr("Laptop")}, {FMAPRSSystem::Icon::MicE, FMAPRSSystem::tr("Mic-E")}, {FMAPRSSystem::Icon::Node, FMAPRSSystem::tr("Node")}, {FMAPRSSystem::Icon::EOC, FMAPRSSystem::tr("EOC")}, {FMAPRSSystem::Icon::Rover, FMAPRSSystem::tr("Rover")}, {FMAPRSSystem::Icon::Grid, FMAPRSSystem::tr("Grid")}, {FMAPRSSystem::Icon::Antenna, FMAPRSSystem::tr("Antenna")}, {FMAPRSSystem::Icon::PowerBoat, FMAPRSSystem::tr("Power boat")}, {FMAPRSSystem::Icon::TruckStop, FMAPRSSystem::tr("Truck stop")}, {FMAPRSSystem::Icon::TruckLarge, FMAPRSSystem::tr("Truck large")}, {FMAPRSSystem::Icon::Van, FMAPRSSystem::tr("Van")}, {FMAPRSSystem::Icon::Water, FMAPRSSystem::tr("Water")}, {FMAPRSSystem::Icon::XAPRS, FMAPRSSystem::tr("XAPRS")}, {FMAPRSSystem::Icon::Yagi, FMAPRSSystem::tr("Yagi")}, {FMAPRSSystem::Icon::Shelter, FMAPRSSystem::tr("Shelter")}}; APRSSystemDialog::APRSSystemDialog(Config *config, QWidget *parent) : QDialog(parent), _config(config), _myAPRS(new FMAPRSSystem()), _aprs(nullptr), ui(new Ui::aprssystemdialog), _icons0(":/icons/aprs/table0.png") { setWindowTitle(tr("Create APRS system")); construct(); } APRSSystemDialog::APRSSystemDialog(Config *config, FMAPRSSystem *aprs, QWidget *parent) : QDialog(parent), _config(config), _myAPRS(new FMAPRSSystem()), _aprs(aprs), ui(new Ui::aprssystemdialog), _icons0(":/icons/aprs/table0.png") { setWindowTitle(tr("Edit APRS system")); if (_aprs) _myAPRS->copy(*_aprs); construct(); } APRSSystemDialog::~APRSSystemDialog() { delete ui; } void APRSSystemDialog::construct() { // Construct UI ui->setupUi(this); Settings settings; // Setup name ui->name->setText(_myAPRS->name()); // Setup analog revert channels ui->channel->addItem(tr("[Selected]"), QVariant::fromValue(nullptr)); if (! _myAPRS->hasRevertChannel()) ui->channel->setCurrentIndex(0); for (int i=0, j=1; i<_config->channelList()->count(); i++) { if (! _config->channelList()->channel(i)->is()) continue; FMChannel *ch = _config->channelList()->channel(i)->as(); ui->channel->addItem(ch->name(), QVariant::fromValue(ch)); if (_myAPRS->hasRevertChannel() && (_myAPRS->revertChannel() == ch)) ui->channel->setCurrentIndex(j); j++; } ui->source->setText(_myAPRS->source()); ui->srcSSID->setValue(_myAPRS->srcSSID()); ui->destination->setText(_myAPRS->destination()); ui->destSSID->setValue(_myAPRS->destSSID()); ui->path->setText(_myAPRS->path()); // Setup icons for (int i=0; i &item = aprsIconTable[i]; ui->icon->addItem(aprsIcon(item.first), item.second, unsigned(item.first)); if (_myAPRS->icon() == item.first) ui->icon->setCurrentIndex(i); } if (_myAPRS->periodDisabled()) ui->updatePeriod->setValue(0); else ui->updatePeriod->setValue(_myAPRS->period().seconds()); ui->message->setText(_myAPRS->message()); ui->extensionView->setObjectName("aprsSystemExtension"); ui->extensionView->setObject(_myAPRS, _config); } FMAPRSSystem * APRSSystemDialog::aprsSystem() { _myAPRS->setName(ui->name->text().simplified()); if (ui->channel->currentData().isNull()) _myAPRS->resetRevertChannel(); else _myAPRS->setRevertChannel(ui->channel->currentData().value()); _myAPRS->setSource(ui->source->text().simplified(), ui->srcSSID->value()); _myAPRS->setDestination(ui->destination->text().simplified(), ui->destSSID->value()); _myAPRS->setPath(ui->path->text().simplified()); _myAPRS->setIcon(FMAPRSSystem::Icon(ui->icon->currentData().toUInt())); if (0 == ui->updatePeriod->value()) _myAPRS->disablePeriod(); else _myAPRS->setPeriod(Interval::fromSeconds(ui->updatePeriod->value())); _myAPRS->setMessage(ui->message->text().simplified()); FMAPRSSystem *system = _myAPRS; if (_aprs) { _aprs->copy(*_myAPRS); system = _aprs; } else { _myAPRS->setParent(nullptr); } return system; } QIcon APRSSystemDialog::aprsIcon(FMAPRSSystem::Icon icon) { unsigned table = (FMAPRSSystem::TABLE_MASK & (unsigned)icon); unsigned idx = (FMAPRSSystem::ICON_MASK & (unsigned)icon); unsigned row = idx/16, col = idx % 16; if (FMAPRSSystem::PRIMARY_TABLE == table) { return QPixmap::fromImage(_icons0.copy(24*col, 24*row, 24,24)); } return QPixmap::fromImage(_icons0.copy(24,0,24,24)); } ================================================ FILE: src/aprssystemdialog.hh ================================================ #ifndef APRSSYSTEMDIALOG_HH #define APRSSYSTEMDIALOG_HH #include #include #include "config.hh" namespace Ui { class aprssystemdialog; } class APRSSystemDialog : public QDialog { Q_OBJECT public: explicit APRSSystemDialog(Config *config, QWidget *parent = nullptr); explicit APRSSystemDialog(Config *config, FMAPRSSystem *aprs, QWidget *parent = nullptr); virtual ~APRSSystemDialog(); FMAPRSSystem *aprsSystem(); protected: void construct(); QIcon aprsIcon(FMAPRSSystem::Icon icon); protected: Config *_config; FMAPRSSystem *_myAPRS; FMAPRSSystem *_aprs; private: Ui::aprssystemdialog *ui; QImage _icons0; }; #endif // APRSSYSTEMDIALOG_HH ================================================ FILE: src/aprssystemdialog.ui ================================================ aprssystemdialog 0 0 569 431 0 0 Edit APRS System 0 Basic 6 6 Name Channel Source 6 - 15 7 Destination WIDE3 6 - 15 3 Path Icon Update period [s] Message s 6000 10 300 Extensions Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
buttonBox accepted() aprssystemdialog accept() 248 254 157 274 buttonBox rejected() aprssystemdialog reject() 316 260 286 274
================================================ FILE: src/bandwidthselect.cc ================================================ #include "bandwidthselect.hh" #include "channel.hh" BandwidthSelect::BandwidthSelect(QWidget *parent) : QComboBox(parent) { addItem(tr("Narrow (12.5 kHz)"), QVariant::fromValue(FMChannel::Bandwidth::Narrow)); addItem(tr("Wide (25 kHz)"), QVariant::fromValue(FMChannel::Bandwidth::Wide)); } void BandwidthSelect::setBandwidth(FMChannel::Bandwidth bw) { setCurrentIndex((FMChannel::Bandwidth::Narrow == bw) ? 0 : 1); } FMChannel::Bandwidth BandwidthSelect::bandwidth() const { return currentData().value(); } ================================================ FILE: src/bandwidthselect.hh ================================================ #ifndef BANDWIDTHSELECT_HH #define BANDWIDTHSELECT_HH #include #include "channel.hh" class BandwidthSelect : public QComboBox { Q_OBJECT public: explicit BandwidthSelect(QWidget *parent=nullptr); void setBandwidth(FMChannel::Bandwidth bw); FMChannel::Bandwidth bandwidth() const; }; #endif // BANDWIDTHSELECT_HH ================================================ FILE: src/channel_type_edit.cc ================================================ #include "channel_type_edit.hh" #include #include #include #include #include #include #include #include #include #include "channel.hh" ChannelTypeEdit::ChannelTypeEdit(QWidget *parent) : QWidget(parent), _types(Channel::Type::None) { _label = new QLabel(); _label->setText(QMetaEnum::fromType().valueToKeys(_types)); _label->setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); auto edit = new QPushButton(); edit->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); edit->setIcon(QIcon::fromTheme("edit-edit")); auto box = new QHBoxLayout(); box->addWidget(edit); box->addWidget(_label); setLayout(box); connect(edit, &QPushButton::clicked, this, &ChannelTypeEdit::onEditClicked); } Channel::Types ChannelTypeEdit::types() const { return _types; } void ChannelTypeEdit::setTypes(Channel::Types types) { _types = types; _label->setText(QMetaEnum::fromType().valueToKeys(_types)); } void ChannelTypeEdit::onEditClicked() { ChannelTypeSelectionDialog dialog(_types); if (QDialog::Accepted != dialog.exec()) return; setTypes(dialog.types()); emit typesChanged(types()); } ChannelTypeSelectionDialog::ChannelTypeSelectionDialog(Channel::Types types, QWidget *parent) : QDialog(parent), _list(new QListWidget(this)) { setWindowTitle(tr("Select channel types")); auto e = QMetaEnum::fromType(); for (int i=1; isetData(Qt::UserRole, QVariant::fromValue(value)); item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled | Qt::ItemIsSelectable); item->setCheckState(types.testFlag(value) ? Qt::Checked : Qt::Unchecked); _list->addItem(item); } auto bbox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto box = new QVBoxLayout(); box->addWidget(_list); box->addWidget(bbox); setLayout(box); connect(bbox, &QDialogButtonBox::accepted, this, &QDialog::accept); connect(bbox, &QDialogButtonBox::rejected, this, &QDialog::reject); } Channel::Types ChannelTypeSelectionDialog::types() const { Channel::Types types = Channel::Type::None; for (int i=0; i<_list->count(); i++) { auto item = _list->item(i); if (item->checkState() == Qt::Checked) types |= item->data(Qt::UserRole).value(); } return types; } ================================================ FILE: src/channel_type_edit.hh ================================================ #ifndef CHANNEL_TYPE_EDIT_HH #define CHANNEL_TYPE_EDIT_HH #include #include "channel.hh" class QLabel; class QListWidget; class ChannelTypeEdit: public QWidget { Q_OBJECT public: explicit ChannelTypeEdit(QWidget *parent = nullptr); Channel::Types types() const; public slots: void setTypes(Channel::Types types); signals: void typesChanged(Channel::Types types); protected slots: void onEditClicked(); protected: QLabel *_label; Channel::Types _types; }; class ChannelTypeSelectionDialog: public QDialog { Q_OBJECT public: ChannelTypeSelectionDialog(Channel::Types types, QWidget *parent = nullptr); Channel::Types types() const; protected: QListWidget *_list; }; #endif //CHANNEL_TYPE_EDIT_HH ================================================ FILE: src/channelcombobox.cc ================================================ #include "channelcombobox.hh" #include "channel.hh" #include /* ********************************************************************************************* * * Implementation of ChannelComboBox * ********************************************************************************************* */ ChannelComboBox::ChannelComboBox(ChannelList *list, bool includeSelectedChannel, QWidget *parent) : QComboBox(parent) { setInsertPolicy(QComboBox::NoInsert); setEditable(true); completer()->setCompletionMode(QCompleter::PopupCompletion); if (includeSelectedChannel) addItem(SelectedChannel::get()->name(), QVariant::fromValue(SelectedChannel::get())); for (int i=0; icount(); i++) addItem(list->channel(i)->name(), QVariant::fromValue(list->channel(i))); } Channel * ChannelComboBox::channel() const { return currentData().value(); } ================================================ FILE: src/channelcombobox.hh ================================================ #ifndef CHANNELCOMBOBOX_HH #define CHANNELCOMBOBOX_HH #include class Channel; class ChannelList; class ChannelComboBox: public QComboBox { Q_OBJECT public: ChannelComboBox(ChannelList *lst, bool includeSelectedChannel=false, QWidget *parent=nullptr); Channel *channel() const; }; #endif // CHANNELCOMBOBOX_HH ================================================ FILE: src/channeldialog.cc ================================================ #include "channeldialog.hh" #include "channel.hh" #include "settings.hh" #include "application.hh" #include "ui_channeldialog.h" ChannelDialog::ChannelDialog(Config *config, QWidget *parent) : QDialog(parent), _config(config), _channel() { ui = new Ui::ChannelDialog(); ui->setupUi(this); Settings settings; if (settings.hideChannelNote()) { ui->hintLabel->setVisible(false); layout()->invalidate(); adjustSize(); } connect(ui->hintLabel, &QLabel::linkActivated, this, &ChannelDialog::onHideChannelHint); ui->offsetComboBox->addItem(QIcon::fromTheme("symbol-none"), ""); ui->offsetComboBox->setItemData(0, tr("No offset"), Qt::ToolTipRole); ui->offsetComboBox->addItem(QIcon::fromTheme("symbol-plus"), ""); ui->offsetComboBox->setItemData(1, tr("Positive offset"), Qt::ToolTipRole); ui->offsetComboBox->addItem(QIcon::fromTheme("symbol-minus"), ""); ui->offsetComboBox->setItemData(2, tr("Negative offset"), Qt::ToolTipRole); connect(ui->txFrequency, &QLineEdit::editingFinished, this, &ChannelDialog::onTxFrequencyEdited); connect(ui->rxFrequency, &QLineEdit::editingFinished, this, &ChannelDialog::onRxFrequencyEdited); connect(ui->offsetLineEdit, &QLineEdit::editingFinished, this, &ChannelDialog::onOffsetFrequencyEdited); connect(ui->offsetComboBox, &QComboBox::currentIndexChanged, this, &ChannelDialog::onOffsetDirectionChanged); ui->powerValue->setItemData(0, unsigned(Channel::Power::Max)); ui->powerValue->setItemData(1, unsigned(Channel::Power::High)); ui->powerValue->setItemData(2, unsigned(Channel::Power::Mid)); ui->powerValue->setItemData(3, unsigned(Channel::Power::Low)); ui->powerValue->setItemData(4, unsigned(Channel::Power::Min)); ui->powerDefault->setChecked(true); ui->powerValue->setEnabled(false); ui->powerValue->setCurrentIndex(1); connect(ui->powerDefault, &QCheckBox::toggled, [this](bool checked) { this->ui->powerValue->setEnabled(! checked); }); ui->totDefault->setChecked(true); ui->totValue->setValue(0); ui->totValue->setEnabled(false); connect(ui->totDefault, &QCheckBox::toggled, [this](bool checked) { this->ui->totValue->setEnabled(! checked); }); ui->scanList->addItem(tr("[None]"), QVariant::fromValue(nullptr)); for (int i=0; i<_config->scanlists()->count(); i++) { auto lst = _config->scanlists()->scanlist(i); ui->scanList->addItem(lst->name(), QVariant::fromValue(lst)); } ui->voxDefault->setChecked(true); ui->voxValue->setValue(0); ui->voxValue->setEnabled(false); connect(ui->voxDefault, &QCheckBox::toggled, [this](bool checked) { this->ui->voxValue->setEnabled(! checked); }); updateOffsetFrequency(); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &ChannelDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &ChannelDialog::reject); } ChannelDialog::~ChannelDialog() { delete ui; } void ChannelDialog::setChannel(Channel *channel) { _channel = channel; if (_channel.isNull()) return; ui->channelName->setText(_channel->name()); ui->rxFrequency->setText(_channel->rxFrequency().format(Frequency::Unit::MHz)); ui->txFrequency->setText(_channel->txFrequency().format(Frequency::Unit::MHz)); updateOffsetFrequency(); if (! _channel->defaultPower()) { ui->powerDefault->setChecked(false); ui->powerValue->setEnabled(true); switch (_channel->power()) { case Channel::Power::Max: ui->powerValue->setCurrentIndex(0); break; case Channel::Power::High: ui->powerValue->setCurrentIndex(1); break; case Channel::Power::Mid: ui->powerValue->setCurrentIndex(2); break; case Channel::Power::Low: ui->powerValue->setCurrentIndex(3); break; case Channel::Power::Min: ui->powerValue->setCurrentIndex(4); break; } } if (! _channel->defaultTimeout()) { ui->totDefault->setChecked(false); ui->totValue->setEnabled(true); ui->totValue->setValue(_channel->timeout().seconds()); } ui->rxOnly->setChecked(_channel->rxOnly()); if (! _channel->defaultVOX()) { ui->voxDefault->setChecked(false); ui->voxValue->setEnabled(true); ui->voxValue->setValue(_channel->vox().value()); } ui->extensionView->setObjectName("ChannelExtension"); ui->extensionView->setObject(_channel, _config); for (int i=0; iscanList->count(); i++) { if (ui->scanList->itemData(i).value() == _channel->scanList()) ui->scanList->setCurrentIndex(i); } } void ChannelDialog::accept() { if (_channel.isNull()) return; _channel->setName(ui->channelName->text().simplified()); _channel->setRXFrequency(Frequency::fromString(ui->rxFrequency->text())); _channel->setTXFrequency(Frequency::fromString(ui->txFrequency->text())); if (ui->powerDefault->isChecked()) { _channel->setDefaultPower(); } else { _channel->setPower(Channel::Power(ui->powerValue->currentData().toUInt())); } if (ui->totDefault->isChecked()) _channel->setDefaultTimeout(); else _channel->setTimeout(Interval::fromSeconds(ui->totValue->value())); _channel->setRXOnly(ui->rxOnly->isChecked()); _channel->setScanList(ui->scanList->currentData().value()); if (ui->voxDefault->isChecked()) _channel->setVOXDefault(); else _channel->setVOX(Level::fromValue(ui->voxValue->value())); QDialog::accept(); } void ChannelDialog::onTxFrequencyEdited() { // Normalize frequency format ui->txFrequency->setText(Frequency::fromString(ui->txFrequency->text()).format()); updateOffsetFrequency(); } void ChannelDialog::onRxFrequencyEdited() { auto rx = Frequency::fromString(ui->rxFrequency->text()); // normalize RX frequency ui->rxFrequency->setText(rx.format()); // if no previous txFrequency set, match rx frequency. if (ui->txFrequency->text().isEmpty()) ui->txFrequency->setText(rx.format()); updateOffsetFrequency(); } void ChannelDialog::onOffsetFrequencyEdited() { auto offsetFrequency = FrequencyOffset::fromString(ui->offsetLineEdit->text()).abs(); // By default use same as RX frequency auto rx = Frequency::fromString(ui->rxFrequency->text()), tx = rx; switch (ui->offsetComboBox->currentIndex()) { case 0: break; case 1: tx = rx + offsetFrequency; break; case 2: tx = rx + offsetFrequency.invert(); break; } ui->offsetLineEdit->setText(offsetFrequency.format()); ui->txFrequency->setText(tx.format()); } void ChannelDialog::onOffsetDirectionChanged(int index) { auto rx = Frequency::fromString(ui->rxFrequency->text()), tx = rx; FrequencyOffset offsetFrequency = FrequencyOffset::fromString(ui->offsetLineEdit->text()).abs(); switch (index) { case 0: ui->offsetLineEdit->setEnabled(false); break; case 1: ui->offsetLineEdit->setEnabled(true); tx = rx + offsetFrequency; break; case 2: ui->offsetLineEdit->setEnabled(true); tx = rx + offsetFrequency.invert(); break; } ui->txFrequency->setText(tx.format()); } void ChannelDialog::updateOffsetFrequency() { Frequency rx,tx; if (!rx.parse(ui->rxFrequency->text().simplified()) || !tx.parse(ui->txFrequency->text().simplified())) return; // Show absolute value ui->offsetLineEdit->setText((tx-rx).abs().format()); // Use combo box to indicate direction updateComboBox(); } void ChannelDialog::updateComboBox() { auto rx = Frequency::fromString(ui->rxFrequency->text()), tx = Frequency::fromString(ui->txFrequency->text()); if (tx == rx) { ui->offsetComboBox->setCurrentIndex(0); ui->offsetLineEdit->setEnabled(false); } else if (tx > rx) { ui->offsetComboBox->setCurrentIndex(1); ui->offsetLineEdit->setEnabled(true); } else { ui->offsetComboBox->setCurrentIndex(2); ui->offsetLineEdit->setEnabled(true); } } void ChannelDialog::onHideChannelHint() { Settings settings; settings.setHideChannelNote(true); ui->hintLabel->setVisible(false); layout()->invalidate(); adjustSize(); } ================================================ FILE: src/channeldialog.hh ================================================ #ifndef CHANNELDIALOG_HH #define CHANNELDIALOG_HH #include #include namespace Ui { class ChannelDialog; } class Config; class Channel; class ChannelDialog : public QDialog { Q_OBJECT public: ChannelDialog(Config *config, QWidget *parent=nullptr); ~ChannelDialog() override; void setChannel(Channel *mychannel); public slots: void accept() override; protected slots: void updateOffsetFrequency(); private slots: void onRxFrequencyEdited(); void onTxFrequencyEdited(); void onOffsetFrequencyEdited(); void onOffsetDirectionChanged(int index); void onHideChannelHint(); private: void updateComboBox(); protected: Ui::ChannelDialog *ui; QPointer _config; QPointer _channel; }; #endif // CHANNELDIALOG_HH ================================================ FILE: src/channeldialog.ui ================================================ ChannelDialog 0 0 680 545 Edit Channel padding:10px;border: 2px solid black; border-radius: 10px; <html><head/><body><p><span style=" font-weight:600;">Note:</span> qdmr provides some auto-completion for channels. That is, start typing the call-sign of a repeater. After three chars are entered, a request is sent to repeaterbook.com to retrieve matching repeaters. These requests may take some time. The results are stored locally in a cache.</p><p>A drop-down list will appear, allowing to select a repeater. Once one repeater is selected, the RX/TX frequencies and CTCSS tones are filled in (if applicable).</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">hide</span></a></p></body></html> Qt::TextFormat::RichText true 0 Basic Name 200 0 Enter a repater call-sign. Rx Frequency Tx Frequency Tx Offset 0 0 62 0 62 16777215 -1 QComboBox::SizeAdjustPolicy::AdjustToMinimumContentsLengthWithIcon true Power 0 0 Max High Mid Low Min Default Tx Timeout 0 0 Off s 9999 Default VOX Level 0 0 Off 10 Default Rx Only Scan List 0 0 QLayout::SizeConstraint::SetDefaultConstraint QFormLayout::FieldGrowthPolicy::FieldsStayAtSizeHint Extensions Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok ExtensionView QWidget
extensionview.hh
1
================================================ FILE: src/channellistview.cc ================================================ #include "channellistview.hh" #include "ui_channellistview.h" #include "fmchanneldialog.hh" #include "amchanneldialog.hh" #include "dmrchanneldialog.hh" #include "config.hh" #include "settings.hh" #include #include #include #include #include "m17channeldialog.hh" ChannelListView::ChannelListView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::ChannelListView), _config(config) { ui->setupUi(this); connect(ui->channelTableView->header(), SIGNAL(sectionCountChanged(int,int)), this, SLOT(loadChannelListSectionState())); connect(ui->channelTableView->header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(storeChannelListSectionState())); ui->channelTableView->setModel(new ChannelListWrapper(_config->channelList(), ui->channelTableView)); auto menu = new QMenu(); menu->addAction(ui->actionAddDMRChannel); menu->addAction(ui->actionAddFMChannel); menu->addAction(ui->actionAddAMChannel); ui->addChannel->setMenu(menu); connect(ui->actionAddFMChannel, &QAction::triggered, this, &ChannelListView::onAddFMChannel); connect(ui->actionAddAMChannel, &QAction::triggered, this, &ChannelListView::onAddAMChannel); connect(ui->actionAddDMRChannel, &QAction::triggered, this, &ChannelListView::onAddDMRChannel); connect(ui->cloneChannel, SIGNAL(clicked()), this, SLOT(onCloneChannel())); connect(ui->remChannel, SIGNAL(clicked()), this, SLOT(onRemChannel())); connect(ui->channelTableView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditChannel(unsigned))); } ChannelListView::~ChannelListView() { delete ui; } void ChannelListView::onAddFMChannel() { FMChannelDialog dialog(_config, parentWidget()); auto newCh = new FMChannel(); dialog.setChannel(newCh); if (QDialog::Accepted != dialog.exec()) { delete newCh; return; } int row=-1; if (ui->channelTableView->hasSelection()) row = ui->channelTableView->selection().second+1; _config->channelList()->add(newCh, row); } void ChannelListView::onAddAMChannel() { AMChannelDialog dialog(_config, parentWidget()); auto newCh = new AMChannel(); dialog.setChannel(newCh); if (QDialog::Accepted != dialog.exec()) { delete newCh; return; } int row=-1; if (ui->channelTableView->hasSelection()) row = ui->channelTableView->selection().second+1; _config->channelList()->add(newCh, row); } void ChannelListView::onAddDMRChannel() { DMRChannelDialog dialog(_config, parentWidget()); auto newCh = new DMRChannel(); dialog.setChannel(newCh); if (QDialog::Accepted != dialog.exec()) { delete newCh; return; } int row=-1; if (ui->channelTableView->hasSelection()) row = ui->channelTableView->selection().second+1; _config->channelList()->add(newCh, row); } void ChannelListView::onCloneChannel() { // get selection int row = ui->channelTableView->selection().first; if ((! ui->channelTableView->hasSelection()) || (row != ui->channelTableView->selection().second)) { QMessageBox::information(nullptr, tr("Select a single channel first"), tr("To clone a channel, please select a single channel to clone."), QMessageBox::Close); return; } // Get selected channel Channel *channel = _config->channelList()->channel(row); if (! channel) return; // Dispatch by type if (channel->is()) { // clone channel FMChannel *clone = channel->clone()->as(); // open editor FMChannelDialog dialog(_config); dialog.setChannel(clone); if (QDialog::Accepted != dialog.exec()) { // if rejected -> destroy clone clone->deleteLater(); return; } // add to list (below selected one) _config->channelList()->add(clone, row+1); } else if (channel->is()) { // clone channel auto clone = channel->clone()->as(); // open editor AMChannelDialog dialog(_config); dialog.setChannel(clone); if (QDialog::Accepted != dialog.exec()) { // if rejected -> destroy clone clone->deleteLater(); return; } // add to list (below selected one) _config->channelList()->add(clone, row+1); } else if (channel->is()) { // clone channel DMRChannel *clone = channel->clone()->as(); // open editor DMRChannelDialog dialog(_config); dialog.setChannel(clone); if (QDialog::Accepted != dialog.exec()) { clone->deleteLater(); return; } // add to list (below selected one) _config->channelList()->add(clone, row+1); } } void ChannelListView::onRemChannel() { if (! ui->channelTableView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete channel"), tr("Cannot delete channel: You have to select a channel first.")); return; } // Get selection and ask for deletion QPair rows = ui->channelTableView->selection(); int rowcount = rows.second-rows.first+1; if (rows.first == rows.second) { QString name = _config->channelList()->channel(rows.first)->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete channel?"), tr("Delete channel %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete channel?"), tr("Delete %1 channels?").arg(rowcount))) return; } // collect all selected channels // need to collect them first as rows change when deleting channels QList channels; channels.reserve(rowcount); for(int row=rows.first; row<=rows.second; row++) channels.push_back(_config->channelList()->channel(row)); // remove channels foreach (Channel *channel, channels) _config->channelList()->del(channel); } void ChannelListView::onEditChannel(unsigned row) { Channel *channel = _config->channelList()->channel(row); if (! channel) return; if (channel->is()) { FMChannelDialog dialog(_config); dialog.setChannel(channel->as()); if (QDialog::Accepted != dialog.exec()) return; } else if (channel->is()) { DMRChannelDialog dialog(_config); dialog.setChannel(channel->as()); if (QDialog::Accepted != dialog.exec()) return; } else if (channel->is()) { AMChannelDialog dialog(_config); dialog.setChannel(channel->as()); if (QDialog::Accepted != dialog.exec()) return; } else if (channel->is()) { M17ChannelDialog dialog(_config); dialog.setChannel(channel->as()); if (QDialog::Accepted != dialog.exec()) return; } } void ChannelListView::loadChannelListSectionState() { Settings settings; ui->channelTableView->header()->restoreState(settings.headerState("channelList")); } void ChannelListView::storeChannelListSectionState() { Settings settings; settings.setHeaderState("channelList", ui->channelTableView->header()->saveState()); } ================================================ FILE: src/channellistview.hh ================================================ #ifndef CHANNELLISTVIEW_HH #define CHANNELLISTVIEW_HH #include class Config; class QSortFilterProxyModel; namespace Ui { class ChannelListView; } class ChannelListView : public QWidget { Q_OBJECT public: explicit ChannelListView(Config *config, QWidget *parent = nullptr); ~ChannelListView(); protected slots: void onAddFMChannel(); void onAddAMChannel(); //void onAddM17Channel(); void onAddDMRChannel(); void onCloneChannel(); void onRemChannel(); void onEditChannel(unsigned row); void loadChannelListSectionState(); void storeChannelListSectionState(); private: Ui::ChannelListView *ui; Config *_config; }; #endif // CHANNELLISTVIEW_HH ================================================ FILE: src/channellistview.ui ================================================ ChannelListView 0 0 547 300 Form 0 0 Add Channel ... Alt+A Clone Channel Alt+C Delete Channel Alt+- Add FM Channel Adds a new FM channel QAction::MenuRole::NoRole Add DMR Channel Adds a new DMR channel. QAction::MenuRole::NoRole Add AM Channel Adds a new AM channel. QAction::MenuRole::NoRole ConfigObjectTableView QWidget
configobjecttableview.hh
1
================================================ FILE: src/channelselectiondialog.cc ================================================ #include "channelselectiondialog.hh" #include "channel.hh" #include "channelcombobox.hh" #include "configreference.hh" #include #include #include #include /* ********************************************************************************************* * * Implementation of ChannelSelectionDialog * ********************************************************************************************* */ ChannelSelectionDialog::ChannelSelectionDialog(ChannelList *lst, bool includeSelectedChannel, QWidget *parent) : QDialog(parent) { _channel = new ChannelComboBox(lst, includeSelectedChannel); QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(new QLabel(tr("Select a channel:"))); layout->addWidget(_channel); layout->addWidget(bbox); setLayout(layout); } Channel * ChannelSelectionDialog::channel() const { return _channel->channel(); } /* ********************************************************************************************* * * Implementation of MultiChannelSelectionDialog * ********************************************************************************************* */ MultiChannelSelectionDialog::MultiChannelSelectionDialog( ChannelList *lst, bool includeSelectedChannel, bool digitalOnly, ChannelRefList *exclude, QWidget *parent) : QDialog(parent) { _channel = new QListWidget(); if (includeSelectedChannel) { QListWidgetItem *item = new QListWidgetItem(tr("[Selected]")); item->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); item->setData(Qt::UserRole, QVariant::fromValue(SelectedChannel::get())); item->setCheckState(Qt::Unchecked); _channel->addItem(item); } for (int i=0; icount(); i++) { Channel *channel = lst->channel(i); if (digitalOnly && channel->is()) continue; if (exclude && exclude->has(channel)) continue; QListWidgetItem *item = new QListWidgetItem(channel->name()); item->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); item->setData(Qt::UserRole, QVariant::fromValue(channel)); item->setCheckState(Qt::Unchecked); _channel->addItem(item); } QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(new QLabel(tr("Select a channel:"))); layout->addWidget(_channel); layout->addWidget(bbox); setLayout(layout); } QList MultiChannelSelectionDialog::channel() const { QList channels; for (int i=0; i<_channel->count(); i++) { if (Qt::Checked == _channel->item(i)->checkState()) channels.push_back(_channel->item(i)->data(Qt::UserRole).value()); } return channels; } ================================================ FILE: src/channelselectiondialog.hh ================================================ #ifndef CHANNELSELECTIONDIALOG_HH #define CHANNELSELECTIONDIALOG_HH #include class Channel; class ChannelList; class ChannelComboBox; class QListWidget; class ChannelRefList; class ChannelSelectionDialog: public QDialog { Q_OBJECT public: ChannelSelectionDialog(ChannelList *lst, bool includeSelectedChannel=false, QWidget *parent=nullptr); Channel *channel() const; protected: ChannelComboBox *_channel; }; class MultiChannelSelectionDialog: public QDialog { Q_OBJECT public: MultiChannelSelectionDialog(ChannelList *lst, bool includeSelectedChannel=false, bool digitalOnly=false, ChannelRefList *exclude=nullptr, QWidget *parent=nullptr); QList channel() const; protected: QListWidget *_channel; }; #endif // CHANNELSELECTIONDIALOG_HH ================================================ FILE: src/channelvalidator.cc ================================================ #include "channelvalidator.hh" #include "channel.hh" /* ********************************************************************************************* * * Implementation of ChannelValidator * ********************************************************************************************* */ ChannelValidator::ChannelValidator(ChannelList *lst, QObject *parent) : QValidator(parent), _channels(lst) { // pass... } QValidator::State ChannelValidator::validate(QString &text, int &pos) const { Q_UNUSED(pos); if (text.size()<1) return QValidator::Intermediate; QValidator::State res = QValidator::Invalid; for (int i=0; i<_channels->count(); i++) { if (0 == text.compare(_channels->channel(i)->name(), Qt::CaseInsensitive)) return QValidator::Acceptable; if (_channels->channel(i)->name().startsWith(text, Qt::CaseInsensitive)) res = QValidator::Intermediate; } return res; } ================================================ FILE: src/channelvalidator.hh ================================================ #ifndef CHANNELVALIDATOR_HH #define CHANNELVALIDATOR_HH #include class ChannelList; class ChannelValidator: public QValidator { Q_OBJECT public: ChannelValidator(ChannelList *lst, QObject *parent=nullptr); QValidator::State validate(QString &input, int &pos) const; protected: ChannelList *_channels; }; #endif // CHANNELVALIDATOR_HH ================================================ FILE: src/collapsablewidget.cc ================================================ #include "collapsablewidget.hh" #include #include CollapsableWidget::CollapsableWidget(QWidget *parent) : QToolButton(parent), _content(nullptr) { setCheckable(true); setStyleSheet("background:none"); setToolButtonStyle(Qt::ToolButtonTextBesideIcon); setIconSize(QSize(8, 8)); setFont(QApplication::font()); hideContent(); setArrowType(Qt::ArrowType::RightArrow); connect(this, &QToolButton::toggled, [=](bool checked) { setArrowType(checked ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); _content != nullptr && checked ? showContent() : hideContent(); }); } void CollapsableWidget::setContent(QWidget *content) { assert(content != nullptr); _content = content; auto animation = new QPropertyAnimation(_content, "maximumHeight"); // QObject with auto delete animation->setStartValue(0); animation->setEasingCurve(QEasingCurve::InOutQuad); animation->setDuration(300); animation->setEndValue(content->geometry().height() + 10); _animator.addAnimation(animation); if (!isChecked()) content->setMaximumHeight(0); } void CollapsableWidget::hideContent() { _animator.setDirection(QAbstractAnimation::Backward); _animator.start(); } void CollapsableWidget::showContent() { _animator.setDirection(QAbstractAnimation::Forward); _animator.start(); } ================================================ FILE: src/collapsablewidget.hh ================================================ #ifndef COLLAPSABLEWIDGET_HH #define COLLAPSABLEWIDGET_HH #include #include class CollapsableWidget : public QToolButton { Q_OBJECT public: explicit CollapsableWidget(QWidget *parent=nullptr); void setContent(QWidget *content); public slots: void hideContent(); void showContent(); private: QWidget *_content; QParallelAnimationGroup _animator; }; #endif // COLLAPSABLEWIDGET_HH ================================================ FILE: src/configitemwrapper.cc ================================================ #include "configitemwrapper.hh" #include #include "logger.hh" #include #include #include /* ********************************************************************************************* * * Implementation of GenericListWrapper * ********************************************************************************************* */ GenericListWrapper::GenericListWrapper(AbstractConfigObjectList *list, QObject *parent) : QAbstractListModel(parent), _list(list) { if (nullptr == _list) return; connect(_list, SIGNAL(destroyed(QObject*)), this, SLOT(onListDeleted())); connect(_list, SIGNAL(elementAdded(int)), this, SLOT(onItemAdded(int))); connect(_list, SIGNAL(elementModified(int)), this, SLOT(onItemModified(int))); connect(_list, SIGNAL(elementRemoved(int)), this, SLOT(onItemRemoved(int))); } int GenericListWrapper::rowCount(const QModelIndex &index) const { Q_UNUSED(index) if (nullptr == _list) return 0; return _list->count(); } int GenericListWrapper::columnCount(const QModelIndex &index) const { Q_UNUSED(index) if (nullptr == _list) return 0; return 1; } Qt::ItemFlags GenericListWrapper::flags(const QModelIndex &index) const { if (index.isValid()) return QAbstractListModel::flags(index) | Qt::ItemIsDragEnabled; return QAbstractListModel::flags(index) | Qt::ItemIsDropEnabled; } Qt::DropActions GenericListWrapper::supportedDropActions() const { return Qt::MoveAction; } bool GenericListWrapper::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) { logDebug() << "Move " << count << " rows from " << sourceRow << " to " << destinationChild; beginMoveRows(sourceParent, sourceRow, sourceRow+count-1, destinationParent, destinationChild); bool success = _list->move(sourceRow, count, destinationChild); endMoveRows(); return success; } void GenericListWrapper::onListDeleted() { beginResetModel(); _list = nullptr; endResetModel(); } void GenericListWrapper::onItemAdded(int idx) { beginInsertRows(QModelIndex(), idx, idx); endInsertRows(); } void GenericListWrapper::onItemRemoved(int idx) { beginRemoveRows(QModelIndex(), idx, idx); //logDebug() << "Signal removal of item at idx=" << idx; endRemoveRows(); } void GenericListWrapper::onItemModified(int idx) { emit dataChanged(index(idx),index(idx)); } /* ********************************************************************************************* * * Implementation of GenericTableWrapper * ********************************************************************************************* */ GenericTableWrapper::GenericTableWrapper(AbstractConfigObjectList *list, QObject *parent) : QAbstractTableModel(parent), _list(list), _insertRow(-1) { if (nullptr == _list) return; connect(_list, SIGNAL(destroyed(QObject*)), this, SLOT(onListDeleted())); connect(_list, SIGNAL(elementAdded(int)), this, SLOT(onItemAdded(int))); connect(_list, SIGNAL(elementModified(int)), this, SLOT(onItemModified(int))); connect(_list, SIGNAL(elementRemoved(int)), this, SLOT(onItemRemoved(int))); } int GenericTableWrapper::rowCount(const QModelIndex &index) const { Q_UNUSED(index) if (nullptr == _list) return 0; return _list->count(); } Qt::ItemFlags GenericTableWrapper::flags(const QModelIndex &index) const { if (index.isValid()) return QAbstractTableModel::flags(index) | Qt::ItemIsDragEnabled; return QAbstractTableModel::flags(index) | Qt::ItemIsDropEnabled; } Qt::DropActions GenericTableWrapper::supportedDropActions() const { return Qt::MoveAction; } bool GenericTableWrapper::insertRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); Q_UNUSED(count); if (-1 == _insertRow) { _insertRow = row; return true; } return false; } bool GenericTableWrapper::removeRows(int row, int count, const QModelIndex &parent) { Q_UNUSED(parent); if (-1 == _insertRow) return false; bool success = moveRows(QModelIndex(), row, count, QModelIndex(), _insertRow); _insertRow = -1; return success; } bool GenericTableWrapper::moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild) { logDebug() << "Move " << count << " rows from " << sourceRow << " to " << destinationChild; beginMoveRows(sourceParent, sourceRow, sourceRow+count-1, destinationParent, destinationChild); bool success = _list->move(sourceRow, count, destinationChild); endMoveRows(); return success; } bool GenericTableWrapper::canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const { if (0 != column) return false; return QAbstractTableModel::canDropMimeData(data, action, row, column, parent); } void GenericTableWrapper::onListDeleted() { beginResetModel(); _list = nullptr; endResetModel(); } void GenericTableWrapper::onItemAdded(int idx) { beginInsertRows(QModelIndex(), idx, idx); endInsertRows(); } void GenericTableWrapper::onItemRemoved(int idx) { beginRemoveRows(QModelIndex(), idx, idx); //logDebug() << "Signal removal of item at idx=" << idx; endRemoveRows(); } void GenericTableWrapper::onItemModified(int idx) { emit dataChanged(index(idx,0),index(idx,columnCount()-1)); } QString GenericTableWrapper::formatExtensions(int idx) const { if (idx >= _list->count()) return QString(); ConfigObject *item = _list->get(idx); QStringList extensions; auto metaObj = item->metaObject(); for (int i=QObject::staticMetaObject.propertyCount(); ipropertyCount(); i++) { auto prop = metaObj->property(i); if (QMetaType::UnknownType == prop.userType()) continue; QMetaType type(prop.userType()); if (! (QMetaType::PointerToQObject & type.flags())) continue; const QMetaObject *propType = type.metaObject(); if (! propType->inherits(&ConfigExtension::staticMetaObject)) continue; if (prop.read(item).isNull()) continue; extensions.append(prop.name()); } return extensions.join(", "); } /* ********************************************************************************************* * * Implementation of ChannelListWrapper * ********************************************************************************************* */ ChannelListWrapper::ChannelListWrapper(ChannelList *list, QObject *parent) : GenericTableWrapper(list, parent) { // pass... } int ChannelListWrapper::columnCount(const QModelIndex &index) const { Q_UNUSED(index); return 22; } QVariant ChannelListWrapper::data(const QModelIndex &index, int role) const { if (nullptr == _list) return QVariant(); if ((! index.isValid()) || (index.row()>=_list->count())) return QVariant(); if (Qt::ForegroundRole == role) { const QPalette &palette = qobject_cast(QObject::parent())->palette(); QColor active = palette.color(QPalette::Active, QPalette::Text); QColor inactive = palette.color(QPalette::Inactive, QPalette::Text); bool isDigital = dynamic_cast(_list)->channel(index.row())->is(); switch(index.column()) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: return active; case 11: case 12: case 13: case 14: return (isDigital ? active : inactive); case 15: return active; case 16: return (isDigital ? active : inactive); case 17: case 18: case 19: case 20: return (isDigital ? inactive : active); } } if ((Qt::DisplayRole!=role) && (Qt::EditRole!=role)) return QVariant(); Channel *channel = dynamic_cast(_list)->channel(index.row()); switch (index.column()) { case 0: if (channel->is()) return tr("DMR"); else if (channel->is()) return tr("FM"); else if (channel->is()) return tr("AM"); else return channel->metaObject()->className(); case 1: return channel->name(); case 2: return channel->rxFrequency().format(Frequency::Unit::MHz); case 3: return channel->txFrequency().format(Frequency::Unit::MHz); case 4: if (channel->defaultPower()) return tr("[Default]"); switch (channel->power()) { case Channel::Power::Max: return tr("Max"); break; case Channel::Power::High: return tr("High"); break; case Channel::Power::Mid: return tr("Mid"); break; case Channel::Power::Low: return tr("Low"); break; case Channel::Power::Min: return tr("Min"); break; } break; case 5: if (channel->defaultTimeout()) return tr("[Default]"); if (channel->timeoutDisabled()) return tr("Off"); return channel->timeout().format(); case 6: return channel->rxOnly() ? tr("On") : tr("Off"); case 7: if (DMRChannel *dmr = channel->as()) { switch (dmr->admit()) { case DMRChannel::Admit::Always: return tr("Always"); break; case DMRChannel::Admit::Free: return tr("Free"); break; case DMRChannel::Admit::ColorCode: return tr("Color"); break; } } else if (FMChannel *fm = channel->as()) { switch (fm->admit()) { case FMChannel::Admit::Always: return tr("Always"); break; case FMChannel::Admit::Free: return tr("Free"); break; case FMChannel::Admit::Tone: return tr("Tone"); break; } } else { return tr("[None]"); } break; case 8: if (channel->scanList()) { return channel->scanList()->name(); } else { return QString("-"); } case 9: { // Collect zones, the channel is a member of QStringList zones; for(int i=0;iconfig()->zones()->count(); i++) { Zone *zone = channel->config()->zones()->zone(i); if (zone->contains(channel)) zones.append(zone->name()); } return zones.join(", "); } break; case 10: if (DMRChannel *digi = channel->as()) { return digi->colorCode(); } else if (M17Channel *m17 = channel->as()) { return m17->accessNumber(); } else if (channel->is()) { return tr("[None]"); } break; case 11: if (DMRChannel *digi = channel->as()) { return (DMRChannel::TimeSlot::TS1 == digi->timeSlot()) ? 1 : 2; } else { return tr("[None]"); } break; case 12: if (DMRChannel *digi = channel->as()) { if (digi->groupList()) { return digi->groupList()->name(); } else { return QString("-"); } } else { return tr("[None]"); } break; case 13: if (DMRChannel *digi = channel->as()) { if (digi->contact()) return digi->contact()->name(); else return QString("-"); } else { return tr("[None]"); } break; case 14: if (DMRChannel *digi = channel->as()) { if ((nullptr == digi->radioId()) || (DefaultRadioID::get() == digi->radioId())) return tr("[Default]"); return digi->radioId()->name(); } else { return tr("[None]"); } break; case 15: if (DMRChannel *digi = channel->as()) { if (digi->aprs()) return digi->aprs()->name(); else return QString("-"); } else if (FMChannel *analog = channel->as()) { if (analog->aprs()) return analog->aprs()->name(); else return QString("-"); } else { return QString("-"); } break; case 16: if (DMRChannel *digi = channel->as()) { if (nullptr == digi->roaming()) return QString("-"); else if (DefaultRoamingZone::get() == digi->roaming()) return tr("[Default]"); return digi->roaming()->name(); } else { return tr("[None]"); } break; case 17: if (channel->is() || channel->is()) { return tr("[None]"); } else if (FMChannel *fm = channel->as()) { if (fm->defaultSquelch()) return tr("[Default]"); if (fm->squelchDisabled()) return tr("Open"); else return fm->squelch().value(); } else if (AMChannel *am = channel->as()) { if (am->defaultSquelch()) return tr("[Default]"); if (am->squelchDisabled()) return tr("Open"); else return am->squelch().value(); } break; case 18: if (channel->is() || channel->is()) { return tr("[None]"); } else if (FMChannel *analog = channel->as()) { return analog->rxTone().format(); } break; case 19: if (channel->is() || channel->is()) { return tr("[None]"); } else if (FMChannel *analog = channel->as()) { return analog->txTone().format(); } break; case 20: if (channel->is() || channel->is()) { return tr("[None]"); } else if (FMChannel *analog = channel->as()) { if (FMChannel::Bandwidth::Wide == analog->bandwidth()) { return tr("Wide"); } else return tr("Narrow"); } break; case 21: { auto exts = formatExtensions(index.row()); if (exts.isEmpty()) return tr("[None]"); return exts; } default: break; } return QVariant(); } QVariant ChannelListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole!=role) || (Qt::Horizontal!=orientation)) return QVariant(); switch (section) { case 0: return tr("Type"); case 1: return tr("Name"); case 2: return tr("Rx Frequency"); case 3: return tr("Tx Frequency"); case 4: return tr("Power"); case 5: return tr("Timeout"); case 6: return tr("Rx Only"); case 7: return tr("Admit"); case 8: return tr("Scanlist"); case 9: return tr("Zones"); case 10: return tr("CC"); case 11: return tr("TS"); case 12: return tr("RX Group List"); case 13: return tr("TX Contact"); case 14: return tr("DMR ID"); case 15: return tr("GPS/APRS"); case 16: return tr("Roaming"); case 17: return tr("Squelch"); case 18: return tr("Rx Tone"); case 19: return tr("Tx Tone"); case 20: return tr("Bandwidth"); case 21: return tr("Extensions"); default: break; } return QVariant(); } /* ********************************************************************************************* * * Implementation of ChannelRefListWrapper * ********************************************************************************************* */ ChannelRefListWrapper::ChannelRefListWrapper(ChannelRefList *list, QObject *parent) : GenericListWrapper(list, parent) { // pass... } QVariant ChannelRefListWrapper::data(const QModelIndex &index, int role) const { if ((Qt::DisplayRole!=role) || (! index.isValid()) || (index.row()>=_list->count())) return QVariant(); return _list->get(index.row())->as()->name(); } QVariant ChannelRefListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((0!=section) || (Qt::Horizontal!=orientation) || (Qt::DisplayRole!=role)) return QVariant(); return tr("Channel"); } /* ********************************************************************************************* * * Implementation of RoamingChannelListWrapper * ********************************************************************************************* */ RoamingChannelListWrapper::RoamingChannelListWrapper(RoamingChannelList *list, QObject *parent) : GenericTableWrapper(list, parent) { // pass... } int RoamingChannelListWrapper::columnCount(const QModelIndex &index) const { Q_UNUSED(index); return 7; } QVariant RoamingChannelListWrapper::data(const QModelIndex &index, int role) const { if ((Qt::DisplayRole!=role) || (! index.isValid()) || (index.row() >= _list->count())) return QVariant(); RoamingChannel *ch = _list->get(index.row())->as(); // Dispatch by column switch (index.column()) { case 0: return ch->name(); case 1: return ch->rxFrequency().format(Frequency::Unit::MHz); case 2: return ch->txFrequency().format(Frequency::Unit::MHz); case 3: if (ch->colorCodeOverridden()) return ch->colorCode(); return tr("[Selected]"); case 4: if (ch->timeSlotOverridden()) { switch(ch->timeSlot()) { case DMRChannel::TimeSlot::TS1: return 1; case DMRChannel::TimeSlot::TS2: return 2; } } return tr("[Selected]"); case 5: { QStringList zones; for(int i=0;iconfig()->roamingZones()->count(); i++) { RoamingZone *zone = ch->config()->roamingZones()->zone(i); if (zone->contains(ch)) zones.append(zone->name()); } return zones.join(", "); } break; case 6: { auto exts = formatExtensions(index.row()); if (exts.isEmpty()) return tr("[None]"); return exts; } default: break; } return QVariant(); } QVariant RoamingChannelListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::Horizontal!=orientation) || (Qt::DisplayRole!=role)) return QVariant(); switch (section) { case 0: return tr("Name"); case 1: return tr("RX Frequency"); case 2: return tr("TX Frequency"); case 3: return tr("CC"); case 4: return tr("TS"); case 5: return tr("Zones"); case 6: return tr("Extensions"); default: break; } return QVariant(); } /* ********************************************************************************************* * * Implementation of RoamingChannelRefListWrapper * ********************************************************************************************* */ RoamingChannelRefListWrapper::RoamingChannelRefListWrapper(RoamingChannelRefList *list, QObject *parent) : GenericListWrapper(list, parent) { // pass... } QVariant RoamingChannelRefListWrapper::data(const QModelIndex &index, int role) const { if ((Qt::DisplayRole!=role) || (! index.isValid()) || (index.row() >= _list->count())) return QVariant(); return _list->get(index.row())->as()->name(); } QVariant RoamingChannelRefListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((0!=section) || (Qt::Horizontal!=orientation) || (Qt::DisplayRole!=role)) return QVariant(); return tr("Roaming Channel"); } /* ********************************************************************************************* * * Implementation of ContactListWrapper * ********************************************************************************************* */ ContactListWrapper::ContactListWrapper(ContactList *list, QObject *parent) : GenericTableWrapper(list, parent) { // pass... } int ContactListWrapper::columnCount(const QModelIndex &index) const { Q_UNUSED(index); return 5; } QVariant ContactListWrapper::data(const QModelIndex &index, int role) const { if ((!index.isValid()) || (index.row()>=_list->count())) return QVariant(); if (Qt::DisplayRole == role) { Contact *contact = _list->get(index.row())->as(); if (contact->is()) { DTMFContact *dtmf = contact->as(); switch (index.column()) { case 0: return tr("DTMF"); case 1: return dtmf->name(); case 2: return dtmf->number(); case 3: return (dtmf->ring() ? tr("On") : tr("Off")); case 4: return formatExtensions(index.row()); default: return QVariant(); } } else if (contact->is()) { DMRContact *dmr = contact->as(); switch (index.column()) { case 0: switch (dmr->type()) { case DMRContact::PrivateCall: return tr("Private Call"); case DMRContact::GroupCall: return tr("Group Call"); case DMRContact::AllCall: return tr("All Call"); } break; case 1: return dmr->name(); case 2: return dmr->number(); case 3: return (dmr->ring() ? tr("On") : tr("Off")); case 4: { auto exts = formatExtensions(index.row()); if (exts.isEmpty()) return tr("[None]"); return exts; } default: return QVariant(); } } else if (contact->is()) { M17Contact *m17 = contact->as(); switch (index.column()) { case 0: return tr("M17"); case 1: return m17->name(); case 2: if (m17->isBroadcast()) return tr("[Broadcast]"); return m17->call(); case 3: return (m17->ring() ? tr("On") : tr("Off")); default: return QVariant(); } } } return QVariant(); } QVariant ContactListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole != role) || (Qt::Horizontal != orientation)) { return QVariant(); } if (0 == section) { return tr("Type"); } else if (1 == section) { return tr("Name"); } else if (2 == section) { return tr("Number"); } else if (3 == section) { return tr("RX Tone"); } else if (4 == section) { return tr("Extensions"); } return QVariant(); } /* ********************************************************************************************* * * Implementation of ZoneListWrapper * ********************************************************************************************* */ ZoneListWrapper::ZoneListWrapper(ZoneList *list, QObject *parent) : GenericListWrapper(list, parent) { // pass... } QVariant ZoneListWrapper::data(const QModelIndex &index, int role) const { if ((Qt::DisplayRole!=role) || (index.row()>=_list->count()) || (0 != index.column())) return QVariant(); Zone *zone = _list->get(index.row())->as(); return zone->name(); } QVariant ZoneListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole!=role) || (Qt::Horizontal!=orientation) || (0 != section)) return QVariant(); return tr("Zone"); } /* ********************************************************************************************* * * Implementation of PositioningSystemListWrapper * ********************************************************************************************* */ PositioningSystemListWrapper::PositioningSystemListWrapper(PositionReportingSystems *list, QObject *parent) : GenericTableWrapper(list, parent) { // pass... } int PositioningSystemListWrapper::columnCount(const QModelIndex &idx) const { Q_UNUSED(idx); return 7; } QVariant PositioningSystemListWrapper::data(const QModelIndex &index, int role) const { if ((! index.isValid()) || (index.row()>=_list->count())) return QVariant(); if ((Qt::DisplayRole!=role) && (Qt::EditRole!=role)) return QVariant(); PositionReportingSystem *sys = _list->get(index.row())->as(); switch (index.column()) { case 0: if (sys->is()) return tr("DMR"); else if (sys->is()) return tr("APRS"); else return QString("Oops!"); case 1: return sys->name(); case 2: if (sys->is()) { if (! sys->as()->hasContact()) return tr("[None]"); return sys->as()->contact()->name(); } else if (sys->is()) return QString("%1-%2").arg(sys->as()->destination()) .arg(sys->as()->destSSID()); break; case 3: if (Qt::DisplayRole == role) return sys->period().format(); else if (Qt::EditRole == role) return QVariant::fromValue(sys->period()); break; case 4: if (sys->is()) { if (! sys->as()->hasRevertChannel()) return tr("[Selected]"); return sys->as()->revertChannel()->name(); } else if (sys->is()) { if (! sys->as()->hasRevertChannel()) return tr("[Selected]"); return sys->as()->revertChannel()->name(); } break; case 5: if (sys->is()) return tr("[None]"); else if (sys->is()) return sys->as()->message(); break; case 6: { auto exts = formatExtensions(index.row()); if (exts.isEmpty()) return tr("[None]"); return exts; } default: break; } return QVariant(); } QVariant PositioningSystemListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole!=role) || (Qt::Horizontal!=orientation)) return QVariant(); switch (section) { case 0: return tr("Type"); case 1: return tr("Name"); case 2: return tr("Destination"); case 3: return tr("Period"); case 4: return tr("Channel"); case 5: return tr("Message"); case 6: return tr("Extensions"); default: break; } return QVariant(); } /* ********************************************************************************************* * * Implementation of ScanListsWrapper * ********************************************************************************************* */ ScanListsWrapper::ScanListsWrapper(ScanLists *list, QObject *parent) : GenericListWrapper(list, parent) { // pass... } QVariant ScanListsWrapper::data(const QModelIndex &index, int role) const { if ((Qt::DisplayRole!=role) || (index.row()>=_list->count()) || (0 != index.column())) return QVariant(); return _list->get(index.row())->as()->name(); } QVariant ScanListsWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole!=role) || (Qt::Horizontal!=orientation) || (0 != section)) return QVariant(); return tr("Scan-List"); } /* ********************************************************************************************* * * Implementation of GroupListsWrapper * ********************************************************************************************* */ GroupListsWrapper::GroupListsWrapper(RXGroupLists *list, QObject *parent) : GenericListWrapper(list, parent) { // pass... } QVariant GroupListsWrapper::data(const QModelIndex &index, int role) const { if ((Qt::DisplayRole!=role) || (! index.isValid()) || (index.row()>=_list->count())) return QVariant(); return _list->get(index.row())->as()->name(); } QVariant GroupListsWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((0!=section) || (Qt::Horizontal!=orientation) || (Qt::DisplayRole!=role)) return QVariant(); return tr("RX Group Lists"); } /* ********************************************************************************************* * * Implementation of GroupListWrapper * ********************************************************************************************* */ GroupListWrapper::GroupListWrapper(RXGroupList *list, QObject *parent) : GenericListWrapper(list->contacts(), parent) { // pass... } QVariant GroupListWrapper::data(const QModelIndex &index, int role) const { if ((Qt::DisplayRole!=role) || (! index.isValid()) || (index.row()>=_list->count())) return QVariant(); return _list->get(index.row())->as()->name(); } QVariant GroupListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((0!=section) || (Qt::Horizontal!=orientation) || (Qt::DisplayRole!=role)) return QVariant(); return tr("Contact"); } /* ********************************************************************************************* * * Implementation of RoamingListWrapper * ********************************************************************************************* */ RoamingListWrapper::RoamingListWrapper(RoamingZoneList *list, QObject *parent) : GenericListWrapper(list, parent) { // pass... } QVariant RoamingListWrapper::data(const QModelIndex &index, int role) const { if ((Qt::DisplayRole!=role) || (index.row()>=_list->count()) || (0 != index.column())) return QVariant(); RoamingZone *zone = _list->get(index.row())->as(); return tr("%1 (containing %2 channels)").arg(zone->name()).arg(zone->count()); } QVariant RoamingListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole!=role) || (Qt::Horizontal!=orientation) || (0 != section)) return QVariant(); return tr("Roaming zone"); } /* ********************************************************************************************* * * Implementation of RadioIdListWrapper * ********************************************************************************************* */ RadioIdListWrapper::RadioIdListWrapper(RadioIDList *list, QObject *parent) : GenericTableWrapper(list, parent) { // pass... } int RadioIdListWrapper::columnCount(const QModelIndex &idx) const { Q_UNUSED(idx); return 4; } QVariant RadioIdListWrapper::data(const QModelIndex &index, int role) const { if ((! index.isValid()) || (index.row()>=_list->count())) return QVariant(); if ((Qt::DisplayRole!=role) && (Qt::EditRole!=role)) return QVariant(); DMRRadioID *id = _list->get(index.row())->as(); switch (index.column()) { case 0: return ("DMR"); case 1: return id->name(); case 2: return id->number(); case 3:{ auto exts = formatExtensions(index.row()); if (exts.isEmpty()) return tr("[None]"); return exts; } default: break; } return QVariant(); } QVariant RadioIdListWrapper::headerData(int section, Qt::Orientation orientation, int role) const { if ((Qt::DisplayRole!=role) || (Qt::Horizontal!=orientation)) return QVariant(); switch (section) { case 0: return tr("Type"); case 1: return tr("Name"); case 2: return tr("Number"); case 3: return tr("Extensions"); default: break; } return QVariant(); } ================================================ FILE: src/configitemwrapper.hh ================================================ #ifndef CONFIG_ITEM_WRAPPER_HH #define CONFIG_ITEM_WRAPPER_HH #include "config.hh" #include class GenericListWrapper: public QAbstractListModel { Q_OBJECT protected: GenericListWrapper(AbstractConfigObjectList *list, QObject *parent=nullptr); public: // QAbstractListModel interface /** Implements QAbstractTableModel, returns number of rows. */ int rowCount(const QModelIndex &index) const; /** Implements QAbstractTableModel, returns number of columns. */ int columnCount(const QModelIndex &index) const; Qt::ItemFlags flags(const QModelIndex &index) const; Qt::DropActions supportedDropActions() const; bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild); signals: /** Gets emitted once the table has been changed. */ void modified(); protected slots: /** Internal used callback on deleted config. */ void onListDeleted(); /** Internal callback on added items. */ void onItemAdded(int idx); /** Internal callback on deleted channels. */ void onItemRemoved(int idx); /** Internal callback on modified channels. */ void onItemModified(int idx); protected: /** Holds a weak reference to the list object. */ AbstractConfigObjectList *_list; }; class GenericTableWrapper: public QAbstractTableModel { Q_OBJECT protected: GenericTableWrapper(AbstractConfigObjectList *list, QObject *parent=nullptr); public: // QAbstractTableModel interface /** Implements QAbstractTableModel, returns number of rows. */ int rowCount(const QModelIndex &index) const; Qt::ItemFlags flags(const QModelIndex &index) const; Qt::DropActions supportedDropActions() const; bool insertRows(int row, int count, const QModelIndex &parent); bool removeRows(int row, int count, const QModelIndex &parent); bool moveRows(const QModelIndex &sourceParent, int sourceRow, int count, const QModelIndex &destinationParent, int destinationChild); bool canDropMimeData(const QMimeData *data, Qt::DropAction action, int row, int column, const QModelIndex &parent) const; signals: /** Gets emitted once the table has been changed. */ void modified(); protected slots: /** Internal used callback on deleted config. */ void onListDeleted(); /** Internal used callback on adding an item. */ void onItemAdded(int idx); /** Internal callback on deleted channels. */ void onItemRemoved(int idx); /** Internal callback on modified channels. */ void onItemModified(int idx); protected: /** Returns a string containing all extension properties set. */ QString formatExtensions(int idx) const; protected: /** Holds a weak reference to the list object. */ AbstractConfigObjectList *_list; /** Insert index for drag & drop move. */ int _insertRow; }; class ChannelListWrapper: public GenericTableWrapper { Q_OBJECT public: ChannelListWrapper(ChannelList *list, QObject *parent=nullptr); public: // QAbstractTableModel interface /** Implements QAbstractTableModel, returns number of columns. */ int columnCount(const QModelIndex &index) const; /** Implements QAbstractTableModel, returns data at cell. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Implements QAbstractTableModel, returns header at section. */ QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const; }; class ChannelRefListWrapper: public GenericListWrapper { Q_OBJECT public: ChannelRefListWrapper(ChannelRefList *list, QObject *parent=nullptr); public: // Implementation of QAbstractListModel /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Implementation of QAbstractListModel, returns the header data at the given section. */ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; }; class RoamingChannelListWrapper: public GenericTableWrapper { Q_OBJECT public: RoamingChannelListWrapper(RoamingChannelList *list, QObject *parent=nullptr); public: // Implementation of QAbstractTableModel /** Returns the number of columns, implements the QAbstractTableModel. */ int columnCount(const QModelIndex &index) const; /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Implementation of QAbstractListModel, returns the header data at the given section. */ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; }; class RoamingChannelRefListWrapper: public GenericListWrapper { Q_OBJECT public: RoamingChannelRefListWrapper(RoamingChannelRefList *list, QObject *parent=nullptr); public: // Implementation of QAbstractListModel /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Implementation of QAbstractListModel, returns the header data at the given section. */ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; }; class ContactListWrapper: public GenericTableWrapper { Q_OBJECT public: ContactListWrapper(ContactList *list, QObject *parent=nullptr); public: // Implementation of QAbstractTableModel /** Returns the number of columns, implements the QAbstractTableModel. */ int columnCount(const QModelIndex &index) const; /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Returns the header at given section, implements the QAbstractTableModel. */ QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const; }; class ZoneListWrapper: public GenericListWrapper { Q_OBJECT public: ZoneListWrapper(ZoneList *list, QObject *parent=nullptr); public: // Implementation of QAbstractListModel /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Implementation of QAbstractListModel, returns the header data at the given section. */ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; }; class PositioningSystemListWrapper: public GenericTableWrapper { Q_OBJECT public: PositioningSystemListWrapper(PositionReportingSystems *list, QObject *parent=nullptr); public: // Implementation of QAbstractTableModel /** Returns the number of columns, implements the QAbstractTableModel. */ int columnCount(const QModelIndex &index) const; /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Returns the header at given section, implements the QAbstractTableModel. */ QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const; }; class ScanListsWrapper: public GenericListWrapper { Q_OBJECT public: ScanListsWrapper(ScanLists *list, QObject *parent=nullptr); public: // Implementation of QAbstractListModel /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Implementation of QAbstractListModel, returns the header data at the given section. */ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; }; class GroupListsWrapper: public GenericListWrapper { Q_OBJECT public: GroupListsWrapper(RXGroupLists *list, QObject *parent=nullptr); public: // Implementation of QAbstractListModel /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Implementation of QAbstractListModel, returns the header data at the given section. */ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; }; class GroupListWrapper: public GenericListWrapper { Q_OBJECT public: GroupListWrapper(RXGroupList *list, QObject *parent=nullptr); public: // Implementation of QAbstractListModel /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Implementation of QAbstractListModel, returns the header data at the given section. */ QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const; }; class RoamingListWrapper: public GenericListWrapper { Q_OBJECT public: RoamingListWrapper(RoamingZoneList *list, QObject *parent=nullptr); public: // Implementation of QAbstractListModel /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Returns the header at given section, implements the QAbstractTableModel. */ QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const; }; class RadioIdListWrapper: public GenericTableWrapper { Q_OBJECT public: RadioIdListWrapper(RadioIDList *list, QObject *parent=nullptr); public: // Implementation of QAbstractTableModel /** Returns the number of columns, implements the QAbstractTableModel. */ int columnCount(const QModelIndex &index) const; /** Returns the cell data at given index, implements the QAbstractTableModel. */ QVariant data(const QModelIndex &index, int role=Qt::DisplayRole) const; /** Returns the header at given section, implements the QAbstractTableModel. */ QVariant headerData(int section, Qt::Orientation orientation, int role=Qt::DisplayRole) const; }; #endif // CONFIG_ITEM_WRAPPER_HH ================================================ FILE: src/configmergedialog.cc ================================================ #include "configmergedialog.hh" #include "ui_configmergedialog.h" #include "configmergevisitor.hh" #include "settings.hh" ConfigMergeDialog::ConfigMergeDialog(QWidget *parent) : QDialog(parent), ui(new Ui::ConfigMergeDialog) { ui->setupUi(this); ui->itemStrategy->setItemData(0, QVariant((uint) ConfigMergeVisitor::ItemStrategy::Ignore)); ui->itemStrategy->setItemData(1, QVariant((uint) ConfigMergeVisitor::ItemStrategy::Override)); ui->itemStrategy->setItemData(2, QVariant((uint) ConfigMergeVisitor::ItemStrategy::Duplicate)); ui->setStrategy->setItemData(0, QVariant((uint) ConfigMergeVisitor::SetStrategy::Ignore)); ui->setStrategy->setItemData(1, QVariant((uint) ConfigMergeVisitor::SetStrategy::Override)); ui->setStrategy->setItemData(2, QVariant((uint) ConfigMergeVisitor::SetStrategy::Duplicate)); ui->setStrategy->setItemData(3, QVariant((uint) ConfigMergeVisitor::SetStrategy::Merge)); Settings settings; switch (settings.configMergeItemStrategy()) { case ConfigMergeVisitor::ItemStrategy::Ignore: ui->itemStrategy->setCurrentIndex(0); break; case ConfigMergeVisitor::ItemStrategy::Override: ui->itemStrategy->setCurrentIndex(1); break; case ConfigMergeVisitor::ItemStrategy::Duplicate: ui->itemStrategy->setCurrentIndex(2); break; } switch (settings.configMergeSetStrategy()) { case ConfigMergeVisitor::SetStrategy::Ignore: ui->setStrategy->setCurrentIndex(0); break; case ConfigMergeVisitor::SetStrategy::Override: ui->setStrategy->setCurrentIndex(1); break; case ConfigMergeVisitor::SetStrategy::Duplicate: ui->setStrategy->setCurrentIndex(2); break; case ConfigMergeVisitor::SetStrategy::Merge: ui->setStrategy->setCurrentIndex(3); break; } onItemStrategySelected(ui->itemStrategy->currentIndex()); onSetStrategySelected(ui->setStrategy->currentIndex()); connect(ui->itemStrategy, SIGNAL(currentIndexChanged(int)), this, SLOT(onItemStrategySelected(int))); connect(ui->setStrategy, SIGNAL(currentIndexChanged(int)), this, SLOT(onSetStrategySelected(int))); connect(ui->buttonBox, SIGNAL(accepted()), SLOT(accept())); connect(ui->buttonBox, SIGNAL(rejected()), SLOT(reject())); } ConfigMergeDialog::~ConfigMergeDialog() { delete ui; } ConfigMergeVisitor::ItemStrategy ConfigMergeDialog::itemStrategy() const { return (ConfigMergeVisitor::ItemStrategy)ui->itemStrategy->currentData().toUInt(); } ConfigMergeVisitor::SetStrategy ConfigMergeDialog::setStrategy() const { return (ConfigMergeVisitor::SetStrategy)ui->setStrategy->currentData().toUInt(); } void ConfigMergeDialog::onItemStrategySelected(int index) { ConfigMergeVisitor::ItemStrategy strategy = (ConfigMergeVisitor::ItemStrategy)ui->itemStrategy->itemData(index).toUInt(); Settings().setConfigMergeItemStrategy(strategy); switch (strategy) { case ConfigMergeVisitor::ItemStrategy::Ignore: ui->itemStrategyLabel->setText(tr("Ignores any duplicate item.")); break; case ConfigMergeVisitor::ItemStrategy::Override: ui->itemStrategyLabel->setText(tr("Replaces any duplicate item with the imported one.")); break; case ConfigMergeVisitor::ItemStrategy::Duplicate: ui->itemStrategyLabel->setText(tr("Imports any duplicate item with a modified name.")); break; } } void ConfigMergeDialog::onSetStrategySelected(int index) { ConfigMergeVisitor::SetStrategy strategy = (ConfigMergeVisitor::SetStrategy)ui->setStrategy->itemData(index).toUInt(); Settings().setConfigMergeSetStrategy(strategy); switch (strategy) { case ConfigMergeVisitor::SetStrategy::Ignore: ui->setStrategyLabel->setText(tr("Ignores any duplicate set.")); break; case ConfigMergeVisitor::SetStrategy::Override: ui->setStrategyLabel->setText(tr("Replaces any duplicate set with the imported one.")); break; case ConfigMergeVisitor::SetStrategy::Duplicate: ui->setStrategyLabel->setText(tr("Imports any duplicate set with a modified name.")); break; case ConfigMergeVisitor::SetStrategy::Merge: ui->setStrategyLabel->setText(tr("Merges duplicate sets.")); break; } } ================================================ FILE: src/configmergedialog.hh ================================================ #ifndef CONFIGMERGEDIALOG_HH #define CONFIGMERGEDIALOG_HH #include #include "configmergevisitor.hh" namespace Ui { class ConfigMergeDialog; } class ConfigMergeDialog : public QDialog { Q_OBJECT public: explicit ConfigMergeDialog(QWidget *parent = nullptr); ~ConfigMergeDialog(); ConfigMergeVisitor::ItemStrategy itemStrategy() const; ConfigMergeVisitor::SetStrategy setStrategy() const; private slots: void onItemStrategySelected(int idx); void onSetStrategySelected(int idx); private: Ui::ConfigMergeDialog *ui; }; #endif // CONFIGMERGEDIALOG_HH ================================================ FILE: src/configmergedialog.ui ================================================ ConfigMergeDialog Qt::NonModal 0 0 487 342 Merging codeplugs ... true 0 0 <html><head/><body><p><span style=" font-weight:600;">Conflict resolution strategies:</span></p><p>If some of the imported objects (channels, contacts, ...) already exist, select how these conflicts are resolved for items and sets.</p></body></html> true 0 0 Items are all atomic objects like radio IDs, channels, contacts and roaming channels. Items Ignore Override Duplicate 0 0 true 0 0 Sets are all objects, containing other elements like group lists, zones, scan lists and roaming zones. Sets Ignore Override Duplicate Merge 0 0 true Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() ConfigMergeDialog accept() 248 254 157 274 buttonBox rejected() ConfigMergeDialog reject() 316 260 286 274 ================================================ FILE: src/configobjectlistview.cc ================================================ #include "configobjectlistview.hh" #include "ui_configobjectlistview.h" #include "searchpopup.hh" #include inline QPair getSelectionRowRange(const QModelIndexList &indices) { int rmin=-1, rmax=-1; foreach (QModelIndex idx, indices) { if ((-1==rmin) || (rmin>idx.row())) rmin = idx.row(); if ((-1==rmax) || (rmax(rmin, rmax); } ConfigObjectListView::ConfigObjectListView(QWidget *parent) : QWidget(parent), ui(new Ui::ConfigObjectListView) { ui->setupUi(this); connect(ui->itemTop, SIGNAL(clicked(bool)), this, SLOT(onMoveItemTop())); connect(ui->itemTenUp, SIGNAL(clicked(bool)), this, SLOT(onMoveItemTenUp())); connect(ui->itemUp, SIGNAL(clicked(bool)), this, SLOT(onMoveItemUp())); connect(ui->itemDown, SIGNAL(clicked(bool)), this, SLOT(onMoveItemDown())); connect(ui->itemTenDown, SIGNAL(clicked(bool)), this, SLOT(onMoveItemTenDown())); connect(ui->itemBottom, SIGNAL(clicked(bool)), this, SLOT(onMoveItemBottom())); connect(ui->listView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onDoubleClicked(QModelIndex))); ui->listView->setSelectionMode(QAbstractItemView::ContiguousSelection); ui->listView->setSelectionBehavior(QAbstractItemView::SelectRows); ui->listView->setDragEnabled(true); ui->listView->viewport()->setAcceptDrops(true); ui->listView->setDropIndicatorShown(true); ui->listView->setDragDropMode(QAbstractItemView::InternalMove); SearchPopup::attach(ui->listView); } ConfigObjectListView::~ConfigObjectListView() { delete ui; } GenericListWrapper * ConfigObjectListView::model() const { return qobject_cast(ui->listView->model()); } void ConfigObjectListView::setModel(GenericListWrapper *model) { ui->listView->setModel(model); } bool ConfigObjectListView::hasSelection() const { return ui->listView->selectionModel()->hasSelection(); } QPair ConfigObjectListView::selection() const { return getSelectionRowRange(ui->listView->selectionModel()->selection().indexes()); } void ConfigObjectListView::onMoveItemUp() { // Check if there is a selection if (! ui->listView->selectionModel()->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot move items."), tr("Cannot move items: You have to select at least one item first.")); return; } // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->listView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>=rows.first) || (0>rows.second)) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), std::max(0, rows.first-1)); } void ConfigObjectListView::onMoveItemTenUp() { // Check if there is a selection if (! ui->listView->selectionModel()->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot move items."), tr("Cannot move items: You have to select at least one item first.")); return; } // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->listView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>=rows.first) || (0>rows.second)) return; // Then move rows int dest = std::max(0, rows.first-9); model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), dest); } void ConfigObjectListView::onMoveItemTop() { // Check if there is a selection if (! ui->listView->selectionModel()->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot move items."), tr("Cannot move items: You have to select at least one item first.")); return; } // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->listView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>=rows.first) || (0>rows.second)) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), 0); } void ConfigObjectListView::onMoveItemDown() { // Check if there is a selection if (! ui->listView->selectionModel()->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot move items."), tr("Cannot move items: You have to select at least one item first.")); return; } // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->listView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>rows.first) || (0>rows.second) || ((rows.second+1)>=model()->rowCount(QModelIndex()))) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), std::min(model()->rowCount(QModelIndex()), rows.second+2)); } void ConfigObjectListView::onMoveItemTenDown() { // Check if there is a selection if (! ui->listView->selectionModel()->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot move items."), tr("Cannot move items: You have to select at least one item first.")); return; } // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->listView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>rows.first) || (0>rows.second) || ((rows.second+1)>=model()->rowCount(QModelIndex()))) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), std::min(rows.second+10, model()->rowCount(QModelIndex()))); } void ConfigObjectListView::onMoveItemBottom() { // Check if there is a selection if (! ui->listView->selectionModel()->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot move items."), tr("Cannot move items: You have to select at least one item first.")); return; } // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->listView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>rows.first) || (0>rows.second) || ((rows.second+1)>=model()->rowCount(QModelIndex()))) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), model()->rowCount(QModelIndex())); } void ConfigObjectListView::onDoubleClicked(QModelIndex idx) { if ((0 > idx.row()) || (idx.row() >= model()->rowCount(QModelIndex()))) return; emit doubleClicked(idx.row()); } ================================================ FILE: src/configobjectlistview.hh ================================================ #ifndef CONFIGOBJECTLISTVIEW_H #define CONFIGOBJECTLISTVIEW_H #include #include "configitemwrapper.hh" namespace Ui { class ConfigObjectListView; } class ConfigObjectListView : public QWidget { Q_OBJECT public: explicit ConfigObjectListView(QWidget *parent = nullptr); ~ConfigObjectListView(); GenericListWrapper *model() const; void setModel(GenericListWrapper *model); bool hasSelection() const; QPair selection() const; signals: void doubleClicked(unsigned row); protected slots: void onMoveItemUp(); void onMoveItemTenUp(); void onMoveItemTop(); void onMoveItemDown(); void onMoveItemTenDown(); void onMoveItemBottom(); void onDoubleClicked(QModelIndex idx); private: Ui::ConfigObjectListView *ui; }; #endif // CONFIGOBJECTLISTVIEW_H ================================================ FILE: src/configobjectlistview.ui ================================================ ConfigObjectListView 0 0 400 300 Form Move selected item(s) to the top. .. 16 16 Move selected item(s) ten positions up. .. 16 16 Move selected item(s) one position up. .. 16 16 Move selected item(s) one position down. .. 16 16 Move selected item(s) ten positions down. .. 16 16 Move selected item(s) to the bottom. .. 16 16 ================================================ FILE: src/configobjecttableview.cc ================================================ #include "configobjecttableview.hh" #include "ui_configobjecttableview.h" #include "searchpopup.hh" #include #include #include "settings.hh" inline QPair getSelectionRowRange(const QModelIndexList &indices) { int rmin=-1, rmax=-1; foreach (QModelIndex idx, indices) { if ((-1==rmin) || (rmin>idx.row())) rmin = idx.row(); if ((-1==rmax) || (rmax(rmin, rmax); } ConfigObjectTableView::ConfigObjectTableView(QWidget *parent) : QWidget(parent), _model(nullptr), ui(new Ui::ConfigObjectTableView) { ui->setupUi(this); ui->filterToggleButton->setDefaultAction(ui->actionToggleFilterSort); connect(ui->itemTop, SIGNAL(clicked(bool)), this, SLOT(onMoveItemTop())); connect(ui->itemTenUp, SIGNAL(clicked(bool)), this, SLOT(onMoveItemTenUp())); connect(ui->itemUp, SIGNAL(clicked(bool)), this, SLOT(onMoveItemUp())); connect(ui->itemDown, SIGNAL(clicked(bool)), this, SLOT(onMoveItemDown())); connect(ui->itemTenDown, SIGNAL(clicked(bool)), this, SLOT(onMoveItemTenDown())); connect(ui->itemBottom, SIGNAL(clicked(bool)), this, SLOT(onMoveItemBottom())); connect(ui->tableView, SIGNAL(doubleClicked(QModelIndex)), this, SLOT(onDoubleClicked(QModelIndex))); ui->tableView->setSelectionMode(QAbstractItemView::ContiguousSelection); ui->tableView->setSelectionBehavior(QAbstractItemView::SelectRows); toggleSortFilter(ui->actionToggleFilterSort->isChecked()); connect(this, &QObject::objectNameChanged, [this](const QString &objName) { this->ui->actionToggleFilterSort->setChecked(Settings().sortFilterEnabled(objName)); }); ui->tableView->setDropIndicatorShown(true); ui->tableView->setDefaultDropAction(Qt::MoveAction); ui->tableView->setDragDropMode(QAbstractItemView::InternalMove); ui->tableView->setDragDropOverwriteMode(false); ui->tableView->addAction(ui->actionToggleFilterSort); connect(ui->actionToggleFilterSort, &QAction::toggled, this, &ConfigObjectTableView::toggleSortFilter); connect(ui->clearButton, &QPushButton::clicked, ui->filterEdit, &QLineEdit::clear); ui->filterEdit->addAction(ui->actionCloseSortFilter); connect(ui->actionCloseSortFilter, &QAction::triggered, [this]() { this->ui->actionToggleFilterSort->setChecked(false); }); SearchPopup::attach(ui->tableView); } ConfigObjectTableView::~ConfigObjectTableView() { delete ui; } GenericTableWrapper * ConfigObjectTableView::model() const { return _model; } void ConfigObjectTableView::setModel(GenericTableWrapper *model) { _model = model; _model->setParent(this); // If sorting is enabled -> set source model of proxy if (ui->actionToggleFilterSort->isChecked()) { proxy()->setSourceModel(_model); } else { // if not, set model directly auto selectionModel = ui->tableView->selectionModel(); ui->tableView->setModel(_model); if (selectionModel) selectionModel->deleteLater(); } } QSortFilterProxyModel * ConfigObjectTableView::proxy() const { if (nullptr == ui->tableView->model()) return nullptr; return qobject_cast(ui->tableView->model()); } bool ConfigObjectTableView::isFilteredOrSorted() const { if (nullptr == ui->tableView->model()) return false; return nullptr != qobject_cast(ui->tableView->model()); } bool ConfigObjectTableView::hasSelection() const { return ui->tableView->selectionModel()->hasSelection(); } QPair ConfigObjectTableView::selection() const { if (isFilteredOrSorted()) return getSelectionRowRange( proxy()->mapSelectionToSource( ui->tableView->selectionModel()->selection()).indexes()); return getSelectionRowRange(ui->tableView->selectionModel()->selection().indexes()); } QHeaderView * ConfigObjectTableView::header() const { return ui->tableView->horizontalHeader(); } void ConfigObjectTableView::onMoveItemUp() { // check if we can move items around safely if (! canMove()) return; // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->tableView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>=rows.first) || (0>rows.second)) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), std::max(0, rows.first-1)); } void ConfigObjectTableView::onMoveItemTenUp() { // check if we can move items around safely if (! canMove()) return; // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->tableView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>=rows.first) || (0>rows.second)) return; // Then move rows int dest = std::max(0, rows.first-9); model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), dest); } void ConfigObjectTableView::onMoveItemTop() { // check if we can move items around safely if (! canMove()) return; // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->tableView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>=rows.first) || (0>rows.second)) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), 0); } void ConfigObjectTableView::onMoveItemDown() { // check if we can move items around safely if (! canMove()) return; // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->tableView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>rows.first) || (0>rows.second) || ((rows.second+1)>=model()->rowCount(QModelIndex()))) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), std::min(model()->rowCount(QModelIndex()), rows.second+2)); } void ConfigObjectTableView::onMoveItemTenDown() { // check if we can move items around safely if (! canMove()) return; // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->tableView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>rows.first) || (0>rows.second) || ((rows.second+1)>=model()->rowCount(QModelIndex()))) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), std::min(rows.second+10, model()->rowCount(QModelIndex()))); } void ConfigObjectTableView::onMoveItemBottom() { // check if we can move items around safely if (! canMove()) return; // Get selection range assuming only continuous selection mode QPair rows = getSelectionRowRange( ui->tableView->selectionModel()->selection().indexes()); // If selection range is invalid or I cannot move at all: done. if ((0>rows.first) || (0>rows.second) || ((rows.second+1)>=model()->rowCount(QModelIndex()))) return; // Then move rows model()->moveRows(QModelIndex(), rows.first, (rows.second-rows.first+1), QModelIndex(), model()->rowCount(QModelIndex())); } void ConfigObjectTableView::onDoubleClicked(QModelIndex idx) { // Map index if sort/filter is enabled if (isFilteredOrSorted()) idx = proxy()->mapToSource(idx); if ((0 > idx.row()) || (idx.row() >= model()->rowCount(QModelIndex()))) return; emit doubleClicked(idx.row()); } bool ConfigObjectTableView::canMove() const { // Check if there is a selection if (! ui->tableView->selectionModel()->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot move items."), tr("Cannot move items: You have to select at least one item first.")); return false; } if (isFilteredOrSorted()) { QMessageBox::information( nullptr, tr("Cannot move items."), tr("Cannot move items as long as there is some filter or sorting applied.")); return false; } return true; } void ConfigObjectTableView::toggleSortFilter(bool enableSortFilter) { Settings().enableSortFilter(objectName(), enableSortFilter); ui->filterWidget->setVisible(enableSortFilter); ui->tableView->setSortingEnabled(enableSortFilter); ui->moveWidget->setVisible(! enableSortFilter); auto selectionModel = ui->tableView->selectionModel(); if (enableSortFilter) { // Setup proxy model ui->tableView->setModel(new QSortFilterProxyModel()); proxy()->setFilterKeyColumn(-1); proxy()->setFilterCaseSensitivity(Qt::CaseInsensitive); if (_model) proxy()->setSourceModel(_model); // Connect filter edit connect(ui->filterEdit, &QLineEdit::textChanged, proxy(), &QSortFilterProxyModel::setFilterFixedString); ui->filterEdit->setFocus(); } else { if (_model) ui->tableView->setModel(_model); ui->filterEdit->clear(); } if (selectionModel) selectionModel->deleteLater(); ui->tableView->setDragEnabled(! enableSortFilter); ui->tableView->viewport()->setAcceptDrops(! enableSortFilter); } ================================================ FILE: src/configobjecttableview.hh ================================================ #ifndef CONFIGOBJECTTABLEVIEW_HH #define CONFIGOBJECTTABLEVIEW_HH #include #include "configitemwrapper.hh" class QHeaderView; class QSortFilterProxyModel; namespace Ui { class ConfigObjectTableView; } class ConfigObjectTableView : public QWidget { Q_OBJECT public: explicit ConfigObjectTableView(QWidget *parent = nullptr); ~ConfigObjectTableView(); GenericTableWrapper *model() const; void setModel(GenericTableWrapper *model); bool hasSelection() const; bool isFilteredOrSorted() const; QPair selection() const; QHeaderView *header() const; signals: void doubleClicked(unsigned row); protected slots: void onMoveItemUp(); void onMoveItemTenUp(); void onMoveItemTop(); void onMoveItemDown(); void onMoveItemTenDown(); void onMoveItemBottom(); void onDoubleClicked(QModelIndex idx); void toggleSortFilter(bool sortFilter); protected: QSortFilterProxyModel *proxy() const; bool canMove() const; private: GenericTableWrapper *_model; Ui::ConfigObjectTableView *ui; }; #endif // CONFIGOBJECTTABLEVIEW_HH ================================================ FILE: src/configobjecttableview.ui ================================================ ConfigObjectTableView 0 0 400 300 Form true 0 0 0 0 0 0 Move selected item(s) to the top. .. Move selected item(s) ten positions up. .. Move selected item(s) one position up. .. Move selected item(s) one position down. .. Move selected item(s) ten positions down. .. Move selected item(s) to the bottom. .. 6 0 0 0 0 .. true .. Toggle Filter and Sorting Ctrl+Shift+L QAction::NoRole Close Sort and Filter Esc QAction::NoRole ================================================ FILE: src/configobjecttypeselectiondialog.cc ================================================ #include "configobjecttypeselectiondialog.hh" #include "ui_configobjecttypeselectiondialog.h" #include "logger.hh" #include ConfigObjectTypeSelectionDialog::ConfigObjectTypeSelectionDialog( const QList &cls, QWidget *parent) : QDialog(parent), ui(new Ui::ConfigObjectTypeSelectionDialog), _types(cls) { ui->setupUi(this); // Iterate over all classes foreach (const QMetaObject type, _types) { logDebug() << "Inspect class '" << type.className() << "'."; ui->typeSelection->addItem(type.className()); } if (0 <= ui->typeSelection->currentIndex()) onSelectionChanged(ui->typeSelection->currentIndex()); ui->description->setWordWrap(true); ui->description->setTextFormat(Qt::RichText); connect(ui->typeSelection, SIGNAL(currentIndexChanged(int)), this, SLOT(onSelectionChanged(int))); } ConfigObjectTypeSelectionDialog::~ConfigObjectTypeSelectionDialog() { delete ui; } const QMetaObject & ConfigObjectTypeSelectionDialog::selectedType() const { return _types.at(ui->typeSelection->currentIndex()); } void ConfigObjectTypeSelectionDialog::onSelectionChanged(int currentIndex) { ui->description->clear(); if (_types.count() <= currentIndex) return; QMetaObject meta = _types.at(currentIndex); QString description = tr("An instance of %1.").arg(meta.className()); QString longDescription; for (int i=meta.classInfoOffset(); idescription->setText(tr("

%1

%2

").arg(description).arg(longDescription)); } ================================================ FILE: src/configobjecttypeselectiondialog.hh ================================================ #ifndef CONFIGOBJECTTYPESELECTIONDIALOG_HH #define CONFIGOBJECTTYPESELECTIONDIALOG_HH #include namespace Ui { class ConfigObjectTypeSelectionDialog; } class ConfigObjectTypeSelectionDialog : public QDialog { Q_OBJECT public: explicit ConfigObjectTypeSelectionDialog(const QList &cls, QWidget *parent = nullptr); ~ConfigObjectTypeSelectionDialog(); const QMetaObject &selectedType() const; protected slots: void onSelectionChanged(int currentIndex); private: Ui::ConfigObjectTypeSelectionDialog *ui; QList _types; }; #endif // CONFIGOBJECTTYPESELECTIONDIALOG_HH ================================================ FILE: src/configobjecttypeselectiondialog.ui ================================================ ConfigObjectTypeSelectionDialog 0 0 400 184 0 0 Create extension object Select the class of object to create 0 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() ConfigObjectTypeSelectionDialog accept() 248 254 157 274 buttonBox rejected() ConfigObjectTypeSelectionDialog reject() 316 260 286 274 ================================================ FILE: src/contactlistview.cc ================================================ #include "contactlistview.hh" #include "ui_contactlistview.h" #include "dmrcontactdialog.hh" #include "m17contactdialog.hh" #include "dtmfcontactdialog.hh" #include "application.hh" #include "settings.hh" #include #include #include ContactListView::ContactListView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::ContactListView), _config(config) { ui->setupUi(this); connect(ui->contactTableView->header(), SIGNAL(sectionCountChanged(int,int)), this, SLOT(loadHeaderState())); connect(ui->contactTableView->header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(storeHeaderState())); ui->contactTableView->setModel(new ContactListWrapper(_config->contacts(), ui->contactTableView)); QMenu *menu = new QMenu(); menu->addAction(ui->actionAdd_DMR_Contact); menu->addAction(ui->actionAdd_M17_Contact); menu->addAction(ui->actionAdd_DTMF_Contact); ui->addContact->setMenu(menu); connect(ui->actionAdd_DMR_Contact, SIGNAL(triggered(bool)), this, SLOT(onAddDMRContact())); connect(ui->actionAdd_M17_Contact, SIGNAL(triggered(bool)), this, SLOT(onAddM17Contact())); connect(ui->actionAdd_DTMF_Contact, SIGNAL(triggered(bool)), this, SLOT(onAddDTMFContact())); connect(ui->remContact, SIGNAL(clicked()), this, SLOT(onRemContact())); connect(ui->contactTableView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditContact(unsigned))); } ContactListView::~ContactListView() { delete ui; } void ContactListView::onAddDMRContact() { Application *app = qobject_cast(QApplication::instance()); DMRContactDialog dialog(app->user(), app->talkgroup(), _config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->contactTableView->hasSelection()) row = ui->contactTableView->selection().second+1; _config->contacts()->add(dialog.contact(), row); } void ContactListView::onAddM17Contact() { M17ContactDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->contactTableView->hasSelection()) row = ui->contactTableView->selection().second+1; _config->contacts()->add(dialog.contact(), row); } void ContactListView::onAddDTMFContact() { DTMFContactDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->contactTableView->hasSelection()) row = ui->contactTableView->selection().second+1; _config->contacts()->add(dialog.contact(), row); } void ContactListView::onRemContact() { // Check if there is any contacts selected if (! ui->contactTableView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete contact"), tr("Cannot delete contact: You have to select a contact first.")); return; } // Get selection and ask for deletion QPair rows = ui->contactTableView->selection(); int numrows = rows.second-rows.first+1; if (rows.first == rows.second) { QString name = _config->contacts()->contact(rows.first)->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete contact?"), tr("Delete contact %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete contacts?"), tr("Delete %1 contacts?").arg(numrows))) return; } // collect all selected contacts // need to collect them first as rows change when deleting contacts QList contacts; contacts.reserve(numrows); for (int i=rows.first; i<=rows.second; i++) contacts.push_back(_config->contacts()->contact(i)); // remove contacts foreach (Contact *contact, contacts) _config->contacts()->del(contact); } void ContactListView::onEditContact(unsigned row) { Application *app = qobject_cast(QApplication::instance()); Contact *contact = _config->contacts()->contact(row); if (DMRContact *dmr = contact->as()) { DMRContactDialog dialog(dmr, app->user(), app->talkgroup(), _config); if (QDialog::Accepted != dialog.exec()) return; dialog.contact(); } else if (M17Contact *m17 = contact->as()) { M17ContactDialog dialog(_config, m17); if (QDialog::Accepted != dialog.exec()) return; dialog.contact(); } else if (DTMFContact *dtmf = contact->as()) { DTMFContactDialog dialog(dtmf, _config); if (QDialog::Accepted != dialog.exec()) return; dialog.contact(); } } void ContactListView::loadHeaderState() { Settings settings; ui->contactTableView->header()->restoreState(settings.headerState("contactList")); } void ContactListView::storeHeaderState() { Settings settings; settings.setHeaderState("contactList", ui->contactTableView->header()->saveState()); } ================================================ FILE: src/contactlistview.hh ================================================ #ifndef CONTACTLISTVIEW_HH #define CONTACTLISTVIEW_HH #include class Config; namespace Ui { class ContactListView; } class ContactListView : public QWidget { Q_OBJECT public: explicit ContactListView(Config *config, QWidget *parent = nullptr); ~ContactListView(); protected slots: void onAddDMRContact(); void onAddM17Contact(); void onAddDTMFContact(); void onRemContact(); void onEditContact(unsigned row); void loadHeaderState(); void storeHeaderState(); private: Ui::ContactListView *ui; Config *_config; }; #endif // CONTACTLISTVIEW_HH ================================================ FILE: src/contactlistview.ui ================================================ ContactListView 0 0 492 306 Form 0 0 Adds a contact to the list. Add Contact Alt++ Delete contact button Delete Contact Alt+- Add M17 Contact Adds an M17 contact to the list. Add DTMF Contact Adds an DTMF (analog) contact to the list. Add DMR Contact Adds an DMR contact to the list. ConfigObjectTableView QWidget
configobjecttableview.hh
1
================================================ FILE: src/contactselectiondialog.cc ================================================ #include "contactselectiondialog.hh" #include "contact.hh" #include #include #include #include #include /* ********************************************************************************************* * * Implementation of MultiGroupCallSelectionDialog * ********************************************************************************************* */ MultiGroupCallSelectionDialog::MultiGroupCallSelectionDialog( ContactList *contacts, bool showPrivateCalls, DMRContactRefList *exclude, QWidget *parent) : QDialog(parent) { _contacts = new QListWidget(); QCheckBox *showPrivCall = new QCheckBox(tr("Show private calls")); showPrivCall->setChecked(showPrivateCalls); for (int i=0; icount(); i++) { Contact *contact = contacts->contact(i); if (! contact->is()) continue; if (exclude && exclude->has(contact)) continue; auto digi = contact->as(); auto item = new QListWidgetItem(digi->name()); item->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); item->setData(Qt::UserRole, QVariant::fromValue(digi)); item->setCheckState(Qt::Unchecked); _contacts->addItem(item); // Hide private calls if showPrivateCall is false (default) item->setHidden((DMRContact::PrivateCall == digi->type()) && (! showPrivateCalls)); } QDialogButtonBox *bbox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); connect(bbox, SIGNAL(accepted()), this, SLOT(accept())); connect(bbox, SIGNAL(rejected()), this, SLOT(reject())); connect(showPrivCall, SIGNAL(toggled(bool)), this, SLOT(showPrivateCallsToggled(bool))); _label = new QLabel(tr("Select a group call:")); QVBoxLayout *layout = new QVBoxLayout(); layout->addWidget(_label); layout->addWidget(_contacts); layout->addWidget(showPrivCall); layout->addWidget(bbox); setLayout(layout); } void MultiGroupCallSelectionDialog::setLabel(const QString &text) { _label->setText(text); } QList MultiGroupCallSelectionDialog::contacts() { QList contacts; for (int i=0; i<_contacts->count(); i++) { if (Qt::Checked == _contacts->item(i)->checkState()) contacts.push_back(_contacts->item(i)->data(Qt::UserRole).value()); } return contacts; } void MultiGroupCallSelectionDialog::showPrivateCallsToggled(bool show) { for (int i=0; i<_contacts->count(); i++) { if (DMRContact::PrivateCall == _contacts->item(i)->data(Qt::UserRole).value()->type()) _contacts->item(i)->setHidden(! show); } } ================================================ FILE: src/contactselectiondialog.hh ================================================ #ifndef CONTACTSELECTIONDIALOG_HH #define CONTACTSELECTIONDIALOG_HH #include #include class DMRContact; class ContactList; class QListWidget; class QLabel; class DMRContactRefList; class MultiGroupCallSelectionDialog: public QDialog { Q_OBJECT public: explicit MultiGroupCallSelectionDialog(ContactList *contacts, bool showPrivateCalls=false, DMRContactRefList *exclude=nullptr, QWidget *parent=nullptr); QList contacts(); void setLabel(const QString &text); protected slots: void showPrivateCallsToggled(bool show); protected: QLabel *_label; QListWidget *_contacts; }; #endif // CONTACTSELECTIONDIALOG_HH ================================================ FILE: src/deviceselectiondialog.cc ================================================ #include "deviceselectiondialog.hh" #include "ui_deviceselectiondialog.h" DeviceSelectionDialog::DeviceSelectionDialog(const QList &interfaces, QWidget *parent) : QDialog(parent), ui(new Ui::DeviceSelectionDialog), _interfaces(interfaces) { ui->setupUi(this); // Populate combo box foreach (USBDeviceDescriptor dev, _interfaces) { ui->comboBox->addItem(dev.description()); } connect(ui->comboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onDeviceSelected(int))); // Select first device if (_interfaces.count()) ui->comboBox->setCurrentIndex(0); connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } DeviceSelectionDialog::~DeviceSelectionDialog() { delete ui; } USBDeviceDescriptor DeviceSelectionDialog::device() const { return _interfaces.at(ui->comboBox->currentIndex()); } void DeviceSelectionDialog::onDeviceSelected(int idx) { // Update description. USBDeviceDescriptor device = _interfaces.at(idx); ui->description->setText(device.description() + ". " + device.longDescription()); } ================================================ FILE: src/deviceselectiondialog.hh ================================================ #ifndef DEVICESELECTIONDIALOG_HH #define DEVICESELECTIONDIALOG_HH #include #include "usbdevice.hh" namespace Ui { class DeviceSelectionDialog; } class DeviceSelectionDialog : public QDialog { Q_OBJECT public: explicit DeviceSelectionDialog(const QList &interfaces, QWidget *parent = nullptr); ~DeviceSelectionDialog(); USBDeviceDescriptor device() const; protected slots: void onDeviceSelected(int idx); private: Ui::DeviceSelectionDialog *ui; const QList &_interfaces; }; #endif // DEVICESELECTIONDIALOG_HH ================================================ FILE: src/deviceselectiondialog.ui ================================================ DeviceSelectionDialog 0 0 400 300 Select a device <html><head/><body><p>There is either more than one device detected or the one found is not considered save to access. Either way, select the device to use.</p></body></html> true 0 0 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() DeviceSelectionDialog accept() 248 254 157 274 buttonBox rejected() DeviceSelectionDialog reject() 316 260 286 274 ================================================ FILE: src/dmrchanneldialog.cc ================================================ #include "dmrchanneldialog.hh" #include "application.hh" #include #include #include "rxgrouplistdialog.hh" #include "repeatercompleter.hh" #include "repeaterdatabase.hh" #include "settings.hh" #include "idselect.hh" #include "admitselect.hh" #include "timeslotselect.hh" #include "dmrcontactdialog.hh" #include "aprsselect.hh" #include "roamingzonedialog.hh" #include "ui_channeldialog.h" /* ********************************************************************************************* * * Implementation of DMRChannelDialog * ********************************************************************************************* */ DMRChannelDialog::DMRChannelDialog(Config *config, QWidget *parent) : ChannelDialog(config, parent), _clone(nullptr), _orig(), _dmrId(nullptr), _admit(nullptr), _colorcode(nullptr), _timeSlot(nullptr), _groupList(nullptr), _contact(nullptr), _aprs(nullptr), _roam(nullptr) { auto app = qobject_cast(qApp); auto completer = new RepeaterCompleter(2, app->repeater(), this); auto filter = new DMRRepeaterFilter(app->repeater(), app->position(), this); filter->setSourceModel(app->repeater()); completer->setModel(filter); ui->channelName->setCompleter(completer); connect(completer, SIGNAL(activated(const QModelIndex &)), this, SLOT(onRepeaterSelected(const QModelIndex &))); ui->rightForm->addRow(tr("Radio Id"), _dmrId = new DMRIdSelect(config)); ui->rightForm->addRow(tr("Tx Admit"), _admit = new DMRAdmitSelect()); ui->rightForm->addRow(tr("Color-code"), _colorcode = new QSpinBox()); _colorcode->setRange(0,15); ui->rightForm->addRow(tr("Time-slot"), _timeSlot = new TimeslotSelect()); ui->rightForm->addRow(tr("Group list"), _groupList = new RXGroupListBox(config->rxGroupLists())); ui->rightForm->addRow(tr("Tx Contact"), _contact = new DMRContactSelect(config)); ui->rightForm->addRow(tr("APRS"), _aprs = new APRSSelect(config)); ui->rightForm->addRow(tr("Roaming zone"), _roam = new RoamingZoneSelect(config)); } void DMRChannelDialog::setChannel(DMRChannel *ch) { if (_clone) { delete _clone; _clone = nullptr; } _orig = ch; if (_orig.isNull()) return; _clone = _orig->clone()->as(); _clone->setParent(this); ChannelDialog::setChannel(_clone); _dmrId->setRadioId(_clone->radioId()); _admit->setAdmit(_clone->admit()); _colorcode->setValue(_clone->colorCode()); _timeSlot->setSlot(_clone->timeSlot()); _groupList->setGroupList(_clone->groupList()); _contact->setContact(_clone->contact()); _aprs->setAPRSSystem(_clone->aprs()); _roam->setRoamingZone(_clone->roaming()); } void DMRChannelDialog::accept() { _clone->setRadioId(_dmrId->radioId()); _clone->setAdmit(_admit->admit()); _clone->setColorCode(_colorcode->value()); _clone->setTimeSlot(_timeSlot->slot()); _clone->setGroupList(_groupList->groupList()); _clone->setContact(_contact->contact()); _clone->setAPRS(_aprs->aprsSystem()); _clone->setRoaming(_roam->roamingZone()); ChannelDialog::accept(); _orig->copy(*_clone); } void DMRChannelDialog::onRepeaterSelected(const QModelIndex &index) { Application *app = qobject_cast(qApp); QModelIndex src = qobject_cast( ui->channelName->completer()->completionModel())->mapToSource(index); src = qobject_cast( ui->channelName->completer()->model())->mapToSource(src); Frequency rx = app->repeater()->get(src.row()).rxFrequency(); Frequency tx = app->repeater()->get(src.row()).txFrequency(); _colorcode->setValue(app->repeater()->get(src.row()).colorCode()); ui->rxFrequency->setText(rx.format()); ui->txFrequency->setText(tx.format()); updateOffsetFrequency(); } ================================================ FILE: src/dmrchanneldialog.hh ================================================ #ifndef DMRCHANNELDIALOG_H #define DMRCHANNELDIALOG_H #include "channeldialog.hh" class DMRChannel; class DMRIdSelect; class DMRAdmitSelect; class QSpinBox; class TimeslotSelect; class RXGroupListBox; class DMRContactSelect; class APRSSelect; class RoamingZoneSelect; class DMRChannelDialog: public ChannelDialog { Q_OBJECT public: DMRChannelDialog(Config *config, QWidget *parent=nullptr); void setChannel(DMRChannel *ch); public slots: void accept() override; protected slots: void onRepeaterSelected(const QModelIndex &index); protected: DMRChannel *_clone; QPointer _orig; DMRIdSelect *_dmrId; DMRAdmitSelect *_admit; QSpinBox *_colorcode; TimeslotSelect *_timeSlot; RXGroupListBox *_groupList; DMRContactSelect *_contact; APRSSelect *_aprs; RoamingZoneSelect *_roam; }; #endif // DMRCHANNELDIALOG_H ================================================ FILE: src/dmrcontactdialog.cc ================================================ #include "dmrcontactdialog.hh" #include "ui_dmrcontactdialog.h" #include #include #include #include #include #include #include #include #include "contact.hh" #include "userdatabase.hh" #include "talkgroupdatabase.hh" #include "settings.hh" #include "config.hh" /* ****************************************************************************************** * * Implementation of DMRContactDialog * ****************************************************************************************** */ DMRContactDialog::DMRContactDialog(UserDatabase *users, TalkGroupDatabase *tgs, Config *context, QWidget *parent) : QDialog(parent), _myContact(new DMRContact(this)), _contact(nullptr), _user_completer(nullptr), _tg_completer(nullptr), _config(context), ui(new Ui::DMRContactDialog) { setWindowTitle(tr("Create DMR Contact")); _user_completer = new QCompleter(users, this); _user_completer->setCompletionColumn(0); _user_completer->setCaseSensitivity(Qt::CaseInsensitive); _tg_completer = new QCompleter(tgs, this); _tg_completer->setCompletionColumn(0); _tg_completer->setCaseSensitivity(Qt::CaseInsensitive); connect(_user_completer, SIGNAL(activated(QModelIndex)), this, SLOT(onCompleterActivated(QModelIndex))); connect(_tg_completer, SIGNAL(activated(QModelIndex)), this, SLOT(onCompleterActivated(QModelIndex))); construct(); } DMRContactDialog::DMRContactDialog(DMRContact *contact, UserDatabase *users, TalkGroupDatabase *tgs, Config *context, QWidget *parent) : QDialog(parent), _myContact(new DMRContact(this)), _contact(contact), _user_completer(nullptr), _tg_completer(nullptr), _config(context), ui(new Ui::DMRContactDialog) { setWindowTitle(tr("Edit DMR Contact")); _user_completer = new QCompleter(users, this); _user_completer->setCompletionColumn(0); _user_completer->setCaseSensitivity(Qt::CaseInsensitive); _tg_completer = new QCompleter(tgs, this); _tg_completer->setCompletionColumn(0); _tg_completer->setCaseSensitivity(Qt::CaseInsensitive); if (_contact) _myContact->copy(*_contact); connect(_user_completer, SIGNAL(activated(QModelIndex)), this, SLOT(onCompleterActivated(QModelIndex))); connect(_tg_completer, SIGNAL(activated(QModelIndex)), this, SLOT(onCompleterActivated(QModelIndex))); construct(); } DMRContactDialog::~DMRContactDialog() { delete ui; } void DMRContactDialog::construct() { ui->setupUi(this); Settings settings; ui->typeComboBox->addItem(tr("Private Call"), unsigned(DMRContact::PrivateCall)); ui->typeComboBox->addItem(tr("Group Call"), unsigned(DMRContact::GroupCall)); ui->typeComboBox->addItem(tr("All Call"), unsigned(DMRContact::AllCall)); if (DMRContact::PrivateCall == _myContact->type()) { ui->typeComboBox->setCurrentIndex(0); ui->nameLineEdit->setCompleter(_user_completer); } else if (DMRContact::GroupCall == _myContact->type()) { ui->typeComboBox->setCurrentIndex(1); ui->nameLineEdit->setCompleter(_tg_completer); } else { ui->typeComboBox->setCurrentIndex(2); ui->nameLineEdit->setCompleter(nullptr); ui->nameLineEdit->setEnabled(false); } ui->nameLineEdit->setText(_myContact->name()); ui->numberLineEdit->setText(QString::number(_myContact->number())); ui->numberLineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression("[0-9]+"))); ui->ringCheckBox->setChecked(_myContact->ring()); ui->extensionView->setObjectName("dmrContactExtension"); ui->extensionView->setObject(_myContact, _config); connect(ui->typeComboBox, SIGNAL(currentIndexChanged(int)), this, SLOT(onTypeChanged(int))); connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } void DMRContactDialog::onTypeChanged(int idx) { if (0 == idx) { ui->numberLineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression("[0-9]+"))); ui->numberLineEdit->setEnabled(true); ui->nameLineEdit->setCompleter(_user_completer); } else if (1 == idx) { ui->numberLineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression("[0-9]+"))); ui->numberLineEdit->setEnabled(true); ui->nameLineEdit->setCompleter(_tg_completer); } else if (2 == idx) { ui->numberLineEdit->setText("16777215"); ui->numberLineEdit->setEnabled(false); ui->nameLineEdit->setCompleter(nullptr); } } void DMRContactDialog::onCompleterActivated(const QModelIndex &idx) { if (0 == ui->typeComboBox->currentIndex()) { // Private call if (nullptr == _user_completer) return; UserDatabase *db = qobject_cast(_user_completer->model()); if (nullptr == db) return; QAbstractProxyModel *model = qobject_cast(_user_completer->completionModel()); if (nullptr == model) return; QModelIndex srcidx = model->mapToSource(idx); ui->numberLineEdit->setText(QString::number(db->user(srcidx.row()).id)); } else if (1 == ui->typeComboBox->currentIndex()) { // Group call if (nullptr == _tg_completer) return; TalkGroupDatabase *db = qobject_cast(_tg_completer->model()); if (nullptr == db) return; QAbstractProxyModel *model = qobject_cast(_tg_completer->completionModel()); if (nullptr == model) return; QModelIndex srcidx = model->mapToSource(idx); ui->numberLineEdit->setText(QString::number(db->talkgroup(srcidx.row()).id)); } } DMRContact * DMRContactDialog::contact() { _myContact->setType(DMRContact::Type(ui->typeComboBox->currentData().toUInt())); _myContact->setName(ui->nameLineEdit->text().simplified()); _myContact->setNumber(ui->numberLineEdit->text().toUInt()); _myContact->setRing(ui->ringCheckBox->isChecked()); DMRContact *contact = _myContact; if (_contact) { _contact->copy(*_myContact); contact = _contact; } else { _myContact->setParent(nullptr); } return contact; } /* ****************************************************************************************** * * Implementation of DMRContactSelect * ****************************************************************************************** */ DMRContactSelect::DMRContactSelect(Config *config, QWidget *parent) : QComboBox(parent) { addItem(tr("[None]"), QVariant::fromValue(nullptr)); for (int i=0; icontacts()->count(); i++) { if (config->contacts()->get(i)->is()) { auto contact = config->contacts()->get(i)->as(); addItem(contact->name(), QVariant::fromValue(contact)); } } } void DMRContactSelect::setContact(DMRContact *contact) { for (int i=0; i() == contact) { setCurrentIndex(i); break; } } } DMRContact * DMRContactSelect::contact() const { return currentData().value(); } ================================================ FILE: src/dmrcontactdialog.hh ================================================ #ifndef DMRCONTACTDIALOG_HH #define DMRCONTACTDIALOG_HH #include #include namespace Ui { class DMRContactDialog; } class QCompleter; class UserDatabase; class TalkGroupDatabase; class DMRContact; class Config; class DMRContactDialog : public QDialog { Q_OBJECT public: explicit DMRContactDialog(UserDatabase *users, TalkGroupDatabase *tgs, Config *context, QWidget *parent=nullptr); explicit DMRContactDialog(DMRContact *contact, UserDatabase *users, TalkGroupDatabase *tgs, Config *context, QWidget *parent=nullptr); ~DMRContactDialog(); public: DMRContact *contact(); protected slots: void onTypeChanged(int idx); void onCompleterActivated(const QModelIndex &idx); protected: void construct(); private: DMRContact *_myContact; DMRContact *_contact; QCompleter *_user_completer; QCompleter *_tg_completer; Config *_config; Ui::DMRContactDialog *ui; }; class DMRContactSelect: public QComboBox { Q_OBJECT public: DMRContactSelect(Config *config, QWidget *parent=nullptr); void setContact(DMRContact *contact); DMRContact *contact() const; }; #endif // DMRCONTACTDIALOG_HH ================================================ FILE: src/dmrcontactdialog.ui ================================================ DMRContactDialog 0 0 392 296 0 0 Dialog 0 Basic Type 0 0 Name Number Ring Extensions Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
buttonBox accepted() DMRContactDialog accept() 248 254 157 274 buttonBox rejected() DMRContactDialog reject() 316 260 286 274
================================================ FILE: src/dmriddialog.cc ================================================ #include "dmriddialog.hh" #include "ui_dmriddialog.h" #include "settings.hh" #include DMRIDDialog::DMRIDDialog(DMRRadioID *radioid, Config *context, QWidget *parent) : QDialog(parent), ui(new Ui::DMRIDDialog), _myID(new DMRRadioID(this)), _editID(radioid), _config(context) { ui->setupUi(this); _myID->copy(*_editID); construct(); } DMRIDDialog::DMRIDDialog(Config *context, QWidget *parent) : QDialog(parent), ui(new Ui::DMRIDDialog), _myID(new DMRRadioID(this)), _editID(nullptr), _config(context) { ui->setupUi(this); construct(); } void DMRIDDialog::construct() { Settings settings; ui->name->setText(_myID->name()); ui->dmrID->setText(QString::number(_myID->number())); ui->dmrID->setValidator(new QIntValidator(1, 16777215)); ui->extensionView->setObjectName("dmrRadioIdExtension"); ui->extensionView->setObject(_myID, _config); } DMRIDDialog::~DMRIDDialog() { delete ui; } DMRRadioID * DMRIDDialog::radioId() { _myID->setName(ui->name->text().simplified()); _myID->setNumber(ui->dmrID->text().toUInt()); if (_editID) { _editID->copy(*_myID); _myID->deleteLater(); _myID = _editID; } else { _myID->setParent(nullptr); } return _myID; } ================================================ FILE: src/dmriddialog.hh ================================================ #ifndef DMRIDDIALOG_HH #define DMRIDDIALOG_HH #include #include "radioid.hh" namespace Ui { class DMRIDDialog; } class DMRIDDialog : public QDialog { Q_OBJECT public: explicit DMRIDDialog(Config *context, QWidget *parent = nullptr); DMRIDDialog(DMRRadioID *radioid, Config *context, QWidget *parent = nullptr); ~DMRIDDialog(); DMRRadioID *radioId(); protected: void construct(); private: Ui::DMRIDDialog *ui; DMRRadioID *_myID; DMRRadioID *_editID; Config *_config; }; #endif // DMRIDDIALOG_HH ================================================ FILE: src/dmriddialog.ui ================================================ DMRIDDialog 0 0 400 300 0 0 Dialog 0 Basic Name DMR ID Extensions Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
buttonBox accepted() DMRIDDialog accept() 248 254 157 274 buttonBox rejected() DMRIDDialog reject() 316 260 286 274
================================================ FILE: src/dtmfcontactdialog.cc ================================================ #include "dtmfcontactdialog.hh" #include "ui_dtmfcontactdialog.h" #include #include "contact.hh" #include "settings.hh" DTMFContactDialog::DTMFContactDialog(Config *context, QWidget *parent) : QDialog(parent), _myContact(new DTMFContact(this)), _contact(nullptr), _config(context), ui(new Ui::DTMFContactDialog) { setWindowTitle(tr("Create DTMF Contact")); construct(); } DTMFContactDialog::DTMFContactDialog(DTMFContact *contact, Config *context, QWidget *parent) : QDialog(parent), _myContact(new DTMFContact(this)), _contact(contact), _config(context), ui(new Ui::DTMFContactDialog) { setWindowTitle(tr("Edit DMR Contact")); if (_contact) _myContact->copy(*_contact); construct(); } DTMFContactDialog::~DTMFContactDialog() { delete ui; } void DTMFContactDialog::construct() { ui->setupUi(this); Settings settings; ui->nameLineEdit->setText(_myContact->name()); ui->numberLineEdit->setText(_myContact->number()); ui->numberLineEdit->setValidator(new QRegularExpressionValidator(QRegularExpression("[0-9a-dA-D\\*#]+"))); ui->ringCheckBox->setChecked(_myContact->ring()); ui->extensionView->setObjectName("dtmfContactExtension"); ui->extensionView->setObject(_myContact, _config); } DTMFContact * DTMFContactDialog::contact() { _myContact->setName(ui->nameLineEdit->text().simplified()); _myContact->setNumber(ui->numberLineEdit->text().simplified()); _myContact->setRing(ui->ringCheckBox->isChecked()); DTMFContact *contact = _myContact; if (_contact) { _contact->copy(*_myContact); contact = _contact; } else { _myContact->setParent(nullptr); } return contact; } ================================================ FILE: src/dtmfcontactdialog.hh ================================================ #ifndef DTMFCONTACTDIALOG_HH #define DTMFCONTACTDIALOG_HH #include namespace Ui { class DTMFContactDialog; } class DTMFContact; class Config; class DTMFContactDialog : public QDialog { Q_OBJECT public: explicit DTMFContactDialog(Config *context, QWidget *parent = nullptr); DTMFContactDialog(DTMFContact *contact, Config *context, QWidget *parent = nullptr); ~DTMFContactDialog(); DTMFContact *contact(); protected: void construct(); private: DTMFContact *_myContact; DTMFContact *_contact; Config *_config; Ui::DTMFContactDialog *ui; }; #endif // DTMFCONTACTDIALOG_HH ================================================ FILE: src/dtmfcontactdialog.ui ================================================ DTMFContactDialog 0 0 379 252 0 0 Dialog 0 Basic Name Number Ring Extensions Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
buttonBox accepted() DTMFContactDialog accept() 248 254 157 274 buttonBox rejected() DTMFContactDialog reject() 316 260 286 274
================================================ FILE: src/errormessageview.cc ================================================ #include "errormessageview.hh" #include "ui_errormessageview.h" ErrorMessageView::ErrorMessageView(const ErrorStack &stack, QWidget *parent) : QDialog(parent), ui(new Ui::ErrorMessageView) { ui->setupUi(this); QFont font = ui->errorMessage->font(); font.setBold(true);ui->errorMessage->setFont(font); if (stack.isEmpty()) { setWindowTitle(tr("Error: Unknown.")); ui->errorMessage->setText("An unknown error has occurred."); ui->errorStack->setVisible(false); return; } setWindowTitle(tr("Error: %1").arg(stack.message(0).message())); if (1 == stack.count()) ui->errorMessage->setText(stack.message(0).message()); else ui->errorMessage->setText( stack.message(0).message() + ":" + stack.message(stack.count()-1).message()); ui->errorStack->setText(stack.format()); } ErrorMessageView::~ErrorMessageView() { delete ui; } ================================================ FILE: src/errormessageview.hh ================================================ #ifndef ERRORMESSAGEVIEW_HH #define ERRORMESSAGEVIEW_HH #include #include "errorstack.hh" namespace Ui { class ErrorMessageView; } class ErrorMessageView : public QDialog { Q_OBJECT public: explicit ErrorMessageView(const ErrorStack &stack, QWidget *parent = nullptr); ~ErrorMessageView(); private: Ui::ErrorMessageView *ui; }; #endif // ERRORMESSAGEVIEW_HH ================================================ FILE: src/errormessageview.ui ================================================ ErrorMessageView Qt::ApplicationModal 0 0 400 300 Dialog TextLabel Traceback: 0 0 Sans Serif TextLabel Qt::PlainText Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop 5 -1 Qt::Horizontal QDialogButtonBox::Close buttonBox accepted() ErrorMessageView accept() 248 254 157 274 buttonBox rejected() ErrorMessageView reject() 316 260 286 274 ================================================ FILE: src/extensionview.cc ================================================ #include "extensionview.hh" #include "ui_extensionview.h" #include "propertydelegate.hh" #include "extensionwrapper.hh" #include "settings.hh" #include ExtensionView::ExtensionView(QWidget *parent) : QWidget(parent), ui(new Ui::ExtensionView), _model(nullptr) { ui->setupUi(this); ui->view->setModel(&_proxy); ui->view->setItemDelegateForColumn(1, &_editor); ui->view->setSelectionMode(QAbstractItemView::SingleSelection); ui->view->setSelectionBehavior(QAbstractItemView::SelectRows); ui->create->setEnabled(false); ui->remove->setEnabled(false); connect(ui->view->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), this, SLOT(onSelectionChanged(QItemSelection,QItemSelection))); connect(ui->create, SIGNAL(clicked(bool)), this, SLOT(onCreate())); connect(ui->remove, SIGNAL(clicked(bool)), this, SLOT(onDelete())); connect(ui->view->header(), SIGNAL(sectionCountChanged(int,int)), this, SLOT(loadSectionState())); connect(ui->view->header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(storeSectionState())); } ExtensionView::~ExtensionView() { delete ui; } void ExtensionView::setObject(ConfigItem *obj, Config *context) { if (nullptr != _model) _model->deleteLater(); _model = new PropertyWrapper(obj, this); _proxy.setSourceModel(_model); _editor.setConfig(context); } void ExtensionView::onSelectionChanged(const QItemSelection ¤t, const QItemSelection &last) { Q_UNUSED(last) // If nothing is selected disable both if (current.isEmpty() || (nullptr == _model)) { ui->create->setEnabled(false); ui->remove->setEnabled(false); return; } // Get selected row QModelIndex row = ui->view->selectionModel()->selectedRows().first(); if (! row.isValid()) { ui->create->setEnabled(false); ui->remove->setEnabled(false); return; } if (_model->isProperty(_proxy.mapToSource(row))) { ConfigItem *obj = _model->parentObject(_proxy.mapToSource(row)); QMetaProperty prop = _model->propertyAt(_proxy.mapToSource(row)); if ((nullptr == obj) || (! prop.isValid())) { ui->create->setEnabled(false); ui->remove->setEnabled(false); return; } if (propIsInstance(prop)) { ui->create->setEnabled(false); ui->remove->setEnabled(false); if (prop.read(obj).value()) { ui->create->setEnabled(false); ui->remove->setEnabled(prop.isWritable()); } else { ui->create->setEnabled(prop.isWritable()); ui->remove->setEnabled(false); } } else if (propIsInstance(prop)) { ui->create->setEnabled(true); ui->remove->setEnabled(false); } } else if (_model->isListElement(_proxy.mapToSource(row))) { ui->create->setEnabled(false); ui->remove->setEnabled(true); } } void ExtensionView::onCreate() { if ((! ui->view->selectionModel()->hasSelection()) || (nullptr == _model)) return; QModelIndex item = _proxy.mapToSource( ui->view->selectionModel()->selectedRows(0).first()); if (! _model->isProperty(item)) return; QMetaProperty prop = _model->propertyAt(item); ConfigItem *obj = _model->parentObject(item); if ((nullptr == obj) || (! prop.isValid())) return; if (propIsInstance(prop) && (! _model->createInstanceAt(item))) { QMessageBox::critical(nullptr, tr("Cannot create extension."), tr("Cannot create extension, consider reporting a bug.")); return; } else if (propIsInstance(prop) && !_model->createElementAt(item)) { QMessageBox::critical(nullptr, tr("Cannot create list element."), tr("Cannot create list element, consider reporting a bug.")); return; } ui->view->selectionModel()->clearSelection(); } void ExtensionView::onDelete() { if ((! ui->view->selectionModel()->hasSelection()) || (nullptr == _model)) return; QModelIndex item = _proxy.mapToSource( ui->view->selectionModel()->selectedRows(0).first()); if (_model->isProperty(item)) _model->deleteInstanceAt(item); else if (_model->isListElement(item)) _model->deleteElementAt(item); ui->view->selectionModel()->clearSelection(); } void ExtensionView::loadSectionState() { Settings settings; ui->view->header()->restoreState(settings.headerState(this->objectName())); } void ExtensionView::storeSectionState() { Settings settings; settings.setHeaderState(this->objectName(), ui->view->header()->saveState()); } ================================================ FILE: src/extensionview.hh ================================================ #ifndef EXTENSIONVIEW_HH #define EXTENSIONVIEW_HH #include #include "extensionwrapper.hh" #include "propertydelegate.hh" namespace Ui { class ExtensionView; } class ConfigItem; class ExtensionView : public QWidget { Q_OBJECT public: explicit ExtensionView(QWidget *parent = nullptr); ~ExtensionView() override; public slots: void setObject(ConfigItem *obj, Config *context); protected slots: void onSelectionChanged(const QItemSelection ¤t, const QItemSelection &last); void onCreate(); void onDelete(); void loadSectionState(); void storeSectionState(); private: Ui::ExtensionView *ui; PropertyWrapper *_model; ExtensionProxy _proxy; PropertyDelegate _editor; }; #endif // EXTENSIONVIEW_HH ================================================ FILE: src/extensionview.ui ================================================ ExtensionView 0 0 400 300 Form Create Remove ================================================ FILE: src/extensionwrapper.cc ================================================ #include "extensionwrapper.hh" #include #include #include "logger.hh" #include "configreference.hh" #include "configobjecttypeselectiondialog.hh" #include "frequency.hh" #include "interval.hh" #include #include "level.hh" /* ******************************************************************************************** * * Implementation of ExtensionProxy * ******************************************************************************************** */ ExtensionProxy::ExtensionProxy(QObject *parent) : QIdentityProxyModel(parent) { // pass... } void ExtensionProxy::setSourceModel(QAbstractItemModel *sourceModel) { QIdentityProxyModel::setSourceModel(sourceModel); _indexP2S.clear(); _indexS2P.clear(); if (PropertyWrapper *model = qobject_cast(sourceModel)) { // Create root element maps for (int s=0,p=0; srowCount(QModelIndex()); s++) { if (model->isExtension(model->index(s,0, QModelIndex()))) { _indexP2S[p] = s; _indexS2P[s] = p; p++; } } } } int ExtensionProxy::rowCount(const QModelIndex &parent) const { if (nullptr == sourceModel()) return 0; // Non root elements if (parent.isValid()) return sourceModel()->rowCount(mapToSource(parent)); // root element return _indexP2S.count(); } int ExtensionProxy::columnCount(const QModelIndex &parent) const { if (nullptr == sourceModel()) return 0; return sourceModel()->columnCount(mapToSource(parent)); } QModelIndex ExtensionProxy::index(int row, int column, const QModelIndex &parent) const { if (parent.isValid()) return mapFromSource(sourceModel()->index(row, column, mapToSource(parent))); return createIndex(row, column, qobject_cast(sourceModel())->root()); } QModelIndex ExtensionProxy::parent(const QModelIndex &child) const { if (! child.isValid()) return QModelIndex(); ConfigItem *root = qobject_cast(sourceModel())->root(); QObject *pptr = reinterpret_cast(child.internalPointer()); if ((root == pptr) || (nullptr == pptr)) return QModelIndex(); QObject *gpptr = pptr->parent(); if (nullptr == gpptr) return QModelIndex(); if (root == gpptr) { // Search for index of parent root: const QMetaObject *meta = root->metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if (! prop.isValid()) continue; if (prop.read(root).value() == pptr) { return createIndex(_indexS2P[p-QObject::staticMetaObject.propertyCount()], 0, reinterpret_cast(root)); } } return QModelIndex(); } return mapFromSource(sourceModel()->parent(child)); } QModelIndex ExtensionProxy::mapToSource(const QModelIndex &proxyIndex) const { // root maps to root if (! proxyIndex.isValid()) return QModelIndex(); // If no source -> blub. if (nullptr == sourceModel()) return QModelIndex(); // If index is not an immediate child of root -> map 1:1 if (proxyIndex.parent().isValid()) { return sourceModel()->index( proxyIndex.row(), proxyIndex.column(), mapToSource(proxyIndex.parent())); } // If index is an immediate child of root -> map only extensions if (_indexP2S.contains(proxyIndex.row())) return sourceModel()->index( _indexP2S[proxyIndex.row()], proxyIndex.column(), QModelIndex()); return QModelIndex(); } QModelIndex ExtensionProxy::mapFromSource(const QModelIndex &sourceIndex) const { // root maps to root if (! sourceIndex.isValid()) return QModelIndex(); // If no source -> blub. if (nullptr == sourceModel()) return QModelIndex(); // If index is not an immediate child of root -> map 1:1 if (sourceModel()->parent(sourceIndex).isValid()) { return createIndex(sourceIndex.row(), sourceIndex.column(), sourceIndex.internalPointer()); } if (_indexS2P.contains(sourceIndex.row())) return createIndex(_indexS2P[sourceIndex.row()], sourceIndex.column(), sourceIndex.internalPointer()); return QModelIndex(); } /* ******************************************************************************************** * * Implementation of PropertyWrapper * ******************************************************************************************** */ PropertyWrapper::PropertyWrapper(ConfigItem *obj, QObject *parent) : QAbstractItemModel(parent), _object(obj) { if (_object) { connect(_object, SIGNAL(beginClear()), this, SLOT(onItemClearing())); connect(_object, SIGNAL(endClear()), this, SLOT(onItemCleared())); } } ConfigItem * PropertyWrapper::root() const { return _object; } ConfigItem * PropertyWrapper::item(const QModelIndex &item) const { if (! item.isValid()) return nullptr; // Get pointer to parent (either list or item) QObject *pptr = _object; if (nullptr != item.internalPointer()) pptr = reinterpret_cast(item.internalPointer()); if (ConfigItem *pobj = qobject_cast(pptr)) { // If parent is item find corresponding property const QMetaObject *meta = pobj->metaObject(); int pcount = meta->propertyCount() - QObject::staticMetaObject.propertyCount(); if (item.row() < pcount) { QMetaProperty prop = meta->property(QObject::staticMetaObject.propertyCount() + item.row()); return prop.read(pobj).value(); } } else if (ConfigObjectList *plst = qobject_cast(pptr)) { // If parent is list if (item.row() < plst->count()) return plst->get(item.row()); } return nullptr; } ConfigObjectList * PropertyWrapper::list(const QModelIndex &item) const { if (! item.isValid()) return nullptr; QObject *pptr = reinterpret_cast(item.internalPointer()); ConfigItem *pobj = qobject_cast(pptr); if (nullptr == pobj) return nullptr; const QMetaObject *meta = pobj->metaObject(); int pcount = meta->propertyCount() - QObject::staticMetaObject.propertyCount(); if (item.row() < pcount) { QMetaProperty prop = meta->property(QObject::staticMetaObject.propertyCount() + item.row()); return prop.read(pobj).value(); } return nullptr; } ConfigObjectList * PropertyWrapper::parentList(const QModelIndex &index) const { if (! index.isValid()) return nullptr; QObject *pptr = reinterpret_cast(index.internalId()); if (nullptr == pptr) return nullptr; if (ConfigObjectList *lst = qobject_cast(pptr)) return lst; return nullptr; } ConfigItem * PropertyWrapper::parentObject(const QModelIndex &index) const { if (! index.isValid()) return _object; QObject *pptr = reinterpret_cast(index.internalId()); return qobject_cast(pptr); } QMetaProperty PropertyWrapper::propertyAt(const QModelIndex &index) const { ConfigItem *pobj = parentObject(index); const QMetaObject *meta = pobj->metaObject(); int propCount = meta->propertyCount() - QObject::staticMetaObject.propertyCount(); if (index.row() < propCount) return meta->property(index.row()+QObject::staticMetaObject.propertyCount()); return QMetaProperty(); } bool PropertyWrapper::isExtension(const QModelIndex &index) const { if (! index.isValid()) return false; QMetaProperty prop = propertyAt(index); return propIsInstance(prop); } bool PropertyWrapper::isProperty(const QModelIndex &index) const { if (! index.isValid()) return true; // Index is property if parent is an ConfigItem QObject *pptr = reinterpret_cast(index.internalPointer()); return nullptr != qobject_cast(pptr); } bool PropertyWrapper::isListElement(const QModelIndex &index) const { if (! index.isValid()) return false; // Index is list element if parent is ConfigObjectList QObject *pptr = reinterpret_cast(index.internalPointer()); if (nullptr == pptr) return false; return nullptr != qobject_cast(pptr); } bool PropertyWrapper::createInstanceAt(const QModelIndex &item) { if (! isProperty(item)) return false; ConfigItem *obj = parentObject(item); QMetaProperty prop = propertyAt(item); if ((nullptr == obj) || (! prop.isValid())) return false; // Check type of property if (! propIsInstance(prop)) return false; // If property is already set -> abort if (prop.read(obj).value()) return false; // Get TypeObject of property if (QMetaType::UnknownType == prop.userType()) return false; QMetaType type(prop.userType()); if (! (QMetaType::PointerToQObject & type.flags())) return false; const QMetaObject *propType = type.metaObject(); // Instantiate extension ConfigItem *ext = qobject_cast( propType->newInstance(QGenericArgument(nullptr, obj))); if (nullptr == ext) return false; // store item beginInsertRows(item, 0, ext->metaObject()->propertyCount()); prop.write(obj, QVariant::fromValue(ext)); endInsertRows(); emit dataChanged(index(item.row(), 0, item.parent()), index(item.row(), 2, item.parent())); emit dataChanged(index(0, 0, item), index(ext->metaObject()->propertyCount(),2, item)); return true; } bool PropertyWrapper::deleteInstanceAt(const QModelIndex &item) { ConfigItem *obj = parentObject(item); QMetaProperty prop = propertyAt(item); if ((nullptr == obj) || (! prop.isValid())) return false; // Check type of property if (! propIsInstance(prop)) return false; // If property is set -> delete if (prop.read(obj).value()) { if (! prop.isWritable()) return false; beginRemoveRows(item, 0, rowCount(item)); prop.write(obj, QVariant(prop.metaType())); endRemoveRows(); return true; } return false; } bool PropertyWrapper::createElementAt(const QModelIndex &item) { ConfigItem *obj = parentObject(item); QMetaProperty prop = propertyAt(item); if ((nullptr == obj) || (! prop.isValid())) return false; ConfigObjectList *lst = prop.read(obj).value(); if (nullptr == lst) return false; ConfigObjectTypeSelectionDialog dialog(lst->elementTypes()); if (QDialog::Accepted != dialog.exec()) return true; QMetaObject type= dialog.selectedType(); // Instantiate element ConfigObject *element = qobject_cast( type.newInstance(QGenericArgument(nullptr, lst))); if (nullptr == element) return false; element->setName(tr("new element")); // store item beginInsertRows(item,lst->count(), lst->count()+1); lst->add(element); endInsertRows(); emit dataChanged(index(item.row(), 0, item.parent()), index(item.row(), 2, item.parent())); QModelIndex elementIndex = createIndex(lst->count(), 0, lst); beginInsertRows(elementIndex, 0, element->metaObject()->propertyCount()); endInsertRows(); emit dataChanged(index(0, 0, item), index(lst->count(), 2, item)); return true; } bool PropertyWrapper::deleteElementAt(const QModelIndex &item) { ConfigObjectList *lst = parentList(item); if (nullptr == lst) return false; if (item.row() >= lst->count()) return false; beginRemoveRows(item, 0, rowCount(item)); lst->del(lst->get(item.row())); endRemoveRows(); return true; } QModelIndex PropertyWrapper::index(int row, int column, const QModelIndex &parent) const { if (! parent.isValid()) { // Handle root element const QMetaObject *meta = _object->metaObject(); int pcount = meta->propertyCount() - QObject::staticMetaObject.propertyCount(); if (row < pcount) return createIndex(row, column, _object); } else if (ConfigItem *pobj = item(parent)) { const QMetaObject *meta = pobj->metaObject(); int pcount = meta->propertyCount() - QObject::staticMetaObject.propertyCount(); if (row < pcount) return createIndex(row, column, pobj); } else if (ConfigObjectList *plst = list(parent)) { if (row < plst->count()) return createIndex(row, column, plst); } return QModelIndex(); } QModelIndex PropertyWrapper::parent(const QModelIndex &child) const { if (! child.isValid()) return QModelIndex(); QObject *pptr = reinterpret_cast(child.internalPointer()); // Handle root if (ConfigItem *pobj = qobject_cast(pptr)) { if (_object == pobj) return QModelIndex(); } QObject *gpptr = pptr->parent(); // Should not happen if (nullptr == gpptr) return QModelIndex(); if (ConfigItem *gp = qobject_cast(gpptr)) { // If grand parent is item: // Search for parent in grand-parent's properties const QMetaObject *meta = gp->metaObject(); for (int p=QObject::staticMetaObject.propertyCount(); ppropertyCount(); p++) { QMetaProperty prop = meta->property(p); if (! prop.isValid()) continue; if (prop.read(gp).value() == pptr) { return createIndex(p-QObject::staticMetaObject.propertyCount(), 0, reinterpret_cast(gp)); } } } else if (ConfigObjectList *gp = qobject_cast(gpptr)) { // If grand parent is item: // Search for parent in grand-parent's elements for (int i=0; icount(); i++) { if (pptr == gp->get(i)) return createIndex(i, 0, reinterpret_cast(gp)); } } return QModelIndex(); } int PropertyWrapper::rowCount(const QModelIndex &parent) const { if (! parent.isValid()) { // If parent is root -> handle _object const QMetaObject *meta = _object->metaObject(); return (meta->propertyCount() - QObject::staticMetaObject.propertyCount()); } if (ConfigItem *pobj = item(parent)) { // If parent is item -> return property count const QMetaObject *meta = pobj->metaObject(); return (meta->propertyCount() - QObject::staticMetaObject.propertyCount()); } else if (ConfigObjectList *plst = list(parent)) { // If parent is list -> return element count. return plst->count(); } return 0; } int PropertyWrapper::columnCount(const QModelIndex &parent) const { Q_UNUSED(parent) return 3; } Qt::ItemFlags PropertyWrapper::flags(const QModelIndex &index) const { if (isProperty(index)) { // check if property is a config object or atomic (or reference) QMetaProperty prop = propertyAt(index); // Object can be selected and expanded, but not edited directly if (propIsInstance(prop) || propIsInstance(prop)) return Qt::ItemIsSelectable | Qt::ItemIsEnabled; // References are edited directly by combo-box. See PropertyDelegate. They cannot be expanded. if (propIsInstance(prop) && prop.isScriptable() && (1 == index.column())) return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemNeverHasChildren; // Atomic properties are directly editable, see also PropertyDelegate. They cannot be expanded. if (prop.isWritable() && (1 == index.column())) return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemIsEditable | Qt::ItemNeverHasChildren; // Some default values. return Qt::ItemIsSelectable | Qt::ItemIsEnabled | Qt::ItemNeverHasChildren; } else if (isListElement(index)) { return Qt::ItemIsSelectable | Qt::ItemIsEnabled; } return Qt::NoItemFlags; } bool PropertyWrapper::hasChildren(const QModelIndex &element) const { // If root element if (! element.isValid()) return true; // Get parent object (might be item or list) QObject *pptr = reinterpret_cast(element.internalId()); if (ConfigItem* pobj = qobject_cast(pptr)) { // If parent is item -> get addressed property QMetaProperty prop = propertyAt(element); // If property is an item or list (and is set) if (propIsInstance(prop)) { return nullptr != prop.read(pobj).value(); } else if (propIsInstance(prop)) { // If element and there are elements within the list. ConfigObjectList *lst = prop.read(pobj).value(); return (nullptr != lst) && (lst->count()>0); } } else if (ConfigObjectList *lst = qobject_cast(pptr)) { // If parent is a list, check row index. List elements are always objects and have children. return lst->count() > element.row(); } return false; } QVariant PropertyWrapper::headerData(int section, Qt::Orientation orientation, int role) const { // Only handle horizontal display data if ((Qt::Horizontal != orientation) || (Qt::DisplayRole != role)) return QAbstractItemModel::headerData(section, orientation, role); // Dispatch by section index switch (section) { case 0: return tr("Property"); case 1: return tr("Value"); case 2: return tr("Description"); default: break; } // default return QAbstractItemModel::headerData(section, orientation, role); } QVariant PropertyWrapper::data(const QModelIndex &index, int role) const { if (! index.isValid()) return QVariant(); if (isProperty(index)) { ConfigItem *pobj = parentObject(index); QMetaProperty prop = propertyAt(index); if (0 == index.column()) { if (Qt::DisplayRole == role) return prop.name(); } else if ((2 == index.column()) && (Qt::DisplayRole == role)) { if (propIsInstance(prop)) { ConfigItem *item = prop.read(pobj).value(); if (item && item->hasDescription()) return item->description(); } if (pobj->hasDescription(prop)) return pobj->description(prop); return QVariant(); } else if (Qt::ToolTipRole == role) { if (propIsInstance(prop)) { ConfigItem *item = prop.read(pobj).value(); if (item && item->hasLongDescription()) return item->longDescription(); } if (pobj->hasLongDescription(prop)) return pobj->longDescription(prop); if (pobj->hasDescription(prop)) return pobj->description(prop); return QVariant(); } QVariant value = prop.read(pobj); if (prop.isFlagType()) { if (Qt::EditRole == role) return value; QMetaEnum e = prop.enumerator(); if (Qt::DisplayRole == role) return e.valueToKeys(value.toInt()); } if (prop.isEnumType() && ((Qt::DisplayRole == role) || (Qt::EditRole == role))) { QMetaEnum e = prop.enumerator(); const char *key = e.valueToKey(value.toInt()); if (nullptr == key) { logError() << "Cannot map value " << value.toUInt() << " to enum " << e.name() << ". Ignore attribute but this points to an incompatibility in some codeplug. " << "Consider reporting it to https://github.com/hmatuschek/qdmr/issues."; return QVariant(); } return QString(key); } else if ((QMetaType::Bool == prop.typeId()) && (Qt::EditRole == role)) { return value; } else if ((QMetaType::Bool == prop.typeId()) && (Qt::DisplayRole == role)) { if (value.toBool()) return tr("true"); return tr("false"); } else if ( ((QMetaType::Int == prop.typeId()) || (QMetaType::UInt == prop.typeId()) || (QMetaType::Double == prop.typeId()) || (QMetaType::QString == prop.typeId())) && ((Qt::DisplayRole == role) || (Qt::EditRole==role)) ) { return value; } else if (QMetaType::fromType() == prop.metaType()) { if (Qt::DisplayRole == role) return value.value().format(); else if (Qt::EditRole == role) return value; } else if (QMetaType::fromType() == prop.metaType()) { if (Qt::DisplayRole == role) { if (value.value().isInfinite()) return QChar(0x221e); return value.value().format(); } else if (Qt::EditRole == role) { return value; } } else if (QMetaType::fromType() == prop.metaType()) { if (Qt::DisplayRole == role) { if (value.value().isInvalid()) return tr("None"); if (value.value().isNull()) return tr("Off"); return value.value().value(); } else if (Qt::EditRole == role) { return value; } } else if (QMetaType::fromType().id() == prop.typeId()) { if (Qt::DisplayRole == role) { if (value.value().isValid()) return QString("%1 / %2") .arg(value.value().latitude()) .arg(value.value().longitude()); return tr("[None]"); } if (Qt::EditRole == role) return value; } else if (value.value() && (Qt::DisplayRole == role)) { ConfigObjectReference *ref = value.value(); ConfigObject *obj = ref->as(); if (nullptr == obj) return tr("[None]"); return QString("%1 (%2)").arg(obj->name()).arg(obj->metaObject()->className()); } else if (value.value() && (Qt::EditRole == role)) { return value; } else if (propIsInstance(prop)) { ConfigItem *item = value.value(); if (Qt::DisplayRole == role) { if (nullptr == item) return tr("[None]"); else return tr("Instance of %1").arg(item->metaObject()->className()); } } else if (propIsInstance(prop)) { ConfigObjectList *lst = value.value(); if (Qt::DisplayRole == role) return tr("List of %1 instances").arg(lst->classNames().join(", ")); } else if (Qt::DisplayRole == role) { logWarn() << "Unhandled property '" << prop.name() << "' of type " << prop.typeName() << "."; } } else if (isListElement(index)) { ConfigObjectList *lst = parentList(index); if (index.row() >= lst->count()) return QVariant(); if ((0 == index.column()) && (Qt::DisplayRole == role)) return lst->get(index.row())->name(); else if ((1 == index.column()) && (Qt::DisplayRole == role)) return lst->get(index.row())->description(); else if ((2 == index.column()) && (Qt::DisplayRole == role)) return lst->get(index.row())->longDescription(); } return QVariant(); } void PropertyWrapper::onItemClearing() { beginResetModel(); } void PropertyWrapper::onItemCleared() { endResetModel(); } ================================================ FILE: src/extensionwrapper.hh ================================================ #ifndef EXTENSIONWRAPPER_HH #define EXTENSIONWRAPPER_HH #include "configobject.hh" #include #include #include #include class ExtensionProxy: public QIdentityProxyModel { Q_OBJECT public: explicit ExtensionProxy(QObject *parent=nullptr); public: void setSourceModel(QAbstractItemModel *sourceModel); int rowCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const; QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex parent(const QModelIndex &child) const; QModelIndex mapFromSource(const QModelIndex &sourceIndex) const; //QItemSelection mapSelectionFromSource(const QItemSelection &selection) const; QModelIndex mapToSource(const QModelIndex &proxyIndex) const; //QItemSelection mapSelectionToSource(const QItemSelection &selection) const; protected: QHash _indexS2P, _indexP2S; }; class PropertyWrapper: public QAbstractItemModel { Q_OBJECT public: PropertyWrapper(ConfigItem *obj, QObject *parent=nullptr); ConfigItem *root() const; ConfigItem *item(const QModelIndex &item) const; ConfigObjectList *list(const QModelIndex &item) const; ConfigObjectList *parentList(const QModelIndex &index) const; ConfigItem *parentObject(const QModelIndex &index) const; QMetaProperty propertyAt(const QModelIndex &index) const; bool isProperty(const QModelIndex &index) const; bool isExtension(const QModelIndex &index) const; bool isListElement(const QModelIndex &index) const; bool createInstanceAt(const QModelIndex &item); bool deleteInstanceAt(const QModelIndex &item); bool createElementAt(const QModelIndex &item); bool deleteElementAt(const QModelIndex &item); QModelIndex index(int row, int column, const QModelIndex &parent) const; QModelIndex parent(const QModelIndex &child) const; int rowCount(const QModelIndex &parent) const; int columnCount(const QModelIndex &parent) const; Qt::ItemFlags flags(const QModelIndex &index) const; bool hasChildren(const QModelIndex &parent) const; QVariant data(const QModelIndex &index, int role) const; QVariant headerData(int section, Qt::Orientation orientation, int role) const; protected slots: void onItemClearing(); void onItemCleared(); protected: ConfigItem *_object; QPointer _original; }; #endif // EXTENSIONWRAPPER_HH ================================================ FILE: src/flageditdialog.cc ================================================ #include "flageditdialog.hh" #include "ui_flageditdialog.h" FlagEditDialog::FlagEditDialog(QMetaEnum metaFlag, QWidget *parent) : QDialog(parent), ui(new Ui::FlagEditDialog), _metaFlag(metaFlag) { ui->setupUi(this); for (int i=0; isetFlags(Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemNeverHasChildren); item->setData(Qt::UserRole, QByteArray(metaFlag.key(i))); ui->list->addItem(item); } } int FlagEditDialog::value() const { QByteArray keys; for (int i=0; ilist->count(); i++) { if (Qt::Checked == ui->list->item(i)->checkState()) { if (! keys.isEmpty()) keys += "|"; keys.append(ui->list->item(i)->data(Qt::UserRole).toByteArray()); } } if (keys.isEmpty()) return 0; return _metaFlag.keysToValue(keys); } void FlagEditDialog::setValue(int value) { QSet keys; for (auto key: _metaFlag.valueToKeys(value).split('|')) keys.insert(key); for (int i=0; ilist->count(); i++) { if (keys.contains(ui->list->item(i)->data(Qt::UserRole).toByteArray())) ui->list->item(i)->setCheckState(Qt::Checked); else ui->list->item(i)->setCheckState(Qt::Unchecked); } } FlagEditDialog::~FlagEditDialog() { delete ui; } ================================================ FILE: src/flageditdialog.hh ================================================ #ifndef FLAGEDITDIALOG_HH #define FLAGEDITDIALOG_HH #include #include namespace Ui { class FlagEditDialog; } class FlagEditDialog : public QDialog { Q_OBJECT public: explicit FlagEditDialog(QMetaEnum metaFlag, QWidget *parent = nullptr); ~FlagEditDialog(); int value() const; void setValue(int value); private: Ui::FlagEditDialog *ui; QMetaEnum _metaFlag; }; #endif // FLAGEDITDIALOG_HH ================================================ FILE: src/flageditdialog.ui ================================================ FlagEditDialog 0 0 247 300 Select Flags Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Ok buttonBox accepted() FlagEditDialog accept() 248 254 157 274 buttonBox rejected() FlagEditDialog reject() 316 260 286 274 ================================================ FILE: src/fmchanneldialog.cc ================================================ #include "fmchanneldialog.hh" #include "application.hh" #include #include "settings.hh" #include "repeatercompleter.hh" #include "repeaterdatabase.hh" #include "ui_channeldialog.h" #include "squelchedit.hh" #include "admitselect.hh" #include "selectivecallbox.hh" #include "bandwidthselect.hh" #include "aprsselect.hh" /* ********************************************************************************************* * * Implementation of FMChannelDialog * ********************************************************************************************* */ FMChannelDialog::FMChannelDialog(Config *config, QWidget *parent) : ChannelDialog(config, parent), _clone(nullptr), _orig(nullptr), _squelch(nullptr), _admit(nullptr), _rxTone(nullptr), _txTone(nullptr), _bandwidth(nullptr), _aprs(nullptr) { auto app = qobject_cast(qApp); auto filter = new FMRepeaterFilter(app->repeater(), app->position(), this); filter->setSourceModel(app->repeater()); auto completer = new RepeaterCompleter(2, app->repeater(), this); completer->setModel(filter); ui->channelName->setCompleter(completer); connect(completer, QOverload::of(&QCompleter::activated), this, &FMChannelDialog::onRepeaterSelected); ui->rightForm->addRow(tr("Squelch"), _squelch = new ChannelSquelchEdit(config->settings()->audio()->squelch())); ui->rightForm->addRow(tr("Tx Admit"), _admit = new FMAdmitSelect()); ui->rightForm->addRow(tr("Rx Tone"), _rxTone = new SelectiveCallBox()); ui->rightForm->addRow(tr("Tx Tone"), _txTone = new SelectiveCallBox()); ui->rightForm->addRow(tr("Bandwidth"), _bandwidth = new BandwidthSelect()); ui->rightForm->addRow(tr("APRS"), _aprs = new FMAPRSSelect(config)); } void FMChannelDialog::setChannel(FMChannel *channel) { if (_clone) { delete _clone; _clone = nullptr; } _orig = channel; if (_orig.isNull()) return; _clone = _orig->clone()->as(); _clone->setParent(this); ChannelDialog::setChannel(_clone); _squelch->setChannel(_clone); _admit->setAdmit(_clone->admit()); _rxTone->setSelectiveCall(_clone->rxTone()); _txTone->setSelectiveCall(_clone->txTone()); _bandwidth->setBandwidth(_clone->bandwidth()); _aprs->setAPRSSystem(_clone->aprs()); } void FMChannelDialog::accept() { _squelch->accept(); _clone->setAdmit(_admit->admit()); _clone->setRXTone(_rxTone->selectiveCall()); _clone->setTXTone(_txTone->selectiveCall()); _clone->setBandwidth(_bandwidth->bandwidth()); _clone->setAPRS(_aprs->aprsSystem()); ChannelDialog::accept(); _orig->copy(*_clone); } void FMChannelDialog::onRepeaterSelected(const QModelIndex &index) { auto app = qobject_cast(qApp); QModelIndex src = qobject_cast( ui->channelName->completer()->completionModel())->mapToSource(index); src = qobject_cast( ui->channelName->completer()->model())->mapToSource(src); Frequency rx = app->repeater()->get(src.row()).rxFrequency(); Frequency tx = app->repeater()->get(src.row()).txFrequency(); _rxTone->setSelectiveCall(app->repeater()->get(src.row()).rxTone()); _txTone->setSelectiveCall(app->repeater()->get(src.row()).txTone()); ui->rxFrequency->setText(rx.format()); ui->txFrequency->setText(tx.format()); updateOffsetFrequency(); } ================================================ FILE: src/fmchanneldialog.hh ================================================ #ifndef FMCHANNELDIALOG_H #define FMCHANNELDIALOG_H #include "channeldialog.hh" #include "config.hh" class ChannelSquelchEdit; class FMAdmitSelect; class SelectiveCallBox; class BandwidthSelect; class FMAPRSSelect; class FMChannelDialog: public ChannelDialog { Q_OBJECT public: FMChannelDialog(Config *config, QWidget *parent=nullptr); void setChannel(FMChannel *fm); public slots: void accept() override; protected slots: void onRepeaterSelected(const QModelIndex &index); protected: FMChannel *_clone; QPointer _orig; ChannelSquelchEdit *_squelch; FMAdmitSelect *_admit; SelectiveCallBox *_rxTone, *_txTone; BandwidthSelect *_bandwidth; FMAPRSSelect *_aprs; }; #endif // FMCHANNELDIALOG_H ================================================ FILE: src/generalsettingsview.cc ================================================ #include "generalsettingsview.hh" #include "channel_type_edit.hh" #include "config.hh" #include "ui_generalsettingsview.h" GeneralSettingsView::GeneralSettingsView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::GeneralSettingsView), _config(config) { ui->setupUi(this); ui->powerValue->setItemData(0, (unsigned)Channel::Power::Max); ui->powerValue->setItemData(1, (unsigned)Channel::Power::High); ui->powerValue->setItemData(2, (unsigned)Channel::Power::Mid); ui->powerValue->setItemData(3, (unsigned)Channel::Power::Low); ui->powerValue->setItemData(4, (unsigned)Channel::Power::Min); ui->extensionView->setObjectName("radioSettingsExtension"); ui->extensionView->setObject(_config->settings(), _config); ui->bootMelodyEdit->setMelody(_config->settings()->tone()->bootMelody()); ui->callStartEdit->setMelody(_config->settings()->tone()->callStartMelody()); ui->callEndEdit->setMelody(_config->settings()->tone()->callEndMelody()); ui->channelIdleEdit->setMelody(_config->settings()->tone()->channelIdleMelody()); ui->callResetEdit->setMelody(_config->settings()->tone()->callResetMelody()); // update view from config onConfigModified(); connect(_config, SIGNAL(modified(ConfigItem*)), this, SLOT(onConfigModified())); connect(ui->introLine1, &QLineEdit::editingFinished, [this]() { this->_config->settings()->boot()->setMessage1(ui->introLine1->text().simplified()); }); connect(ui->introLine2, &QLineEdit::editingFinished, [this]() { this->_config->settings()->boot()->setMessage2(ui->introLine2->text().simplified()); }); connect(ui->squelch, &QSpinBox::valueChanged, [this](int value) { this->_config->settings()->audio()->setSquelch(Level::fromValue(value)); if (this->ui->dmrSquelchDefault->isChecked()) this->ui->dmrSquelch->setValue(value); }); connect(ui->dmrSquelch, &QSpinBox::valueChanged, [this](int value) { if (! this->ui->dmrSquelchDefault->isChecked()) this->_config->settings()->audio()->setDMRSquelch(Level::fromValue(value)); }); connect(ui->dmrSquelchDefault, &QCheckBox::toggled, [this](bool checked) { if (checked) { this->_config->settings()->audio()->disableDMRSquelch(); this->ui->dmrSquelch->setEnabled(false); } else { this->_config->settings()->audio()->setDMRSquelch(Level::fromValue(this->ui->dmrSquelch->value())); this->ui->dmrSquelch->setEnabled(true); } }); connect(ui->mic, &QSpinBox::valueChanged, [this](int value) { this->_config->settings()->audio()->setMicGain(Level::fromValue(value)); }); connect(ui->fmMicGain, &QSpinBox::valueChanged, [this] (int value) { this->_config->settings()->audio()->setFMMicGain(Level::fromValue(value)); }); connect(ui->voxValue, &QSpinBox::valueChanged, [this](int value) { if (0 == value) this->_config->settings()->audio()->disableVox(); else this->_config->settings()->audio()->setVox(Level::fromValue(value)); }); connect(ui->voxDelay, &QLineEdit::editingFinished, [this]() { Interval delay; delay.parse(this->ui->voxDelay->text().simplified()); this->_config->settings()->audio()->setVOXDelay(delay); }); connect(ui->maxSpeakerVolume, &QSpinBox::valueChanged, [this](int value) { this->_config->settings()->audio()->setMaxSpeakerVolume(Level::fromValue(value)); }); connect(ui->maxHeadphoneVolume, &QSpinBox::valueChanged, [this](int value) { this->_config->settings()->audio()->setMaxHeadphoneVolume(Level::fromValue(value)); }); connect(ui->speech, &QCheckBox::toggled, [this](bool checked) { this->_config->settings()->audio()->enableSpeechSynthesis(checked); }); connect(ui->disableAllTones, &QCheckBox::toggled, [this](bool checked) { this->_config->settings()->tone()->enableSilent(checked); }); connect(ui->keyToneVolume, &QSpinBox::valueChanged, [this](int value) { this->_config->settings()->tone()->setKeyToneVolume(Level::fromValue(value)); }); connect(ui->bootMelodyEnable, &QCheckBox::toggled, [this](bool checked) { this->_config->settings()->tone()->enableBootTone(checked); }); connect(ui->talkPermitSelect, &ChannelTypeEdit::typesChanged, [this](Channel::Types types) { this->_config->settings()->tone()->setTalkPermit(types); }); connect(ui->callStartSelect, &ChannelTypeEdit::typesChanged, [this](Channel::Types types) { this->_config->settings()->tone()->setCallStart(types); }); connect(ui->callEndSelect, &ChannelTypeEdit::typesChanged, [this](Channel::Types types) { this->_config->settings()->tone()->setCallEnd(types); }); connect(ui->channelIdleSelect, &ChannelTypeEdit::typesChanged, [this](Channel::Types types) { this->_config->settings()->tone()->setChannelIdle(types); }); connect(ui->callResetEnable, &QCheckBox::toggled, [this](bool checked) { this->_config->settings()->tone()->enableCallReset(checked); }); connect(ui->powerValue, &QComboBox::currentIndexChanged, [this](int index) { this->_config->settings()->setPower(this->ui->powerValue->itemData(index).value()); }); connect(ui->totValue, &QSpinBox::valueChanged, [this](int value) { if (0 == value) this->_config->settings()->disableTOT(); else this->_config->settings()->setTOT(Interval::fromSeconds(value)); }); } GeneralSettingsView::~GeneralSettingsView() { delete ui; } void GeneralSettingsView::onConfigModified() { // boot settings ui->introLine1->setText(_config->settings()->boot()->message1()); ui->introLine2->setText(_config->settings()->boot()->message2()); // audio settings ui->squelch->setValue(_config->settings()->audio()->squelch().value()); if (_config->settings()->audio()->dmrSquelchEnabled()) { ui->dmrSquelch->setValue(_config->settings()->audio()->dmrSquelch().value()); ui->dmrSquelchDefault->setChecked(false); ui->dmrSquelch->setEnabled(true); } else { ui->dmrSquelch->setValue(3); ui->dmrSquelchDefault->setChecked(true); ui->dmrSquelch->setEnabled(false); } ui->mic->setValue(_config->settings()->audio()->micGain().value()); ui->fmMicGain->setValue(_config->settings()->audio()->fmMicGain().value()); ui->voxValue->setValue(_config->settings()->audio()->vox().value()); ui->voxDelay->setText(_config->settings()->audio()->voxDelay().format()); ui->speech->setChecked(_config->settings()->audio()->speechSynthesisEnabled()); // Tone settings: ui->disableAllTones->setChecked(_config->settings()->tone()->silent()); ui->keyToneVolume->setValue(_config->settings()->tone()->keyToneVolume().value()); ui->bootMelodyEnable->setChecked(_config->settings()->tone()->bootToneEnabled()); ui->talkPermitSelect->setTypes(_config->settings()->tone()->talkPermit()); ui->callStartSelect->setTypes(_config->settings()->tone()->callStart()); ui->callEndSelect->setTypes(_config->settings()->tone()->callEnd()); ui->channelIdleSelect->setTypes(_config->settings()->tone()->channelIdle()); ui->callResetEnable->setChecked(_config->settings()->tone()->callResetEnabled()); // channel default settings switch(_config->settings()->power()) { case Channel::Power::Max: ui->powerValue->setCurrentIndex(0); break; case Channel::Power::High: ui->powerValue->setCurrentIndex(1); break; case Channel::Power::Mid: ui->powerValue->setCurrentIndex(2); break; case Channel::Power::Low: ui->powerValue->setCurrentIndex(3); break; case Channel::Power::Min: ui->powerValue->setCurrentIndex(4); break; } if (_config->settings()->totDisabled()) ui->totValue->setValue(0); else ui->totValue->setValue(_config->settings()->tot().seconds()); } ================================================ FILE: src/generalsettingsview.hh ================================================ #ifndef GENERALSETTINGSVIEW_HH #define GENERALSETTINGSVIEW_HH #include class Config; namespace Ui { class GeneralSettingsView; } class GeneralSettingsView : public QWidget { Q_OBJECT public: explicit GeneralSettingsView(Config *config, QWidget *parent = nullptr); ~GeneralSettingsView(); protected slots: void onConfigModified(); private: Ui::GeneralSettingsView *ui; Config *_config; }; #endif // GENERALSETTINGSVIEW_HH ================================================ FILE: src/generalsettingsview.ui ================================================ GeneralSettingsView 0 0 644 557 0 Boot Settings Intro Line 1 First greeting line (if supported by the radio). Intro line 1 Intro Line 2 Second greeting line (if supported by the radio). Intro line 2 Audio Settings Default Microphone Amplification Specifies the default (DMR) microphone amplifiction level. 1 10 FM Microphone Amplifiction default 10 Default Squelch open 10 3 DMR Squelch 0 0 open 10 Disables a separate squelch level for DMR. Uses the default one. Default VOX Sensitivity Specifies the VOX sensitivity. Off 10 VOX Delay Specifies the delay between voice detection and transmission. E.g. 500 ms. Maximum Speaker Volume Limits the maximum speaker volume. 1 10 10 Maximum Headphone Volume Limits the maximum headphone volume. 1 10 10 Speech Synthesis Tone Settings Disable all Disables all tones. Key tone volume off 10 SMS tone Enables a tone on SMS reception. Ringtone Enables the ringtone on incoming private calls. Talk permit Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop Boot melody Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop bootMelodyEnable Enabled Call Start Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop callStartSelect Call End Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop callEndSelect Channel Idle channelIdleSelect Call Reset Qt::AlignmentFlag::AlignLeading|Qt::AlignmentFlag::AlignLeft|Qt::AlignmentFlag::AlignTop callResetEnable 0 0 0 0 Enabled Default Values Power Max High Mid Low Min Transmit Timeout Off s 9999 Extensions ExtensionView QWidget
extensionview.hh
1
MelodyEdit QWidget
melody_edit.hh
1
ChannelTypeEdit QWidget
channel_type_edit.hh
1
================================================ FILE: src/gpssystemdialog.cc ================================================ #include "gpssystemdialog.hh" #include "settings.hh" GPSSystemDialog::GPSSystemDialog(Config *config, QWidget *parent) : QDialog(parent), _config(config), _myGPSSystem(new DMRAPRSSystem(this)), _gpsSystem(nullptr) { setWindowTitle(tr("Create DMR APRS System")); construct(); } GPSSystemDialog::GPSSystemDialog(Config *config, DMRAPRSSystem *gps, QWidget *parent) : QDialog(parent), _config(config), _myGPSSystem(new DMRAPRSSystem(this)), _gpsSystem(gps) { setWindowTitle(tr("Edit DMR APRS System")); if (_gpsSystem) _myGPSSystem->copy(*_gpsSystem); construct(); } void GPSSystemDialog::construct() { setupUi(this); Settings settings; // setup name entry name->setText(_myGPSSystem->name()); // setup contact entry for (int i=0; i<_config->contacts()->count(); i++) { if (! _config->contacts()->get(i)->is()) continue; auto cont = _config->contacts()->get(i)->as(); destination->addItem(cont->name(), QVariant::fromValue(cont)); if (_myGPSSystem->contact() == cont) destination->setCurrentIndex(i); } // setup period if (_myGPSSystem->periodDisabled()) period->setValue(0); else period->setValue(_myGPSSystem->period().seconds()); // setup revert channel revChannel->addItem(tr("[Selected]"), QVariant::fromValue(nullptr)); if (! _myGPSSystem->hasRevertChannel()) revChannel->setCurrentIndex(0); for (int i=1,j=0; j<_config->channelList()->count(); j++) { if (! _config->channelList()->channel(j)->is()) continue; revChannel->addItem(_config->channelList()->channel(j)->name(), QVariant::fromValue(_config->channelList()->channel(j)->as())); if (_myGPSSystem->hasRevertChannel() && (_myGPSSystem->revertChannel() == _config->channelList()->channel(j)->as())) revChannel->setCurrentIndex(i); i++; } extensionView->setObjectName("dmrAPRSSystemExtension"); extensionView->setObject(_myGPSSystem, _config); } DMRAPRSSystem * GPSSystemDialog::gpsSystem() { _myGPSSystem->setName(name->text().simplified()); _myGPSSystem->setContact(destination->currentData().value()); if (0 == period->value()) _myGPSSystem->disablePeriod(); else _myGPSSystem->setPeriod(Interval::fromSeconds(period->value())); if (revChannel->currentData().isNull()) _myGPSSystem->resetRevertChannel(); else _myGPSSystem->setRevertChannel(revChannel->currentData().value()); DMRAPRSSystem *sys = _myGPSSystem; if (_gpsSystem) { _gpsSystem->copy(*_myGPSSystem); sys = _gpsSystem; } else { _myGPSSystem->setParent(nullptr); } return sys; } ================================================ FILE: src/gpssystemdialog.hh ================================================ #ifndef GPSSYSTEMDIALOG_HH #define GPSSYSTEMDIALOG_HH #include "config.hh" #include #include "ui_gpssystemdialog.h" class GPSSystemDialog : public QDialog, private Ui::GPSSystemDialog { Q_OBJECT public: GPSSystemDialog(Config *config, QWidget *parent=nullptr); GPSSystemDialog(Config *config, DMRAPRSSystem *gps, QWidget *parent=nullptr); DMRAPRSSystem *gpsSystem(); protected: void construct(); protected: Config *_config; DMRAPRSSystem *_myGPSSystem; DMRAPRSSystem *_gpsSystem; }; #endif // GPSSYSTEMDIALOG_HH ================================================ FILE: src/gpssystemdialog.ui ================================================ GPSSystemDialog 0 0 400 300 0 0 Edit GPS System 0 Basic Name 0 0 Destination Update period 0 0 s 60 2700 Revert Channel 0 0 Extensions Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
buttonBox accepted() GPSSystemDialog accept() 248 254 157 274 buttonBox rejected() GPSSystemDialog reject() 316 260 286 274
================================================ FILE: src/grouplistsview.cc ================================================ #include "grouplistsview.hh" #include "ui_grouplistsview.h" #include "rxgrouplistdialog.hh" #include "config.hh" #include #include GroupListsView::GroupListsView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::GroupListsView), _config(config) { ui->setupUi(this); ui->listView->setModel(new GroupListsWrapper(_config->rxGroupLists(), ui->listView)); connect(ui->addRXGroup, SIGNAL(clicked()), this, SLOT(onAddRxGroup())); connect(ui->remRXGroup, SIGNAL(clicked()), this, SLOT(onRemRxGroup())); connect(ui->listView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditRxGroup(unsigned))); } GroupListsView::~GroupListsView() { delete ui; } void GroupListsView::onAddRxGroup() { RXGroupListDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->listView->hasSelection()) row = ui->listView->selection().second+1; _config->rxGroupLists()->add(dialog.groupList(), row); } void GroupListsView::onRemRxGroup() { // Check if there is any groups selected if (! ui->listView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete RX group list"), tr("Cannot delete RX group lists: You have to select a group list first.")); return; } // Get selection and ask for deletion QPair rows = ui->listView->selection(); int rowcount = rows.second-rows.first+1; if (rows.first == rows.second) { QString name = _config->rxGroupLists()->list(rows.first)->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete RX group list?"), tr("Delete RX group list %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete RX group list?"), tr("Delete %1 RX group lists?").arg(rowcount))) return; } // collect all selected group lists // need to collect them first as rows change when deleting QList lists; lists.reserve(rowcount); for (int row=rows.first; row<=rows.second; row++) lists.push_back(_config->rxGroupLists()->list(row)); // remove list foreach (RXGroupList *list, lists) _config->rxGroupLists()->del(list); } void GroupListsView::onEditRxGroup(unsigned row) { RXGroupListDialog dialog(_config, _config->rxGroupLists()->list(row)); if (QDialog::Accepted != dialog.exec()) return; dialog.groupList(); } ================================================ FILE: src/grouplistsview.hh ================================================ #ifndef GROUPLISTSVIEW_HH #define GROUPLISTSVIEW_HH #include class Config; namespace Ui { class GroupListsView; } class GroupListsView : public QWidget { Q_OBJECT public: explicit GroupListsView(Config *config, QWidget *parent = nullptr); ~GroupListsView(); protected slots: void onAddRxGroup(); void onRemRxGroup(); void onEditRxGroup(unsigned row); private: Ui::GroupListsView *ui; Config *_config; }; #endif // GROUPLISTSVIEW_HH ================================================ FILE: src/grouplistsview.ui ================================================ GroupListsView 0 0 400 300 Form 0 0 Add RX Group Alt++ Delete RX Group Alt+- ConfigObjectListView QWidget
configobjectlistview.hh
1
================================================ FILE: src/hearhamrepeatersource.cc ================================================ #include "hearhamrepeatersource.hh" #include "logger.hh" #include #include #include #include #include HearhamRepeaterSource::HearhamRepeaterSource(QObject *parent) : DownloadableRepeaterDatabaseSource( "hearham.cache.json", QUrl("https://hearham.com/api/repeaters/v1"), 5, parent) { // pass... } bool HearhamRepeaterSource::parse(const QByteArray &json) { QJsonParseError err; auto doc = QJsonDocument::fromJson(json, &err); if (doc.isNull()) { logError() << "Cannot received JSON: " << err.errorString() << "."; return false; } if (! doc.isArray()) { logError() << "Malformed result."; return false; } QRegularExpression ccPattern(R"(CC\w*([0-9]{1,2}))"); QRegularExpression dcsPattern(R"(DCS\w*([0-9]{3})\w*(N|I|))"); for (const QJsonValue &val: doc.array()) { if (! val.isObject()) continue; auto obj = val.toObject(); auto call = obj["callsign"].toString().simplified().toUpper(); auto mode = obj["mode"].toString(); auto rx = Frequency::fromHz(obj["frequency"].toInt()), tx = Frequency::fromHz(rx.inHz() + obj["offset"].toInt()); auto location = QGeoCoordinate(obj["latitude"].toDouble(), obj["longitude"].toDouble()); auto qth = obj["city"].toString(); if ("FM" == mode) { SelectiveCall rxTone, txTone; auto rxDCS = dcsPattern.match(obj["decode"].toString()); auto txDCS = dcsPattern.match(obj["encode"].toString()); if (rxDCS.isValid()) rxTone = SelectiveCall(rxDCS.captured(1).toUInt(), "I" == rxDCS.captured(2)); else if (0 != obj["decode"].toString().toDouble()) rxTone = SelectiveCall(obj["decode"].toString().toDouble()); if (txDCS.isValid()) txTone = SelectiveCall(txDCS.captured(1).toUInt(), "I" == txDCS.captured(2)); else if (0 != obj["encode"].toString().toDouble()) txTone = SelectiveCall(obj["encode"].toString().toDouble()); cache(RepeaterDatabaseEntry::fm(call, rx, tx, location, qth, rxTone, txTone)); } else if ("DMR" == mode) { unsigned int colorCode = 0; auto CC = ccPattern.match(obj["encode"].toString()); if (CC.isValid()) colorCode = CC.captured(1).toUInt(); cache(RepeaterDatabaseEntry::dmr(call, rx, tx, location, qth, colorCode)); } } logDebug() << "Loaded " << _cache.size() << " elements from " << _url.toDisplayString() << "."; return true; } ================================================ FILE: src/hearhamrepeatersource.hh ================================================ #ifndef HEARHAMREPEATERSOURCE_HH #define HEARHAMREPEATERSOURCE_HH #include "repeaterdatabase.hh" class HearhamRepeaterSource : public DownloadableRepeaterDatabaseSource { Q_OBJECT public: explicit HearhamRepeaterSource(QObject *parent = nullptr); protected: bool parse(const QByteArray &doc); }; #endif // HEARHAMREPEATERSOURCE_HH ================================================ FILE: src/idselect.cc ================================================ #include "idselect.hh" #include "config.hh" #include "radioid.hh" DMRIdSelect::DMRIdSelect(Config *config, QWidget *parent) : QComboBox(parent) { addItem(tr("[Default]"), QVariant::fromValue(DefaultRadioID::get())); for (int i=0; iradioIDs()->count(); i++) { if (config->radioIDs()->get(i)->is()) { DMRRadioID *id = config->radioIDs()->get(i)->as(); addItem(id->name(), QVariant::fromValue(id)); } } } void DMRIdSelect::setRadioId(DMRRadioID *id) { for (int i=0; i() == id) { setCurrentIndex(i); break; } } } DMRRadioID * DMRIdSelect::radioId() const { return currentData().value(); } ================================================ FILE: src/idselect.hh ================================================ #ifndef IDSELECT_HH #define IDSELECT_HH #include "config.hh" #include class Config; class DMRRadioID; class DMRIdSelect : public QComboBox { Q_OBJECT public: DMRIdSelect(Config *config, QWidget *parent=nullptr); void setRadioId(DMRRadioID *id); DMRRadioID *radioId() const; }; #endif // IDSELECT_HH ================================================ FILE: src/m17channeldialog.cc ================================================ #include "m17channeldialog.hh" #include "rxgrouplistdialog.hh" #include "channel.hh" #include "config.hh" #include "m17contactdialog.hh" #include "ui_channeldialog.h" /* ****************************************************************************************** * * N17 channel mode selection * ****************************************************************************************** */ M17ChannelModeSelect::M17ChannelModeSelect(QWidget *parent) : QComboBox(parent) { addItem(tr("Voice"), QVariant::fromValue(M17Channel::Mode::Voice)); addItem(tr("Data"), QVariant::fromValue(M17Channel::Mode::Data)); addItem(tr("Voice + Data"), QVariant::fromValue(M17Channel::Mode::VoiceAndData)); } void M17ChannelModeSelect::setMode(M17Channel::Mode mode) { for (int i=0; i() == mode) { setCurrentIndex(i); break; } } } M17Channel::Mode M17ChannelModeSelect::mode() const { return currentData().value(); } /* ****************************************************************************************** * * N17 channel dialog * ****************************************************************************************** */ M17ChannelDialog::M17ChannelDialog(Config *config, QWidget *parent) : ChannelDialog(config, parent), _channel(), _mode(nullptr), _access(nullptr), _contact(nullptr) { ui->rightForm->addRow(tr("Channel mode"), _mode = new M17ChannelModeSelect()); ui->rightForm->addRow(tr("Access number"), _access = new QSpinBox()); _access->setRange(0,15); ui->rightForm->addRow(tr("Tx contact"), _contact = new M17ContactSelect(config)); ui->rightForm->addRow(tr("Send position"), _aprs = new QCheckBox("")); } void M17ChannelDialog::setChannel(M17Channel *ch) { ChannelDialog::setChannel(ch); _channel = ch; _mode->setMode(_channel->mode()); _access->setValue(_channel->accessNumber()); _contact->setContact(_channel->contact()); _aprs->setChecked(_channel->aprsEnabled()); } void M17ChannelDialog::accept() { _channel->setMode(_mode->mode()); _channel->setAccessNumber(_access->value()); _channel->setContact(_contact->contact()); _channel->enableAPRS(_aprs->isChecked()); ChannelDialog::accept(); } ================================================ FILE: src/m17channeldialog.hh ================================================ #ifndef M17CHANNELDIALOG_HH #define M17CHANNELDIALOG_HH #include "channeldialog.hh" #include "channel.hh" #include #include #include class Config; class M17ContactSelect; class M17ChannelModeSelect: public QComboBox { Q_OBJECT public: explicit M17ChannelModeSelect(QWidget *parent=nullptr); void setMode(M17Channel::Mode mode); M17Channel::Mode mode() const; }; class M17ChannelDialog : public ChannelDialog { Q_OBJECT public: M17ChannelDialog(Config *config, QWidget *parent = nullptr); void setChannel(M17Channel *ch); public slots: void accept() override; private: QPointer _channel; M17ChannelModeSelect *_mode; QSpinBox *_access; M17ContactSelect *_contact; QCheckBox *_aprs; }; #endif // M17CHANNELDIALOG_HH ================================================ FILE: src/m17contactdialog.cc ================================================ #include "m17contactdialog.hh" #include "ui_m17contactdialog.h" #include "contact.hh" #include "settings.hh" #include "config.hh" #include /* ******************************************************************************************* * * M17 contact dialog * ******************************************************************************************* */ M17ContactDialog::M17ContactDialog(Config *config, QWidget *parent) : QDialog(parent), ui(new Ui::M17ContactDialog), _config(config), _contact(nullptr), _myContact(new M17Contact(this)) { construct(); } M17ContactDialog::M17ContactDialog(Config *config, M17Contact *contact, QWidget *parent) : QDialog(parent), ui(new Ui::M17ContactDialog), _config(config), _contact(contact), _myContact(new M17Contact(this)) { if (_contact) _myContact->copy(*_contact); construct(); } M17ContactDialog::~M17ContactDialog() { delete ui; } void M17ContactDialog::construct() { ui->setupUi(this); Settings settings; ui->call->setValidator(new QRegularExpressionValidator(QRegularExpression(R"([A-Z0-9\./\-]{0,9})"))); if (_contact) { setWindowTitle(tr("Edit M17 Contact")); ui->name->setText(_contact->name()); ui->call->setText(_contact->call()); ui->broadcast->setChecked(_contact->isBroadcast()); ui->call->setEnabled(! _contact->isBroadcast()); } else { setWindowTitle(tr("Create M17 Contact")); } ui->extensionView->setObjectName("m17ContactExtension"); ui->extensionView->setObject(_myContact, _config); connect(ui->broadcast, SIGNAL(toggled(bool)), this, SLOT(onBroadcastToggled(bool))); connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } M17Contact * M17ContactDialog::contact() const { _myContact->setName(ui->name->text().simplified()); _myContact->setCall(ui->call->text()); _myContact->setBroadcast(ui->broadcast->isChecked()); _myContact->setRing(ui->ring->isChecked()); M17Contact *contact = _myContact; if (_contact) { _contact->copy(*_myContact); contact = _contact; } else { _myContact->setParent(nullptr); } return contact; return _contact; } void M17ContactDialog::onBroadcastToggled(bool enable) { ui->call->setEnabled(! enable); } /* ******************************************************************************************* * * M17 contact selection * ******************************************************************************************* */ M17ContactSelect::M17ContactSelect(Config *config, QWidget *parent) : QComboBox(parent) { addItem(tr("[None]"), QVariant::fromValue(nullptr)); for (int i=0; icontacts()->count(); i++) { if (! config->contacts()->contact(i)->is()) continue; auto contact = config->contacts()->contact(i)->as(); addItem(contact->name(), QVariant::fromValue(contact)); } } void M17ContactSelect::setContact(M17Contact *contact) { for (int i=0; i()==contact) { setCurrentIndex(i); break; } } } M17Contact * M17ContactSelect::contact() const { return currentData().value(); } ================================================ FILE: src/m17contactdialog.hh ================================================ #ifndef M17CONTACTDIALOG_HH #define M17CONTACTDIALOG_HH #include #include class M17Contact; class Config; namespace Ui { class M17ContactDialog; } class M17ContactDialog : public QDialog { Q_OBJECT public: explicit M17ContactDialog(Config *config, QWidget *parent = nullptr); M17ContactDialog(Config *config, M17Contact *contact, QWidget *parent = nullptr); ~M17ContactDialog(); M17Contact *contact() const; protected: /** Assembles the GUI, populates elements. */ void construct(); protected slots: void onBroadcastToggled(bool enable); private: Ui::M17ContactDialog *ui; Config *_config; M17Contact *_contact; M17Contact *_myContact; }; class M17ContactSelect: public QComboBox { Q_OBJECT public: M17ContactSelect(Config *config, QWidget *parent=nullptr); void setContact(M17Contact *contact); M17Contact *contact() const; }; #endif // M17CONTACTDIALOG_HH ================================================ FILE: src/m17contactdialog.ui ================================================ M17ContactDialog 0 0 400 300 Edit M17 Contact 0 Basic Name The name of the contact. Call The callsign of the contact. Must be not longer than 9 chars, A-Z, 0-9, ., /, -. Ring Broadcast Sets this contact to be the M17 broadcast contact, the specified call is then ignored. Extensions Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
buttonBox accepted() M17ContactDialog accept() 248 254 157 274 buttonBox rejected() M17ContactDialog reject() 316 260 286 274
================================================ FILE: src/main.cc ================================================ #include "application.hh" #include #include #include "mainwindow.hh" #include #include "logger.hh" #include "settings.hh" #include #include int main(int argc, char *argv[]) { QTextStream out(stderr); auto handler = new StreamLogHandler(out, LogMessage::Level::INFO); Logger::get().addHandler(handler); Application app(argc, argv); // Parse options QCommandLineParser parser; parser.addHelpOption(); parser.addVersionOption(); parser.addPositionalArgument("codeplug", QCoreApplication::translate("main", "Codeplug file to load.")); parser.addOption({"loglevel", QCoreApplication::translate("main", "Specifies applications log-level to stdout. Must be one of `debug`, `info`, `warning`, `error` or `fatal`."), "loglevel", "info"}); parser.parse(app.arguments()); // Handle args (if there are some) if (parser.positionalArguments().count()) { ErrorStack err; if (! app.loadCodeplug(parser.positionalArguments().at(0), err)) logError() << "Cannot open codeplug file '" << argv[1] << "': " << err.format(); } if (parser.isSet("loglevel")) { if ("debug" == parser.value("loglevel")) { handler->setMinLevel(LogMessage::Level::DEBUG); } else if ("info" == parser.value("loglevel")) { handler->setMinLevel(LogMessage::Level::INFO); } else if ("warning" == parser.value("loglevel")) { handler->setMinLevel(LogMessage::Level::WARNING); } else if ("error" == parser.value("loglevel")) { handler->setMinLevel(LogMessage::Level::ERROR); } else if ("fatal" == parser.value("loglevel")) { handler->setMinLevel(LogMessage::Level::FATAL); } } auto mainWindow = app.mainWindow(); mainWindow->show(); Settings settings; if (settings.showDisclaimer()) { app.showAbout(); settings.setShowDisclaimer(false); } app.exec(); return 0; } ================================================ FILE: src/mainwindow.cc ================================================ #include "mainwindow.hh" #include "ui_mainwindow.h" #include #include #include #include #include "settings.hh" #include "logger.hh" #include "application.hh" #include "generalsettingsview.hh" #include "radioidlistview.hh" #include "contactlistview.hh" #include "grouplistsview.hh" #include "channellistview.hh" #include "zonelistview.hh" #include "scanlistsview.hh" #include "positioningsystemlistview.hh" #include "roamingchannellistview.hh" #include "roamingzonelistview.hh" #include "extensionview.hh" #include "talkgroupdatabase.hh" #include "satellitedatabase.hh" MainWindow::MainWindow(Config *config, QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); Application *app = qobject_cast(QApplication::instance()); Settings settings; logDebug() << "Create main window using icon theme '" << QIcon::themeName() << "'."; QProgressBar *progress = new QProgressBar(); progress->setObjectName("progress"); ui->statusbar->addPermanentWidget(progress); progress->setVisible(false); ui->actionExportToCHIRP->setIcon(QIcon::fromTheme("document-export")); ui->actionImport->setIcon(QIcon::fromTheme("document-import")); ui->actionRefreshCallsignDB->setIcon(QIcon::fromTheme("document-download")); ui->actionRefreshTalkgroupDB->setIcon(QIcon::fromTheme("document-download")); ui->actionRefreshOrbitalElements->setIcon(QIcon::fromTheme("document-download")); ui->actionWriteSatellites->setIcon(QIcon::fromTheme("device-write-satellites")); ui->actionEditSatellites->setIcon(QIcon::fromTheme("edit-satellites")); #if QT_VERSION >= QT_VERSION_CHECK(6,5,0) connect(qApp->styleHints(), &QStyleHints::colorSchemeChanged, this, [=](Qt::ColorScheme scheme) { bool isDarkTheme = scheme == Qt::ColorScheme::Dark ? true : false; QIcon::setThemeName(isDarkTheme ? "dark" : "light"); }); #endif connect(ui->actionNewCodeplug, SIGNAL(triggered()), app, SLOT(newCodeplug())); connect(ui->actionOpenCodeplug, SIGNAL(triggered()), app, SLOT(loadCodeplug())); connect(ui->actionSaveCodeplug, SIGNAL(triggered()), app, SLOT(saveCodeplug())); connect(ui->actionExportToCHIRP, SIGNAL(triggered()), app, SLOT(exportCodeplugToChirp())); connect(ui->actionImport, SIGNAL(triggered()), app, SLOT(importCodeplug())); connect(ui->actionQuit, SIGNAL(triggered()), this, SLOT(close())); connect(ui->actionAbout, SIGNAL(triggered()), app, SLOT(showAbout())); connect(ui->actionSettings, SIGNAL(triggered()), app, SLOT(showSettings())); connect(ui->actionHelp, SIGNAL(triggered()), app, SLOT(showHelp())); connect(ui->actionRefreshCallsignDB, &QAction::triggered, app->user(), &UserDatabase::download); QObject::connect(app->user(), &UserDatabase::error, this, [this](const QString &msg) { this->ui->statusbar->showMessage(tr("Cannot update callsign DB: %1").arg(msg), 10000); }, Qt::QueuedConnection); QObject::connect(app->user(), &UserDatabase::loaded, this, [this]() { this->ui->statusbar->showMessage(tr("Callsign database updated & loaded."), 10000); }, Qt::QueuedConnection); connect(ui->actionRefreshTalkgroupDB, &QAction::triggered, app->talkgroup(), &TalkGroupDatabase::download); QObject::connect(app->talkgroup(), &TalkGroupDatabase::error, this, [this](const QString &msg) { this->ui->statusbar->showMessage(tr("Cannot update talkgroup DB: %1").arg(msg), 10000); }, Qt::QueuedConnection); QObject::connect(app->talkgroup(), &TalkGroupDatabase::loaded, this, [this]() { this->ui->statusbar->showMessage(tr("Talkgroup database updated & loaded."), 10000); }, Qt::QueuedConnection); connect(ui->actionRefreshOrbitalElements, &QAction::triggered, app->satellite(), &SatelliteDatabase::update); QObject::connect(app->satellite(), &SatelliteDatabase::error, this, [this](const QString &msg) { this->ui->statusbar->showMessage(tr("Cannot update orbital elements: %1").arg(msg), 10000); }, Qt::QueuedConnection); QObject::connect(app->satellite(), &SatelliteDatabase::loaded, this, [this]() { this->ui->statusbar->showMessage(tr("Orbital elements updated & loaded."), 10000); }, Qt::QueuedConnection); connect(ui->actionUploadCallsignDB, SIGNAL(triggered()), app, SLOT(uploadCallsignDB())); connect(app->user(), &UserDatabase::readyChanged, this, [this](bool ready){ this->ui->actionUploadCallsignDB->setEnabled(ready); }, Qt::QueuedConnection); ui->actionUploadCallsignDB->setEnabled(app->user()->ready()); connect(ui->actionEditSatellites, SIGNAL(triggered()), app, SLOT(editSatellites())); connect(ui->actionDetectDevice, SIGNAL(triggered()), app, SLOT(detectRadio())); connect(ui->actionVerifyCodeplug, SIGNAL(triggered()), app, SLOT(verifyCodeplug())); connect(ui->actionDownload, SIGNAL(triggered()), app, SLOT(downloadCodeplug())); connect(ui->actionUpload, SIGNAL(triggered()), app, SLOT(uploadCodeplug())); connect(ui->actionWriteSatellites, SIGNAL(triggered()), app, SLOT(uploadSatellites())); // Wire-up "General Settings" view _generalSettings = new GeneralSettingsView(config); ui->tabs->addTab(_generalSettings, tr("Settings")); // Wire-up "Radio IDs" view _radioIdTab = new RadioIDListView(config); ui->tabs->addTab(_radioIdTab, tr("Radio IDs")); // Wire-up "Contact List" view ui->tabs->addTab(new ContactListView(config), tr("Contacts")); // Wire-up "RX Group List" view ui->tabs->addTab(new GroupListsView(config), tr("Group Lists")); // Wire-up "Channel List" view ui->tabs->addTab(new ChannelListView(config), tr("Channels")); // Wire-up "Zone List" view ui->tabs->addTab(new ZoneListView(config), tr("Zones")); // Wire-up "Scan List" view ui->tabs->addTab(new ScanListsView(config), tr("Scan Lists")); // Wire-up "GPS System List" view ui->tabs->addTab(new PositioningSystemListView(config), tr("GPS/APRS")); // Wire-up "Roaming Zone List" view ui->tabs->addTab(new RoamingChannelListView(config), tr("Roaming Channels")); // Wire-up "Roaming Zone List" view _roamingZoneList = new RoamingZoneListView(config); ui->tabs->addTab(_roamingZoneList, tr("Roaming Zones")); // Wire-up "extension view" for direct editing _extensionView = new ExtensionView(); _extensionView->setObject(config, config); ui->tabs->addTab(_extensionView, tr("Extensions")); restoreGeometry(settings.mainWindowState()); connect(config, &ConfigItem::modified, [this, config]() { this->setWindowModified(config->isModified()); }); } void MainWindow::closeEvent(QCloseEvent *event) { if (qobject_cast(Application::instance())->isModified()) { if (QMessageBox::Ok != QMessageBox::question(nullptr, tr("Unsaved changes to codeplug."), tr("There are unsaved changes to the current codeplug. " "These changes are lost if you proceed."), QMessageBox::Cancel|QMessageBox::Ok)) event->ignore(); return; } Settings().setMainWindowState(saveGeometry()); event->accept(); } ================================================ FILE: src/mainwindow.hh ================================================ #ifndef MAINWINDOW_HH #define MAINWINDOW_HH #include class Config; class GeneralSettingsView; class RadioIDListView; class RoamingZoneListView; class ExtensionView; namespace Ui { class MainWindow; } class MainWindow: public QMainWindow { Q_OBJECT public: explicit MainWindow(Config *config, QWidget *parent=nullptr); protected: void closeEvent(QCloseEvent *event); private: Ui::MainWindow *ui; GeneralSettingsView *_generalSettings; RadioIDListView *_radioIdTab; RoamingZoneListView *_roamingZoneList; ExtensionView *_extensionView; }; #endif // MAINWINDOW_HH ================================================ FILE: src/mainwindow.ui ================================================ MainWindow 0 0 1280 800 800 600 qdmr [*] true true 0 0 0 0 0 Tablist There are several tabs providing general settings, contact, Rx group, channel, zone and scan lists. -1 0 0 1280 23 File Device Help Databases false toolBar Toolbar TopToolBarArea false New Creates a new Codeplug. Ctrl+N Open ... <html><head/><body><p>Imports a codeplug from &quot;conf&quot; files.</p></body></html> Ctrl+O Save ... <html><head/><body><p>Saves the codeplug in a &quot;conf&quot; file.</p></body></html> Ctrl+S Quit Quits the application. Ctrl+Q Detect Detect connected radios. Verify <html><head/><body><p>Verifies the current codeplug with connected radios.</p></body></html> Ctrl+R Read Reads a codeplug from connected radios. Write Writes the codeplug to the connected radio. About qdmr Help Read the handbook. F1 Settings Shows settings dialog Write Callsign DB Writes call-sign DB to radio. Refresh Callsign DB Refreshes the downloaded callsign DB Refresh Talkgroup DB Refreshes the downloaded talkgroup DB Export to CHIRP ... Exports all FM channels to CHIRP CSV. Import ... Imports and merges a codeplug into the current one. Refresh Orbital Elements Refreshes the orbital elements. true Edit Satellites ... Opens an editor to edit your satellite database. true Write satellites Writes the orbital elements and transponder information onto the connected device. true ================================================ FILE: src/melody_edit.cc ================================================ #include "melody_edit.hh" #include "melody.hh" #include "melody_player.hh" #include #include #include #include #include MelodyEdit::MelodyEdit(QWidget *parent) : QWidget(parent), _melody(), _bpm(new QSpinBox(this)), _melodyEdit(new QLineEdit(this)), _player(new MelodyPlayer(this)) { _bpm->setRange(10,1000); _bpm->setSuffix(tr("bpm", "Beats per minute. Unit in a spin box.")); _melodyEdit->setToolTip(tr("Specify the melody in Lilypond format.", "Tooltip for a melody entry field.")); _melodyEdit->setValidator( new QRegularExpressionValidator( QRegularExpression(R"(([a-zA-Z]+)([,]+|[']+|)(1|2|4|8|16|)(\.|))" R"((\h+([a-zA-Z]+)([,]+|[']+|)(1|2|4|8|16|)(\.|))*)"))); _melodyEdit->setPlaceholderText(tr("Melody")); auto play = new QPushButton(); play->setIcon(QIcon::fromTheme("media-play")); play->setCheckable(true); play->setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Preferred); connect(play, &QPushButton::toggled, _player, &MelodyPlayer::togglePlay); connect(_player, &MelodyPlayer::stateChanged, play, &QPushButton::setChecked); auto hbox = new QHBoxLayout(this); hbox->addWidget(_bpm); hbox->addWidget(_melodyEdit); hbox->addWidget(play); setLayout(hbox); _bpm->setEnabled(false); _melodyEdit->setEnabled(false); connect(_bpm, &QSpinBox::valueChanged, [this](int value) { if (!_melody.isNull()) _melody->setBPM(value); }); connect(_melodyEdit, &QLineEdit::editingFinished, [this]() { if (! _melody.isNull()) _melody->fromLilypond(this->_melodyEdit->text().simplified()); }); } void MelodyEdit::setMelody(Melody *melody) { if (! _melody.isNull()) disconnect(_melody, &Melody::modified, this, &MelodyEdit::onMelodyChanged); _melody = melody; if (! _melody.isNull()) { onMelodyChanged(_melody); connect(_melody, &ConfigItem::modified, this, &MelodyEdit::onMelodyChanged); } _bpm->setEnabled(! _melody.isNull()); _melodyEdit->setEnabled(! _melody.isNull()); _player->setMelody(melody); } void MelodyEdit::onMelodyChanged(ConfigItem *item) { Q_UNUSED(item); _bpm->setValue(_melody->bpm()); _melodyEdit->setText(_melody->toLilypond()); _player->togglePlay(false); } ================================================ FILE: src/melody_edit.hh ================================================ #ifndef QDMR_MELODY_EDIT_HH #define QDMR_MELODY_EDIT_HH #include #include #include "melody_player.hh" class Melody; class QSpinBox; class QLineEdit; class ConfigItem; class MelodyEdit: public QWidget { Q_OBJECT public: explicit MelodyEdit(QWidget *parent = nullptr); public slots: /** Sets the melody to edit. */ void setMelody(Melody *melody); protected slots: /** Applies the changes to the wrapped melody object. */ void onMelodyChanged(ConfigItem *item); private: QPointer _melody; QSpinBox *_bpm; QLineEdit *_melodyEdit; MelodyPlayer *_player; }; #endif //QDMR_MELODY_EDIT_HH ================================================ FILE: src/melody_player.cc ================================================ #include "melody_player.hh" #include "melody_stream.hh" #include #include #include #include MelodyPlayer::MelodyPlayer(QObject *parent) : QObject(parent), _stream(new MelodyStream(this)), _playbackSink(nullptr) { QAudioDevice info(QMediaDevices::defaultAudioOutput()); if (! info.isFormatSupported(_stream->audioFormat())) return; _playbackSink = new QAudioSink(_stream->audioFormat(), this); connect(_playbackSink, &QAudioSink::stateChanged, [this](QAudio::State state) { emit stateChanged(state == QAudio::ActiveState); }); } MelodyPlayer::~MelodyPlayer() { _playbackSink->stop(); _stream->close(); } void MelodyPlayer::setMelody(Melody *melody) { if (_stream->isOpen()) _stream->close(); _stream->setMelody(melody); _stream->open(QIODevice::ReadOnly); } void MelodyPlayer::togglePlay(bool enable) { if (nullptr == _playbackSink) return; if (enable && (QAudio::ActiveState != _playbackSink->state())) _playbackSink->start(_stream); else if (! enable && (QAudio::ActiveState == _playbackSink->state())) _playbackSink->stop(); } ================================================ FILE: src/melody_player.hh ================================================ #ifndef QDMR_MELODY_PLAYER_HH #define QDMR_MELODY_PLAYER_HH #include class Melody; class MelodyStream; class QAudioSink; /** Plays some melody. */ class MelodyPlayer: public QObject { Q_OBJECT public: explicit MelodyPlayer(QObject *parent = nullptr); virtual ~MelodyPlayer(); public slots: void setMelody(Melody *melody); void togglePlay(bool play); signals: void stateChanged(bool playing); protected: MelodyStream *_stream; QAudioSink *_playbackSink; }; #endif //QDMR_MELODY_PLAYER_HH ================================================ FILE: src/positioningsystemlistview.cc ================================================ #include "positioningsystemlistview.hh" #include "ui_positioningsystemlistview.h" #include "config.hh" #include "aprssystemdialog.hh" #include "gpssystemdialog.hh" #include "settings.hh" #include #include PositioningSystemListView::PositioningSystemListView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::PositioningSystemListView), _config(config) { Settings settings; ui->setupUi(this); connect(ui->aprsTableView->header(), SIGNAL(sectionCountChanged(int,int)), this, SLOT(loadPositioningSectionState())); connect(ui->aprsTableView->header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(storePositioningSectionState())); ui->aprsTableView->setModel(new PositioningSystemListWrapper(_config->posSystems(), ui->aprsTableView)); if (settings.hideGSPNote()) ui->gpsNote->setVisible(false); connect(ui->addGPS, SIGNAL(clicked()), this, SLOT(onAddGPS())); connect(ui->addAPRS, SIGNAL(clicked()), this, SLOT(onAddAPRS())); connect(ui->remGPS, SIGNAL(clicked()), this, SLOT(onRemGPS())); connect(ui->aprsTableView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditGPS(unsigned))); connect(ui->gpsNote, SIGNAL(linkActivated(QString)), this, SLOT(onHideGPSNote())); } PositioningSystemListView::~PositioningSystemListView() { delete ui; } void PositioningSystemListView::onAddGPS() { GPSSystemDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->aprsTableView->hasSelection()) row = ui->aprsTableView->selection().second+1; _config->posSystems()->add(dialog.gpsSystem(), row); } void PositioningSystemListView::onAddAPRS() { APRSSystemDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->aprsTableView->hasSelection()) row = ui->aprsTableView->selection().second+1; _config->posSystems()->add(dialog.aprsSystem(), row); } void PositioningSystemListView::onRemGPS() { if (! ui->aprsTableView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete GPS system"), tr("Cannot delete GPS system: You have to select a GPS system first.")); return; } // Get selection and ask for deletion QPair rows = ui->aprsTableView->selection(); int rowcount = rows.second-rows.first+1; if (rows.first == rows.second) { QString name = _config->posSystems()->system(rows.first)->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete positioning system?"), tr("Delete positioning system %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete positioning system?"), tr("Delete %1 positioning systems?").arg(rowcount))) return; } // collect all selected systems // need to collect them first as rows change when deleting systems QList systems; systems.reserve(rowcount); for(int row=rows.first; row<=rows.second; row++) systems.push_back(_config->posSystems()->system(row)); // remove systems foreach (PositionReportingSystem *system, systems) _config->posSystems()->del(system); } void PositioningSystemListView::onEditGPS(unsigned row) { PositionReportingSystem *sys = _config->posSystems()->system(row); if (sys->is()) { GPSSystemDialog dialog(_config, sys->as()); if (QDialog::Accepted != dialog.exec()) return; dialog.gpsSystem(); } else if (sys->is()) { APRSSystemDialog dialog(_config, sys->as()); if (QDialog::Accepted != dialog.exec()) return; dialog.aprsSystem(); } } void PositioningSystemListView::onHideGPSNote() { Settings setting; setting.setHideGPSNote(true); ui->gpsNote->setVisible(false); } void PositioningSystemListView::loadPositioningSectionState() { Settings settings; ui->aprsTableView->header()->restoreState(settings.headerState("positioningList")); } void PositioningSystemListView::storePositioningSectionState() { Settings settings; settings.setHeaderState("positioningList", ui->aprsTableView->header()->saveState()); } ================================================ FILE: src/positioningsystemlistview.hh ================================================ #ifndef POSITIONINGSYSTEMLISTVIEW_HH #define POSITIONINGSYSTEMLISTVIEW_HH #include class Config; namespace Ui { class PositioningSystemListView; } class PositioningSystemListView : public QWidget { Q_OBJECT public: explicit PositioningSystemListView(Config *config, QWidget *parent = nullptr); ~PositioningSystemListView(); protected slots: void onAddGPS(); void onAddAPRS(); void onRemGPS(); void onEditGPS(unsigned); void onHideGPSNote(); void loadPositioningSectionState(); void storePositioningSectionState(); private: Ui::PositioningSystemListView *ui; Config *_config; }; #endif // POSITIONINGSYSTEMLISTVIEW_HH ================================================ FILE: src/positioningsystemlistview.ui ================================================ PositioningSystemListView 0 0 445 300 Form padding:10px;border: 2px solid black; border-radius: 10px; QFrame::Shape::NoFrame QFrame::Shadow::Plain 2 0 <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support GPS or APRS. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Qt::TextFormat::RichText true 5 -1 false Qt::TextInteractionFlag::LinksAccessibleByKeyboard|Qt::TextInteractionFlag::LinksAccessibleByMouse 0 0 Add GPS System Alt+G true Add APRS System Alt+A Delete Position System Alt+- ConfigObjectTableView QWidget
configobjecttableview.hh
1
================================================ FILE: src/propertydelegate.cc ================================================ #include "propertydelegate.hh" #include "extensionwrapper.hh" #include #include #include #include #include #include "flageditdialog.hh" #include "configreference.hh" #include "extensionwrapper.hh" #include "config.hh" #include "frequency.hh" #include "interval.hh" PropertyDelegate::PropertyDelegate(QObject *parent) : QStyledItemDelegate(parent), _config(nullptr) { // pass... } void PropertyDelegate::setConfig(Config *config) { _config = config; } const PropertyWrapper * PropertyDelegate::getModel(const QAbstractItemModel *model) { while (const QAbstractProxyModel *proxy = qobject_cast(model)) model = proxy->sourceModel(); return qobject_cast(model); } QWidget * PropertyDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option) const PropertyWrapper *model = getModel(index.model()); QMetaProperty prop = model->propertyAt(index); ConfigItem *obj = model->parentObject(index); if (! prop.isValid()) return nullptr; // Dispatch by type if (prop.isFlagType()) { return new FlagEditDialog(prop.enumerator(), parent); } if (prop.isEnumType()) { return new QComboBox(parent); } else if (QMetaType::Bool == prop.typeId()) { return new QComboBox(parent); } else if (QMetaType::Int == prop.typeId()) { QSpinBox *edit = new QSpinBox(parent); edit->setMinimum(std::numeric_limits::min()); edit->setMaximum(std::numeric_limits::max()); return edit; } else if (QMetaType::UInt == prop.typeId()) { QSpinBox *edit = new QSpinBox(parent); edit->setMinimum(0); edit->setMaximum(std::numeric_limits::max()); return edit; } else if (QMetaType::Double == prop.typeId()) { QLineEdit *edit = new QLineEdit(parent); edit->setValidator(new QDoubleValidator(edit)); return edit; } else if (QMetaType::QString == prop.typeId()) { return new QLineEdit(parent); } else if (QMetaType::fromType() == prop.metaType()) { return new QLineEdit(parent); } else if (QMetaType::fromType() == prop.metaType()) { return new QLineEdit(parent); } else if (QMetaType::fromType() == prop.metaType()) { auto box = new QComboBox(parent); box->addItem(tr("None"), QVariant::fromValue(Level::invalid())); box->addItem(tr("Off"), QVariant::fromValue(Level::null())); for (uint i=1; i<=10; i++) box->addItem(QString::number(i), QVariant::fromValue(Level::fromValue(i))); return box; } else if (QMetaType::fromType() == prop.metaType()) { return new QLineEdit(parent); } else if (prop.read(obj).value()) { return new QComboBox(parent); } else if (propIsInstance(prop)) { return nullptr; } return nullptr; } void PropertyDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const { const PropertyWrapper *model = getModel(index.model()); ConfigItem *obj = model->parentObject(index); QMetaProperty prop = model->propertyAt(index); if (! prop.isValid()) return; // Dispatch by type if (prop.isFlagType()) { auto box = dynamic_cast(editor); box->setValue(prop.read(obj).toInt()); } else if (prop.isEnumType()) { QComboBox *box = dynamic_cast(editor); QMetaEnum etype = prop.enumerator(); for (int i=0; iaddItem(etype.key(i), QVariant(etype.value(i))); if (etype.value(i) == prop.read(obj).toInt()) box->setCurrentIndex(i); } } else if (QMetaType::Bool == prop.typeId()) { QComboBox *box = dynamic_cast(editor); box->addItem(tr("False"), false); box->addItem(tr("True"), true); if (prop.read(obj).toBool()) box->setCurrentIndex(1); else box->setCurrentIndex(0); } else if ((QMetaType::Int == prop.typeId()) || (QMetaType::UInt == prop.typeId())) { dynamic_cast(editor)->setValue(prop.read(obj).toInt()); } else if (QMetaType::Double == prop.typeId()) { dynamic_cast(editor)->setText(QString::number(prop.read(obj).toDouble())); } else if (QMetaType::QString == prop.typeId()) { dynamic_cast(editor)->setText(prop.read(obj).toString()); } else if (QMetaType::fromType() == prop.metaType()) { dynamic_cast(editor)->setText(prop.read(obj).value().format()); } else if (QMetaType::fromType() == prop.metaType()) { dynamic_cast(editor)->setText(prop.read(obj).value().format()); } else if (QMetaType::fromType() == prop.metaType()) { auto box = dynamic_cast(editor); auto level = prop.read(obj).value(); for (int i=0; icount(); i++) if (box->itemData(i).value() == level) box->setCurrentIndex(i); } else if (prop.read(obj).value()) { ConfigObjectReference *ref = prop.read(obj).value(); // Find all matching elements in config that can be referenced QSet items; if (_config) _config->findItemsOfTypes(ref->elementTypeNames(), items); // Assemble combo box with references QComboBox *box = dynamic_cast(editor); box->addItem(tr("[None]"), QVariant::fromValue(nullptr)); foreach (ConfigItem *item, items) { if (! item->is()) continue; ConfigObject *o = item->as(); box->addItem(o->name(),QVariant::fromValue(o)); } } } void PropertyDelegate::setModelData(QWidget *editor, QAbstractItemModel *abstractmodel, const QModelIndex &index) const { Q_UNUSED(abstractmodel); const PropertyWrapper *model = getModel(index.model()); ConfigItem *obj = model->parentObject(index); QMetaProperty prop = model->propertyAt(index); if (! prop.isValid()) return; // Dispatch by type if (prop.isFlagType()) { auto edit = dynamic_cast(editor); prop.write(obj, edit->value()); } else if (prop.isEnumType()) { prop.write(obj, dynamic_cast(editor)->currentData()); } else if (QMetaType::Bool == prop.typeId()) { prop.write(obj, dynamic_cast(editor)->currentData()); } else if ((QMetaType::Int == prop.typeId()) || (QMetaType::UInt == prop.typeId())) { prop.write(obj, dynamic_cast(editor)->value()); } else if (QMetaType::Double == prop.typeId()) { prop.write(obj, dynamic_cast(editor)->text().toDouble()); } else if (QMetaType::QString == prop.typeId()) { prop.write(obj, dynamic_cast(editor)->text()); } else if (QMetaType::fromType() == prop.metaType()) { Frequency f; if (f.parse(dynamic_cast(editor)->text())) prop.write(obj, QVariant::fromValue(f)); } else if (QMetaType::fromType() == prop.metaType()) { Interval I; if (I.parse(dynamic_cast(editor)->text())) prop.write(obj, QVariant::fromValue(I)); } else if (QMetaType::fromType() == prop.metaType()) { prop.write(obj, qobject_cast(editor)->currentData()); } else if (prop.read(obj).value()) { ConfigObjectReference *ref = prop.read(obj).value(); ref->set(dynamic_cast(editor)->currentData().value()); } } ================================================ FILE: src/propertydelegate.hh ================================================ #ifndef PROPERTYDELEGATE_HH #define PROPERTYDELEGATE_HH #include class Config; class PropertyWrapper; class PropertyDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit PropertyDelegate(QObject *parent=nullptr); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex &index) const; void setModelData(QWidget *editor, QAbstractItemModel *abstractmodel, const QModelIndex &index) const; void setConfig(Config *config); protected: static const PropertyWrapper *getModel(const QAbstractItemModel *model); protected: Config *_config; }; #endif // PROPERTYDELEGATE_HH ================================================ FILE: src/radioidlistview.cc ================================================ #include "radioidlistview.hh" #include "ui_radioidlistview.h" #include "config.hh" #include "configobjecttableview.hh" #include "configitemwrapper.hh" #include "dmriddialog.hh" #include "settings.hh" #include #include RadioIDListView::RadioIDListView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::RadioIDListView), _config(config) { ui->setupUi(this); connect(ui->radioIdTableView->header(), SIGNAL(sectionCountChanged(int,int)), this, SLOT(loadHeaderState())); connect(ui->radioIdTableView->header(), SIGNAL(sectionResized(int,int,int)), this, SLOT(storeHeaderState())); ui->radioIdTableView->setModel(new RadioIdListWrapper(_config->radioIDs(), ui->radioIdTableView)); ui->defaultID->setModel(new RadioIdListWrapper(_config->radioIDs(), ui->defaultID)); ui->defaultID->setModelColumn(1); connect(_config, SIGNAL(modified(ConfigItem*)), this, SLOT(onConfigModified())); connect(ui->addID, SIGNAL(clicked(bool)), this, SLOT(onAddID())); connect(ui->delID, SIGNAL(clicked(bool)), this, SLOT(onDeleteID())); connect(ui->radioIdTableView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditID(unsigned))); connect(ui->defaultID, SIGNAL(currentIndexChanged(int)), this, SLOT(onDefaultIDSelected(int))); } RadioIDListView::~RadioIDListView() { delete ui; } void RadioIDListView::onConfigModified() { if (_config->settings()->defaultId()) { ui->defaultID->setCurrentIndex(_config->radioIDs()->indexOf(_config->settings()->defaultId())); } else { ui->defaultID->setCurrentIndex(-1); } } void RadioIDListView::onAddID() { DMRIDDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row = -1; if (ui->radioIdTableView->hasSelection()) row = ui->radioIdTableView->selection().second; _config->radioIDs()->add(dialog.radioId(), row); } void RadioIDListView::onDeleteID() { if (! ui->radioIdTableView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete radio IDs"), tr("Cannot delete radio IDs: You have to select a radio ID first.")); return; } // Get selection and ask for deletion QPair rows = ui->radioIdTableView->selection(); int numrows = rows.second-rows.first+1; if (rows.first == rows.second) { QString name = _config->radioIDs()->get(rows.first)->as()->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete radio ID?"), tr("Delete radio ID %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete scan lists?"), tr("Delete %1 scan lists?").arg(numrows))) return; } // collect all selected scan lists // need to collect them first as rows change when deleting QList ids; ids.reserve(numrows); for(int i=rows.first; i<=rows.second; i++) ids.push_back(_config->radioIDs()->get(i)->as()); // remove foreach (RadioID *id, ids) _config->radioIDs()->del(id); } void RadioIDListView::onEditID(unsigned row) { if ((int(row) >= _config->radioIDs()->count()) || (! _config->radioIDs()->get(row)->as())) return; auto id = _config->radioIDs()->get(row)->as(); DMRIDDialog dialog(id, _config); if (QDialog::Accepted != dialog.exec()) return; // Apply changes dialog.radioId(); } void RadioIDListView::onDefaultIDSelected(int idx) { if (((idx < 0) || (idx >= _config->radioIDs()->count()) || (! _config->radioIDs()->get(idx)->is()))) return; _config->settings()->setDefaultId(_config->radioIDs()->get(idx)->as()); } void RadioIDListView::loadHeaderState() { ui->radioIdTableView->header()->restoreState(Settings().headerState("radioIDList")); } void RadioIDListView::storeHeaderState() { Settings().setHeaderState("radioIDList", ui->radioIdTableView->header()->saveState()); } ================================================ FILE: src/radioidlistview.hh ================================================ #ifndef RADIOIDLISTVIEW_HH #define RADIOIDLISTVIEW_HH #include class Config; namespace Ui { class RadioIDListView; } class RadioIDListView : public QWidget { Q_OBJECT public: explicit RadioIDListView(Config *config, QWidget *parent = nullptr); ~RadioIDListView(); protected slots: void onConfigModified(); void onAddID(); void onDeleteID(); void onEditID(unsigned row); void onDefaultIDSelected(int idx); void loadHeaderState(); void storeHeaderState(); private: Ui::RadioIDListView *ui; Config *_config; }; #endif // RADIOIDLISTVIEW_HH ================================================ FILE: src/radioidlistview.ui ================================================ RadioIDListView 0 0 400 300 Form Default Radio ID 0 0 Add Radio ID Delete Radio ID ConfigObjectTableView QWidget
configobjecttableview.hh
1
================================================ FILE: src/radioidrepeatersource.cc ================================================ #include "radioidrepeatersource.hh" #include #include #include #include #include #include "logger.hh" RadioidRepeaterSource::RadioidRepeaterSource(QObject *parent) : DownloadableRepeaterDatabaseSource( "radioidrepeater.cache.json", QUrl("https://radioid.net/static/map.json"), 5, parent) { // pass... } bool RadioidRepeaterSource::parse(const QByteArray &json) { QJsonParseError err; auto doc = QJsonDocument::fromJson(json, &err); if (doc.isNull()) { logError() << "Cannot received JSON: " << err.errorString() << "."; return false; } if ((! doc.isObject()) || (! doc.object().contains("markers")) || (! doc.object()["markers"].isArray())) { logError() << "Malformed result."; return false; } QJsonArray list = doc.object()["markers"].toArray(); for (auto val: list) { if (! val.isObject()) continue; auto repeater = val.toObject(); if ("ACTIVE" != repeater["status"].toString()) continue; auto call = repeater["callsign"].toString().simplified().toUpper(); auto rx = Frequency::fromMHz(repeater["frequency"].toString().toDouble()); auto tx = Frequency::fromMHz(rx.inMHz() + repeater["offset"].toString().toDouble()); auto colorCode = repeater["color_code"].toInt(); auto position = QGeoCoordinate(repeater["lat"].toString().toDouble(), repeater["lng"].toString().toDouble()); auto qth = repeater["city"].toString(); cache(RepeaterDatabaseEntry::dmr(call, rx, tx, position, qth, colorCode)); } logDebug() << "Loaded " << _cache.size() << " elements from " << _url.toDisplayString() << "."; return true; } ================================================ FILE: src/radioidrepeatersource.hh ================================================ #ifndef RADIOIDREPEATERSOURCE_HH #define RADIOIDREPEATERSOURCE_HH #include "repeaterdatabase.hh" class RadioidRepeaterSource : public DownloadableRepeaterDatabaseSource { Q_OBJECT public: explicit RadioidRepeaterSource(QObject *parent = nullptr); protected: bool parse(const QByteArray &doc); }; #endif // RADIOIDREPEATERSOURCE_HH ================================================ FILE: src/radioselectiondialog.cc ================================================ #include "radioselectiondialog.hh" #include "ui_radioselectiondialog.h" RadioSelectionDialog::RadioSelectionDialog(const USBDeviceDescriptor &device, QWidget *parent) : QDialog(parent), ui(new Ui::RadioSelectionDialog) { ui->setupUi(this); _radios = RadioInfo::allRadios(device); foreach (RadioInfo info, _radios) { ui->radios->addItem(QString("%1 %2").arg(info.manufacturer(), info.name())); } if (!_radios.isEmpty()) ui->radios->setCurrentIndex(0); connect(ui->buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(ui->buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } RadioSelectionDialog::~RadioSelectionDialog() { delete ui; } RadioInfo RadioSelectionDialog::radioInfo() const { return _radios.at(ui->radios->currentIndex()); } ================================================ FILE: src/radioselectiondialog.hh ================================================ #ifndef RADIOSELECTIONDIALOG_HH #define RADIOSELECTIONDIALOG_HH #include #include "usbdevice.hh" #include "radioinfo.hh" namespace Ui { class RadioSelectionDialog; } class RadioSelectionDialog : public QDialog { Q_OBJECT public: explicit RadioSelectionDialog(const USBDeviceDescriptor &device, QWidget *parent = nullptr); ~RadioSelectionDialog(); RadioInfo radioInfo() const; private: Ui::RadioSelectionDialog *ui; QList _radios; }; #endif // RADIOSELECTIONDIALOG_HH ================================================ FILE: src/radioselectiondialog.ui ================================================ RadioSelectionDialog 0 0 400 300 Cannot auto-detect radio Select a specific radio 0 0 Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() RadioSelectionDialog accept() 248 254 157 274 buttonBox rejected() RadioSelectionDialog reject() 316 260 286 274 ================================================ FILE: src/releasenotes.cc ================================================ #include "releasenotes.hh" #include "config.h" #include "settings.hh" #include "logger.hh" #include #include #include #include ReleaseNotes::ReleaseNotes(QObject *parent) : QObject(parent) { connect(&_net, SIGNAL(finished(QNetworkReply*)), this, SLOT(onResponse(QNetworkReply*))); } void ReleaseNotes::checkForUpdate() { Settings settings; if (! settings.isUpdated()) return; QString url = QString("https://api.github.com/repos/hmatuschek/qdmr/releases/tags/v%1"); _net.get(QNetworkRequest(QUrl(url.arg(VERSION_STRING)))); } void ReleaseNotes::onResponse(QNetworkReply *reply) { if (reply->error()) { show(tr("Cannot download release notes from https://github.com/hmatuschek/qdmr\n\t %1") .arg(reply->errorString())); return; } QByteArray data = reply->readAll(); QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(data, &err); if (QJsonParseError::NoError != err.error) { logWarn() << "JSON error in release data: " << err.errorString(); } if (! doc.isObject()) { show(tr("Cannot read release notes from https://github.com/hmatuschek/qdmr\n\t" "Release is not a JSON object!")); return; } if (! doc.object().contains("body")) { show(tr("Cannot read release notes from https://github.com/hmatuschek/qdmr\n\t" "Release does not contain a release note.")); return; } show(doc.object()["body"].toString()); } void ReleaseNotes::show(const QString ¬es) { QMessageBox::information( nullptr, tr("qDMR was updated to version %1").arg(VERSION_STRING), notes); Settings().markUpdated(); } ================================================ FILE: src/releasenotes.hh ================================================ #ifndef RELEASENOTES_HH #define RELEASENOTES_HH #include #include class ReleaseNotes : public QObject { Q_OBJECT public: explicit ReleaseNotes(QObject *parent = nullptr); void checkForUpdate(); public slots: void show(const QString ¬es); protected slots: void onResponse(QNetworkReply *reply); protected: QNetworkAccessManager _net; }; #endif // RELEASENOTES_HH ================================================ FILE: src/repeaterbooksource.cc ================================================ #include "repeaterbooksource.hh" #include #include #include #include #include #include #include #include #include "logger.hh" #include "settings.hh" #include "config.h" RepeaterBookSource::RepeaterBookSource(QObject *parent) : CachedRepeaterDatabaseSource("repeaterbook.cache.json", parent), _network(), _currentReply(nullptr), _callsignPattern(R"re(([a-z]|[a-z0-9][a-z]|[a-z][a-z0-9])[0-9]+[a-z]*)re", QRegularExpression::CaseInsensitiveOption) { connect(&_network, SIGNAL(finished(QNetworkReply*)), this, SLOT(onRequestFinished(QNetworkReply*))); } bool RepeaterBookSource::load(const QString &queryCall) { // Cancel running requests if (_currentReply) _currentReply->abort(); QRegularExpressionMatch match = _callsignPattern.match(queryCall); if (! match.hasMatch()) return false; QString call = match.captured().toUpper(); logDebug() << "Search for (partial) call '" << call << "'."; QUrl url; if (Region::World == Settings().repeaterBookRegion()) url = QUrl("https://www.repeaterbook.com/api/exportROW.php"); else url = QUrl("https://www.repeaterbook.com/api/export.php"); QUrlQuery query; query.addQueryItem("callsign", QString("%1%").arg(call)); url.setQuery(query); QNetworkRequest request(url); request.setHeader(QNetworkRequest::UserAgentHeader, "qdmr " VERSION_STRING " https://dm3mat.darc.de/qdmr/"); logDebug() << "Query RepeaterBook at " << url.toString() << " as '" << request.header(QNetworkRequest::UserAgentHeader).toString() << "'."; _currentReply = _network.get(request); return true; } void RepeaterBookSource::onRequestFinished(QNetworkReply *reply) { if (reply->error()) { logError() << "Cannot download repeater list: " << reply->errorString(); reply->deleteLater(); _currentReply = nullptr; return; } QByteArray content = reply->readAll(); QJsonParseError err; QJsonDocument doc = QJsonDocument::fromJson(content, &err); if (doc.isNull()) { logError() << "Cannot parse response: " << err.errorString() << "."; logDebug() << "Got '" << content << "'."; reply->deleteLater(); _currentReply = nullptr; return; } QString query = QUrlQuery(reply->request().url()) .queryItemValue("callsign", QUrl::FullyDecoded).remove("%"); reply->deleteLater(); _currentReply = nullptr; if ((! doc.isObject()) || (! doc.object().contains("results")) || (! doc.object()["results"].isArray())) { logError() << "Cannot parse response: Unexpected structure."; reply->deleteLater(); _currentReply = nullptr; return; } QJsonArray results = doc.object()["results"].toArray(); foreach (const QJsonValue &rep, results) { if (! rep.isObject()) continue; QJsonObject obj = rep.toObject(); // Handle basic properties auto call = obj["Callsign"].toString().simplified().toUpper(); auto rxFrequency = Frequency::fromMHz(obj["Frequency"].toString().toDouble()); auto txFrequency = Frequency::fromMHz(obj["Input Freq"].toString().toDouble()); auto location = QGeoCoordinate(obj["Lat"].toString().toDouble(), obj["Long"].toString().toDouble()); auto qth = obj["Nearest City"].toString(); auto type = RepeaterDatabaseEntry::Type::Invalid; SelectiveCall rxTone, txTone; unsigned int colorCode = 0; QDateTime updated; if (obj["FM Analog"].toString() == "Yes") { type = RepeaterDatabaseEntry::Type::FM; if (obj.contains("PL")) txTone = SelectiveCall(obj["PL"].toString().toDouble()); if (obj.contains("TSQ")) rxTone = SelectiveCall(obj["TSQ"].toString().toDouble()); } if (obj["DMR"].toString() == "Yes") { type = RepeaterDatabaseEntry::Type::DMR; if (obj.contains("DMR Color Code")) colorCode = obj["DMR Color Code"].toString().toInt(); } if (obj.contains("timestamp")) updated = QDateTime::fromString(obj["timestamp"].toString(), Qt::ISODate); if (RepeaterDatabaseEntry::Type::FM == type) { cache(RepeaterDatabaseEntry::fm(call, rxFrequency, txFrequency, location, qth, rxTone, txTone, updated)); } else if (RepeaterDatabaseEntry::Type::DMR == type) { cache(RepeaterDatabaseEntry::dmr(call, rxFrequency, txFrequency, location, qth, colorCode, updated)); } } logDebug() << "Updated repeater cache with " << results.count() << " entries."; saveCache(); } ================================================ FILE: src/repeaterbooksource.hh ================================================ #ifndef REPEATERBOOKSOURCE_HH #define REPEATERBOOKSOURCE_HH #include "repeaterdatabase.hh" #include class RepeaterBookSource : public CachedRepeaterDatabaseSource { Q_OBJECT public: enum Region { World, NorthAmerica }; Q_ENUM(Region) public: explicit RepeaterBookSource(QObject *parent = nullptr); protected slots: void onRequestFinished(QNetworkReply *reply); protected: bool load(const QString &call) override; protected: QNetworkAccessManager _network; QNetworkReply *_currentReply; QRegularExpression _callsignPattern; }; #endif // REPEATERBOOKSOURCE_HH ================================================ FILE: src/repeatercompleter.cc ================================================ #include "repeatercompleter.hh" #include "repeaterdatabase.hh" /* ********************************************************************************************* * * RepeaterCompleter * ********************************************************************************************* */ RepeaterCompleter::RepeaterCompleter(int minPrefixLength, RepeaterDatabase *repeater, QObject *parent) : QCompleter(parent), _repeaters(repeater), _minPrefixLength(minPrefixLength) { setModel(_repeaters); setCaseSensitivity(Qt::CaseInsensitive); } QStringList RepeaterCompleter::splitPath(const QString &path) const { if (path.length() >= _minPrefixLength) _repeaters->query(path); return QCompleter::splitPath(path); } /* ********************************************************************************************* * * NearestRepeaterFilter * ********************************************************************************************* */ NearestRepeaterFilter::NearestRepeaterFilter(RepeaterDatabase *repeater, const QGeoCoordinate &location, QObject *parent) : QSortFilterProxyModel(parent), _repeater(repeater), _location(location) { setSourceModel(repeater); sort(0); } bool NearestRepeaterFilter::lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const { if (! _location.isValid()) return source_left.row() < source_right.row(); double ldist = _location.distanceTo(_repeater->get(source_left.row()).location()); double rdist = _location.distanceTo(_repeater->get(source_right.row()).location()); return ldist < rdist; } /* ********************************************************************************************* * * DMRRepeaterFilter * ********************************************************************************************* */ DMRRepeaterFilter::DMRRepeaterFilter(RepeaterDatabase *repeater, const QGeoCoordinate &location, QObject *parent) : NearestRepeaterFilter(repeater, location, parent) { invalidateFilter(); } bool DMRRepeaterFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { Q_UNUSED(source_parent); if (source_row >= _repeater->rowCount(QModelIndex())) return false; return RepeaterDatabaseEntry::Type::DMR == _repeater->get(source_row).type(); } /* ********************************************************************************************* * * FMRepeaterFilter * ********************************************************************************************* */ FMRepeaterFilter::FMRepeaterFilter(RepeaterDatabase *repeater, const QGeoCoordinate &location, QObject *parent) : NearestRepeaterFilter(repeater, location, parent) { invalidateFilter(); } bool FMRepeaterFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const { Q_UNUSED(source_parent); if (source_row >= _repeater->rowCount(QModelIndex())) return false; return RepeaterDatabaseEntry::Type::FM == _repeater->get(source_row).type(); } ================================================ FILE: src/repeatercompleter.hh ================================================ #ifndef REPEATERCOMPLETER_HH #define REPEATERCOMPLETER_HH #include #include #include class RepeaterDatabase; class RepeaterCompleter: public QCompleter { Q_OBJECT public: explicit RepeaterCompleter(int minPrefixLength, RepeaterDatabase *repeater, QObject *parent=nullptr); QStringList splitPath(const QString &path) const override; protected: RepeaterDatabase *_repeaters; int _minPrefixLength; }; class NearestRepeaterFilter: public QSortFilterProxyModel { Q_OBJECT public: /** Constructor. */ explicit NearestRepeaterFilter(RepeaterDatabase *repeater, const QGeoCoordinate &location, QObject *parent=nullptr); protected: bool lessThan(const QModelIndex &source_left, const QModelIndex &source_right) const override; protected: RepeaterDatabase *_repeater; QGeoCoordinate _location; }; /** A filter proxy for DMR repeaters. * @ingroup util */ class DMRRepeaterFilter: public NearestRepeaterFilter { Q_OBJECT public: /** Constructor. */ explicit DMRRepeaterFilter(RepeaterDatabase *repeater, const QGeoCoordinate &location, QObject *parent=nullptr); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; }; /** A filter proxy for analog FM repeaters. * @ingroup util */ class FMRepeaterFilter: public NearestRepeaterFilter { Q_OBJECT public: /** Constructor. */ explicit FMRepeaterFilter(RepeaterDatabase *repeater, const QGeoCoordinate &location, QObject *parent=nullptr); protected: bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override; }; #endif // REPEATERCOMPLETER_HH ================================================ FILE: src/repeaterdatabase.cc ================================================ #include "repeaterdatabase.hh" #include "config.h" #include "logger.hh" #include "utils.hh" #include #include #include #include #include #include #include #include #include /* ********************************************************************************************* * * Helper functions * ********************************************************************************************* */ static const QSet _aprs_frequencies = { 144.390, 144.575, 144.660, 144.800, 144.930, 145.175, 145.570, 432.500 }; inline QString bandName(const Frequency &F) { if (30 >= F.inMHz()) return "HF"; else if (300 >= F.inMHz()) return "VHF"; else if (3000 >= F.inMHz()) return "UHF"; else if (30000 >= F.inMHz()) return "SHF"; return "EHF"; } inline QString bandName(const Frequency &rx, const Frequency & tx) { QString rband = bandName(rx), tband=bandName(tx); if ((rx == tx) && (_aprs_frequencies.contains(rx.inMHz()))) return "APRS"; else if (rband == tband) return rband; return QString("%1/%2").arg(rband,tband); } /* ********************************************************************************************* * * Implementation of RepeaterDatabaseEntry * ********************************************************************************************* */ RepeaterDatabaseEntry::RepeaterDatabaseEntry(const QString &call, const Frequency &rxFrequency, const Frequency &txFrequency, const QGeoCoordinate &location, const QString &qth, const SelectiveCall &rxTone, const SelectiveCall &txTone, const QDateTime &updated, const QDateTime& loaded) : _type(Type::FM), _call(call), _rxFrequency(rxFrequency), _txFrequency(txFrequency), _location(location), _qth(qth), _rxTone(rxTone), _txTone(txTone), _colorCode(0), _updated(updated), _loaded(loaded) { // pass... } RepeaterDatabaseEntry::RepeaterDatabaseEntry(const QString &call, const Frequency &rxFrequency, const Frequency &txFrequency, const QGeoCoordinate &location, const QString &qth, unsigned int colorCode, const QDateTime &updated, const QDateTime& loaded) : _type(Type::DMR), _call(call), _rxFrequency(rxFrequency), _txFrequency(txFrequency), _location(location), _qth(qth), _rxTone(), _txTone(), _colorCode(colorCode), _updated(updated), _loaded(loaded) { // pass... } RepeaterDatabaseEntry::RepeaterDatabaseEntry() : _type(Type::Invalid), _call(), _rxFrequency(), _txFrequency(), _location(), _rxTone(), _txTone(), _colorCode(0), _updated() { // pass... } bool RepeaterDatabaseEntry::operator==(const RepeaterDatabaseEntry &other) const { if (other._type != _type) return false; // Invalid entries are all equal if (Type::Invalid == _type) return true; return (other._call == _call) && (other._rxFrequency == _rxFrequency); } bool RepeaterDatabaseEntry::operator<(const RepeaterDatabaseEntry &other) const { return (_type < other._type) || (_call < other._call) || (_rxFrequency < other._rxFrequency); } QJsonValue RepeaterDatabaseEntry::toJson() const { QJsonObject obj; switch (type()) { case Type::Invalid: return QJsonValue(); case Type::FM: obj.insert("type", "FM"); break; case Type::DMR: obj.insert("type", "DMR"); break; case Type::M17: obj.insert("type", "M17"); break; } obj.insert("call", call()); obj.insert("rx", rxFrequency().format()); if (txFrequency().inHz()) obj.insert("tx", txFrequency().format()); obj.insert("latitude", location().latitude()); obj.insert("longitude", location().longitude()); obj.insert("qth", qth()); if (Type::FM == type()) { if (rxTone().isInvalid()) obj.insert("rx-tone", rxTone().format()); if (txTone().isInvalid()) obj.insert("tx-tone", txTone().format()); } else if (Type::DMR == type()) { obj.insert("color-code", (int)colorCode()); } if (updated().isValid()) obj.insert("updated", updated().toString(Qt::ISODate)); if (loaded().isValid()) obj.insert("loaded", loaded().toString(Qt::ISODate)); return obj; } RepeaterDatabaseEntry & RepeaterDatabaseEntry::operator+=(const RepeaterDatabaseEntry &other) { if ((_type != other.type()) || (_call != other.call())) return *this; if ((!_loaded.isValid()) || (_loaded < other._loaded)) return (*this)=other; if ((!_updated.isValid()) || (_updated < other._updated)) return (*this)=other; if (! _location.isValid()) _location = other._location; if (_qth.isEmpty()) _qth = other.qth(); if (Type::FM == _type) { if (_rxTone.isInvalid()) _rxTone = other.rxTone(); if (_txTone.isInvalid()) _txTone = other.txTone(); } else if (Type::DMR == _type) { if (0 == _colorCode) _colorCode = other.colorCode(); } return *this; } bool RepeaterDatabaseEntry::isValid() const { return (Type::Invalid != _type) && (! _call.isEmpty()) && (_rxFrequency.inHz()) && (_txFrequency.inHz()); } RepeaterDatabaseEntry::Type RepeaterDatabaseEntry::type() const { return _type; } const QString & RepeaterDatabaseEntry::call() const { return _call; } const Frequency & RepeaterDatabaseEntry::rxFrequency() const { return _rxFrequency; } const Frequency & RepeaterDatabaseEntry::txFrequency() const { return _txFrequency; } const QGeoCoordinate & RepeaterDatabaseEntry::location() const { return _location; } QString RepeaterDatabaseEntry::locator() const { return deg2loc(_location); } const QString & RepeaterDatabaseEntry::qth() const { return _qth; } const SelectiveCall & RepeaterDatabaseEntry::rxTone() const { return _rxTone; } const SelectiveCall & RepeaterDatabaseEntry::txTone() const { return _txTone; } unsigned int RepeaterDatabaseEntry::colorCode() const { return _colorCode; } const QDateTime & RepeaterDatabaseEntry::updated() const { return _updated; } const QDateTime & RepeaterDatabaseEntry::loaded() const { return _loaded; } RepeaterDatabaseEntry RepeaterDatabaseEntry::fm(const QString &call, const Frequency &rxFrequency, const Frequency &txFrequency, const QGeoCoordinate &location, const QString &qth, const SelectiveCall &rxTone, const SelectiveCall &txTone, const QDateTime &updated, const QDateTime& loaded) { return RepeaterDatabaseEntry( call.simplified().toUpper(), rxFrequency, txFrequency, location, qth, rxTone, txTone, updated, loaded); } RepeaterDatabaseEntry RepeaterDatabaseEntry::dmr(const QString &call, const Frequency &rxFrequency, const Frequency &txFrequency, const QGeoCoordinate &location, const QString &qth, unsigned int colorCode, const QDateTime &updated, const QDateTime& loaded) { return RepeaterDatabaseEntry( call.simplified().toUpper(), rxFrequency, txFrequency, location, qth, colorCode, updated, loaded); } RepeaterDatabaseEntry RepeaterDatabaseEntry::fromJson(const QJsonObject &obj) { if (obj.isEmpty() || (! obj.contains("type"))) return RepeaterDatabaseEntry(); Type type = Type::Invalid; if ("FM" == obj.value("type").toString()) type = Type::FM; else if ("DMR" == obj.value("type").toString()) type = Type::DMR; else if ("M17" == obj.value("type").toString()) type = Type::M17; QString call = obj.value("call").toString().simplified().toUpper(); Frequency rx = Frequency::fromString(obj.value("rx").toString()), tx; if (obj.contains("tx")) tx = Frequency::fromString(obj.value("tx").toString()); QGeoCoordinate location(obj.value("latitude").toDouble(), obj.value("longitude").toDouble()); QString qth = obj.value("qth").toString(); QDateTime updated, loaded; if (obj.contains("updated")) updated = QDateTime::fromString(obj.value("updated").toString(), Qt::ISODate); if (obj.contains("loaded")) loaded = QDateTime::fromString(obj.value("loaded").toString(), Qt::ISODate); if (Type::FM == type) { SelectiveCall rxTone, txTone; if (obj.contains("rx-tone")) rxTone = SelectiveCall::parseCTCSS(obj.value("rx-tone").toString()); if (obj.contains("tx-tone")) txTone = SelectiveCall::parseCTCSS(obj.value("tx-tone").toString()); return RepeaterDatabaseEntry::fm( call, rx, tx, location, qth, rxTone, txTone, updated, loaded); } else if (Type::DMR == type) { unsigned int colorCode = 0; if (obj.contains("color-code")) colorCode = obj.value("color-code").toInt(); return RepeaterDatabaseEntry::dmr( call, rx, tx, location, qth, colorCode, updated, loaded); } return RepeaterDatabaseEntry(); } /* ********************************************************************************************* * * Implementation of RepeaterDatabaseSource * ********************************************************************************************* */ RepeaterDatabaseSource::RepeaterDatabaseSource(QObject *parent) : QObject{parent} { // pass... } void RepeaterDatabaseSource::setSearchRadius(const QGeoCoordinate &position, unsigned int radius) { Q_UNUSED(position); Q_UNUSED(radius); } bool RepeaterDatabaseSource::query(const QString &call) { return this->load(call); } unsigned int RepeaterDatabaseSource::count() const { return 0; } RepeaterDatabaseEntry RepeaterDatabaseSource::get(unsigned int idx) const { Q_UNUSED(idx); return RepeaterDatabaseEntry(); } /* ********************************************************************************************* * * Implementation of CachedRepeaterDatabaseSource * ********************************************************************************************* */ CachedRepeaterDatabaseSource::CachedRepeaterDatabaseSource(const QString &filename, QObject *parent) : RepeaterDatabaseSource{parent}, _parsing() { QString path = QStandardPaths::writableLocation(QStandardPaths::AppDataLocation); QDir directory; if ((! directory.exists(path)) && (!directory.mkpath(path))) { logError() << "Cannot create path '" << path << "'."; return; } QFileInfo info(path + "/" + filename); _cacheFile.setFileName(info.absoluteFilePath()); if (! info.isFile() || !info.isReadable()) return; _parsing = QtConcurrent::run([this](){ QList entries; this->parseCache(entries); return entries; }) .then([this](const QList &entries) { return this->loadEntries(entries); }); } bool CachedRepeaterDatabaseSource::loadCache() { QList entries; if (! parseCache(entries)) return false; return loadEntries(entries); } bool CachedRepeaterDatabaseSource::parseCache(QList &entries) { if (! _cacheFile.open(QIODevice::ReadOnly)) { logError() << "Cannot open cache '" << _cacheFile.fileName() << "': " << _cacheFile.errorString() << "."; return false; } QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson(_cacheFile.readAll(), &error); _cacheFile.close(); if (doc.isNull()) { logError() << "Cannot parse cache '" << _cacheFile.fileName() << "': " << error.errorString() << "."; return false; } if (! doc.isArray()) { logError() << "Malformed cache file."; return false; } entries.clear(); for (QJsonValue obj: doc.array()) { if (! obj.isObject()) continue; RepeaterDatabaseEntry entry = RepeaterDatabaseEntry::fromJson(obj.toObject()); if (! entry.isValid()) continue; entries.append(entry); } logDebug() << "Loaded " << entries.size() << " entries from '" << _cacheFile.fileName() << "'."; return true; } bool CachedRepeaterDatabaseSource::loadEntries(const QList &entries) { _cache.clear(); for (auto entry: entries) { if (! entry.isValid()) continue; _cache.append(entry); emit updated(entry); } return true; } void CachedRepeaterDatabaseSource::saveCache() { if (! _cacheFile.open(QIODevice::WriteOnly)) { logError() << "Cannot open cache '" << _cacheFile.fileName() << "': " << _cacheFile.errorString() << "."; return; } QJsonArray entries; for (auto entry: _cache) entries.append(entry.toJson()); if (! _cacheFile.write(QJsonDocument(entries).toJson())) { logError() << "Cannot write cache '" << _cacheFile.fileName() << "': " << _cacheFile.errorString() << "."; } _cacheFile.flush(); _cacheFile.close(); } void CachedRepeaterDatabaseSource::cache(const RepeaterDatabaseEntry &entry) { if (! _indices.contains(entry)) { _indices[entry] = _cache.size(); _cache.append(entry); } else { _cache[_indices[entry]] += entry; } emit updated(entry); } unsigned int CachedRepeaterDatabaseSource::count() const { return _cache.size(); } RepeaterDatabaseEntry CachedRepeaterDatabaseSource::get(unsigned int idx) const { return _cache.value(idx, RepeaterDatabaseEntry()); } bool CachedRepeaterDatabaseSource::query(const QString &call) { QString query = call.simplified().toUpper(); QDateTime newest; for (const RepeaterDatabaseEntry &entry: _cache) { if (entry.call().startsWith(query) && entry.loaded().isValid()) if ((! newest.isValid()) || (newest < entry.loaded())) newest = entry.loaded(); } if (newest.isValid() && (newest.daysTo(QDateTime::currentDateTime()) <= _maxAge)) return true; return RepeaterDatabaseSource::query(call); } /* ********************************************************************************************* * * Implementation of DownloadableRepeaterDatabaseSource * ********************************************************************************************* */ DownloadableRepeaterDatabaseSource::DownloadableRepeaterDatabaseSource( const QString &filename, const QUrl &source, unsigned int maxAge, QObject *parent) : CachedRepeaterDatabaseSource(filename, parent), _url(source), _maxAge(maxAge), _network(), _currentReply(nullptr) { connect(&_network, SIGNAL(finished(QNetworkReply*)), this, SLOT(onRequestFinished(QNetworkReply*))); if (needsUpdate()) QTimer::singleShot(0, this, &DownloadableRepeaterDatabaseSource::download); } bool DownloadableRepeaterDatabaseSource::needsUpdate() const { QFileInfo info(_cacheFile); return (! info.exists()) || (info.lastModified().daysTo(QDateTime::currentDateTime()) > _maxAge); } bool DownloadableRepeaterDatabaseSource::load(const QString &call) { Q_UNUSED(call); // No action needed, downloads entire dataset. return true; } void DownloadableRepeaterDatabaseSource::download() { // If there is already a current request, done. if (_currentReply) return; QNetworkRequest request(_url); if (! prepareRequest(request)) return; logDebug() << "Query " << _url.toString() << "'."; _currentReply = _network.get(request); } bool DownloadableRepeaterDatabaseSource::prepareRequest(QNetworkRequest &request) { if (request.hasRawHeader("X-API-Token") && request.rawHeader("X-API-Token").isEmpty()) { logError() << "An empty API token is set!"; return false; } if (! request.hasRawHeader("User-Agent")) { request.setHeader( QNetworkRequest::UserAgentHeader, QLatin1String("qdmr/%1 (%2)").arg(VERSION_STRING).arg(QGuiApplication::platformName())); } return true; } void DownloadableRepeaterDatabaseSource::onRequestFinished(QNetworkReply *reply) { if (reply->error()) { logError() << "Cannot download repeater list: " << reply->errorString(); reply->deleteLater(); _currentReply = nullptr; return; } QByteArray content = reply->readAll(); reply->deleteLater(); _currentReply = nullptr; if (parse(content)) saveCache(); } /* ********************************************************************************************* * * Implementation of RepeaterDatabase * ********************************************************************************************* */ RepeaterDatabase::RepeaterDatabase(QObject *parent) : QAbstractListModel{parent}, _searchPosition(), _searchRadius(0), _sources(), _indices(), _entries() { // pass... } void RepeaterDatabase::setSearchRadius(const QGeoCoordinate &position, unsigned int radius) { if ((0 == radius) || !position.isValid()) return; for (auto source: _sources) { source->setSearchRadius(position, radius); } } void RepeaterDatabase::addSource(RepeaterDatabaseSource *source) { if ((nullptr == source) || _sources.contains(source)) return; for (unsigned int i=0; icount(); i++) { merge(source->get(i)); } _sources.append(source); source->setParent(this); connect(source, &RepeaterDatabaseSource::updated, this, &RepeaterDatabase::merge, Qt::QueuedConnection); } void RepeaterDatabase::merge(const RepeaterDatabaseEntry &entry) { if (_indices.contains(entry)) { int row = _indices[entry]; RepeaterDatabaseEntry myEntry(_entries[row]); if ((! myEntry.updated().isValid()) || (myEntry.updated().isValid() && entry.updated().isValid() && (myEntry.updated() < entry.updated()))) { _entries[_indices[entry]] = entry; emit dataChanged(index(row), index(row)); } return; } int row = _entries.size(); beginInsertRows(QModelIndex(), row, row); _entries.append(entry); _indices[entry] = row; endInsertRows(); } int RepeaterDatabase::rowCount(const QModelIndex &parent) const { Q_UNUSED(parent); return _entries.size(); } RepeaterDatabaseEntry RepeaterDatabase::get(unsigned int idx) const { if (idx >= (unsigned int)_entries.size()) return RepeaterDatabaseEntry(); return _entries[idx]; } QVariant RepeaterDatabase::data(const QModelIndex &index, int role) const { if (index.row() >= _entries.count()) return QVariant(); if (Qt::EditRole == role) { return _entries[index.row()].call(); } else if (Qt::DisplayRole == role) { return QString("%1 (%2, %3, %4)") .arg(_entries[index.row()].call()) .arg(_entries[index.row()].qth()) .arg(bandName(_entries[index.row()].rxFrequency(), _entries[index.row()].txFrequency())) .arg(_entries[index.row()].locator()); } return QVariant(); } bool RepeaterDatabase::query(const QString &call) { for (auto source: _sources) { source->query(call); } return true; } ================================================ FILE: src/repeaterdatabase.hh ================================================ #ifndef REPEATERDATABASE_HH #define REPEATERDATABASE_HH #include #include #include #include #include #include #include #include #include "frequency.hh" #include "signaling.hh" class QNetworkReply; /** Repeater database entry. * Just a collection of some meaningful information for FM and DMR repeater. */ class RepeaterDatabaseEntry { public: /** The possible repeater types. */ enum class Type { Invalid, FM, DMR, M17 }; protected: /** FM Constructor. */ RepeaterDatabaseEntry(const QString &call, const Frequency &rxFrequency, const Frequency &txFrequency, const QGeoCoordinate &location, const QString &qth, const SelectiveCall &rxTone, const SelectiveCall &txTone, const QDateTime &updated, const QDateTime &loaded); /** DMR Constructor. */ RepeaterDatabaseEntry(const QString &call, const Frequency &rxFrequency, const Frequency &txFrequency, const QGeoCoordinate &location, const QString &qth, unsigned int colorCode, const QDateTime &updated, const QDateTime &loaded); public: /** Default constructor, constructs an invalid entry. */ RepeaterDatabaseEntry(); /** Copy constructor. */ RepeaterDatabaseEntry(const RepeaterDatabaseEntry &other) = default; /** Assignment operator. */ RepeaterDatabaseEntry &operator=(const RepeaterDatabaseEntry &other) = default; /** Comparison. */ bool operator==(const RepeaterDatabaseEntry &other) const; /** Comparison. */ bool operator<(const RepeaterDatabaseEntry &other) const; /** Update operator. */ RepeaterDatabaseEntry &operator +=(const RepeaterDatabaseEntry &other); QJsonValue toJson() const; bool isValid() const; Type type() const; const QString &call() const; const Frequency &rxFrequency() const; const Frequency &txFrequency() const; const QGeoCoordinate &location() const; QString locator() const; const QString &qth() const; const SelectiveCall &rxTone() const; const SelectiveCall &txTone() const; unsigned int colorCode() const; const QDateTime &updated() const; const QDateTime &loaded() const; public: static RepeaterDatabaseEntry fm(const QString &call, const Frequency &rxFrequency, const Frequency &txFrequency, const QGeoCoordinate &location, const QString &qth="", const SelectiveCall &rxTone=SelectiveCall(), const SelectiveCall &txTone=SelectiveCall(), const QDateTime &updated = QDateTime(), const QDateTime &loaded = QDateTime::currentDateTime()); static RepeaterDatabaseEntry dmr(const QString &call, const Frequency &rxFrequency, const Frequency &txFrequency, const QGeoCoordinate &location, const QString &qth="", unsigned int colorCode = 0, const QDateTime &updated = QDateTime(), const QDateTime &loaded = QDateTime::currentDateTime()); static RepeaterDatabaseEntry fromJson(const QJsonObject &obj); protected: Type _type; QString _call; Frequency _rxFrequency; Frequency _txFrequency; QGeoCoordinate _location; QString _qth; SelectiveCall _rxTone; SelectiveCall _txTone; unsigned int _colorCode; QDateTime _updated; QDateTime _loaded; }; /** Base class for all database sources. */ class RepeaterDatabaseSource: public QObject { Q_OBJECT protected: explicit RepeaterDatabaseSource(QObject *parent = nullptr); public: /** Sets the search radius (needed for some sources). */ virtual void setSearchRadius(const QGeoCoordinate &position, unsigned int radius); /** Query some (partial-) call. */ virtual bool query(const QString &call); /** Returns the number of stored entries. By default, none are stored. */ virtual unsigned int count() const; /** Returns the i-th stored entry. */ virtual RepeaterDatabaseEntry get(unsigned int idx) const; signals: /** Gets emitted, once an entry gets updated or is added. * @warning This signal may originate in a separate thread.*/ void updated(const RepeaterDatabaseEntry &entry); protected: /** Needs to be implemented to query new entries. */ virtual bool load(const QString &call) = 0; }; /** Base class for all cached database sources. */ class CachedRepeaterDatabaseSource: public RepeaterDatabaseSource { Q_OBJECT protected: CachedRepeaterDatabaseSource(const QString &filename, QObject *parent = nullptr); public: unsigned int count() const override; RepeaterDatabaseEntry get(unsigned int idx) const override; bool query(const QString &call) override; protected: bool loadCache(); bool parseCache(QList &entries); bool loadEntries(const QList &entries); void cache(const RepeaterDatabaseEntry &entry); void saveCache(); protected: unsigned int _maxAge; QFile _cacheFile; QMap _indices; QVector _cache; QFuture _parsing; }; class DownloadableRepeaterDatabaseSource: public CachedRepeaterDatabaseSource { Q_OBJECT protected: DownloadableRepeaterDatabaseSource(const QString &filename, const QUrl &source, unsigned int maxAge=5, QObject *parent=nullptr); public: bool needsUpdate() const; protected: /** Gets called to prepare a network request to fetch the data. */ virtual bool prepareRequest(QNetworkRequest &request); virtual bool parse(const QByteArray &doc) = 0; bool load(const QString &call) override; protected slots: void onRequestFinished(QNetworkReply *reply); void download(); protected: QUrl _url; unsigned int _maxAge; QNetworkAccessManager _network; QNetworkReply *_currentReply; }; /** Base class of all repeater databases. */ class RepeaterDatabase : public QAbstractListModel { Q_OBJECT public: /** Constructor. */ explicit RepeaterDatabase(QObject *parent = nullptr); /** Some sources require a search area to be set. This method forwards the search radius to all * registered sources. */ void setSearchRadius(const QGeoCoordinate &coordinate, unsigned int radius); RepeaterDatabaseEntry get(unsigned int idx) const; void addSource(RepeaterDatabaseSource *source); virtual bool query(const QString &call); int rowCount(const QModelIndex &parent = QModelIndex()) const; QVariant data(const QModelIndex &index, int role) const; protected slots: void merge(const RepeaterDatabaseEntry &entry); protected: QGeoCoordinate _searchPosition; int _searchRadius; QList _sources; QMap _indices; QVector _entries; }; #endif // REPEATERDATABASE_HH ================================================ FILE: src/repeatermapsource.cc ================================================ #include "repeatermapsource.hh" #include "logger.hh" #include #include #include #include #include #include #include RepeaterMapSource::RepeaterMapSource(const QByteArray &apiToken, const QGeoCoordinate &position, unsigned int radius, QObject *parent) : DownloadableRepeaterDatabaseSource( "repeatermap.cache.json", QUrl("https://repeatermap.de/api/v1/repeaters.php"), 5, parent), _apiToken(apiToken), _searchPosition(position), _searchRadius(radius) { // pass... } void RepeaterMapSource::setSearchRadius(const QGeoCoordinate &pos, unsigned int radius) { // Store position QGeoCoordinate oldPos = _searchPosition; _searchRadius = radius; _searchPosition = pos; // If position has moved by 10% of the search radius -> update if (_searchRadius && _searchPosition.isValid() && _searchPosition.distanceTo(oldPos) > 100*_searchRadius) QTimer::singleShot(0, this, &RepeaterMapSource::download); } bool RepeaterMapSource::prepareRequest(QNetworkRequest &request) { // Set API token request.setRawHeader("X-API-Token", _apiToken.toHex().toLower()); // If position & radius is set -> add to request if (_searchPosition.isValid() && _searchRadius) { QUrl url(request.url()); QUrlQuery query(url); logDebug() << "Get repeater near " << _searchPosition.toString() << " (radius " << _searchRadius << "km) from repeatermap.de"; query.addQueryItem("lat", QString::number(_searchPosition.latitude())); query.addQueryItem("lon", QString::number(_searchPosition.longitude())); query.addQueryItem("radius", QString::number(_searchRadius)); url.setQuery(query); request.setUrl(url); } else { logInfo() << "No search position/radius set, request list of german repeaters from repeatermap.de."; } // Done. return DownloadableRepeaterDatabaseSource::prepareRequest(request); } bool RepeaterMapSource::parse(const QByteArray &json) { QJsonParseError err; auto doc = QJsonDocument::fromJson(json, &err); if (doc.isNull()) { logError() << "Cannot received JSON: " << err.errorString() << "."; return false; } if ((! doc.isObject()) || (! doc.object().contains("repeaters")) || (! doc.object()["repeaters"].isArray())) { logError() << "Malformed result."; return false; } QJsonArray list = doc.object()["repeaters"].toArray(); for (auto val: list) { if (! val.isObject()) continue; auto repeater = val.toObject(); auto call = repeater["call"].toString().simplified().toUpper(); auto mode = repeater["mode"].toString(); auto tx = Frequency::fromMHz(repeater["tx"].toDouble()); auto rx = Frequency::fromMHz(repeater["rx"].toDouble()); auto position = QGeoCoordinate(repeater["lat"].toDouble(), repeater["lon"].toDouble()); auto qth = repeater["qth"].toString(); if ("FM" == mode) { cache(RepeaterDatabaseEntry::fm(call, rx, tx, position, qth)); } else if ("DMR" == mode) { cache(RepeaterDatabaseEntry::dmr(call, rx, tx, position, qth)); } } logDebug() << "Loaded " << _cache.size() << " elements from " << _url.toDisplayString() << "."; return true; } ================================================ FILE: src/repeatermapsource.hh ================================================ #ifndef REPEATERMAPSOURCE_HH #define REPEATERMAPSOURCE_HH #include "repeaterdatabase.hh" class RepeaterMapSource : public DownloadableRepeaterDatabaseSource { Q_OBJECT public: explicit RepeaterMapSource(const QByteArray &apiToken, const QGeoCoordinate &position, unsigned int radius, QObject *parent = nullptr); void setSearchRadius(const QGeoCoordinate &position, unsigned int radius) override; protected: bool prepareRequest(QNetworkRequest &request) override; bool parse(const QByteArray &doc) override; protected: QByteArray _apiToken; QGeoCoordinate _searchPosition; unsigned int _searchRadius; }; #endif // REPEATERMAPSOURCE_HH ================================================ FILE: src/roamingchanneldialog.cc ================================================ #include "roamingchanneldialog.hh" #include "ui_roamingchanneldialog.h" #include "roamingchannel.hh" #include "utils.hh" RoamingChannelDialog::RoamingChannelDialog(Config *config, QWidget *parent) : QDialog(parent), ui(new Ui::RoamingChannelDialog), _myChannel(new RoamingChannel(this)), _channel(nullptr) { Q_UNUSED(config) ui->setupUi(this); construct(); } RoamingChannelDialog::RoamingChannelDialog(Config *config, RoamingChannel *channel, QWidget *parent) : QDialog(parent), ui(new Ui::RoamingChannelDialog), _myChannel(new RoamingChannel(this)), _channel(channel) { Q_UNUSED(config) ui->setupUi(this); if (_channel) _myChannel->copy(*_channel); construct(); } RoamingChannelDialog::~RoamingChannelDialog() { delete ui; } void RoamingChannelDialog::construct() { if (_channel) setWindowTitle(tr("Edit roaming channel")); else setWindowTitle(tr("Create roaming channel")); ui->name->setText(_myChannel->name()); ui->rxFrequency->setValidator(new QDoubleValidator(0,500,5)); ui->rxFrequency->setText(_myChannel->rxFrequency().format(Frequency::Unit::MHz)); ui->txFrequency->setValidator(new QDoubleValidator(0,500,5)); ui->txFrequency->setText(_myChannel->txFrequency().format(Frequency::Unit::MHz)); ui->timeSlot->addItem(tr("TS 1"), QVariant::fromValue(DMRChannel::TimeSlot::TS1)); ui->timeSlot->addItem(tr("TS 2"), QVariant::fromValue(DMRChannel::TimeSlot::TS2)); ui->timeSlot->setCurrentIndex( (DMRChannel::TimeSlot::TS1 == _myChannel->timeSlot()) ? 0 : 1); if (! _myChannel->timeSlotOverridden()) { ui->overrideTimeSlot->setChecked(true); ui->timeSlot->setEnabled(false); } ui->colorCode->setValue(_myChannel->colorCode()); if (! _myChannel->colorCodeOverridden()) { ui->overrideColorCode->setChecked(true); ui->colorCode->setEnabled(false); } connect(ui->overrideTimeSlot, SIGNAL(toggled(bool)), this, SLOT(onOverrideTimeSlotToggled(bool))); connect(ui->overrideColorCode, SIGNAL(toggled(bool)), this, SLOT(onOverrideColorCodeToggled(bool))); } void RoamingChannelDialog::onOverrideTimeSlotToggled(bool override) { ui->timeSlot->setEnabled(! override); } void RoamingChannelDialog::onOverrideColorCodeToggled(bool override) { ui->colorCode->setEnabled(! override); } RoamingChannel * RoamingChannelDialog::channel() { _myChannel->setName(ui->name->text().simplified()); _myChannel->setRXFrequency(Frequency::fromString(ui->rxFrequency->text())); _myChannel->setTXFrequency(Frequency::fromString(ui->txFrequency->text())); _myChannel->setTimeSlot(ui->timeSlot->currentData().value()); _myChannel->overrideTimeSlot(! ui->overrideTimeSlot->isChecked()); _myChannel->setColorCode(ui->colorCode->value()); _myChannel->overrideColorCode(! ui->overrideColorCode->isChecked()); if (_channel) { _channel->copy(*_myChannel); return _channel; } _myChannel->setParent(nullptr); return _myChannel; } ================================================ FILE: src/roamingchanneldialog.hh ================================================ #ifndef ROAMINGCHANNELDIALOG_HH #define ROAMINGCHANNELDIALOG_HH #include // Forward declarations namespace Ui { class RoamingChannelDialog; } class Config; class RoamingChannel; class RoamingChannelDialog : public QDialog { Q_OBJECT public: RoamingChannelDialog(Config *config, QWidget *parent = nullptr); RoamingChannelDialog(Config *config, RoamingChannel *channel, QWidget *parent = nullptr); ~RoamingChannelDialog(); RoamingChannel *channel(); protected slots: void construct(); void onOverrideTimeSlotToggled(bool override); void onOverrideColorCodeToggled(bool override); private: Ui::RoamingChannelDialog *ui; RoamingChannel *_myChannel; RoamingChannel *_channel; }; #endif // ROAMINGCHANNELDIALOG_HH ================================================ FILE: src/roamingchanneldialog.ui ================================================ RoamingChannelDialog 0 0 400 300 0 0 Dialog Name RX Frequency [MHz] TX Frequency [MHz] Time Slot Color Code Selected 0 0 Selected 0 0 Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() RoamingChannelDialog accept() 248 254 157 274 buttonBox rejected() RoamingChannelDialog reject() 316 260 286 274 ================================================ FILE: src/roamingchannellistview.cc ================================================ #include "roamingchannellistview.hh" #include "ui_roamingchannellistview.h" #include "roamingchanneldialog.hh" #include "configitemwrapper.hh" #include "config.hh" #include "settings.hh" #include RoamingChannelListView::RoamingChannelListView(Config *config, QWidget *parent) : QWidget(parent), _config(config), ui(new Ui::RoamingChannelListView) { Settings settings; ui->setupUi(this); ui->roamingChannelTableView->setModel( new RoamingChannelListWrapper(_config->roamingChannels(), ui->roamingChannelTableView)); connect(ui->addRoamingChannel, SIGNAL(clicked()), this, SLOT(onAddChannel())); connect(ui->remRoamingChannel, SIGNAL(clicked()), this, SLOT(onRemChannel())); connect(ui->roamingChannelTableView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditChannel(unsigned))); connect(ui->roamingChannelListHint, SIGNAL(linkActivated(QString)), this, SLOT(onHideRoamingNote())); if (settings.hideRoamingNote()) ui->roamingChannelListHint->setHidden(true); } RoamingChannelListView::~RoamingChannelListView() { delete ui; } void RoamingChannelListView::onAddChannel() { RoamingChannelDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->roamingChannelTableView->hasSelection()) row = ui->roamingChannelTableView->selection().second+1; _config->roamingChannels()->add(dialog.channel(), row); } void RoamingChannelListView::onEditChannel(unsigned int idx) { RoamingChannelDialog dialog(_config, _config->roamingChannels()->channel(idx)); if (QDialog::Accepted != dialog.exec()) return; dialog.channel(); } void RoamingChannelListView::onRemChannel() { if (! ui->roamingChannelTableView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete roaming channel"), tr("Cannot delete roaming channel: You have to select a channel first.")); return; } // Get selection and ask for deletion QPair rows = ui->roamingChannelTableView->selection(); int rowcount = rows.second - rows.first + 1; if (rows.first==rows.second) { QString name = _config->roamingChannels()->channel(rows.first)->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete roaming channel?"), tr("Delete roaming channel %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete roaming channel?"), tr("Delete %1 roaming channel?").arg(rowcount))) return; } // collect all selected channels // need to collect them first as rows change when deleting QList lists; lists.reserve(rowcount); for (int row=rows.first; row<=rows.second; row++) lists.push_back(_config->roamingChannels()->channel(row)); // remove foreach (RoamingChannel *channel, lists) _config->roamingChannels()->del(channel); } void RoamingChannelListView::onHideRoamingNote() { Settings settings; settings.setHideRoamingNote(true); ui->roamingChannelListHint->setVisible(false); } ================================================ FILE: src/roamingchannellistview.hh ================================================ #ifndef ROAMINGCHANNELLISTVIEW_HH #define ROAMINGCHANNELLISTVIEW_HH #include // Forward declarations namespace Ui { class RoamingChannelListView; } class Config; class RoamingChannelListView : public QWidget { Q_OBJECT public: explicit RoamingChannelListView(Config *config, QWidget *parent = nullptr); ~RoamingChannelListView(); protected slots: void onAddChannel(); void onEditChannel(unsigned int idx); void onRemChannel(); void onHideRoamingNote(); protected: /** Holds a weak reference to the abstract config. */ Config *_config; private: Ui::RoamingChannelListView *ui; }; #endif // ROAMINGCHANNELLISTVIEW_HH ================================================ FILE: src/roamingchannellistview.ui ================================================ RoamingChannelListView 0 0 400 300 Form padding:10px;border: 2px solid black; border-radius: 10px; QFrame::NoFrame <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p><span style=" font-weight:600;">Hint:</span> You do not need to add roaming channel explicitly, just create roaming zones and add channels there. These channels are then added to this list.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Qt::RichText true 0 0 Add Roaming Channel Delete Roaming Channel ConfigObjectTableView QWidget
configobjecttableview.hh
1
================================================ FILE: src/roamingchannelselectiondialog.cc ================================================ #include "roamingchannelselectiondialog.hh" #include "config.hh" #include #include #include MultiRoamingChannelSelectionDialog::MultiRoamingChannelSelectionDialog( Config *config, RoamingChannelRefList *exclude, QWidget *parent) : QDialog(parent), _channels(nullptr) { setWindowTitle(tr("Select roaming channels")); _channels = new QListWidget(); for (int i=0; iroamingChannels()->count(); i++) { auto channel = config->roamingChannels()->channel(i); if (exclude && exclude->has(channel)) continue; auto item = new QListWidgetItem(channel->name()); item->setFlags(Qt::ItemIsUserCheckable|Qt::ItemIsEnabled); item->setData(Qt::UserRole, QVariant::fromValue(channel)); item->setCheckState(Qt::Unchecked); _channels->addItem(item); } auto box = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel); auto layout = new QVBoxLayout(); layout->addWidget(_channels); layout->addWidget(box); setLayout(layout); connect(box, SIGNAL(accepted()), this, SLOT(accept())); connect(box, SIGNAL(rejected()), this, SLOT(reject())); } QList MultiRoamingChannelSelectionDialog::channels() const { QList channels; for (int i=0; i<_channels->count(); i++) { if (Qt::Checked ==_channels->item(i)->checkState()) channels.push_back(_channels->item(i)->data(Qt::UserRole).value()); } return channels; } ================================================ FILE: src/roamingchannelselectiondialog.hh ================================================ #ifndef MULTIROAMINGCHANNELSELECTIONDIALOG_HH #define MULTIROAMINGCHANNELSELECTIONDIALOG_HH #include #include // Forward declarations class Config; class RoamingChannel; class QListWidget; class RoamingChannelRefList; class MultiRoamingChannelSelectionDialog : public QDialog { Q_OBJECT public: MultiRoamingChannelSelectionDialog(Config *config, RoamingChannelRefList *exclude=nullptr, QWidget *parent=nullptr); QList channels() const; protected: QListWidget *_channels; }; #endif // MULTIROAMINGCHANNELSELECTIONDIALOG_HH ================================================ FILE: src/roamingzonedialog.cc ================================================ #include "roamingzonedialog.hh" #include "settings.hh" #include "config.hh" #include "channelselectiondialog.hh" #include "roamingchannelselectiondialog.hh" #include /* ****************************************************************************************** * * RoamingZoneDialog * ****************************************************************************************** */ RoamingZoneDialog::RoamingZoneDialog(Config *config, QWidget *parent) : QDialog(parent), _config(config), _myZone(new RoamingZone(this)), _zone(nullptr) { setWindowTitle(tr("Create Roaming Zone")); construct(); } RoamingZoneDialog::RoamingZoneDialog(Config *config, RoamingZone *zone, QWidget *parent) : QDialog(parent), _config(config), _myZone(new RoamingZone(this)), _zone(zone) { setWindowTitle(tr("Set Roaming Zone")); if (_zone) _myZone->copy(*_zone); construct(); } void RoamingZoneDialog::construct() { setupUi(this); Settings settings; zoneName->setText(_myZone->name()); channelListView->setModel(new RoamingChannelRefListWrapper(_myZone->channels(), channelListView)); extensionView->setObjectName("roamingZoneExtension"); extensionView->setObject(_myZone, _config); connect(addRoam, SIGNAL(clicked(bool)), this, SLOT(onAddRoamingChannel())); connect(addDMR, SIGNAL(clicked(bool)), this, SLOT(onAddDMRChannel())); connect(rem, SIGNAL(clicked(bool)), this, SLOT(onRemChannel())); } void RoamingZoneDialog::onAddDMRChannel() { MultiChannelSelectionDialog dia(_config->channelList(), false, true); if (QDialog::Accepted != dia.exec()) return; QList lst = dia.channel(); foreach (Channel *channel, lst) { if (0 <= _myZone->channels()->indexOf(channel)) continue; if (! channel->is()) continue; RoamingChannel *rch = RoamingChannel::fromDMRChannel(channel->as()); _config->roamingChannels()->add(rch); _myZone->addChannel(rch); } } void RoamingZoneDialog::onAddRoamingChannel() { MultiRoamingChannelSelectionDialog dia(_config, _myZone->channels()); if (QDialog::Accepted != dia.exec()) return; QList lst = dia.channels(); foreach (RoamingChannel *channel, lst) { if (0 <= _myZone->channels()->indexOf(channel)) continue; _myZone->addChannel(channel); } } void RoamingZoneDialog::onRemChannel() { if (! channelListView->hasSelection()) { QMessageBox::information(nullptr, tr("Cannot remove channels."), tr("Cannot remove channels. Select at least one channel first."), QMessageBox::Close, QMessageBox::Close); return; } QPair selection = channelListView->selection(); QList channels; for (int i=selection.first; i<=selection.second; i++) channels.push_back(_myZone->channels()->get(i)->as()); foreach (RoamingChannel *channel, channels) { _myZone->channels()->del(channel); } } RoamingZone * RoamingZoneDialog::zone() { _myZone->setName(zoneName->text().simplified()); RoamingZone *zone = _myZone; if (_zone) { _zone->copy(*_myZone); zone = _zone; } else { _myZone->setParent(nullptr); } return zone; } /* ****************************************************************************************** * * RoamingZoneSelect * ****************************************************************************************** */ RoamingZoneSelect::RoamingZoneSelect(Config *config, QWidget *parent) : QComboBox(parent) { addItem(tr("[None]"), QVariant::fromValue((RoamingZone *)nullptr)); addItem(tr("[Default]"), QVariant::fromValue(DefaultRoamingZone::get())); for (int i=0; iroamingZones()->count(); i++) { auto zone = config->roamingZones()->zone(i); addItem(zone->name(), QVariant::fromValue(zone)); } } void RoamingZoneSelect::setRoamingZone(RoamingZone *z) { for (int i=0; i() == z) { setCurrentIndex(i); break; } } } RoamingZone * RoamingZoneSelect::roamingZone() const { return currentData().value(); } ================================================ FILE: src/roamingzonedialog.hh ================================================ #ifndef ROAMINGZONEDIALOG_HH #define ROAMINGZONEDIALOG_HH #include #include #include "roamingzone.hh" #include "ui_roamingzonedialog.h" class Config; class RoamingZoneDialog : public QDialog, private Ui_RoamingZoneDialog { Q_OBJECT public: explicit RoamingZoneDialog(Config *config, QWidget *parent = nullptr); RoamingZoneDialog(Config *config, RoamingZone *zone, QWidget *parent=nullptr); RoamingZone *zone(); protected slots: void construct(); void onAddRoamingChannel(); void onAddDMRChannel(); void onRemChannel(); protected: Config *_config; RoamingZone *_myZone; RoamingZone *_zone; }; class RoamingZoneSelect: public QComboBox { Q_OBJECT public: RoamingZoneSelect(Config *config, QWidget *parent=nullptr); void setRoamingZone(RoamingZone *z); RoamingZone *roamingZone() const; }; #endif // ROAMINGZONEDIALOG_HH ================================================ FILE: src/roamingzonedialog.ui ================================================ RoamingZoneDialog 0 0 485 413 0 0 Dialog 0 Basic Name: 0 0 Add Roaming Channel Add DMR Channel Alt++ Remove Channel Alt+- Extension Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
ConfigObjectListView QWidget
configobjectlistview.hh
1
buttonBox accepted() RoamingZoneDialog accept() 248 254 157 274 buttonBox rejected() RoamingZoneDialog reject() 316 260 286 274
================================================ FILE: src/roamingzonelistview.cc ================================================ #include "roamingzonelistview.hh" #include "ui_roamingzonelistview.h" #include "settings.hh" #include "config.hh" #include "roamingzonedialog.hh" #include "contactselectiondialog.hh" #include RoamingZoneListView::RoamingZoneListView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::RoamingZoneListView), _config(config) { Settings settings; ui->setupUi(this); ui->listView->setModel(new RoamingListWrapper(_config->roamingZones(), ui->listView)); connect(ui->addRoamingZone, SIGNAL(clicked()), this, SLOT(onAddRoamingZone())); connect(ui->genRoamingZone, SIGNAL(clicked(bool)), this, SLOT(onGenRoamingZone())); connect(ui->remRoamingZone, SIGNAL(clicked()), this, SLOT(onRemRoamingZone())); connect(ui->listView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditRoamingZone(unsigned))); connect(ui->roamingNote, SIGNAL(linkActivated(QString)), this, SLOT(onHideRoamingNote())); if (settings.hideRoamingNote()) ui->roamingNote->setHidden(true); } RoamingZoneListView::~RoamingZoneListView() { delete ui; } void RoamingZoneListView::onAddRoamingZone() { RoamingZoneDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->listView->hasSelection()) row = ui->listView->selection().second+1; _config->roamingZones()->add(dialog.zone(), row); } void RoamingZoneListView::onGenRoamingZone() { MultiGroupCallSelectionDialog contSel(_config->contacts(), false); contSel.setWindowTitle(tr("Generate roaming zone")); contSel.setLabel(tr("Create a roaming zone by collecting all channels with these group calls.")); if (QDialog::Accepted != contSel.exec()) return; QList contacts = contSel.contacts(); RoamingZone *zone = new RoamingZone("Name"); for (int i=0; i<_config->channelList()->count(); i++) { DMRChannel *dch = _config->channelList()->channel(i)->as(); if (nullptr == dch) continue; if (contacts.contains(dch->contact())) { RoamingChannel *rch = RoamingChannel::fromDMRChannel(dch); _config->roamingChannels()->add(rch); zone->addChannel(rch); } } RoamingZoneDialog dialog(_config, zone); if (QDialog::Accepted != dialog.exec()) { zone->deleteLater(); return; } int row=-1; if (ui->listView->hasSelection()) row = ui->listView->selection().second+1; _config->roamingZones()->add(dialog.zone(), row); } void RoamingZoneListView::onRemRoamingZone() { if (! ui->listView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete roaming zone"), tr("Cannot delete roaming zone: You have to select a zone first.")); return; } // Get selection and ask for deletion QPair rows = ui->listView->selection(); int rowcount = rows.second - rows.first + 1; if (rows.first==rows.second) { QString name = _config->roamingZones()->zone(rows.first)->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete roaming zone?"), tr("Delete roaming zone %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete roaming zones?"), tr("Delete %1 roaming zones?").arg(rowcount))) return; } // collect all selected zones // need to collect them first as rows change when deleting QList lists; lists.reserve(rowcount); for (int row=rows.first; row<=rows.second; row++) lists.push_back(_config->roamingZones()->zone(row)); // remove foreach (RoamingZone *zone, lists) _config->roamingZones()->del(zone); } void RoamingZoneListView::onEditRoamingZone(unsigned row) { RoamingZoneDialog dialog(_config, _config->roamingZones()->zone(row)); if (QDialog::Accepted != dialog.exec()) return; dialog.zone(); } void RoamingZoneListView::onHideRoamingNote() { Settings setting; setting.setHideRoamingNote(true); ui->roamingNote->setVisible(false); } ================================================ FILE: src/roamingzonelistview.hh ================================================ #ifndef ROAMINGZONELISTVIEW_HH #define ROAMINGZONELISTVIEW_HH #include class Config; namespace Ui { class RoamingZoneListView; } class RoamingZoneListView : public QWidget { Q_OBJECT public: explicit RoamingZoneListView(Config *_config, QWidget *parent = nullptr); ~RoamingZoneListView(); protected slots: void onAddRoamingZone(); void onGenRoamingZone(); void onRemRoamingZone(); void onEditRoamingZone(unsigned row); void onHideRoamingNote(); private: Ui::RoamingZoneListView *ui; Config *_config; }; #endif // ROAMINGZONELISTVIEW_HH ================================================ FILE: src/roamingzonelistview.ui ================================================ RoamingZoneListView 0 0 497 300 Form padding:10px;border: 2px solid black; border-radius: 10px; <html><head/><body><p><span style=" font-weight:600;">Note:</span> QDMR is a device independent CPS. However, not all radios support Roaming. Hence these settings might be ignored when programming the code-plug to the device. </p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Qt::RichText true 0 0 Add Roaming Zone Alt++ Generate Roaming Zone Delete Roaming Zone Alt+- ConfigObjectListView QWidget
configobjectlistview.hh
1
================================================ FILE: src/rxgrouplistdialog.cc ================================================ #include "rxgrouplistdialog.hh" #include "config.hh" #include "contact.hh" #include "contactselectiondialog.hh" #include "settings.hh" #include #include /* ********************************************************************************************* * * Implementation of RXGroupListDialog * ********************************************************************************************* */ RXGroupListDialog::RXGroupListDialog(Config *config, QWidget *parent) : QDialog(parent), _config(config), _myGroupList(new RXGroupList(this)), _list(nullptr) { setWindowTitle(tr("Create Group List")); construct(); } RXGroupListDialog::RXGroupListDialog(Config *config, RXGroupList *list, QWidget *parent) : QDialog(parent), _config(config), _myGroupList(new RXGroupList(this)), _list(list) { setWindowTitle(tr("Edit Group List")); if (_list) _myGroupList->copy(*_list); construct(); } RXGroupList * RXGroupListDialog::groupList() { _myGroupList->setName(groupListName->text().simplified()); RXGroupList *list = _myGroupList; if (_list) { _list->copy(*_myGroupList); list = _myGroupList; } else { _myGroupList->setParent(nullptr); } // Return modified list. return list; } void RXGroupListDialog::construct() { setupUi(this); Settings settings; connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(addContact, SIGNAL(clicked()), this, SLOT(onAddGroup())); connect(remContact, SIGNAL(clicked()), this, SLOT(onRemGroup())); groupListName->setText(_myGroupList->name()); contactListView->setModel(new GroupListWrapper(_myGroupList, contactListView)); extensionView->setObjectName("groupListExtension"); extensionView->setObject(_myGroupList, _config); } void RXGroupListDialog::onAddGroup() { MultiGroupCallSelectionDialog dialog(_config->contacts(), false, _myGroupList->contacts()); if (QDialog::Accepted != dialog.exec()) return; QList contacts = dialog.contacts(); foreach (DMRContact *contact, contacts) { if (_myGroupList->contacts()->has(contact)) continue; _myGroupList->addContact(contact); } } void RXGroupListDialog::onRemGroup() { if (!contactListView->hasSelection()) { QMessageBox::information(nullptr, tr("Cannot remove group call"), tr("Cannot remove group call: You have to select at least one group call first.")); return; } QPair selection = contactListView->selection(); // collect all selected group lists // need to collect them first as rows change when deleting QList lists; for (int row=selection.first; row<=selection.second; row++) lists.push_back(_myGroupList->contact(row)); // remove list foreach (DMRContact *cont, lists) _myGroupList->contacts()->del(cont); } /* ********************************************************************************************* * * Implementation of RXGroupListBox * ********************************************************************************************* */ RXGroupListBox::RXGroupListBox(RXGroupLists *groups, QWidget *parent) : QComboBox(parent) { populateRXGroupListBox(this, groups); } void RXGroupListBox::setGroupList(RXGroupList *lst) { for (int i=0; i() == lst) { setCurrentIndex(i); break; } } } RXGroupList * RXGroupListBox::groupList() const { return currentData().value(); } void populateRXGroupListBox(QComboBox *box, RXGroupLists *groups, RXGroupList *list) { box->addItem(QObject::tr("[None]"), QVariant::fromValue(nullptr)); for (int i=0; icount(); i++) { box->addItem(groups->list(i)->name(), QVariant::fromValue(groups->list(i))); if (groups->list(i) == list) box->setCurrentIndex(i+1); } } ================================================ FILE: src/rxgrouplistdialog.hh ================================================ #ifndef RXGROUPLISTDIALOG_HH #define RXGROUPLISTDIALOG_HH #include #include #include #include #include "rxgrouplist.hh" #include "ui_rxgrouplistdialog.h" class Config; class RXGroupListDialog: public QDialog, private Ui::RXGroupListDialog { Q_OBJECT public: explicit RXGroupListDialog(Config *config, QWidget *parent=nullptr); RXGroupListDialog(Config *config, RXGroupList *list, QWidget *parent=nullptr); RXGroupList *groupList(); protected slots: void onAddGroup(); void onRemGroup(); protected: void construct(); protected: Config *_config; RXGroupList *_myGroupList; RXGroupList *_list; }; class RXGroupListBox: public QComboBox { Q_OBJECT public: RXGroupListBox(RXGroupLists *groups, QWidget *parent=0); void setGroupList(RXGroupList *lst); RXGroupList *groupList() const; }; void populateRXGroupListBox(QComboBox *box, RXGroupLists *groups, RXGroupList *list=nullptr); #endif // RXGROUPLISTDIALOG_HH ================================================ FILE: src/rxgrouplistdialog.ui ================================================ RXGroupListDialog 0 0 425 357 0 0 0 Basic Name groupListName 0 0 Add Contact Alt++ Remove Contact Alt+- Extensions Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
ConfigObjectListView QWidget
configobjectlistview.hh
1
buttonBox rejected() RXGroupListDialog reject() 316 260 286 274 buttonBox accepted() RXGroupListDialog accept() 248 254 157 274
================================================ FILE: src/satellitedatabasedialog.cc ================================================ #include "satellitedatabasedialog.hh" #include "ui_satellitedatabasedialog.h" #include "satellitedatabase.hh" #include "satelliteselectiondialog.hh" #include "transponderfrequencydelegate.hh" #include "settings.hh" #include "selectivecallbox.hh" #include "satellitetransponderdialog.hh" SatelliteDatabaseDialog::SatelliteDatabaseDialog(SatelliteDatabase *db, QWidget *parent) : QDialog(parent), ui(new Ui::SatelliteDatabaseDialog), _database(db) { ui->setupUi(this); setWindowIcon(QIcon::fromTheme("edit-satellites")); connect(ui->addButton, &QPushButton::clicked, this, &SatelliteDatabaseDialog::onAddSatellite); connect(ui->delButton, &QPushButton::clicked, this, &SatelliteDatabaseDialog::onDeleteSatellite); ui->satellitesView->setModel(_database); connect(ui->satellitesView, &QTableView::doubleClicked, this, &SatelliteDatabaseDialog::onEditTransponder); // FM downlink //ui->satellitesView->setItemDelegateForColumn(2, new TransponderFrequencyDelegate(false, Transponder::Mode::FM)); // FM uplink //ui->satellitesView->setItemDelegateForColumn(3, new TransponderFrequencyDelegate(true, Transponder::Mode::FM)); // FM downlink sub tone //ui->satellitesView->setItemDelegateForColumn(4, new SelectiveCallDelegate()); // FM uplink sub tone //ui->satellitesView->setItemDelegateForColumn(5, new SelectiveCallDelegate()); // APRS downlink //ui->satellitesView->setItemDelegateForColumn(6, new TransponderFrequencyDelegate(false, Transponder::Mode::APRS)); // APRS uplink //ui->satellitesView->setItemDelegateForColumn(7, new TransponderFrequencyDelegate(true, Transponder::Mode::APRS)); // APRS downlink sub tone //ui->satellitesView->setItemDelegateForColumn(8, new SelectiveCallDelegate()); // APRS uplink sub tone //ui->satellitesView->setItemDelegateForColumn(9, new SelectiveCallDelegate()); // Beacon //ui->satellitesView->setItemDelegateForColumn(10, new TransponderFrequencyDelegate(false, Transponder::Mode::CW)); this->restoreGeometry(Settings().headerState(objectName())); ui->satellitesView->horizontalHeader()->restoreState( Settings().headerState(ui->satellitesView->objectName())); } SatelliteDatabaseDialog::~SatelliteDatabaseDialog() { Settings settings; settings.setHeaderState(this->objectName(), this->saveGeometry()); settings.setHeaderState(ui->satellitesView->objectName(), ui->satellitesView->horizontalHeader()->saveState()); delete ui; } void SatelliteDatabaseDialog::onEditTransponder(const QModelIndex &idx) { if ((! idx.isValid()) || (idx.row() >= (int)_database->count())) return; SatelliteTransponderDialog dialog(_database->getAt(idx.row()), _database->transponders()); if (QDialog::Accepted != dialog.exec()) return; _database->setAt(dialog.satellite(), idx.row()); } void SatelliteDatabaseDialog::onAddSatellite() { SatelliteSelectionDialog dialog(&(_database->orbitalElements())); if (QDialog::Accepted != dialog.exec()) return; _database->add(_database->orbitalElements().getById(dialog.satellite())); } void SatelliteDatabaseDialog::onDeleteSatellite() { QModelIndexList selected = ui->satellitesView->selectionModel()->selectedRows(); if (selected.isEmpty()) return; _database->removeRow(selected.back().row()); } ================================================ FILE: src/satellitedatabasedialog.hh ================================================ #ifndef SATELLITEDATABASEDIALOG_HH #define SATELLITEDATABASEDIALOG_HH #include namespace Ui { class SatelliteDatabaseDialog; } class SatelliteDatabase; class SatelliteDatabaseDialog : public QDialog { Q_OBJECT public: explicit SatelliteDatabaseDialog(SatelliteDatabase *db, QWidget *parent = nullptr); ~SatelliteDatabaseDialog(); private slots: void onEditTransponder(const QModelIndex &idx); void onAddSatellite(); void onDeleteSatellite(); private: Ui::SatelliteDatabaseDialog *ui; SatelliteDatabase *_database; }; #endif // SATELLITEDATABASEDIALOG_HH ================================================ FILE: src/satellitedatabasedialog.ui ================================================ SatelliteDatabaseDialog 0 0 400 300 0 0 Edit satellite database QAbstractItemView::EditTrigger::NoEditTriggers QAbstractItemView::SelectionMode::SingleSelection QAbstractItemView::SelectionBehavior::SelectRows false Add Delete Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Save buttonBox accepted() SatelliteDatabaseDialog accept() 248 254 157 274 buttonBox rejected() SatelliteDatabaseDialog reject() 316 260 286 274 ================================================ FILE: src/satelliteselectiondialog.cc ================================================ #include "satelliteselectiondialog.hh" #include "ui_satelliteselectiondialog.h" #include "orbitalelementsdatabase.hh" #include "logger.hh" #include "settings.hh" #include "searchpopup.hh" SatelliteSelectionDialog::SatelliteSelectionDialog(OrbitalElementsDatabase *db, QWidget *parent) : QDialog(parent), ui(new Ui::SatelliteSelectionDialog), _database(db) { ui->setupUi(this); setWindowIcon(QIcon::fromTheme("edit-satellites")); ui->satellites->setModel(_database); ui->satellites->hideColumn(2); SearchPopup::attach(ui->satellites); this->restoreGeometry(Settings().headerState(objectName())); } SatelliteSelectionDialog::~SatelliteSelectionDialog() { Settings().setHeaderState(this->objectName(), this->saveGeometry()); delete ui; } unsigned int SatelliteSelectionDialog::satellite() const { QModelIndexList selection = ui->satellites->selectionModel()->selectedRows(); if (0 == selection.count()) return 0; return _database->getAt(selection.back().row()).id(); } ================================================ FILE: src/satelliteselectiondialog.hh ================================================ #ifndef SATELLITESELECTIONDIALOG_HH #define SATELLITESELECTIONDIALOG_HH #include namespace Ui { class SatelliteSelectionDialog; } class OrbitalElementsDatabase; class SatelliteSelectionDialog : public QDialog { Q_OBJECT public: explicit SatelliteSelectionDialog(OrbitalElementsDatabase *db, QWidget *parent = nullptr); ~SatelliteSelectionDialog(); unsigned int satellite() const; private: Ui::SatelliteSelectionDialog *ui; OrbitalElementsDatabase *_database; }; #endif // SATELLITESELECTIONDIALOG_HH ================================================ FILE: src/satelliteselectiondialog.ui ================================================ SatelliteSelectionDialog 0 0 400 300 0 0 Select a satellite Select a satellite QAbstractItemView::SelectRows false false Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok buttonBox accepted() SatelliteSelectionDialog accept() 248 254 157 274 buttonBox rejected() SatelliteSelectionDialog reject() 316 260 286 274 ================================================ FILE: src/satellitetransponderdialog.cc ================================================ #include "satellitetransponderdialog.hh" #include "ui_satellitetransponderdialog.h" SatelliteTransponderDialog::SatelliteTransponderDialog(const Satellite &sat, TransponderDatabase &transponder, QWidget *parent) : QDialog(parent) , ui(new Ui::SatelliteTransponderDialog), _satellite(sat) { ui->setupUi(this); setWindowIcon(QIcon::fromTheme("edit-satellites")); ui->fmDownlinkFrequency->populate(_satellite.id(), false, Transponder::Mode::FM, transponder); ui->fmDownlinkFrequency->setFrequency(_satellite.fmDownlink()); ui->fmDownlinkTone->setSelectiveCall(_satellite.fmDownlinkTone()); ui->fmUplinkFrequency->populate(_satellite.id(), true, Transponder::Mode::FM, transponder); ui->fmUplinkFrequency->setFrequency(_satellite.fmUplink()); ui->fmUplinkTone->setSelectiveCall(_satellite.fmUplinkTone()); ui->aprsDownlinkFrequency->populate(_satellite.id(), false, Transponder::Mode::APRS, transponder); ui->aprsDownlinkFrequency->setFrequency(_satellite.aprsDownlink()); ui->aprsDownlinkTone->setSelectiveCall(sat.aprsDownlinkTone()); ui->aprsUplinkFrequency->populate(_satellite.id(), true, Transponder::Mode::APRS, transponder); ui->aprsUplinkFrequency->setFrequency(_satellite.aprsUplink()); ui->aprsUplinkTone->setSelectiveCall(_satellite.aprsUplinkTone()); ui->beaconFrequency->populate(_satellite.id(), false, Transponder::Mode::CW, transponder); ui->beaconFrequency->setFrequency(_satellite.beacon()); } SatelliteTransponderDialog::~SatelliteTransponderDialog() { delete ui; } const Satellite & SatelliteTransponderDialog::satellite() const { return _satellite; } void SatelliteTransponderDialog::accept() { _satellite.setFMUplink(ui->fmUplinkFrequency->frequency()); _satellite.setFMUplinkTone(ui->fmUplinkTone->selectiveCall()); _satellite.setFMDownlink(ui->fmDownlinkFrequency->frequency()); _satellite.setFMDownlinkTone(ui->fmDownlinkTone->selectiveCall()); _satellite.setAPRSUplink(ui->aprsUplinkFrequency->frequency()); _satellite.setAPRSUplinkTone(ui->aprsUplinkTone->selectiveCall()); _satellite.setAPRSDownlink(ui->aprsDownlinkFrequency->frequency()); _satellite.setAPRSDownlinkTone(ui->aprsDownlinkTone->selectiveCall()); _satellite.setBeacon(ui->beaconFrequency->frequency()); QDialog::accept(); } ================================================ FILE: src/satellitetransponderdialog.hh ================================================ #ifndef SATELLITETRANSPONDERDIALOG_HH #define SATELLITETRANSPONDERDIALOG_HH #include #include "satellitedatabase.hh" namespace Ui { class SatelliteTransponderDialog; } class SatelliteTransponderDialog : public QDialog { Q_OBJECT public: SatelliteTransponderDialog(const Satellite &sat, TransponderDatabase &transponder, QWidget *parent = nullptr); ~SatelliteTransponderDialog(); const Satellite &satellite() const; public slots: void accept() override; private: Ui::SatelliteTransponderDialog *ui; Satellite _satellite; }; #endif // SATELLITETRANSPONDERDIALOG_HH ================================================ FILE: src/satellitetransponderdialog.ui ================================================ SatelliteTransponderDialog 0 0 496 495 Edit Satellite Transponder 0 0 FM Voice Transponder Uplink Frequency fmUplinkFrequency Uplink Tone fmUplinkTone Downlink Frequency fmDownlinkFrequency Downlink Tone fmDownlinkTone 0 0 APRS Transponder 0 0 Uplink Frequency aprsUplinkFrequency 0 0 Uplink Tone aprsUplinkTone 0 0 Downlink Frequency aprsDownlinkFrequency 0 0 Downlink Tone aprsDownlinkTone 0 0 Beacon 0 0 Beacon Frequency beaconFrequency Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok SelectiveCallBox QWidget
selectivecallbox.hh
1
TransponderFrequencyEditor QComboBox
transponderfrequencydelegate.hh
buttonBox accepted() SatelliteTransponderDialog accept() 257 631 157 274 buttonBox rejected() SatelliteTransponderDialog reject() 325 631 286 274
================================================ FILE: src/scanlistdialog.cc ================================================ #include "scanlistdialog.hh" #include "config.hh" #include "channelselectiondialog.hh" #include "settings.hh" /* ********************************************************************************************* * * Implementation of ScanListDialog * ********************************************************************************************* */ ScanListDialog::ScanListDialog(Config *config, ScanList *scanlist, QWidget *parent) : QDialog(parent), _config(config), _myScanList(new ScanList(this)), _scanlist(scanlist) { setWindowTitle(tr("Edit Scan List")); if (_scanlist) _myScanList->copy(*_scanlist); construct(); } ScanListDialog::ScanListDialog(Config *config, QWidget *parent) : QDialog(parent), _config(config), _myScanList(new ScanList(this)), _scanlist(nullptr) { setWindowTitle(tr("Create Scan List")); construct(); } void ScanListDialog::construct() { setupUi(this); Settings settings; priorityChannel1->addItem(tr("[None]"), QVariant::fromValue(nullptr)); priorityChannel1->addItem(tr("[Selected]"), QVariant::fromValue(SelectedChannel::get())); priorityChannel2->addItem(tr("[None]"), QVariant::fromValue(nullptr)); priorityChannel2->addItem(tr("[Selected]"), QVariant::fromValue(SelectedChannel::get())); transmitChannel->addItem(tr("[Last]"), QVariant::fromValue(nullptr)); transmitChannel->addItem(tr("[Selected]"), QVariant::fromValue(SelectedChannel::get())); for (int i=0; i<_config->channelList()->count(); i++) { Channel *channel = _config->channelList()->channel(i); priorityChannel1->addItem(channel->name(), QVariant::fromValue(channel)); priorityChannel2->addItem(channel->name(), QVariant::fromValue(channel)); transmitChannel->addItem(channel->name(), QVariant::fromValue(channel)); } scanListName->setText(_myScanList->name()); // set priority channel if (_myScanList->primaryChannel()) priorityChannel1->setCurrentIndex(_config->channelList()->indexOf(_myScanList->primaryChannel())+2); // set secondary priority channel if (_myScanList->secondaryChannel()) priorityChannel2->setCurrentIndex(_config->channelList()->indexOf(_myScanList->secondaryChannel())+2); if (_myScanList->revertChannel()) transmitChannel->setCurrentIndex(_config->channelList()->indexOf(_myScanList->revertChannel())+2); channelListView->setModel(new ChannelRefListWrapper(_myScanList->channels(), channelListView)); extensionView->setObjectName("scanListExtension"); extensionView->setObject(_myScanList, _config); connect(addChannel, SIGNAL(clicked()), this, SLOT(onAddChannel())); connect(remChannel, SIGNAL(clicked()), this, SLOT(onRemChannel())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); } void ScanListDialog::onAddChannel() { MultiChannelSelectionDialog dia(_config->channelList(), true); if (QDialog::Accepted != dia.exec()) return; QList channels = dia.channel(); foreach (Channel *channel, channels) { if (0 <= _myScanList->channels()->indexOf(channel)) continue; _myScanList->channels()->add(channel); } } void ScanListDialog::onRemChannel() { if (! channelListView->hasSelection()) return; QPair selection = channelListView->selection(); QList channels; for (int i=selection.first; i<=selection.second; i++) channels.push_back(_myScanList->channels()->get(i)->as()); foreach (Channel *channel, channels) _myScanList->channels()->del(channel); } ScanList * ScanListDialog::scanlist() { _myScanList->setName(scanListName->text().simplified()); // Set priority and transmit channels _myScanList->setPrimaryChannel(priorityChannel1->currentData(Qt::UserRole).value()); _myScanList->setSecondaryChannel(priorityChannel2->currentData(Qt::UserRole).value()); _myScanList->setRevertChannel(transmitChannel->currentData(Qt::UserRole).value()); ScanList *scanlist = _myScanList; if (_scanlist) { _scanlist->copy(*_myScanList); scanlist = _scanlist; } else { _myScanList->setParent(nullptr); } return scanlist; } ================================================ FILE: src/scanlistdialog.hh ================================================ #ifndef SCANLISTDIALOG_HH #define SCANLISTDIALOG_HH #include #include "ui_scanlistdialog.h" class Config; class ScanList; class ScanListDialog: public QDialog, private Ui::ScanListDialog { Q_OBJECT public: ScanListDialog(Config *config, QWidget *parent=nullptr); ScanListDialog(Config *config, ScanList *list, QWidget *parent=nullptr); ScanList *scanlist(); protected slots: void onAddChannel(); void onRemChannel(); protected: void construct(); protected: Config *_config; ScanList *_myScanList; ScanList *_scanlist; }; #endif // SCANLISTDIALOG_HH ================================================ FILE: src/scanlistdialog.ui ================================================ ScanListDialog 0 0 437 536 0 0 Edit Scan List 0 Basic Name Primary Channel (50%) 0 0 Secondary Channel (25%) 0 0 Transmit Channel 0 0 0 0 Add Channel Alt++ Remove Channel Alt+- Extensions Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
ConfigObjectListView QWidget
configobjectlistview.hh
1
buttonBox accepted() ScanListDialog accept() 248 254 157 274 buttonBox rejected() ScanListDialog reject() 316 260 286 274
================================================ FILE: src/scanlistsview.cc ================================================ #include "scanlistsview.hh" #include "ui_scanlistsview.h" #include "scanlistdialog.hh" #include ScanListsView::ScanListsView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::ScanListsView), _config(config) { ui->setupUi(this); ui->listView->setModel(new ScanListsWrapper(_config->scanlists(), ui->listView)); connect(ui->addScanList, SIGNAL(clicked()), this, SLOT(onAddScanList())); connect(ui->remScanList, SIGNAL(clicked()), this, SLOT(onRemScanList())); connect(ui->listView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditScanList(unsigned))); } ScanListsView::~ScanListsView() { delete ui; } void ScanListsView::onAddScanList() { ScanListDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->listView->hasSelection()) row = ui->listView->selection().second+1; _config->scanlists()->add(dialog.scanlist(), row); } void ScanListsView::onRemScanList() { if (! ui->listView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete scanlist"), tr("Cannot delete scanlist: You have to select a scanlist first.")); return; } // Get selection and ask for deletion QPair rows = ui->listView->selection(); int rowcount = rows.second-rows.first+1; if (rows.first == rows.second) { QString name = _config->scanlists()->scanlist(rows.first)->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete scan list?"), tr("Delete scan list %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete scan lists?"), tr("Delete %1 scan lists?").arg(rowcount))) return; } // collect all selected scan lists // need to collect them first as rows change when deleting QList lists; lists.reserve(rowcount); for (int row=rows.first; row<=rows.second; row++) lists.push_back(_config->scanlists()->scanlist(row)); // remove foreach (ScanList *list, lists) _config->scanlists()->del(list); } void ScanListsView::onEditScanList(unsigned row) { ScanListDialog dialog(_config, _config->scanlists()->scanlist(row)); if (QDialog::Accepted != dialog.exec()) return; dialog.scanlist(); } ================================================ FILE: src/scanlistsview.hh ================================================ #ifndef SCANLISTSVIEW_HH #define SCANLISTSVIEW_HH #include class Config; namespace Ui { class ScanListsView; } class ScanListsView : public QWidget { Q_OBJECT public: explicit ScanListsView(Config *config, QWidget *parent = nullptr); ~ScanListsView(); protected slots: void onAddScanList(); void onRemScanList(); void onEditScanList(unsigned row); private: Ui::ScanListsView *ui; Config *_config; }; #endif // SCANLISTSVIEW_HH ================================================ FILE: src/scanlistsview.ui ================================================ ScanListsView 0 0 400 300 Form 0 0 Add Scan List Alt++ Delete Scan List Alt+- ConfigObjectListView QWidget
configobjectlistview.hh
1
================================================ FILE: src/searchpopup.cc ================================================ #include "searchpopup.hh" #include #include #include #include #include #include "logger.hh" SearchPopup::SearchPopup(QAbstractItemView *parent) : QFrame(parent) { setWindowFlags(Qt::Popup | Qt::FramelessWindowHint | Qt::NoDropShadowWindowHint); setFrameStyle(QFrame::Panel); setWindowModality(Qt::ApplicationModal); QAction *search = new QAction(this); search->setShortcut(QKeySequence(tr("Ctrl+F"))); parent->addAction(search); connect(search, SIGNAL(triggered(bool)), this, SLOT(showPopup())); _search = new QLineEdit(); connect(_search, SIGNAL(textChanged(QString)), this, SLOT(onSearchChanged(QString))); QAction *hide = new QAction(_search); hide->setShortcut(QKeySequence(Qt::Key_Escape)); _search->addAction(hide, QLineEdit::TrailingPosition); connect(hide, SIGNAL(triggered(bool)), this, SLOT(hide())); _label = new QLabel(); QToolButton *up = new QToolButton(this); up->setIcon(QIcon::fromTheme("edit-move-up")); connect(up, SIGNAL(clicked(bool)), this, SLOT(onPrevious())); QToolButton *down = new QToolButton(this); down->setIcon(QIcon::fromTheme("edit-move-down")); connect(down, SIGNAL(clicked(bool)), this, SLOT(onNext())); QToolButton *close = new QToolButton(this); close->setIcon(QIcon::fromTheme("application-exit")); connect(close, SIGNAL(clicked(bool)), this, SLOT(hide())); QHBoxLayout *layout = new QHBoxLayout(); layout->addWidget(_search); layout->addWidget(_label); layout->addWidget(up); layout->addWidget(down); layout->addWidget(close); setLayout(layout); move(parent->mapToGlobal(QPoint(parent->width()-width()-5, parent->height()-height()+5))); this->hide(); } void SearchPopup::showPopup() { show(); auto itemView = qobject_cast(parent()); move(itemView->mapToGlobal(QPoint(itemView->width()-width()-5, itemView->height()-height()+5))); _search->clear(); _search->setFocus(); } void SearchPopup::onSearchChanged(const QString &text) { QAbstractItemView *itemView = qobject_cast(parent()); QAbstractItemModel *model = itemView->model(); if (text.isEmpty()) { _currentMatch = 0; _matches.clear(); itemView->selectionModel()->clear(); _label->setText(""); return; } _currentMatch = 0; itemView->selectionModel()->clear(); _matches.clear(); for (int i=0; icolumnCount(); i++) _matches.append(model->match(model->index(0,i), Qt::DisplayRole, text, -1, Qt::MatchContains|Qt::MatchWrap)); std::sort( _matches.begin(), _matches.end(), [](const QModelIndex &a, const QModelIndex &b) { if (a.row() < b.row()) return true; if (a.row() > b.row()) return false; return a.column() < b.column(); }); if (_matches.count()) { _label->setText(tr("%1/%2").arg(_currentMatch+1).arg(_matches.count())); itemView->setCurrentIndex(_matches.at(_currentMatch)); } else { _label->setText(""); } } void SearchPopup::onNext() { if (0 == _matches.count()) return; QAbstractItemView *itemView = qobject_cast(parent()); if ((++_currentMatch) >= _matches.count()) _currentMatch = 0; _label->setText(tr("%1/%2").arg(_currentMatch+1).arg(_matches.count())); itemView->setCurrentIndex(_matches.at(_currentMatch)); } void SearchPopup::onPrevious() { if (0 == _matches.count()) return; QAbstractItemView *itemView = qobject_cast(parent()); if (0 == _currentMatch) _currentMatch = _matches.count()-1; _label->setText(QString("%1/%2").arg(_currentMatch+1).arg(_matches.count())); itemView->setCurrentIndex(_matches.at(_currentMatch)); } void SearchPopup::attach(QAbstractItemView *itemview) { if (nullptr == itemview) return; new SearchPopup(itemview); } ================================================ FILE: src/searchpopup.hh ================================================ #ifndef SEARCHPOPUP_HH #define SEARCHPOPUP_HH #include #include class QLabel; class SearchPopup : public QFrame { Q_OBJECT protected: explicit SearchPopup(QAbstractItemView *parent); public: static void attach(QAbstractItemView *itemview); public slots: void showPopup(); protected slots: void onSearchChanged(const QString &text); void onNext(); void onPrevious(); protected: QLineEdit *_search; QLabel *_label; int _currentMatch; QModelIndexList _matches; }; #endif // SEARCHPOPUP_HH ================================================ FILE: src/selectivecallbox.cc ================================================ #include "selectivecallbox.hh" #include #include #include #include /* ********************************************************************************************* * * SelectiveCallBox * ********************************************************************************************* */ SelectiveCallBox::SelectiveCallBox(QWidget *parent) : QWidget{parent}, _typeSelection(nullptr), _stack(nullptr), _ctcss(nullptr), _dcs(nullptr), _inverted(nullptr) { _typeSelection = new QComboBox(); _typeSelection->addItem(tr("None")); _typeSelection->addItem(tr("CTCSS")); _typeSelection->addItem(tr("DCS")); _stack = new QStackedWidget(); _stack->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); _stack->setContentsMargins(0,0,0,0); auto emptyLayout = new QHBoxLayout(); emptyLayout->setContentsMargins(0,0,0,0); auto emptyLabel = new QLabel("None"); emptyLabel->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); emptyLayout->addWidget(emptyLabel); auto emptyWidget = new QWidget(); emptyWidget->setLayout(emptyLayout); emptyWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); emptyWidget->setContentsMargins(0,0,0,0); _stack->addWidget(emptyWidget); // CTCSS settings _ctcss = new QComboBox(); _ctcss->setEditable(true); auto ctcssLayout = new QHBoxLayout(); ctcssLayout->setContentsMargins(0,0,0,0); ctcssLayout->addWidget(_ctcss); ctcssLayout->addWidget(new QLabel(tr("Hz"))); auto ctcssWidget = new QWidget(); ctcssWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); ctcssWidget->setContentsMargins(0,0,0,0); ctcssWidget->setLayout(ctcssLayout); _stack->addWidget(ctcssWidget); // DCS settings _dcs = new QComboBox(); _dcs->setEditable(true); _inverted = new QCheckBox(tr("Inverted")); auto dscLayout = new QHBoxLayout(); dscLayout->setContentsMargins(0,0,0,0); dscLayout->addWidget(_dcs); dscLayout->addWidget(_inverted); auto dcsWidget = new QWidget(); dcsWidget->setLayout(dscLayout); dcsWidget->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); dcsWidget->setContentsMargins(0,0,0,0); _stack->addWidget(dcsWidget); connect(_typeSelection, &QComboBox::currentTextChanged, [=]() { _stack->setCurrentIndex(_typeSelection->currentIndex()); }); foreach (const SelectiveCall &call, SelectiveCall::standard()) { if (call.isInvalid()) continue; if (call.isCTCSS()) _ctcss->addItem(QString("%1.%2").arg(call.mHz()/1000).arg((call.mHz()/100)%10), QVariant::fromValue(call)); else if (call.isDCS() && (! call.isInverted())) _dcs->addItem(QString("%1").arg(call.binCode(), 3, 8, QChar('0')), QVariant(call.binCode())); } auto layout = new QHBoxLayout(); layout->addWidget(_typeSelection,0); layout->addWidget(_stack, 1); layout->setContentsMargins(0,0,0,0); setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); setContentsMargins(0,0,0,0); setLayout(layout); } void SelectiveCallBox::setSelectiveCall(const SelectiveCall &call) { if (call.isInvalid()) { _typeSelection->setCurrentIndex(0); } else if (call.isCTCSS()) { _typeSelection->setCurrentIndex(1); int idx = _ctcss->findData(QVariant::fromValue(call)); if (idx >= 0) _ctcss->setCurrentIndex(idx); else _ctcss->setEditText(call.format()); } else if (call.isDCS()) { _typeSelection->setCurrentIndex(2); int idx = _dcs->findData(QVariant(call.binCode())); if (idx >= 0) _dcs->setCurrentIndex(idx); else _dcs->setEditText(QString("%1").arg(call.binCode(), 3, 8, QChar('0'))); _inverted->setChecked(call.isInverted()); } } SelectiveCall SelectiveCallBox::selectiveCall() const { if (0 == _typeSelection->currentIndex()) { return SelectiveCall(); } else if (1 == _typeSelection->currentIndex()) { int idx = _ctcss->currentIndex(); if (0 >= idx) return _ctcss->currentData().value(); return SelectiveCall::parseCTCSS(_ctcss->currentText()); } else if (2 == _typeSelection->currentIndex()) { int idx = _dcs->currentIndex(); if (0 >= idx) return SelectiveCall(_dcs->currentData().toUInt(), _inverted->isChecked()); return SelectiveCall(_dcs->currentText().toUInt(), _inverted->isChecked()); } return SelectiveCall(); } /* ********************************************************************************************* * * SelectiveCallDelegate * ********************************************************************************************* */ SelectiveCallDelegate::SelectiveCallDelegate(QObject *parent) : QStyledItemDelegate(parent) { // pass... } QWidget * SelectiveCallDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); Q_UNUSED(index); auto seditor = new SelectiveCallBox(parent); seditor->setSelectiveCall(index.data(Qt::EditRole).value()); return seditor; } void SelectiveCallDelegate::setEditorData(QWidget *editor, const QModelIndex index) { auto seditor = qobject_cast(editor); seditor->setSelectiveCall(index.data(Qt::EditRole).value()); } void SelectiveCallDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { auto seditor = qobject_cast(editor); model->setData(index, QVariant::fromValue(seditor->selectiveCall())); } ================================================ FILE: src/selectivecallbox.hh ================================================ #ifndef SELECTIVECALLBOX_H #define SELECTIVECALLBOX_H #include #include "signaling.hh" #include #include #include #include class SelectiveCallBox : public QWidget { Q_OBJECT public: explicit SelectiveCallBox(QWidget *parent = nullptr); void setSelectiveCall(const SelectiveCall &call); SelectiveCall selectiveCall() const; signals: void selected(const SelectiveCall &call); private: QComboBox *_typeSelection; QStackedWidget *_stack; QComboBox *_ctcss; QComboBox *_dcs; QCheckBox *_inverted; }; class SelectiveCallDelegate: public QStyledItemDelegate { Q_OBJECT public: explicit SelectiveCallDelegate(QObject *parent=nullptr); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex index); void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; }; #endif // SELECTIVECALLBOX_H ================================================ FILE: src/settings.cc ================================================ #include "settings.hh" #include "logger.hh" #include "config.h" #include "utils.hh" #include #include /* ********************************************************************************************* * * Implementation of SettingsDialog * ********************************************************************************************* */ Settings::Settings(QObject *parent) : QSettings(parent) { // pass... } QDateTime Settings::lastRepeaterUpdate() const { if (! contains("lastRepeaterUpdate")) return QDateTime(); return value("lastRepeaterUpdate").toDateTime(); } void Settings::repeaterUpdated() { setValue("lastRepeaterUpdate", QDateTime::currentDateTime()); } bool Settings::repeaterUpdateNeeded(unsigned period) const { QDateTime last = lastRepeaterUpdate(); if (! last.isValid()) return true; QDateTime now = QDateTime::currentDateTime(); return last.daysTo(now) >= period; } bool Settings::queryPosition() const { return value("queryPosition", false).toBool(); } void Settings::setQueryPosition(bool enable) { setValue("queryPosition", enable); } QString Settings::locator() const { return value("locator","").toString(); } void Settings::setLocator(const QString &locator) { setValue("locator", locator); } QGeoCoordinate Settings::position() const { return loc2deg(locator()); } int Settings::repeaterSearchRadius() const { return value("repeaterSource/repeaterSearchRadius", 50).toInt(); } void Settings::setRepeaterSearchRadius(int radius) { radius = std::min(150, std::max(20, radius)); setValue("repeaterSource/repeaterSearchRadius", radius); } bool Settings::repeaterBookSourceEnabled() const { return value("repeaterSource/repeaterBook", true).toBool(); } void Settings::enableRepeaterBookSource(bool enabled) { return setValue("repeaterSource/repeaterBook", enabled); } bool Settings::repeaterMapSourceEnabled() const { return value("repeaterSource/repeaterMap", true).toBool(); } void Settings::enableRepeaterMapSource(bool enabled) { return setValue("repeaterSource/repeaterMap", enabled); } QByteArray Settings::repeaterMapAPIToken() const { return value("repeaterSource/repeaterMapAPIToken", QByteArray()).toByteArray(); } void Settings::setRepeaterMapAPIToken(const QByteArray &token) { setValue("repeaterSource/repeaterMapAPIToken", token); } bool Settings::hearhamSourceEnabled() const { return value("repeaterSource/hearham", false).toBool(); } void Settings::enableHearhamSource(bool enabled) { return setValue("repeaterSource/hearham", enabled); } bool Settings::radioIdRepeaterSourceEnabled() const { return value("repeaterSource/radioId", true).toBool(); } void Settings::enableRadioIdRepeaterSource(bool enabled) { return setValue("repeaterSource/radioId", enabled); } RepeaterBookSource::Region Settings::repeaterBookRegion() const { return value("repeaterBookRegion", RepeaterBookSource::Region::World).value(); } void Settings::setRepeaterBookRegion(RepeaterBookSource::Region region) { setValue("repeaterBookRegion", region); } bool Settings::disableAutoDetect() const { return value("disableAutoDetect", false).toBool(); } void Settings::setDisableAutoDetect(bool disable) { setValue("disableAutoDetect", disable); } bool Settings::updateCodeplug() const { return value("updateCodeplug", true).toBool(); } void Settings::setUpdateCodeplug(bool update) { setValue("updateCodeplug", update); } bool Settings::updateDeviceClock() const { return value("updateDeviceClock", false).toBool(); } void Settings::setUpdateDeviceClock(bool update) { setValue("updateDeviceClock", update); } bool Settings::autoEnableGPS() const { return value("autoEnableGPS", false).toBool(); } void Settings::setAutoEnableGPS(bool update) { setValue("autoEnableGPS", update); } bool Settings::autoEnableRoaming() const { return value("autoEnableRoaming", false).toBool(); } void Settings::setAutoEnableRoaming(bool update) { setValue("autoEnableRoaming", update); } QDir Settings::lastDirectory() const { return QDir(value("lastDir", QStandardPaths::standardLocations(QStandardPaths::HomeLocation).first()).toString()); } void Settings::setLastDirectoryDir(const QDir &dir) { setValue("lastDir", dir.absolutePath()); } Codeplug::Flags Settings::codePlugFlags() const { Codeplug::Flags flags; flags.setUpdateDeviceClock(updateDeviceClock()); flags.setUpdateCodeplug(updateCodeplug()); flags.setAutoEnableGPS(autoEnableGPS()); flags.setAutoEnableRoaming(autoEnableRoaming()); return flags; } bool Settings::limitCallSignDBEntries() const { return value("limitCallSignDBEntries", false).toBool(); } void Settings::setLimitCallSignDBEnties(bool enable) { setValue("limitCallSignDBEntries", enable); } unsigned Settings::maxCallSignDBEntries() const { return value("maxCallSignDBEntries", 1).toInt(); } void Settings::setMaxCallSignDBEntries(unsigned max) { setValue("maxCallSignDBEntries", max); } bool Settings::selectUsingUserDMRID() { int num = beginReadArray("callSignDBPrefixes"); endArray(); return value("selectCallSignDBUsingUserDMRID", true).toBool() || (0 == num); } void Settings::setSelectUsingUserDMRID(bool enable) { setValue("selectCallSignDBUsingUserDMRID", enable); } QSet Settings::callSignDBPrefixes() { QSet prefixes; int num = beginReadArray("callSignDBPrefixes"); for (int i=0; i &prefixes) { beginWriteArray("callSignDBPrefixes"); unsigned i = 0; foreach (unsigned prefix, prefixes) { setArrayIndex(i); setValue("prefix", prefix); i++; } endArray(); } bool Settings::ignoreVerificationWarning() const { return value("ignoreVerificationWarning", true).toBool(); } void Settings::setIgnoreVerificationWarning(bool ignore) { setValue("ignoreVerificationWarning", ignore); } bool Settings::ignoreFrequencyLimits() const { return value("ignoreFrequencyLimits", false).toBool(); } void Settings::setIgnoreFrequencyLimits(bool ignore) { setValue("ignoreFrequencyLimits", ignore); } bool Settings::hideChannelNote() const { return value("hideChannelNote", false).toBool(); } void Settings::setHideChannelNote(bool hide) { setValue("hideChannelNote", hide); } bool Settings::hideGSPNote() const { return value("hideGPSNote", false).toBool(); } void Settings::setHideGPSNote(bool hide) { setValue("hideGPSNote", hide); } bool Settings::hideRoamingChannelNote() const { return value("hideRoamingChannelNote", false).toBool(); } void Settings::setHideRoamingChannelNote(bool hide) { setValue("hideRoamingChannelNote", hide); } bool Settings::hideRoamingNote() const { return value("hideRoamingNote", false).toBool(); } void Settings::setHideRoamingNote(bool hide) { setValue("hideRoamingNote", hide); } bool Settings::hideZoneNote() const { return value("hideZoneNote", false).toBool(); } void Settings::setHideZoneNote(bool hide) { setValue("hideZoneNote", hide); } bool Settings::showDisclaimer() const { return value("showDisclaimer", true).toBool(); } void Settings::setShowDisclaimer(bool show) { setValue("showDisclaimer", show); } ConfigMergeVisitor::ItemStrategy Settings::configMergeItemStrategy() const { return (ConfigMergeVisitor::ItemStrategy)value( "configMergeItemStrategy", (uint) ConfigMergeVisitor::ItemStrategy::Duplicate).toUInt(); } void Settings::setConfigMergeItemStrategy(ConfigMergeVisitor::ItemStrategy strategy) { setValue("configMergeItemStrategy", (uint)strategy); } ConfigMergeVisitor::SetStrategy Settings::configMergeSetStrategy() const { return (ConfigMergeVisitor::SetStrategy)value( "configMergeSetStrategy", (uint) ConfigMergeVisitor::SetStrategy::Merge).toUInt(); } void Settings::setConfigMergeSetStrategy(ConfigMergeVisitor::SetStrategy strategy) { setValue("configMergeSetStrategy", (uint)strategy); } QByteArray Settings::mainWindowState() const { return value("mainWindowState", QByteArray()).toByteArray(); } void Settings::setMainWindowState(const QByteArray &state) { setValue("mainWindowState", state); } QByteArray Settings::headerState(const QString &objName) const { if (objName.isEmpty()) return QByteArray(); QString key = QString("headerState/%1").arg(objName); return value(key, QByteArray()).toByteArray(); } void Settings::setHeaderState(const QString &objName, const QByteArray &state) { if (objName.isEmpty()) return; QString key = QString("headerState/%1").arg(objName); setValue(key, state); } bool Settings::sortFilterEnabled(const QString &objName) const { if (objName.isEmpty()) return false; QString key = QString("sortFilter/%1").arg(objName); return value(key, false).toBool(); } void Settings::enableSortFilter(const QString &objName, bool enable) { if (objName.isEmpty()) return; QString key = QString("sortFilter/%1").arg(objName); setValue(key, enable); } bool Settings::isUpdated() const { if (! contains("version")) return false; return VERSION_STRING == value("version").toString(); } void Settings::markUpdated() { setValue("version", VERSION_STRING); } /* ********************************************************************************************* * * Implementation of SettingsDialog * ********************************************************************************************* */ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent) { setupUi(this); Settings settings; _source = QGeoPositionInfoSource::createDefaultSource(this); if (_source) { connect(_source, SIGNAL(positionUpdated(QGeoPositionInfo)), this, SLOT(positionUpdated(QGeoPositionInfo))); if (settings.queryPosition()) _source->startUpdates(); } queryLocation->setChecked(settings.queryPosition()); locatorEntry->setText(settings.locator()); if (queryLocation->isChecked()) locatorEntry->setEnabled(false); Ui::SettingsDialog::searchRadius->setValue(settings.repeaterSearchRadius()); Ui::SettingsDialog::repeaterBookEnable->setChecked(settings.repeaterBookSourceEnabled()); switch (settings.repeaterBookRegion()) { case RepeaterBookSource::World: Ui::SettingsDialog::repeaterBookRegion->setCurrentIndex(0); break; case RepeaterBookSource::NorthAmerica: Ui::SettingsDialog::repeaterBookRegion->setCurrentIndex(1); break; } Ui::SettingsDialog::repeaterMapEnable->setChecked(settings.repeaterMapSourceEnabled()); Ui::SettingsDialog::repeatermapAPIToken->setText(settings.repeaterMapAPIToken().toHex()); Ui::SettingsDialog::hearhamEnable->setChecked(settings.hearhamSourceEnabled()); Ui::SettingsDialog::radioIdEnable->setChecked(settings.radioIdRepeaterSourceEnabled()); connect(Ui::SettingsDialog::ignoreFrequencyLimits, SIGNAL(toggled(bool)), this, SLOT(onIgnoreFrequencyLimitsSet(bool))); connect(queryLocation, SIGNAL(toggled(bool)), this, SLOT(onSystemLocationToggled(bool))); Ui::SettingsDialog::disableAutoDetect->setChecked(settings.disableAutoDetect()); Ui::SettingsDialog::updateCodeplug->setChecked(settings.updateCodeplug()); Ui::SettingsDialog::updateDeviceClock->setChecked(settings.updateDeviceClock()); Ui::SettingsDialog::autoEnableGPS->setChecked(settings.autoEnableGPS()); Ui::SettingsDialog::autoEnableRoaming->setChecked(settings.autoEnableRoaming()); Ui::SettingsDialog::ignoreVerificationWarnings->setChecked(settings.ignoreVerificationWarning()); Ui::SettingsDialog::ignoreFrequencyLimits->setChecked(settings.ignoreFrequencyLimits()); Ui::SettingsDialog::dbLimitEnable->setChecked(settings.limitCallSignDBEntries()); if (! settings.limitCallSignDBEntries()) Ui::SettingsDialog::dbLimit->setEnabled(false); Ui::SettingsDialog::dbLimit->setValue(settings.maxCallSignDBEntries()); Ui::SettingsDialog::useUserId->setChecked(settings.selectUsingUserDMRID()); if (settings.selectUsingUserDMRID()) Ui::SettingsDialog::prefixes->setEnabled(false); QSet prefs = settings.callSignDBPrefixes(); QStringList prefs_text; foreach (unsigned prefix, prefs) { prefs_text.append(QString::number(prefix)); } Ui::SettingsDialog::prefixes->setText(prefs_text.join(", ")); connect(Ui::SettingsDialog::dbLimitEnable, SIGNAL(toggled(bool)), this, SLOT(onDBLimitToggled(bool))); connect(Ui::SettingsDialog::useUserId, SIGNAL(toggled(bool)), this, SLOT(onUseUserDMRIdToggled(bool))); } bool SettingsDialog::systemLocationEnabled() const { return queryLocation->isChecked(); } QString SettingsDialog::locator() const { return locatorEntry->text().simplified(); } void SettingsDialog::onSystemLocationToggled(bool enabled) { locatorEntry->setEnabled(! enabled); if (enabled && _source) _source->startUpdates(); else if (_source) _source->stopUpdates(); } void SettingsDialog::onIgnoreFrequencyLimitsSet(bool enabled) { if (enabled) { Ui::SettingsDialog::ignoreFrequencyLimits->setText(tr("Warning!")); } else { Ui::SettingsDialog::ignoreFrequencyLimits->setText(""); } } void SettingsDialog::positionUpdated(const QGeoPositionInfo &info) { logDebug() << "Application: Current position: " << info.coordinate().toString(); if (info.isValid() && queryLocation->isChecked()) { locatorEntry->setText(deg2loc(info.coordinate())); } } void SettingsDialog::onDBLimitToggled(bool enable) { Ui::SettingsDialog::dbLimit->setEnabled(enable); } void SettingsDialog::onUseUserDMRIdToggled(bool enable) { Ui::SettingsDialog::prefixes->setEnabled(! enable); } void SettingsDialog::accept() { Settings settings; settings.setQueryPosition(queryLocation->isChecked()); settings.setLocator(locatorEntry->text().simplified()); settings.setRepeaterSearchRadius(Ui::SettingsDialog::searchRadius->value()); settings.setDisableAutoDetect(disableAutoDetect->isChecked()); settings.enableRepeaterBookSource(Ui::SettingsDialog::repeaterBookEnable->isChecked()); if (0 == Ui::SettingsDialog::repeaterBookRegion->currentIndex()) settings.setRepeaterBookRegion(RepeaterBookSource::World); else settings.setRepeaterBookRegion(RepeaterBookSource::NorthAmerica); settings.enableRepeaterMapSource(Ui::SettingsDialog::repeaterMapEnable->isChecked()); settings.setRepeaterMapAPIToken( QByteArray::fromHex(Ui::SettingsDialog::repeatermapAPIToken->text().simplified().toLatin1())); settings.enableHearhamSource(Ui::SettingsDialog::hearhamEnable->isChecked()); settings.enableRadioIdRepeaterSource(Ui::SettingsDialog::radioIdEnable->isChecked()); settings.setUpdateCodeplug(updateCodeplug->isChecked()); settings.setUpdateDeviceClock(updateDeviceClock->isChecked()); settings.setAutoEnableGPS(autoEnableGPS->isChecked()); settings.setAutoEnableRoaming(autoEnableRoaming->isChecked()); settings.setIgnoreVerificationWarning(ignoreVerificationWarnings->isChecked()); settings.setIgnoreFrequencyLimits(ignoreFrequencyLimits->isChecked()); settings.setLimitCallSignDBEnties(dbLimitEnable->isChecked()); settings.setMaxCallSignDBEntries(dbLimit->value()); settings.setSelectUsingUserDMRID(useUserId->isChecked()); QStringList prefs_text = prefixes->text().split(","); QSet prefs; foreach (QString pref, prefs_text) { bool ok=true; unsigned prefix = pref.toUInt(&ok); if (ok) prefs.insert(prefix); } settings.setCallSignDBPrefixes(prefs); QDialog::accept(); } ================================================ FILE: src/settings.hh ================================================ #ifndef SETTINGS_HH #define SETTINGS_HH #include #include #include #include #include #include "ui_settingsdialog.h" #include "codeplug.hh" #include "configmergevisitor.hh" #include "repeaterbooksource.hh" class Settings : public QSettings { Q_OBJECT public: explicit Settings(QObject *parent=nullptr); QDateTime lastRepeaterUpdate() const; bool repeaterUpdateNeeded(unsigned period=7) const; void repeaterUpdated(); bool queryPosition() const; void setQueryPosition(bool enable); QString locator() const; void setLocator(const QString &locator); QGeoCoordinate position() const; int repeaterSearchRadius() const; void setRepeaterSearchRadius(int radius); bool repeaterBookSourceEnabled() const; void enableRepeaterBookSource(bool enable); bool repeaterMapSourceEnabled() const; void enableRepeaterMapSource(bool enable); QByteArray repeaterMapAPIToken() const; void setRepeaterMapAPIToken(const QByteArray &token); bool hearhamSourceEnabled() const; void enableHearhamSource(bool enable); bool radioIdRepeaterSourceEnabled() const; void enableRadioIdRepeaterSource(bool enable); RepeaterBookSource::Region repeaterBookRegion() const; void setRepeaterBookRegion(RepeaterBookSource::Region region); bool disableAutoDetect() const; void setDisableAutoDetect(bool disable); bool updateCodeplug() const; void setUpdateCodeplug(bool update); bool updateDeviceClock() const; void setUpdateDeviceClock(bool update); bool autoEnableGPS() const; void setAutoEnableGPS(bool enable); bool autoEnableRoaming() const; void setAutoEnableRoaming(bool enable); QDir lastDirectory() const; void setLastDirectoryDir(const QDir &dir); Codeplug::Flags codePlugFlags() const; bool limitCallSignDBEntries() const; void setLimitCallSignDBEnties(bool enable); unsigned maxCallSignDBEntries() const; void setMaxCallSignDBEntries(unsigned max); bool selectUsingUserDMRID(); void setSelectUsingUserDMRID(bool enable); QSet callSignDBPrefixes(); void setCallSignDBPrefixes(const QSet &prefixes); bool ignoreVerificationWarning() const; void setIgnoreVerificationWarning(bool ignore); bool ignoreFrequencyLimits() const; void setIgnoreFrequencyLimits(bool ignore); bool hideChannelNote() const; void setHideChannelNote(bool hide); bool hideGSPNote() const; void setHideGPSNote(bool hide); bool hideRoamingChannelNote() const; void setHideRoamingChannelNote(bool hide); bool hideRoamingNote() const; void setHideRoamingNote(bool hide); bool hideZoneNote() const; void setHideZoneNote(bool hide); bool showDisclaimer() const; void setShowDisclaimer(bool show); ConfigMergeVisitor::ItemStrategy configMergeItemStrategy() const; void setConfigMergeItemStrategy(ConfigMergeVisitor::ItemStrategy strategy); ConfigMergeVisitor::SetStrategy configMergeSetStrategy() const; void setConfigMergeSetStrategy(ConfigMergeVisitor::SetStrategy strategy); QByteArray mainWindowState() const; void setMainWindowState(const QByteArray &state); QByteArray headerState(const QString &objName) const; void setHeaderState(const QString &objName, const QByteArray &state); bool sortFilterEnabled(const QString &objName) const; void enableSortFilter(const QString &objName, bool enable); bool isUpdated() const; void markUpdated(); }; class SettingsDialog: public QDialog, private Ui::SettingsDialog { Q_OBJECT public: explicit SettingsDialog(QWidget *parent=nullptr); bool systemLocationEnabled() const; QString locator() const; public slots: void accept(); protected slots: void onSystemLocationToggled(bool enable); void positionUpdated(const QGeoPositionInfo &info); void onIgnoreFrequencyLimitsSet(bool enabled); void onDBLimitToggled(bool enable); void onUseUserDMRIdToggled(bool enable); protected: QGeoPositionInfoSource *_source; }; #endif // SETTINGS_HH ================================================ FILE: src/settingsdialog.ui ================================================ SettingsDialog 0 0 599 826 0 0 Settings 0 Data Sources 0 0 Location System location Locator Search radius Some data sources require a search area specified by the location and this radius. km 20 150 10 QAbstractSpinBox::StepType::AdaptiveDecimalStepType 50 0 0 Repeater Info Sources 0 repeaterbook.com Enable Area 0 0 World North America API Token false QLineEdit::EchoMode::PasswordEchoOnEdit paste token here repeatermap.de Enable API Token QLineEdit::EchoMode::PasswordEchoOnEdit paste token here hearham.com Enable radioid.net Enable Programming 0 0 Radio Interfaces disable auto-detect 0 0 Radio Programming QFormLayout::FieldGrowthPolicy::AllNonFixedFieldsGrow Update codeplug <html><head/><body><p>Update the codeplug on the radio. If not selected, the codeplug on the radio gets overridden with possibly incomplete default values.</p><p><br/></p><p>If selected, QDMR downloads the codeplug from the radio and updates only those settings specified. The remaining settings within the radio are not touched (recommended).</p></body></html> Auto-enable GPS When a GPS or APRS system is defined and used for any channel, the GPS module gets enabled automatically. Auto-enable roaming When a roaming zone is defined and used by any channel, the automatic roaming gets enabled. Ignore verification warnings <html><head/><body><p>As the communication interface to the radio is kept open after verification, time-outs may occur and the code-plug upload may fail when the verification dialog pops up. To prevent this, verification warnings can be ignored, eliminating the time-gap between verification and upload. Verification errors still prevent the upload.</p></body></html> Ignore frequency limits Do not set this option unless you know what you are doing. Update Device Clock 0 0 Call-Sign DB Limit number of DB entries When enabled, the number of DB entries will be limited. Otherwise the maximum number of entries are generated (device dependent). Number of DB entries 0 0 Specifies the number of DB entries (if enabled above). 1 999999 Select using my DMR ID If enabled, the entries are selected using the users DMR ID. Select using prefixes If enabled, these comma separated DMR ID prefixes are used to select the call-sign DB entries. Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Cancel|QDialogButtonBox::StandardButton::Ok buttonBox accepted() SettingsDialog accept() 248 254 157 274 buttonBox rejected() SettingsDialog reject() 316 260 286 274 ================================================ FILE: src/squelchedit.cc ================================================ #include "squelchedit.hh" #include "ui_squelchedit.h" #include "channel.hh" ChannelSquelchEdit::ChannelSquelchEdit(Level globalDefault, QWidget *parent) : QWidget(parent) , ui(new Ui::SquelchEdit), _globalDefault(globalDefault) { ui->setupUi(this); ui->defaultBox->setChecked(true); ui->valueBox->setEnabled(false); ui->valueBox->setValue(_globalDefault.value()); connect(ui->defaultBox, &QCheckBox::toggled, [this](bool checked) { this->ui->valueBox->setEnabled(!checked); }); } ChannelSquelchEdit::~ChannelSquelchEdit() { delete ui; } void ChannelSquelchEdit::setChannel(AnalogChannel *ch) { _channel = ch; if (ch->defaultSquelch()) { ui->defaultBox->setChecked(true); ui->valueBox->setValue(_globalDefault.value()); } else ui->valueBox->setValue(ch->squelch().value()); } void ChannelSquelchEdit::accept() { if (ui->defaultBox->isChecked()) { _channel->setSquelchDefault(); } else { _channel->setSquelch(Level::fromValue(ui->valueBox->value())); } } ================================================ FILE: src/squelchedit.hh ================================================ #ifndef SQUELCHEDIT_HH #define SQUELCHEDIT_HH #include "level.hh" #include #include namespace Ui { class SquelchEdit; } class AnalogChannel; class ChannelSquelchEdit : public QWidget { Q_OBJECT public: explicit ChannelSquelchEdit(Level globalDefault=Level::fromValue(1), QWidget *parent = nullptr); ~ChannelSquelchEdit(); void setChannel(AnalogChannel *ch); public slots: virtual void accept(); private: Ui::SquelchEdit *ui; QPointer _channel; Level _globalDefault; }; #endif // SQUELCHEDIT_HH ================================================ FILE: src/squelchedit.ui ================================================ SquelchEdit 0 0 267 45 0 0 0 0 Open 10 0 Uses the global squelch setting if enabled. Default ================================================ FILE: src/timeslotselect.cc ================================================ #include "timeslotselect.hh" TimeslotSelect::TimeslotSelect(QWidget *parent) : QComboBox(parent) { addItem(tr("Slot 1"), QVariant::fromValue(DMRChannel::TimeSlot::TS1)); addItem(tr("Slot 2"), QVariant::fromValue(DMRChannel::TimeSlot::TS2)); } void TimeslotSelect::setSlot(DMRChannel::TimeSlot slot) { setCurrentIndex((slot == DMRChannel::TimeSlot::TS1) ? 0 : 1); } DMRChannel::TimeSlot TimeslotSelect::slot() const { return currentData().value(); } ================================================ FILE: src/timeslotselect.hh ================================================ #ifndef TIMESLOTSELECT_HH #define TIMESLOTSELECT_HH #include #include "channel.hh" class TimeslotSelect : public QComboBox { Q_OBJECT public: explicit TimeslotSelect(QWidget *parent=nullptr); void setSlot(DMRChannel::TimeSlot slot); DMRChannel::TimeSlot slot() const; }; #endif // TIMESLOTSELECT_HH ================================================ FILE: src/transponderfrequencydelegate.cc ================================================ #include "transponderfrequencydelegate.hh" #include "satellitedatabase.hh" /* ********************************************************************************************* * * Implementation of TransponderFrequencyEditor * ********************************************************************************************* */ TransponderFrequencyEditor::TransponderFrequencyEditor(QWidget *parent) : QComboBox(parent) { setEditable(true); } TransponderFrequencyEditor::TransponderFrequencyEditor(unsigned int satId, bool uplink, Transponder::Mode mode, const TransponderDatabase &transponder, QWidget *parent) : QComboBox(parent) { setEditable(true); populate(satId, uplink, mode, transponder); } void TransponderFrequencyEditor::populate(unsigned int satId, bool uplink, Transponder::Mode mode, const TransponderDatabase &transponder) { clear(); addItem(tr("None"), QVariant::fromValue(Frequency())); for (const Transponder &tp: transponder) { if (tp.satellite() != satId) continue; if (tp.mode() != mode) continue; Frequency f = uplink ? tp.uplink() : tp.downlink(); if (0 == f.inHz()) continue; addItem(QString("%1 (%2)").arg(tp.name(), f.format()), QVariant::fromValue(f)); } } void TransponderFrequencyEditor::setFrequency(const Frequency ¤t) { for (int i=0; i() == current) { setCurrentIndex(i); break; } } } Frequency TransponderFrequencyEditor::frequency() const { return currentData().value(); } /* ********************************************************************************************* * * Implementation of TransponderFrequencyDelegate * ********************************************************************************************* */ TransponderFrequencyDelegate::TransponderFrequencyDelegate(bool uplink, Transponder::Mode mode, QObject *parent) : QStyledItemDelegate(parent), _uplink(uplink), _mode(mode) { // pass... } QWidget * TransponderFrequencyDelegate::createEditor( QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const { Q_UNUSED(option); auto model = qobject_cast(index.model()); unsigned int satId = model->getAt(index.row()).id(); return new TransponderFrequencyEditor(satId, _uplink, _mode, model->transponders(), parent); } void TransponderFrequencyDelegate::setEditorData(QWidget *editor, const QModelIndex index) { auto feditor = qobject_cast(editor); feditor->setFrequency(index.data(Qt::EditRole).value()); } void TransponderFrequencyDelegate::setModelData( QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const { model->setData( index, QVariant::fromValue(qobject_cast(editor)->frequency())); } ================================================ FILE: src/transponderfrequencydelegate.hh ================================================ #ifndef TRANSPONDERFREQUENCYDELEGATE_HH #define TRANSPONDERFREQUENCYDELEGATE_HH #include #include #include "transponderdatabase.hh" class TransponderFrequencyEditor: public QComboBox { Q_OBJECT public: explicit TransponderFrequencyEditor(QWidget *parent=nullptr); TransponderFrequencyEditor( unsigned int satId, bool uplink, Transponder::Mode mode, const TransponderDatabase &transponder, QWidget *parent=nullptr); void populate(unsigned int satId, bool uplink, Transponder::Mode mode, const TransponderDatabase &transponder); void setFrequency(const Frequency ¤t); Frequency frequency() const; }; class TransponderFrequencyDelegate: public QStyledItemDelegate { Q_OBJECT public: explicit TransponderFrequencyDelegate(bool uplink, Transponder::Mode mode, QObject *parent=nullptr); QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const; void setEditorData(QWidget *editor, const QModelIndex index); void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const; protected: bool _uplink; Transponder::Mode _mode; }; #endif // TRANSPONDERFREQUENCYDELEGATE_HH ================================================ FILE: src/verifydialog.cc ================================================ #include "verifydialog.hh" #include "radiolimits.hh" #include "application.hh" #include #include VerifyDialog::VerifyDialog(const RadioLimitContext &issues, bool upload, QWidget *parent) : QDialog(parent) { setupUi(this); uploadMessage->setVisible(false); // Sort issues by severity QList issueList; for (int i=0; i b.severity(); }); Application *app = qobject_cast(QApplication::instance()); bool valid = true; foreach(auto issue, issueList) { auto item = new QTreeWidgetItem(treeWidget); auto label = new QLabel(issue.message()); label->setMargin(5); label->setWordWrap(true); treeWidget->setItemWidget(item, 0, label); // Dispatch by severity switch (issue.severity()) { case RadioLimitIssue::Silent: break; case RadioLimitIssue::Hint: label->setStyleSheet("color: gray;"); item->setIcon(0, QIcon::fromTheme("symbol-info")); break; case RadioLimitIssue::Warning: if (app->isDarkMode()) label->setStyleSheet("color: white;"); else label->setStyleSheet("color: black;"); item->setIcon(0, QIcon::fromTheme("symbol-warning")); break; case RadioLimitIssue::Critical: if (app->isDarkMode()) label->setStyleSheet("color: red;"); else label->setStyleSheet("color: darkred;"); valid = false; item->setIcon(0, QIcon::fromTheme("symbol-error")); break; } // Add traceback (in reverse order) foreach(auto step, issue.stack()) { item->insertChild(0, new QTreeWidgetItem({step})); } } if (upload) { buttonBox->setStandardButtons(QDialogButtonBox::Cancel|QDialogButtonBox::Ok); QPushButton *ignore = buttonBox->button(QDialogButtonBox::Ok); ignore->setEnabled(valid); uploadMessage->setVisible(! valid); } } ================================================ FILE: src/verifydialog.hh ================================================ #ifndef VERIFYDIALOG_HH #define VERIFYDIALOG_HH #include #include "radio.hh" #include "ui_verifydialog.h" class RadioLimitContext; class VerifyDialog : public QDialog, private Ui::VerifyDialog { Q_OBJECT public: public: explicit VerifyDialog(const RadioLimitContext &ctx, bool upload, QWidget *parent = nullptr); }; #endif // VERIFYDIALOG_HH ================================================ FILE: src/verifydialog.ui ================================================ VerifyDialog 0 0 400 300 Verify Codeplug The codeplug cannot be uploaded, unless all critical issues (red) are resolved. true QAbstractItemView::EditTrigger::NoEditTriggers false true 32 32 true false 1 Qt::Orientation::Horizontal QDialogButtonBox::StandardButton::Close buttonBox accepted() VerifyDialog accept() 248 254 157 274 buttonBox rejected() VerifyDialog reject() 316 260 286 274 ================================================ FILE: src/zonedialog.cc ================================================ #include "zonedialog.hh" #include "settings.hh" #include "config.hh" #include "zone.hh" #include "channel.hh" #include "channelselectiondialog.hh" #include "settings.hh" #include /* ********************************************************************************************* * * Implementation of ZoneDialog * ********************************************************************************************* */ ZoneDialog::ZoneDialog(Config *config, Zone *zone, QWidget *parent) : QDialog(parent), _config(config), _myZone(new Zone(this)), _zone(zone) { setWindowTitle(tr("Edit Zone")); if (_zone) _myZone->copy(*_zone); construct(); } ZoneDialog::ZoneDialog(Config *config, QWidget *parent) : QDialog(parent), _config(config), _myZone(new Zone(this)), _zone(nullptr) { setWindowTitle(tr("Create Zone")); construct(); } void ZoneDialog::construct() { setupUi(this); Settings settings; if (settings.hideZoneNote()) zoneHint->setVisible(false); zoneName->setText(_myZone->name()); listAView->setModel(new ChannelRefListWrapper(_myZone->A())); listBView->setModel(new ChannelRefListWrapper(_myZone->B())); extensionView->setObjectName("zoneExtension"); extensionView->setObject(_myZone, _config); connect(addChannelA, SIGNAL(clicked()), this, SLOT(onAddChannelA())); connect(remChannelA, SIGNAL(clicked()), this, SLOT(onRemChannelA())); connect(addChannelB, SIGNAL(clicked()), this, SLOT(onAddChannelB())); connect(remChannelB, SIGNAL(clicked()), this, SLOT(onRemChannelB())); connect(buttonBox, SIGNAL(accepted()), this, SLOT(accept())); connect(buttonBox, SIGNAL(rejected()), this, SLOT(reject())); connect(zoneHint, SIGNAL(linkActivated(QString)), this, SLOT(onHideZoneHint())); } void ZoneDialog::onAddChannelA() { MultiChannelSelectionDialog dia(_config->channelList(), false, false, _myZone->A()); if (QDialog::Accepted != dia.exec()) return; QList channels = dia.channel(); foreach (Channel *channel, channels) { if (0 <= _myZone->A()->indexOf(channel)) continue; _myZone->A()->add(channel); } } void ZoneDialog::onRemChannelA() { if (! listAView->hasSelection()) { QMessageBox::information(nullptr, tr("Cannot remove channel"), tr("Select at least one channel first.")); return; } QPair selection = listAView->selection(); QList channels; for (int i=selection.first; i<=selection.second; i++) channels.push_back(_myZone->A()->get(i)->as()); foreach (Channel *channel, channels) { _myZone->A()->del(channel); } } void ZoneDialog::onAddChannelB() { MultiChannelSelectionDialog dia(_config->channelList(), false, false, _myZone->B()); if (QDialog::Accepted != dia.exec()) return; QList channels = dia.channel(); foreach (Channel *channel, channels) { if (0 <= _myZone->B()->indexOf(channel)) continue; _myZone->B()->add(channel); } } void ZoneDialog::onRemChannelB() { if (! listBView->hasSelection()) { QMessageBox::information(nullptr, tr("Cannot remove channel"), tr("Select at least one channel first.")); return; } QPair selection = listBView->selection(); QList channels; for (int i=selection.first; i<=selection.second; i++) channels.push_back(_myZone->B()->get(i)->as()); foreach (Channel *channel, channels) { _myZone->B()->del(channel); } } void ZoneDialog::onHideZoneHint() { zoneHint->setVisible(false); Settings settings; settings.setHideZoneNote(true); } Zone * ZoneDialog::zone() { _myZone->setName(zoneName->text()); Zone *zone = _myZone; if (_zone) { _zone->copy(*_myZone); zone = _zone; } else { _myZone->setParent(nullptr); } return zone; } ================================================ FILE: src/zonedialog.hh ================================================ #ifndef ZONEDIALOG_HH #define ZONEDIALOG_HH #include #include "ui_zonedialog.h" class Config; class Zone; class ZoneDialog: public QDialog, private Ui::ZoneDialog { Q_OBJECT public: ZoneDialog(Config *config, QWidget *parent=nullptr); ZoneDialog(Config *config, Zone *zone, QWidget *parent=nullptr); Zone *zone(); protected slots: void onAddChannelA(); void onRemChannelA(); void onAddChannelB(); void onRemChannelB(); void onHideZoneHint(); protected: void construct(); protected: Config *_config; Zone *_myZone; Zone *_zone; }; #endif // ZONEDIALOG_HH ================================================ FILE: src/zonedialog.ui ================================================ ZoneDialog 0 0 550 475 0 0 Edit Zone 0 Basic padding:10px;border: 2px solid black; border-radius: 10px; <html><head/><body><p align="justify"><span style=" font-weight:600;">Note:</span> Zones are collections of channels that are usually valid for a specific region. I.e., a collection of channels for repeaters within a certain region. </p><p align="justify">QDMR manages zones by allowing for two independent channel lists for each VFO of the radio (if it has two). Many radios however, allow one to assign zones to each VFO individually. In these cases, QDMR will split the zone into two (A &amp; B) and program them individually into the radio.</p><p align="right"><a href="#hide"><span style=" text-decoration: underline; color:#0000ff;">Hide</span></a></p></body></html> Qt::RichText true Name zoneName 0 0 Channels A 0 0 add remove 0 0 Channels B 0 0 add remove Extension Qt::Horizontal QDialogButtonBox::Cancel|QDialogButtonBox::Ok ExtensionView QWidget
extensionview.hh
1
ConfigObjectListView QWidget
configobjectlistview.hh
1
buttonBox accepted() ZoneDialog accept() 248 254 157 274 buttonBox rejected() ZoneDialog reject() 316 260 286 274
================================================ FILE: src/zonelistview.cc ================================================ #include "zonelistview.hh" #include "ui_zonelistview.h" #include "config.hh" #include "zonedialog.hh" #include ZoneListView::ZoneListView(Config *config, QWidget *parent) : QWidget(parent), ui(new Ui::ZoneListView), _config(config) { ui->setupUi(this); ui->listView->setModel(new ZoneListWrapper(_config->zones(), ui->listView)); connect(ui->addZone, SIGNAL(clicked()), this, SLOT(onAddZone())); connect(ui->remZone, SIGNAL(clicked()), this, SLOT(onRemZone())); connect(ui->listView, SIGNAL(doubleClicked(unsigned)), this, SLOT(onEditZone(unsigned))); } ZoneListView::~ZoneListView() { delete ui; } void ZoneListView::onAddZone() { ZoneDialog dialog(_config); if (QDialog::Accepted != dialog.exec()) return; int row=-1; if (ui->listView->hasSelection()) row = ui->listView->selection().second+1; _config->zones()->add(dialog.zone(), row); } void ZoneListView::onRemZone() { // Check if there is any zones selected if (! ui->listView->hasSelection()) { QMessageBox::information( nullptr, tr("Cannot delete zone"), tr("Cannot delete zone: You have to select a zone first.")); return; } // Get selection and ask for deletion QPair rows = ui->listView->selection(); int rowcount = rows.second-rows.first+1; if (rows.first == rows.second) { QString name = _config->zones()->zone(rows.first)->name(); if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete zone?"), tr("Delete zone %1?").arg(name))) return; } else { if (QMessageBox::No == QMessageBox::question( nullptr, tr("Delete zones?"), tr("Delete %1 zones?").arg(rowcount))) return; } // collect all selected zones // need to collect them first as rows change when deleting QList lists; lists.reserve(rowcount); for(int row=rows.first; row<=rows.second; row++) lists.push_back(_config->zones()->zone(row)); // remove foreach (Zone *zone, lists) _config->zones()->del(zone); } void ZoneListView::onEditZone(unsigned row) { ZoneDialog dialog(_config, _config->zones()->zone(row)); if (QDialog::Accepted != dialog.exec()) return; dialog.zone(); } ================================================ FILE: src/zonelistview.hh ================================================ #ifndef ZONELISTVIEW_HH #define ZONELISTVIEW_HH #include class Config; namespace Ui { class ZoneListView; } class ZoneListView : public QWidget { Q_OBJECT public: explicit ZoneListView(Config *config, QWidget *parent = nullptr); ~ZoneListView(); protected slots: void onAddZone(); void onRemZone(); void onEditZone(unsigned row); private: Ui::ZoneListView *ui; Config *_config; }; #endif // ZONELISTVIEW_HH ================================================ FILE: src/zonelistview.ui ================================================ ZoneListView 0 0 400 300 Form 0 0 Add Zone Alt++ Delete Zone Alt+- ConfigObjectListView QWidget
configobjectlistview.hh
1
================================================ FILE: test/CMakeLists.txt ================================================ qt_add_resources(TESTDATA resources.qrc) qt_add_library(libdmrconfigtest STATIC libdmrconfigtest.cc libdmrconfigtest.hh ${TESTDATA}) target_link_libraries(libdmrconfigtest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf ${ADDITIONAL_LIBS}) qt_add_executable(configtest configtest.cc configtest.hh ${TESTDATA}) target_link_libraries(configtest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(tableformattest tableformattest.cc tableformattest.hh ${TESTDATA}) target_link_libraries(tableformattest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(copytest copytest.cc copytest.hh ${TESTDATA}) target_link_libraries(copytest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(labeltest labeltest.cc labeltest.hh ${TESTDATA}) target_link_libraries(labeltest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(trafotest trafotest.cc trafotest.hh ${TESTDATA}) target_link_libraries(trafotest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(crc32test crc32test.cc crc32test.hh ${TESTDATA}) target_link_libraries(crc32test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(utilstest utilstest.cc utilstest.hh ${TESTDATA}) target_link_libraries(utilstest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(chirptest chirptest.cc chirptest.hh ${TESTDATA}) target_link_libraries(chirptest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(mergetest mergetest.cc mergetest.hh ${TESTDATA}) target_link_libraries(mergetest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(smstemplatetest smstemplatetest.cc smstemplatetest.hh ${TESTDATA}) target_link_libraries(smstemplatetest PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) # Unit tests for Radioddity devices qt_add_executable(rd5r_test rd5r_test.cc rd5r_test.hh ${TESTDATA}) target_link_libraries(rd5r_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(gd73_test gd73_test.cc gd73_test.hh ${TESTDATA}) target_link_libraries(gd73_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(gd77_test gd77_test.cc gd77_test.hh ${TESTDATA}) target_link_libraries(gd77_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) # Unit tests for OpenGD77 firmware qt_add_executable(opengd77_test opengd77_test.cc opengd77_test.hh ${TESTDATA}) target_link_libraries(opengd77_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) # Unit tests for OpenGD77 firmware qt_add_executable(openuv380_test openuv380_test.cc openuv380_test.hh ${TESTDATA}) target_link_libraries(openuv380_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) # Unit tests for TyT devices qt_add_executable(md390_test md390_test.cc md390_test.hh ${TESTDATA}) target_link_libraries(md390_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(uv390_test uv390_test.cc uv390_test.hh ${TESTDATA}) target_link_libraries(uv390_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(md2017_test md2017_test.cc md2017_test.hh ${TESTDATA}) target_link_libraries(md2017_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) # Unit tests for AnyTone devices qt_add_executable(d868uve_test d868uve_test.cc d868uve_test.hh ${TESTDATA}) target_link_libraries(d868uve_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(d878uv_test d878uv_test.cc d878uv_test.hh ${TESTDATA}) target_link_libraries(d878uv_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(d878uv2_test d878uv2_test.cc d878uv2_test.hh ${TESTDATA}) target_link_libraries(d878uv2_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(d578uv_test d578uv_test.cc d578uv_test.hh ${TESTDATA}) target_link_libraries(d578uv_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(d168uv_test d168uv_test.cc d168uv_test.hh ${TESTDATA}) target_link_libraries(d168uv_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) # Unit tests for Baofeng devices qt_add_executable(dmr6x2uv_test dmr6x2uv_test.cc dmr6x2uv_test.hh ${TESTDATA}) target_link_libraries(dmr6x2uv_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(dmr6x2uv2_test dmr6x2uv2_test.cc dmr6x2uv2_test.hh ${TESTDATA}) target_link_libraries(dmr6x2uv2_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(dm1701_test dm1701_test.cc dm1701_test.hh ${TESTDATA}) target_link_libraries(dm1701_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(dr1801_test dr1801_test.cc dr1801_test.hh ${TESTDATA}) target_link_libraries(dr1801_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) qt_add_executable(dm32uv_test dm32uv_test.cc dm32uv_test.hh ${TESTDATA}) target_link_libraries(dm32uv_test PRIVATE Qt6::Core Qt6::Network Qt6::Positioning Qt6::SerialPort Qt6::Test ${YAMLCPP_LIBRARIES} libdmrconf libdmrconfigtest ${ADDITIONAL_LIBS}) add_test(NAME Config COMMAND configtest) add_test(NAME TableFormat COMMAND tableformattest) add_test(NAME Copy COMMAND copytest) add_test(NAME Label COMMAND labeltest) add_test(NAME Transformations COMMAND trafotest) add_test(NAME CRC32 COMMAND crc32test) add_test(NAME Utils COMMAND utilstest) add_test(NAME CHIRP COMMAND chirptest) add_test(NAME Merge COMMAND mergetest) add_test(NAME SMSTemplates COMMAND smstemplatetest) add_test(NAME RD5R COMMAND rd5r_test) add_test(NAME GD73 COMMAND gd73_test) add_test(NAME GD77 COMMAND gd77_test) add_test(NAME OpenGD77 COMMAND opengd77_test) add_test(NAME OpenUV380 COMMAND openuv380_test) add_test(NAME MD390 COMMAND md390_test) add_test(NAME UV390 COMMAND uv390_test) add_test(NAME MD2017 COMMAND md2017_test) add_test(NAME D868UVE COMMAND d868uve_test) add_test(NAME D878UV COMMAND d878uv_test) add_test(NAME D878UV2 COMMAND d878uv2_test) add_test(NAME D578UV COMMAND d578uv_test) add_test(NAME DMR6X2UV COMMAND dmr6x2uv_test) add_test(NAME DMR6X2UV2 COMMAND dmr6x2uv2_test) add_test(NAME DM1701 COMMAND dm1701_test) add_test(NAME DR1801 COMMAND dr1801_test) add_test(NAME DM32UV COMMAND dm32uv_test) ================================================ FILE: test/chirptest.cc ================================================ #include "chirptest.hh" #include #include "chirpformat.hh" #include "config.hh" ChirpTest::ChirpTest(QObject *parent) : QObject{parent} { // pass... } void ChirpTest::testReaderBasic() { QFile file(":/data/chirp_simple.csv"); if (! file.open(QIODevice::ReadOnly)) { QFAIL("Cannot open CHIRP file."); } QTextStream stream(&file); Config config; ErrorStack err; if (! ChirpReader::read(stream, &config, err)) { QFAIL(QString("Cannot read codeplug file:\n%1").arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->count(), 3); QCOMPARE(config.channelList()->channel(0)->name(), "KD8BMI"); QCOMPARE(config.channelList()->channel(0)->rxFrequency().inMHz(), 147.075000); QCOMPARE(config.channelList()->channel(0)->txFrequency().inMHz(), 147.675000); QCOMPARE(config.channelList()->channel(0)->as()->txTone(), SelectiveCall(103.5)); QCOMPARE(config.channelList()->channel(0)->as()->rxTone(), SelectiveCall()); QCOMPARE(config.channelList()->channel(1)->rxFrequency().inMHz(), 146.760000); QCOMPARE(config.channelList()->channel(1)->txFrequency().inMHz(), 146.160000); } void ChirpTest::testReaderCTCSS() { QFile file(":/data/chirp_ctcss.csv"); if (! file.open(QIODevice::ReadOnly)) { QFAIL("Cannot open CHIRP file."); } QTextStream stream(&file); Config config; ErrorStack err; if (! ChirpReader::read(stream, &config, err)) { QFAIL(QString("Cannot read codeplug file:\n%1").arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->count(), 3); QCOMPARE(config.channelList()->channel(0)->as()->txTone().format(), SelectiveCall().format()); QCOMPARE(config.channelList()->channel(0)->as()->rxTone(), SelectiveCall()); QCOMPARE(config.channelList()->channel(1)->as()->txTone(), SelectiveCall(67.0)); QCOMPARE(config.channelList()->channel(1)->as()->rxTone(), SelectiveCall()); QCOMPARE(config.channelList()->channel(2)->as()->txTone(), SelectiveCall(77.0)); QCOMPARE(config.channelList()->channel(2)->as()->rxTone(), SelectiveCall(77.0)); } void ChirpTest::testReaderDCS() { QFile file(":/data/chirp_dcs.csv"); if (! file.open(QIODevice::ReadOnly)) { QFAIL("Cannot open CHIRP file."); } QTextStream stream(&file); Config config; ErrorStack err; if (! ChirpReader::read(stream, &config, err)) { QFAIL(QString("Cannot read codeplug file:\n%1").arg(err.format()).toStdString().c_str()); } SelectiveCall cmp(23, false); QCOMPARE(config.channelList()->channel(0)->as()->txTone().format(), cmp.format()); QCOMPARE(config.channelList()->channel(0)->as()->rxTone(), SelectiveCall(23, false)); QCOMPARE(config.channelList()->channel(1)->as()->txTone(), SelectiveCall(23, false)); QCOMPARE(config.channelList()->channel(1)->as()->rxTone(), SelectiveCall(23, true)); QCOMPARE(config.channelList()->channel(2)->as()->txTone(), SelectiveCall(23, true)); QCOMPARE(config.channelList()->channel(2)->as()->rxTone(), SelectiveCall(23, false)); QCOMPARE(config.channelList()->channel(3)->as()->txTone(), SelectiveCall(23, true)); QCOMPARE(config.channelList()->channel(3)->as()->rxTone(), SelectiveCall(23, true)); } void ChirpTest::testReaderCross() { QFile file(":/data/chirp_cross.csv"); if (! file.open(QIODevice::ReadOnly)) { QFAIL("Cannot open CHIRP file."); } QTextStream stream(&file); Config config; ErrorStack err; if (! ChirpReader::read(stream, &config, err)) { QFAIL(QString("Cannot read codeplug file:\n%1").arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->count(), 8); QCOMPARE(config.channelList()->channel(0)->as()->txTone(), SelectiveCall()); QCOMPARE(config.channelList()->channel(0)->as()->rxTone(), SelectiveCall(67.0)); QCOMPARE(config.channelList()->channel(1)->as()->txTone(), SelectiveCall()); QCOMPARE(config.channelList()->channel(1)->as()->rxTone().format(), SelectiveCall(23, false).format()); QCOMPARE(config.channelList()->channel(2)->as()->txTone(), SelectiveCall(67.0)); QCOMPARE(config.channelList()->channel(2)->as()->rxTone(), SelectiveCall()); QCOMPARE(config.channelList()->channel(3)->as()->txTone(), SelectiveCall(67.0)); QCOMPARE(config.channelList()->channel(3)->as()->rxTone(), SelectiveCall(77.0)); QCOMPARE(config.channelList()->channel(4)->as()->txTone(), SelectiveCall(67.0)); QCOMPARE(config.channelList()->channel(4)->as()->rxTone(), SelectiveCall(23, false)); QCOMPARE(config.channelList()->channel(5)->as()->txTone(), SelectiveCall(23, false)); QCOMPARE(config.channelList()->channel(5)->as()->rxTone(), SelectiveCall()); QCOMPARE(config.channelList()->channel(6)->as()->txTone(), SelectiveCall(23, false)); QCOMPARE(config.channelList()->channel(6)->as()->rxTone(), SelectiveCall(67.0)); QCOMPARE(config.channelList()->channel(7)->as()->txTone(), SelectiveCall(23, false)); QCOMPARE(config.channelList()->channel(7)->as()->rxTone(), SelectiveCall(32, false)); } void ChirpTest::testReaderBandwidth() { QFile file(":/data/chirp_bandwidth.csv"); if (! file.open(QIODevice::ReadOnly)) { QFAIL("Cannot open CHIRP file."); } QTextStream stream(&file); Config config; ErrorStack err; if (! ChirpReader::read(stream, &config, err)) { QFAIL(QString("Cannot read codeplug file:\n%1").arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->count(), 2); QVERIFY(config.channelList()->channel(0)->is()); QCOMPARE(config.channelList()->channel(0)->as()->bandwidth(), FMChannel::Bandwidth::Wide); QCOMPARE(config.channelList()->channel(1)->as()->bandwidth(), FMChannel::Bandwidth::Narrow); } void ChirpTest::testWriterBasic() { Config orig; FMChannel *fm0 = new FMChannel(); fm0->setName("DB0SP"); fm0->setRXFrequency(Frequency::fromMHz(145.6)); fm0->setTXFrequency(Frequency::fromMHz(145.0)); orig.channelList()->add(fm0); FMChannel *fm1 = new FMChannel(); fm1->setName("DB0SP"); fm1->setRXFrequency(Frequency::fromMHz(145.6)); fm1->setTXFrequency(Frequency::fromMHz(144.4)); orig.channelList()->add(fm1); QString csv; QTextStream stream(&csv); ErrorStack err; if (! ChirpWriter::write(stream, &orig, err)) QFAIL(QString("Cannot serialize codeplug:\n%1").arg(err.format()).toStdString().c_str()); Config parsed; if (! ChirpReader::read(stream, &parsed, err)) QFAIL(QString("Cannot parse CHIRP CSV:\n%1").arg(err.format()).toStdString().c_str()); QCOMPARE(parsed.channelList()->count(), orig.channelList()->count()); QCOMPARE(parsed.channelList()->channel(0)->name(), orig.channelList()->channel(0)->name()); QCOMPARE(parsed.channelList()->channel(0)->rxFrequency(), orig.channelList()->channel(0)->rxFrequency()); QCOMPARE(parsed.channelList()->channel(0)->txFrequency(), orig.channelList()->channel(0)->txFrequency()); QCOMPARE(parsed.channelList()->channel(1)->rxFrequency(), orig.channelList()->channel(1)->rxFrequency()); QCOMPARE(parsed.channelList()->channel(1)->txFrequency(), orig.channelList()->channel(1)->txFrequency()); } void ChirpTest::testWriterCTCSS() { Config orig; FMChannel *fm0 = new FMChannel(); fm0->setName("DB0SP"); fm0->setRXFrequency(Frequency::fromMHz(145.6)); fm0->setTXFrequency(Frequency::fromMHz(145.0)); fm0->setTXTone(SelectiveCall(67.0)); orig.channelList()->add(fm0); FMChannel *fm1 = new FMChannel(); fm1->setName("DB0SP"); fm1->setRXFrequency(Frequency::fromMHz(145.6)); fm1->setTXFrequency(Frequency::fromMHz(145.0)); fm1->setTXTone(SelectiveCall(67.0)); fm1->setRXTone(SelectiveCall(67.0)); orig.channelList()->add(fm1); FMChannel *fm2 = new FMChannel(); fm2->setName("DB0SP"); fm2->setRXFrequency(Frequency::fromMHz(145.6)); fm2->setTXFrequency(Frequency::fromMHz(145.0)); fm2->setTXTone(SelectiveCall(67.0)); fm2->setRXTone(SelectiveCall(77.0)); orig.channelList()->add(fm2); QString csv; QTextStream stream(&csv); ErrorStack err; if (! ChirpWriter::write(stream, &orig, err)) QFAIL(QString("Cannot serialize codeplug:\n%1").arg(err.format()).toStdString().c_str()); Config parsed; if (! ChirpReader::read(stream, &parsed, err)) QFAIL(QString("Cannot parse CHIRP CSV:\n%1").arg(err.format()).toStdString().c_str()); FMChannel *pfm1 = parsed.channelList()->channel(0)->as(); QCOMPARE(pfm1->txTone().format(), fm0->txTone().format()); FMChannel *pfm2 = parsed.channelList()->channel(1)->as(); QCOMPARE(pfm2->txTone(), fm1->txTone()); QCOMPARE(pfm2->rxTone(), fm1->rxTone()); FMChannel *pfm3 = parsed.channelList()->channel(2)->as(); QCOMPARE(pfm3->txTone(), fm2->txTone()); QCOMPARE(pfm3->rxTone(), fm2->rxTone()); } void ChirpTest::testWriterDCS() { Config orig; FMChannel *fm0 = new FMChannel(); fm0->setName("DB0SP"); fm0->setRXFrequency(Frequency::fromMHz(145.6)); fm0->setTXFrequency(Frequency::fromMHz(145.0)); fm0->setTXTone(SelectiveCall(23, false)); fm0->setRXTone(SelectiveCall(23, false)); orig.channelList()->add(fm0); FMChannel *fm1 = new FMChannel(); fm1->setName("DB0SP"); fm1->setRXFrequency(Frequency::fromMHz(145.6)); fm1->setTXFrequency(Frequency::fromMHz(145.0)); fm1->setTXTone(SelectiveCall(23, false)); fm1->setRXTone(SelectiveCall(23, true)); orig.channelList()->add(fm1); FMChannel *fm2 = new FMChannel(); fm2->setName("DB0SP"); fm2->setRXFrequency(Frequency::fromMHz(145.6)); fm2->setTXFrequency(Frequency::fromMHz(145.0)); fm2->setTXTone(SelectiveCall(23, false)); fm2->setRXTone(SelectiveCall(77.0)); orig.channelList()->add(fm2); FMChannel *fm3 = new FMChannel(); fm3->setName("DB0SP"); fm3->setRXFrequency(Frequency::fromMHz(145.6)); fm3->setTXFrequency(Frequency::fromMHz(145.0)); fm3->setTXTone(SelectiveCall(23, false)); fm3->setRXTone(SelectiveCall(32, false)); orig.channelList()->add(fm3); QString csv; QTextStream stream(&csv); ErrorStack err; if (! ChirpWriter::write(stream, &orig, err)) QFAIL(QString("Cannot serialize codeplug:\n%1").arg(err.format()).toStdString().c_str()); qDebug() << csv; Config parsed; if (! ChirpReader::read(stream, &parsed, err)) QFAIL(QString("Cannot parse CHIRP CSV:\n%1").arg(err.format()).toStdString().c_str()); FMChannel *pfm0 = parsed.channelList()->channel(0)->as(); QCOMPARE(pfm0->txTone(), fm0->txTone()); QCOMPARE(pfm0->rxTone(), fm0->rxTone()); FMChannel *pfm1 = parsed.channelList()->channel(1)->as(); QCOMPARE(pfm1->txTone(), fm1->txTone()); QCOMPARE(pfm1->rxTone(), fm1->rxTone()); FMChannel *pfm2 = parsed.channelList()->channel(2)->as(); QCOMPARE(pfm2->txTone(), fm2->txTone()); QCOMPARE(pfm2->rxTone(), fm2->rxTone()); FMChannel *pfm3 = parsed.channelList()->channel(3)->as(); QCOMPARE(pfm3->txTone(), fm3->txTone()); QCOMPARE(pfm3->rxTone(), fm3->rxTone()); } void ChirpTest::testWriterCross() { } void ChirpTest::testWriterBandwidth() { Config orig; FMChannel *fm0 = new FMChannel(); fm0->setName("DB0SP"); fm0->setRXFrequency(Frequency::fromMHz(145.6)); fm0->setTXFrequency(Frequency::fromMHz(145.0)); fm0->setBandwidth(FMChannel::Bandwidth::Wide); orig.channelList()->add(fm0); FMChannel *fm1 = new FMChannel(); fm1->setName("DB0SP"); fm1->setRXFrequency(Frequency::fromMHz(145.6)); fm1->setTXFrequency(Frequency::fromMHz(145.0)); fm1->setBandwidth(FMChannel::Bandwidth::Narrow); orig.channelList()->add(fm1); QString csv; QTextStream stream(&csv); ErrorStack err; if (! ChirpWriter::write(stream, &orig, err)) QFAIL(QString("Cannot serialize codeplug:\n%1").arg(err.format()).toStdString().c_str()); qDebug() << csv; Config parsed; if (! ChirpReader::read(stream, &parsed, err)) QFAIL(QString("Cannot parse CHIRP CSV:\n%1").arg(err.format()).toStdString().c_str()); FMChannel *pfm0 = parsed.channelList()->channel(0)->as(); QCOMPARE(pfm0->bandwidth(), fm0->bandwidth()); FMChannel *pfm1 = parsed.channelList()->channel(1)->as(); QCOMPARE(pfm1->bandwidth(), fm1->bandwidth()); } QTEST_GUILESS_MAIN(ChirpTest) ================================================ FILE: test/chirptest.hh ================================================ #ifndef CHIRPTEST_HH #define CHIRPTEST_HH #include class ChirpTest : public QObject { Q_OBJECT public: explicit ChirpTest(QObject *parent = nullptr); private slots: void testReaderBasic(); void testReaderCTCSS(); void testReaderDCS(); void testReaderCross(); void testReaderBandwidth(); void testWriterBasic(); void testWriterCTCSS(); void testWriterDCS(); void testWriterCross(); void testWriterBandwidth(); }; #endif // CHIRPTEST_HH ================================================ FILE: test/configtest.cc ================================================ #include "configtest.hh" #include "config.hh" #include "gnsssettings.hh" #include "errorstack.hh" #include "melody.hh" #include #include #include "logger.hh" #include #include "gd73_limits.hh" #include "configcopyvisitor.hh" ConfigTest::ConfigTest(QObject *parent) : UnitTestBase(parent), _stderr(stderr) { Logger::get().addHandler(new StreamLogHandler(_stderr, LogMessage::DEBUG)); } void ConfigTest::initTestCase() { UnitTestBase::initTestCase(); ErrorStack err; if (! _ctcssCopyTest.readYAML(":/data/ctcss_copy_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } } void ConfigTest::testImmediateRefInvalidation() { ErrorStack err; Config *copy = ConfigCopy::copy(&_basicConfig, err)->as(); if (nullptr == copy) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(_basicConfig.posSystems()->count(), 1); QVERIFY(_basicConfig.channelList()->get(1)); QVERIFY(_basicConfig.channelList()->get(1)->is()); QCOMPARE(_basicConfig.channelList()->get(1)->as()->aprs(), _basicConfig.posSystems()->get(0)->as()); // Delete DMR APRS system and check if reference to it (channel 2) is removed as well. QVERIFY(copy->posSystems()->del(copy->posSystems()->get(0))); QCOMPARE(copy->posSystems()->count(), 0); QCOMPARE(copy->channelList()->get(1)->as()->aprs(), nullptr); QVERIFY(_basicConfig.channelList()->get(2)); QCOMPARE(_basicConfig.zones()->get(0)->as()->B()->count(), 1); QCOMPARE(_basicConfig.zones()->get(0)->as()->B()->get(0), _basicConfig.channelList()->get(2)); // Delete channel 3, check if zone 1, list B is empty copy->channelList()->del(copy->channelList()->get(2)); QCOMPARE(copy->zones()->get(0)->as()->B()->count(), 0); } void ConfigTest::testCloneChannelBasic() { // Check if a channel can be cloned Channel *clone = _basicConfig.channelList()->channel(0)->clone()->as(); // Check if channels are the same QCOMPARE(clone->compare(*_basicConfig.channelList()->channel(0)), 0); } void ConfigTest::testCloneChannelCTCSS() { // Check if a channel can be cloned QHash table; ConfigCloneVisitor visitor(table); ErrorStack err; if (! visitor.processItem(_ctcssCopyTest.channelList()->channel(0))) QFAIL(err.format().toLocal8Bit().constData()); Channel *clone = visitor.takeResult(err)->as(); if (nullptr == clone) QFAIL(err.format().toLocal8Bit().constData()); // Check if channels are the same QCOMPARE(clone->compare(*_ctcssCopyTest.channelList()->channel(0)), 0); QCOMPARE(clone->as()->txTone(), _ctcssCopyTest.channelList()->channel(0)->as()->txTone()); QCOMPARE(clone->as()->rxTone(), _ctcssCopyTest.channelList()->channel(0)->as()->rxTone()); } void ConfigTest::testMultipleRadioIDs() { ErrorStack err; Config multipleRadioIds; if (! multipleRadioIds.readYAML(":/data/audio_settings_extension.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } Config::Context ctx; if (! multipleRadioIds.label(ctx, err)) { QFAIL(QString("Cannot label codeplug: %1") .arg(err.format()).toStdString().c_str()); } YAML::Node node = multipleRadioIds.serialize(ctx, err); if (node.IsNull()) { QFAIL(QString("Cannot serialize codeplug: %1") .arg(err.format()).toStdString().c_str()); } } void ConfigTest::testMelodyLilypond() { QString lilypond = "a8 b e2 cis4 d"; Melody melody; melody.fromLilypond(lilypond); QString serialized = melody.toLilypond(); QCOMPARE(serialized, lilypond); QVector> tones = melody.toTones(); QCOMPARE(std::round(tones[0].first*100), 44000); QCOMPARE(tones[0].second, 300); QCOMPARE(std::round(tones[1].first*100), 49388); QCOMPARE(tones[1].second, 300); QCOMPARE(std::round(tones[2].first*100), 32963); QCOMPARE(tones[2].second,1200); QCOMPARE(std::round(tones[3].first*100), 27718); QCOMPARE(tones[3].second, 600); QCOMPARE(std::round(tones[4].first*100), 29366); QCOMPARE(tones[4].second, 600); } void ConfigTest::testMelodyEncoding() { QString lilypond = "a8 b e2 cis4 d"; Melody melody; melody.fromLilypond(lilypond); QVector> tones = melody.toTones(); QCOMPARE(std::round(tones[0].first*100), 44000); QCOMPARE(tones[0].second, 300); QCOMPARE(std::round(tones[1].first*100), 49388); QCOMPARE(tones[1].second, 300); QCOMPARE(std::round(tones[2].first*100), 32963); QCOMPARE(tones[2].second,1200); QCOMPARE(std::round(tones[3].first*100), 27718); QCOMPARE(tones[3].second, 600); QCOMPARE(std::round(tones[4].first*100), 29366); QCOMPARE(tones[4].second, 600); } void ConfigTest::testMelodyDecoding() { QString lilypond = "a8 b e2 cis4 d"; QVector> tones = { {440.00, 300}, {493.88, 300}, {329.63, 1200}, {277.18, 600}, {293.66, 600} }; Melody melody; melody.infer(tones); QCOMPARE(melody.toLilypond(), lilypond); // infer() should also infer a BPM of 100 QCOMPARE(melody.bpm(), 100); } void ConfigTest::testGNSSSettings() { Config config; config.readYAML(":/data/config_test.yaml"); config.settings()->gnss()->setFixedPositionLocator("JO62jl45"); config.settings()->gnss()->enableFixedPosition(true); config.settings()->gnss()->setSystems(GNSSSettings::System::GPS | GNSSSettings::System::Beidou); qDebug() << "Before" << config.settings()->gnss()->fixedPositionLocator(); QString buffer; QTextStream stream(&buffer); ErrorStack err; if (! config.toYAML(stream, err)) QFAIL(err.format().toLocal8Bit().constData()); Config testConfig; Config::Context ctx; YAML::Node doc = YAML::Load(buffer.toStdString()); if (! testConfig.parse(doc, ctx, err)) QFAIL(err.format().toLocal8Bit().constData()); if (! testConfig.link(doc, ctx, err)) QFAIL(err.format().toLocal8Bit().constData()); qDebug() << "After" << testConfig.settings()->gnss()->fixedPositionLocator(); QCOMPARE(testConfig.settings()->gnss()->fixedPositionEnabled(), true); QCOMPARE(testConfig.settings()->gnss()->fixedPositionLocator(), "JO62jl45"); QCOMPARE(testConfig.settings()->gnss()->systems(), GNSSSettings::System::GPS | GNSSSettings::System::Beidou); } void ConfigTest::testAudioSettings() { Config config; config.readYAML(":/data/audio_settings_extension.yaml"); QCOMPARE(config.settings()->audio()->micGain(), Level::fromValue(3)); QCOMPARE(config.settings()->audio()->fmMicGain(), Level::fromValue(6)); Config copyConfig; QVERIFY(copyConfig.copy(config)); QCOMPARE(copyConfig.settings()->audio()->micGain(), config.settings()->audio()->micGain()); QCOMPARE(copyConfig.settings()->audio()->fmMicGain(), config.settings()->audio()->fmMicGain()); } void ConfigTest::testCTCSSNull() { ErrorStack err; Config ctcssConfig; if (! ctcssConfig.readYAML(":/data/ctcss_null_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } // Test backward compatibility QVERIFY(ctcssConfig.channelList()->channel(0)->is()); QCOMPARE(SelectiveCall() ,ctcssConfig.channelList()->channel(0)->as()->rxTone()); QCOMPARE(SelectiveCall(67.0) ,ctcssConfig.channelList()->channel(0)->as()->txTone()); // Test new format QVERIFY(ctcssConfig.channelList()->channel(1)->is()); QCOMPARE(SelectiveCall(37, true), ctcssConfig.channelList()->channel(1)->as()->rxTone()); QCOMPARE(SelectiveCall(67.0) ,ctcssConfig.channelList()->channel(1)->as()->txTone()); } void ConfigTest::testSelectiveCallDCS() { ErrorStack err; auto dcsFromOctal = SelectiveCall(0, false); auto dcsFromBinary = SelectiveCall::fromBinaryDCS(0x0, false); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "n000"); QCOMPARE(dcsFromOctal.binCode(), 0x0); QCOMPARE(dcsFromOctal.octalCode(), 0); QCOMPARE(dcsFromOctal.isInverted(), false); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(1, false); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x1, false); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "n001"); QCOMPARE(dcsFromOctal.binCode(), 0x1); QCOMPARE(dcsFromOctal.octalCode(), 1); QCOMPARE(dcsFromOctal.isInverted(), false); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(7, false); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x7, false); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "n007"); QCOMPARE(dcsFromOctal.binCode(), 0x7); QCOMPARE(dcsFromOctal.octalCode(), 7); QCOMPARE(dcsFromOctal.isInverted(), false); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(10, false); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x8, false); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "n010"); QCOMPARE(dcsFromOctal.binCode(), 0x8); QCOMPARE(dcsFromOctal.octalCode(), 10); QCOMPARE(dcsFromOctal.isInverted(), false); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(42, false); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x22, false); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "n042"); QCOMPARE(dcsFromOctal.binCode(), 0x22); QCOMPARE(dcsFromOctal.octalCode(), 42); QCOMPARE(dcsFromOctal.isInverted(), false); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(777, false); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x1ff, false); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "n777"); QCOMPARE(dcsFromOctal.binCode(), 0x1ff); QCOMPARE(dcsFromOctal.octalCode(), 777); QCOMPARE(dcsFromOctal.isInverted(), false); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(0, true); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x200 - 512, true); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "i000"); QCOMPARE(dcsFromOctal.binCode(), 0x0); QCOMPARE(dcsFromOctal.octalCode(), 0); QCOMPARE(dcsFromOctal.isInverted(), true); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(7, true); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x207 - 512, true); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "i007"); QCOMPARE(dcsFromOctal.binCode(), 0x7); QCOMPARE(dcsFromOctal.octalCode(), 7); QCOMPARE(dcsFromOctal.isInverted(), true); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(10, true); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x208 - 512, true); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "i010"); QCOMPARE(dcsFromOctal.binCode(), 0x8); QCOMPARE(dcsFromOctal.octalCode(), 10); QCOMPARE(dcsFromOctal.isInverted(), true); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(42, true); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x222 - 512, true); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "i042"); QCOMPARE(dcsFromOctal.binCode(), 0x22); QCOMPARE(dcsFromOctal.octalCode(), 42); QCOMPARE(dcsFromOctal.isInverted(), true); QCOMPARE(dcsFromOctal, dcsFromBinary); dcsFromOctal = SelectiveCall(777, true); dcsFromBinary = SelectiveCall::fromBinaryDCS(0x3ff - 512, true); QVERIFY(dcsFromOctal.isDCS()); QCOMPARE(dcsFromOctal.format(), "i777"); QCOMPARE(dcsFromOctal.binCode(), 0x1ff); QCOMPARE(dcsFromOctal.octalCode(), 777); QCOMPARE(dcsFromOctal.isInverted(), true); QCOMPARE(dcsFromOctal, dcsFromBinary); } void ConfigTest::testDMRIdVerification() { GD73Limits limits; RadioLimitContext ctx; ErrorStack err; Config cfg; if (! cfg.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } /*limits.verifyConfig(&cfg, ctx); for (unsigned int i=0; iget(0)->as()->setNumber(0x12345678); QVERIFY(!limits.verifyConfig(&cfg, ctx)); QVERIFY(ctx.maxSeverity() == RadioLimitIssue::Severity::Critical); } QTEST_GUILESS_MAIN(ConfigTest) ================================================ FILE: test/configtest.hh ================================================ #ifndef CONFIGTEST_HH #define CONFIGTEST_HH #include #include "libdmrconfigtest.hh" #include "config.hh" class ConfigTest : public UnitTestBase { Q_OBJECT public: explicit ConfigTest(QObject *parent = nullptr); private slots: void initTestCase(); void testImmediateRefInvalidation(); void testCloneChannelBasic(); void testCloneChannelCTCSS(); /** Regression test for issue #388. */ void testMultipleRadioIDs(); void testMelodyLilypond(); void testMelodyEncoding(); void testMelodyDecoding(); void testGNSSSettings(); void testAudioSettings(); /** Regression test for #509. */ void testCTCSSNull(); void testSelectiveCallDCS(); /// Regression test #674. void testDMRIdVerification(); protected: QTextStream _stderr; Config _ctcssCopyTest; }; #endif // CONFIGTEST_HH ================================================ FILE: test/copytest.cc ================================================ #include "copytest.hh" #include "configcopyvisitor.hh" CopyTest::CopyTest(QObject *parent) : UnitTestBase(parent) { // pass... } void CopyTest::testChannelClone() { QHash map; ErrorStack err; ConfigCloneVisitor cloner(map); Config config; if (! config.readYAML(":/data/config_test.yaml")) QFAIL("Cannot load config."); auto ch = config.channelList()->channel(0); { if (! cloner.processItem(ch, err)) QFAIL(err.format().toLocal8Bit().constData()); ConfigItem *item = cloner.takeResult(err); QVERIFY(item); QCOMPARE(ch->compare(*item), 0); delete item; } { ch->setPower(Channel::Power::Min); ch->setTimeout(Interval::fromSeconds(60)); ch->setVOX(Level::fromValue(3)); if (! cloner.processItem(ch, err)) QFAIL(err.format().toLocal8Bit().constData()); ConfigItem *item = cloner.takeResult(err); QVERIFY(item); QVERIFY(item->is()); QCOMPARE(item->as()->power(), Channel::Power::Min); QCOMPARE(item->as()->timeout(), Interval::fromSeconds(60)); QCOMPARE(item->as()->vox(), Level::fromValue(3)); delete item; } { ch->setDefaultPower(); ch->setDefaultTimeout(); ch->setVOXDefault(); if (! cloner.processItem(ch, err)) QFAIL(err.format().toLocal8Bit().constData()); ConfigItem *item = cloner.takeResult(err); QVERIFY(item); QVERIFY(item->is()); QVERIFY(item->as()->defaultPower()); QVERIFY(item->as()->defaultTimeout()); QVERIFY(item->as()->defaultVOX()); delete item; } } void CopyTest::testConfigClone() { QHash map; ConfigCloneVisitor cloner(map); ErrorStack err; if (! cloner.process(&_basicConfig, err)) { QFAIL(err.format().toLocal8Bit().constData()); } ConfigItem *item = cloner.takeResult(err); QVERIFY(item); QCOMPARE(_basicConfig.compare(*item), 0); } void CopyTest::testConfigCopy() { QHash map; ConfigCloneVisitor cloner(map); FixReferencesVisistor fixer(map); ErrorStack err; if (! cloner.process(&_basicConfig, err)) { QFAIL(err.format().toLocal8Bit().constData()); } ConfigItem *item = cloner.takeResult(err); QVERIFY(item); QVERIFY(item->is()); if (! fixer.process(item->as(), err)) { QFAIL(err.format().toLocal8Bit().constData()); } QCOMPARE(_basicConfig.compare(*item), 0); } void CopyTest::testAPRSSystemCopy() { // Regression test for issue #468. QHash map; ConfigCloneVisitor cloner(map); FixReferencesVisistor fixer(map); Config config; ErrorStack err; if (! config.readYAML(":/data/fm_aprs_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1\n") .arg(err.format()).toStdString().c_str()); } if (! cloner.process(&config, err)) { QFAIL(err.format().toLocal8Bit().constData()); } ConfigItem *item = cloner.takeResult(err); QVERIFY(item); QVERIFY(item->is()); if (! fixer.process(item->as(), err)) { QFAIL(err.format().toLocal8Bit().constData()); } Config *comp_config = item->as(); QCOMPARE(config.compare(*item), 0); QCOMPARE(config.posSystems()->count(), 1); QCOMPARE(comp_config->posSystems()->count(), config.posSystems()->count()); QVERIFY(config.posSystems()->get(0)->is()); QVERIFY(comp_config->posSystems()->get(0)->is()); FMAPRSSystem *aprs = config.posSystems()->get(0)->as(), *comp_aprs = comp_config->posSystems()->get(0)->as(); QCOMPARE(comp_aprs->name(), aprs->name()); QCOMPARE(comp_aprs->period(), aprs->period()); QCOMPARE(comp_aprs->destination(), aprs->destination()); QCOMPARE(comp_aprs->destSSID(), aprs->destSSID()); QCOMPARE(comp_aprs->source(), aprs->source()); QCOMPARE(comp_aprs->srcSSID(), aprs->srcSSID()); QCOMPARE(comp_aprs->path(), aprs->path()); QCOMPARE(comp_aprs->icon(), aprs->icon()); QCOMPARE(comp_aprs->message(), aprs->message()); } QTEST_GUILESS_MAIN(CopyTest) ================================================ FILE: test/copytest.hh ================================================ #ifndef COPYTEST_HH #define COPYTEST_HH #include "libdmrconfigtest.hh" class CopyTest: public UnitTestBase { Q_OBJECT public: explicit CopyTest(QObject *parent=nullptr); private slots: void testChannelClone(); void testConfigClone(); void testConfigCopy(); void testAPRSSystemCopy(); }; #endif // COPYTEST_HH ================================================ FILE: test/crc32test.cc ================================================ #include "crc32test.hh" #include "crc32.hh" #include CRC32Test::CRC32Test(QObject *parent) : QObject(parent) { // pass... } void CRC32Test::testCRC32() { QString txt("The quick brown fox jumps over the lazy dog"); CRC32 crc; crc.update(txt.toLocal8Bit()); QCOMPARE(crc.get(), 0x414FA339U^0xFFFFFFFF); } QTEST_GUILESS_MAIN(CRC32Test) ================================================ FILE: test/crc32test.hh ================================================ #ifndef CRC32TEST_H #define CRC32TEST_H #include class CRC32Test : public QObject { Q_OBJECT public: explicit CRC32Test(QObject *parent = nullptr); private slots: void testCRC32(); }; #endif // CRC32TEST_H ================================================ FILE: test/d168uv_test.cc ================================================ #include "d168uv_test.hh" #include "logger.hh" #include "d168uv_codeplug.hh" D168UVTest::D168UVTest(QObject *parent) : UnitTestBase(parent), _stderr(stderr) { Logger::get().addHandler(new StreamLogHandler(_stderr, LogMessage::DEBUG)); } void D168UVTest::encodeDecode(Config &input, Config &output) { ErrorStack err; D168UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&input, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } if (! codeplug.decode(&output, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } } void D168UVTest::testFixedLocation() { ErrorStack err; Config decoded, config, reencoded; config.copy(_basicConfig); config.settings()->gnss()->setFixedPositionLocator("JO62kk45"); config.settings()->gnss()->enableFixedPosition(true); encodeDecode(config, decoded); QVERIFY(decoded.settings()->gnss()->fixedPositionEnabled()); QCOMPARE(decoded.settings()->gnss()->fixedPositionLocator(), QString("JO62kk45")); encodeDecode(decoded, reencoded); QVERIFY(reencoded.settings()->gnss()->fixedPositionEnabled()); QCOMPARE(reencoded.settings()->gnss()->fixedPositionLocator(), QString("JO62kk45")); } QTEST_GUILESS_MAIN(D168UVTest) ================================================ FILE: test/d168uv_test.hh ================================================ #ifndef D168UVTEST_HH #define D168UVTEST_HH #include "libdmrconfigtest.hh" class D168UVTest: public UnitTestBase { Q_OBJECT public: explicit D168UVTest(QObject *parent=nullptr); protected: void encodeDecode(Config &input, Config &output); private slots: void testFixedLocation(); protected: QTextStream _stderr; }; #endif // D168UVTEST_HH ================================================ FILE: test/d578uv_test.cc ================================================ #include "d578uv_test.hh" #include "config.hh" #include "d578uv_codeplug.hh" #include "errorstack.hh" #include D578UVTest::D578UVTest(QObject *parent) : UnitTestBase(parent) { // pass... } void D578UVTest::encodeDecode(Config &input, Config &output) { ErrorStack err; D578UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&input, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D578UV: %1") .arg(err.format()).toStdString().c_str()); } if (! codeplug.decode(&output, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D578UV: %1") .arg(err.format()).toStdString().c_str()); } } void D578UVTest::testBasicConfigEncoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D578UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D578UV: %1") .arg(err.format()).toStdString().c_str()); } } void D578UVTest::testBasicConfigDecoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D578UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D578UV: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D578UV: %1") .arg(err.format()).toStdString().c_str()); } } void D578UVTest::testChannelFrequency() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D578UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void D578UVTest::testChannelDataACK() { ErrorStack err; Config config; if (! config.readYAML(":/data/anytone_channel_data_ack.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->count(), 2); QVERIFY(config.channelList()->channel(0)->is()); QCOMPARE(config.channelList()->channel(0)->as() ->extended()->dataConfirm(), false); QVERIFY(config.channelList()->channel(1)->is()); QCOMPARE(config.channelList()->channel(1)->as() ->extended()->dataConfirm(), true); Codeplug::Flags flags; flags.setUpdateCodeplug(false); D578UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(testConfig.channelList()->channel(0)->as() ->extended()->dataConfirm(), false); QCOMPARE(testConfig.channelList()->channel(1)->as() ->extended()->dataConfirm(), true); } void D578UVTest::testSettingsDisplayVolumeChangePrompt() { ErrorStack err; Config config; if (! config.readYAML(":/data/anytone_settings_display.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.settings()->anytoneExtension()->displaySettings()->volumeChangePromptEnabled(), false); Codeplug::Flags flags; flags.setUpdateCodeplug(false); D578UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(testConfig.settings()->anytoneExtension()->displaySettings()->volumeChangePromptEnabled(), false); } void D578UVTest::testMicGain() { Config copy, config; config.copy(_basicConfig); config.settings()->audio()->setMicGain(Level::fromValue(10)); encodeDecode(config, copy); QCOMPARE(copy.settings()->audio()->micGain(), Level::fromValue(10)); QVERIFY(copy.settings()->anytoneExtension()); // FM mic gain enabled only if it differs from DMR gain QVERIFY(! copy.settings()->audio()->fmMicGainEnabled()); QList> pairs = {{1,1}, {2,1}, {3,1}, {4,3}, {5,3}, {6,5}, {7,5}, {8,7}, {9,7}, {10,10}}; for (auto pair: pairs) { config.settings()->audio()->setMicGain(Level::fromValue(pair.first)); encodeDecode(config, copy); QCOMPARE(copy.settings()->audio()->micGain(), Level::fromValue(pair.second)); } } void D578UVTest::testSettingsRoamingNotificationCount() { ErrorStack err; Config config; if (! config.readYAML(":/data/anytone_settings_roaming.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.settings()->anytoneExtension()->roamingSettings()->notificationCount(), 1); Codeplug::Flags flags; flags.setUpdateCodeplug(false); D578UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(testConfig.settings()->anytoneExtension()->roamingSettings()->notificationCount(), 1); } void D578UVTest::testIdleToneEnable() { ErrorStack err; Config config, testConfig; if (! config.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } config.settings()->tone()->setChannelIdle(Channel::Type::FM); encodeDecode(config, testConfig); QCOMPARE(testConfig.settings()->tone()->channelIdle(), Channel::Type::FM); config.settings()->tone()->setChannelIdle(Channel::Type::DMR); encodeDecode(config, testConfig); QCOMPARE(testConfig.settings()->tone()->channelIdle(), Channel::Type::DMR); config.settings()->tone()->setChannelIdle(Channel::Type::FM|Channel::Type::DMR); encodeDecode(config, testConfig); QCOMPARE(testConfig.settings()->tone()->channelIdle(), Channel::Type::FM|Channel::Type::DMR); config.settings()->tone()->setChannelIdle(Channel::Type::None); encodeDecode(config, testConfig); QCOMPARE(testConfig.settings()->tone()->channelIdle(), Channel::Type::None); } void D578UVTest::testARC4Encryption() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/arc4_encryption.yaml", err)) { QFAIL(QString("Cannot open codeplug file:\n%1") .arg(err.format(" ")).toStdString().c_str()); } D578UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D578UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D578UV: %1") .arg(err.format()).toStdString().c_str()); } // Verify existence of key QVERIFY(nullptr != decoded.commercialExtension()); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->count(), 1); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->key(0)->key(), QByteArray::fromHex("1122334455")); // Verify link to channel QVERIFY(nullptr != decoded.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(decoded.channelList()->channel(0)->as()->commercialExtension()->encryptionKey(), decoded.commercialExtension()->encryptionKeys()->key(0)); } void D578UVTest::testAMChannel() { // Load config from file ErrorStack err; Config config, testConfig; if (! config.readYAML(":/data/am_channel_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file:\n%1") .arg(err.format(" ")).toStdString().c_str()); } encodeDecode(config, testConfig); // There must be an AM and a FM channel. The order is not defined. QCOMPARE(testConfig.channelList()->count(), 2); auto am = testConfig.channelList()->channel(0)->is() ? testConfig.channelList()->channel(0)->as() : testConfig.channelList()->channel(1)->as(); QCOMPARE(am->name(), config.channelList()->channel(0)->as()->name()); QCOMPARE(am->rxFrequency(), config.channelList()->channel(0)->as()->rxFrequency()); } QTEST_GUILESS_MAIN(D578UVTest) ================================================ FILE: test/d578uv_test.hh ================================================ #ifndef D578UVTEST_HH #define D578UVTEST_HH #include "libdmrconfigtest.hh" class D578UVTest : public UnitTestBase { Q_OBJECT public: explicit D578UVTest(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testARC4Encryption(); void testAMChannel(); void testMicGain(); // Retression test for #773 void testChannelDataACK(); // Regression test for #813 void testSettingsDisplayVolumeChangePrompt(); // Regression test for #813 void testSettingsRoamingNotificationCount(); // Regression test for #813 void testIdleToneEnable(); // Regression test for #911 protected: void encodeDecode(Config &input, Config &ouput); }; #endif // D578UVTEST_HH ================================================ FILE: test/d868uve_test.cc ================================================ #include "d868uve_test.hh" #include "config.hh" #include "d868uv_codeplug.hh" #include "errorstack.hh" #include D868UVETest::D868UVETest(QObject *parent) : UnitTestBase(parent) { // pass... } void D868UVETest::encodeDecode(Config &input, Config &output) { ErrorStack err; D868UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&input, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } if (! codeplug.decode(&output, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } } void D868UVETest::testBasicConfigEncoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D868UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } } void D868UVETest::testBasicConfigDecoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D868UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UVII: %1") .arg(err.format()).toStdString().c_str()); } } void D868UVETest::testChannelFrequency() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D868UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D868UV: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D868UV: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void D868UVETest::testAutoRepeaterOffset() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/anytone_auto_repeater_extension.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } // Check config QVERIFY2(config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); AnytoneAutoRepeaterSettingsExtension *ext = config.settings()->anytoneExtension()->autoRepeaterSettings(); // There should be two offset frequencies QCOMPARE(ext->offsets()->count(), 2); QCOMPARE(ext->offsets()->get(0)->as()->offset().inHz(), 600000); QCOMPARE(ext->offsets()->get(1)->as()->offset().inHz(), 7600000); // check if VHF and UHF frequency offsets are correct QVERIFY(! ext->vhfRef()->isNull()); QVERIFY(! ext->uhfRef()->isNull()); QCOMPARE(ext->vhfRef()->as(), ext->offsets()->get(0)->as()); QCOMPARE(ext->uhfRef()->as(), ext->offsets()->get(1)->as()); // Encode D868UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } // Decode Config comp_config; if (! codeplug.decode(&comp_config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } // Compare QVERIFY2(comp_config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); ext = comp_config.settings()->anytoneExtension()->autoRepeaterSettings(); // There should be two offset frequencies QCOMPARE(ext->offsets()->count(), 2); QCOMPARE(ext->offsets()->get(0)->as()->offset().inHz(), 600000); QCOMPARE(ext->offsets()->get(1)->as()->offset().inHz(), 7600000); // check if VHF and UHF frequency offsets are correct QVERIFY(! ext->vhfRef()->isNull()); QVERIFY(! ext->uhfRef()->isNull()); QCOMPARE(ext->vhfRef()->as(), ext->offsets()->get(0)->as()); QCOMPARE(ext->uhfRef()->as(), ext->offsets()->get(1)->as()); } void D868UVETest::testDTMFContacts() { // Assemble config Config base; DTMFContact *contact0 = new DTMFContact(); contact0->setName("Contact 0"); contact0->setNumber("0123456789ABCD#*"); base.contacts()->add(contact0); // Encode D868UVCodeplug codeplug; ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&base, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } // Decode Config comp_config; if (! codeplug.decode(&comp_config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } // Check contacts QCOMPARE(comp_config.contacts()->count(), 1); QVERIFY(comp_config.contacts()->get(0)->is()); QCOMPARE(comp_config.contacts()->get(0)->as()->name(), "Contact 0"); // Contacts are limited to 14 digit numbers. QCOMPARE(comp_config.contacts()->get(0)->as()->number(), "0123456789ABCD"); } void D868UVETest::testSMSTemplates() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); config.smsExtension()->smsTemplates()->add(sms0); config.smsExtension()->smsTemplates()->add(sms1); ErrorStack err; D868UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } // For now, messages are not encoded QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); } void D868UVETest::testBasicEncryption() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/basic_encryption.yaml", err)) { QFAIL(QString("Cannot open codeplug file:\n%1") .arg(err.format(" ")).toStdString().c_str()); } D868UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } // Verify existence of key QVERIFY(nullptr != decoded.commercialExtension()); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->count(), 1); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->key(0)->key(), QByteArray::fromHex("0123")); // Verify link to channel QVERIFY(nullptr != decoded.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(decoded.channelList()->channel(0)->as()->commercialExtension()->encryptionKey(), decoded.commercialExtension()->encryptionKeys()->key(0)); } void D868UVETest::testRegressionSMSTemplateOffset() { // Regression test for issue #469, message index offset error if more than one bank is used // (i.e., more than 8 SMS). Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); for (int i=0; i<9; i++) { SMSTemplate *sms = new SMSTemplate(); sms->setName(QString("SMS%1").arg(i)); sms->setMessage("ABC"); config.smsExtension()->smsTemplates()->add(sms); } ErrorStack err; D868UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } // For now, messages are not encoded QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 9); } void D868UVETest::testRegressionSMSCount() { // Regression test for issue #482 (tries to encode too many SMS) Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); for (int i=0; i<101; i++) { SMSTemplate *sms = new SMSTemplate(); sms->setName(QString("SMS%1").arg(i)); sms->setMessage("ABC"); config.smsExtension()->smsTemplates()->add(sms); } ErrorStack err; D868UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } } void D868UVETest::testRegressionDefaultChannel() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } config.settings()->setAnytoneExtension(new AnytoneSettingsExtension()); config.settings()->boot()->zoneA()->set(config.zones()->zone(0)); config.settings()->boot()->channelA()->set(config.zones()->zone(0)->A()->get(0)); config.settings()->boot()->zoneB()->set(config.zones()->zone(1)); config.settings()->boot()->channelB()->set(config.zones()->zone(1)->A()->get(0)); config.settings()->boot()->enableDefaultChannel(true); D868UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } QVERIFY(decoded.settings()->anytoneExtension()); QVERIFY(decoded.settings()->boot()->defaultChannelEnabled()); QCOMPARE(decoded.settings()->boot()->zoneA()->as(), decoded.zones()->zone(0)); QCOMPARE(decoded.settings()->boot()->channelA()->as()->name(), decoded.zones()->zone(0)->A()->get(0)->name()); QCOMPARE(decoded.settings()->boot()->zoneB()->as(), decoded.zones()->zone(1)); QCOMPARE(decoded.settings()->boot()->channelB()->as()->name(), decoded.zones()->zone(1)->A()->get(0)->name()); } void D868UVETest::testChannelDataACK() { ErrorStack err; Config config; if (! config.readYAML(":/data/anytone_channel_data_ack.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->count(), 2); QVERIFY(config.channelList()->channel(0)->is()); QCOMPARE(config.channelList()->channel(0)->as() ->extended()->dataConfirm(), false); QVERIFY(config.channelList()->channel(1)->is()); QCOMPARE(config.channelList()->channel(1)->as() ->extended()->dataConfirm(), true); Codeplug::Flags flags; flags.setUpdateCodeplug(false); D868UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D578UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(testConfig.channelList()->channel(0)->as() ->extended()->dataConfirm(), false); QCOMPARE(testConfig.channelList()->channel(1)->as() ->extended()->dataConfirm(), true); } void D868UVETest::testMicGain() { ErrorStack err; Config copy, config; config.copy(_basicConfig); QList> pairs = {{1,1}, {2,1}, {3,1}, {4,3}, {5,3}, {6,5}, {7,5}, {8,7}, {9,7}, {10,10}}; for (auto pair: pairs) { config.settings()->audio()->setMicGain(Level::fromValue(pair.first)); encodeDecode(config, copy); QCOMPARE(copy.settings()->audio()->micGain().value(), Level::fromValue(pair.second).value()); } } QTEST_GUILESS_MAIN(D868UVETest) ================================================ FILE: test/d868uve_test.hh ================================================ #ifndef D868UVETEST_HH #define D868UVETEST_HH #include "libdmrconfigtest.hh" class D868UVETest : public UnitTestBase { Q_OBJECT public: explicit D868UVETest(QObject *parent = nullptr); protected: void encodeDecode(Config &input, Config &output); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testAutoRepeaterOffset(); void testDTMFContacts(); void testSMSTemplates(); void testBasicEncryption(); void testRegressionSMSTemplateOffset(); void testRegressionSMSCount(); void testRegressionDefaultChannel(); void testChannelDataACK(); ///< Regression test for #813 void testMicGain(); }; #endif // D878UV2TEST_HH ================================================ FILE: test/d878uv2_test.cc ================================================ #include "d878uv2_test.hh" #include "config.hh" #include "d878uv2.hh" #include "d878uv2_codeplug.hh" #include "errorstack.hh" #include #include D878UV2Test::D878UV2Test(QObject *parent) : UnitTestBase(parent) { // pass... } void D878UV2Test::testBasicConfigEncoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D878UV2Codeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UVII: %1") .arg(err.format()).toStdString().c_str()); } } void D878UV2Test::testBasicConfigDecoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D878UV2Codeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UVII: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UVII: %1") .arg(err.format()).toStdString().c_str()); } } void D878UV2Test::testChannelFrequency() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D868UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D878UV II: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D878UV II: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void D878UV2Test::testKeyFunctions() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/anytone_key_function.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } // Check config QVERIFY2(config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); AnytoneKeySettingsExtension *ext = config.settings()->anytoneExtension()->keySettings(); QCOMPARE(ext->funcKey1Short(), AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel); QCOMPARE(ext->funcKey1Long(), AnytoneKeySettingsExtension::KeyFunction::GPS); QCOMPARE(ext->funcKey2Short(), AnytoneKeySettingsExtension::KeyFunction::ToggleVFO); QCOMPARE(ext->funcKey2Long(), AnytoneKeySettingsExtension::KeyFunction::ChannelType); QCOMPARE(ext->funcKeyAShort(), AnytoneKeySettingsExtension::KeyFunction::Off); QCOMPARE(ext->funcKeyALong(), AnytoneKeySettingsExtension::KeyFunction::Encryption); QCOMPARE(ext->funcKeyBShort(), AnytoneKeySettingsExtension::KeyFunction::Voltage); QCOMPARE(ext->funcKeyBLong(), AnytoneKeySettingsExtension::KeyFunction::Call); QCOMPARE(ext->funcKeyBShort(), AnytoneKeySettingsExtension::KeyFunction::Voltage); QCOMPARE(ext->funcKeyBLong(), AnytoneKeySettingsExtension::KeyFunction::Call); // Encode D878UV2Codeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UVE: %1") .arg(err.format()).toStdString().c_str()); } // Decode Config comp_config; if (! codeplug.decode(&comp_config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UVII: %1") .arg(err.format()).toStdString().c_str()); } // Check config QVERIFY2(comp_config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); ext = comp_config.settings()->anytoneExtension()->keySettings(); QCOMPARE(ext->funcKey1Short(), AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel); QCOMPARE(ext->funcKey1Long(), AnytoneKeySettingsExtension::KeyFunction::GPS); QCOMPARE(ext->funcKey2Short(), AnytoneKeySettingsExtension::KeyFunction::ToggleVFO); QCOMPARE(ext->funcKey2Long(), AnytoneKeySettingsExtension::KeyFunction::ChannelType); QCOMPARE(ext->funcKeyAShort(), AnytoneKeySettingsExtension::KeyFunction::Off); QCOMPARE(ext->funcKeyALong(), AnytoneKeySettingsExtension::KeyFunction::Encryption); QCOMPARE(ext->funcKeyBShort(), AnytoneKeySettingsExtension::KeyFunction::Voltage); QCOMPARE(ext->funcKeyBLong(), AnytoneKeySettingsExtension::KeyFunction::Call); QCOMPARE(ext->funcKeyBShort(), AnytoneKeySettingsExtension::KeyFunction::Voltage); QCOMPARE(ext->funcKeyBLong(), AnytoneKeySettingsExtension::KeyFunction::Call); } void D878UV2Test::testAESEncryption() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/aes_encryption.yaml", err)) { QFAIL(QString("Cannot open codeplug file:\n%1") .arg(err.format(" ")).toStdString().c_str()); } D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } // Verify existence of key QVERIFY(nullptr != decoded.commercialExtension()); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->count(), 1); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->key(0)->key(), QByteArray::fromHex("11223344556677889900AABBCCDDEEFF")); // Verify link to channel QVERIFY(nullptr != decoded.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(decoded.channelList()->channel(0)->as()->commercialExtension()->encryptionKey(), decoded.commercialExtension()->encryptionKeys()->key(0)); } QTEST_GUILESS_MAIN(D878UV2Test) ================================================ FILE: test/d878uv2_test.hh ================================================ #ifndef D878UV2TEST_HH #define D878UV2TEST_HH #include "libdmrconfigtest.hh" class D878UV2Test : public UnitTestBase { Q_OBJECT public: explicit D878UV2Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testKeyFunctions(); void testAESEncryption(); }; #endif // D878UV2TEST_HH ================================================ FILE: test/d878uv_test.cc ================================================ #include "d878uv_test.hh" #include "config.hh" #include "d878uv_codeplug.hh" #include "d878uv_limits.hh" #include "errorstack.hh" #include #include "logger.hh" D878UVTest::D878UVTest(QObject *parent) : UnitTestBase(parent), _stderr(stderr) { Logger::get().addHandler(new StreamLogHandler(_stderr, LogMessage::DEBUG)); } void D878UVTest::encodeDecode(Config &input, Config &output) { ErrorStack err; D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&input, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } if (! codeplug.decode(&output, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } } void D878UVTest::initTestCase() { UnitTestBase::initTestCase(); ErrorStack err; if (! _micGainConfig.readYAML(":/data/audio_settings_extension.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } } void D878UVTest::testBasicConfigEncoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D878UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } } void D878UVTest::testBasicConfigDecoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D878UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } } void D878UVTest::testAnalogMicGain() { Config config; encodeDecode(_micGainConfig, config); QVERIFY(config.settings()->audio()->fmMicGainEnabled()); QCOMPARE(config.settings()->audio()->fmMicGain(), Level::fromValue(5)); } void D878UVTest::testChannelFrequency() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D868UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D878UV: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D878UV: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void D878UVTest::testRoaming() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D878UVCodeplug codeplug; if (! codeplug.encode(&_roamingConfig, flags, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D878UV: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.roamingChannels()->count(), 3); QCOMPARE(config.roamingZones()->count(), 2); QCOMPARE(config.roamingZones()->count(), 2); QCOMPARE(config.roamingZones()->get(0)->as()->count(), 2); QCOMPARE(config.roamingZones()->get(0)->as()->channel(0), config.roamingChannels()->get(0)->as()); QCOMPARE(config.roamingZones()->get(0)->as()->channel(1), config.roamingChannels()->get(1)->as()); QCOMPARE(config.roamingZones()->get(1)->as()->count(), 2); QCOMPARE(config.roamingZones()->get(1)->as()->channel(0), config.roamingChannels()->get(0)->as()); QCOMPARE(config.roamingZones()->get(1)->as()->channel(1), config.roamingChannels()->get(2)->as()); } void D878UVTest::testHangTime() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/anytone_call_hangtime.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } // Check config QVERIFY2(config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); QCOMPARE(config.settings()->dmr()->privateCallHangTime().seconds(), 4ULL); QCOMPARE(config.settings()->dmr()->groupCallHangTime().seconds(), 5ULL); // Encode D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); Config *intermediate = codeplug.preprocess(&config, err); if (nullptr == intermediate) { QFAIL(QString("Cannot prepare codeplug for AnyTone AT-D878UVE: %1") .arg(err.format()).toStdString().c_str()); } if (! codeplug.encode(intermediate, flags, err)) { delete intermediate; QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UVE: %1") .arg(err.format()).toStdString().c_str()); } delete intermediate; // Decode Config comp_config; if (! codeplug.decode(&comp_config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UVII: %1") .arg(err.format()).toStdString().c_str()); } // Check config QVERIFY2(comp_config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); QCOMPARE(config.settings()->dmr()->privateCallHangTime().seconds(), 4ULL); QCOMPARE(config.settings()->dmr()->groupCallHangTime().seconds(), 5ULL); } void D878UVTest::testKeyFunctions() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/anytone_key_function.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } // Check config QVERIFY2(config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); AnytoneKeySettingsExtension *ext = config.settings()->anytoneExtension()->keySettings(); QCOMPARE(ext->funcKey1Short(), AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel); QCOMPARE(ext->funcKey1Long(), AnytoneKeySettingsExtension::KeyFunction::GPS); QCOMPARE(ext->funcKey2Short(), AnytoneKeySettingsExtension::KeyFunction::ToggleVFO); QCOMPARE(ext->funcKey2Long(), AnytoneKeySettingsExtension::KeyFunction::ChannelType); QCOMPARE(ext->funcKeyAShort(), AnytoneKeySettingsExtension::KeyFunction::Off); QCOMPARE(ext->funcKeyALong(), AnytoneKeySettingsExtension::KeyFunction::Encryption); QCOMPARE(ext->funcKeyBShort(), AnytoneKeySettingsExtension::KeyFunction::Voltage); QCOMPARE(ext->funcKeyBLong(), AnytoneKeySettingsExtension::KeyFunction::Call); QCOMPARE(ext->funcKeyBShort(), AnytoneKeySettingsExtension::KeyFunction::Voltage); QCOMPARE(ext->funcKeyBLong(), AnytoneKeySettingsExtension::KeyFunction::Call); // Encode D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D868UV: %1") .arg(err.format()).toStdString().c_str()); } // Decode Config comp_config; if (! codeplug.decode(&comp_config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } // Check config QVERIFY2(comp_config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); ext = comp_config.settings()->anytoneExtension()->keySettings(); QCOMPARE(ext->funcKey1Short(), AnytoneKeySettingsExtension::KeyFunction::ToggleMainChannel); QCOMPARE(ext->funcKey1Long(), AnytoneKeySettingsExtension::KeyFunction::GPS); QCOMPARE(ext->funcKey2Short(), AnytoneKeySettingsExtension::KeyFunction::ToggleVFO); QCOMPARE(ext->funcKey2Long(), AnytoneKeySettingsExtension::KeyFunction::ChannelType); QCOMPARE(ext->funcKeyAShort(), AnytoneKeySettingsExtension::KeyFunction::Off); QCOMPARE(ext->funcKeyALong(), AnytoneKeySettingsExtension::KeyFunction::Encryption); QCOMPARE(ext->funcKeyBShort(), AnytoneKeySettingsExtension::KeyFunction::Voltage); QCOMPARE(ext->funcKeyBLong(), AnytoneKeySettingsExtension::KeyFunction::Call); QCOMPARE(ext->funcKeyBShort(), AnytoneKeySettingsExtension::KeyFunction::Voltage); QCOMPARE(ext->funcKeyBLong(), AnytoneKeySettingsExtension::KeyFunction::Call); } void D878UVTest::testFMAPRSSettings() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/fm_aprs_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1\n") .arg(err.format()).toStdString().c_str()); } // Check config QCOMPARE(config.posSystems()->count(), 1); QVERIFY(config.posSystems()->get(0)->is()); FMAPRSSystem *aprs = config.posSystems()->get(0)->as(); QCOMPARE(aprs->source(), "DM3MAT"); QCOMPARE(aprs->srcSSID(), 7); QCOMPARE(aprs->destination(), "APAT81"); QCOMPARE(aprs->destSSID(), 0); QCOMPARE(aprs->path(), "WIDE1-1,WIDE2-1"); QCOMPARE(aprs->period(), Interval::fromMinutes(5)); // Encode D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); Config *intermediate = codeplug.preprocess(&config, err); if (nullptr == intermediate) { QFAIL(QString("Cannot prepare codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } if (! codeplug.encode(intermediate, flags, err)) { delete intermediate; QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } delete intermediate; // Decode Config comp_config; if (! codeplug.decode(&comp_config, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } // Check config QCOMPARE(comp_config.posSystems()->count(), 1); QVERIFY(comp_config.posSystems()->get(0)->is()); FMAPRSSystem *comp_aprs = comp_config.posSystems()->get(0)->as(); QCOMPARE(comp_aprs->source(), aprs->source()); QCOMPARE(comp_aprs->srcSSID(), aprs->srcSSID()); QCOMPARE(comp_aprs->destination(), aprs->destination()); QCOMPARE(comp_aprs->destSSID(), aprs->destSSID()); QCOMPARE(comp_aprs->path(), aprs->path()); QCOMPARE(comp_aprs->period(), aprs->period()); } void D878UVTest::testAESEncryption() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/aes_encryption.yaml", err)) { QFAIL(QString("Cannot open codeplug file:\n%1") .arg(err.format(" ")).toStdString().c_str()); } D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } // Verify existence of key QVERIFY(nullptr != decoded.commercialExtension()); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->count(), 1); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->key(0)->key(), QByteArray::fromHex("11223344556677889900AABBCCDDEEFF")); // Verify link to channel QVERIFY(nullptr != decoded.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(decoded.channelList()->channel(0)->as()->commercialExtension()->encryptionKey(), decoded.commercialExtension()->encryptionKeys()->key(0)); } void D878UVTest::testARC4Encryption() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/arc4_encryption.yaml", err)) { QFAIL(QString("Cannot open codeplug file:\n%1") .arg(err.format(" ")).toStdString().c_str()); } D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } // Verify existence of key QVERIFY(nullptr != decoded.commercialExtension()); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->count(), 1); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->key(0)->key(), QByteArray::fromHex("1122334455")); // Verify link to channel QVERIFY(nullptr != decoded.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(decoded.channelList()->channel(0)->as()->commercialExtension()->encryptionKey(), decoded.commercialExtension()->encryptionKeys()->key(0)); } void D878UVTest::testRegressionDefaultChannel() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } config.settings()->setAnytoneExtension(new AnytoneSettingsExtension()); config.settings()->boot()->zoneA()->set(config.zones()->zone(0)); config.settings()->boot()->channelA()->set(config.zones()->zone(0)->A()->get(0)); config.settings()->boot()->zoneB()->set(config.zones()->zone(1)); config.settings()->boot()->channelB()->set(config.zones()->zone(1)->A()->get(0)); config.settings()->boot()->enableDefaultChannel(true); D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } QVERIFY(decoded.settings()->boot()->defaultChannelEnabled()); QCOMPARE(decoded.settings()->boot()->zoneA()->as(), decoded.zones()->zone(0)); QCOMPARE(decoded.settings()->boot()->channelA()->as()->name(), decoded.zones()->zone(0)->A()->get(0)->name()); QCOMPARE(decoded.settings()->boot()->zoneB()->as(), decoded.zones()->zone(1)); QCOMPARE(decoded.settings()->boot()->channelB()->as()->name(), decoded.zones()->zone(1)->A()->get(0)->name()); } void D878UVTest::testRegressionAutoRepeater() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } config.settings()->setAnytoneExtension(new AnytoneSettingsExtension()); D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } QVERIFY(decoded.settings()->anytoneExtension()); QCOMPARE(decoded.settings()->anytoneExtension()->autoRepeaterSettings()->vhf2Min().inMHz(), 136.0); QCOMPARE(decoded.settings()->anytoneExtension()->autoRepeaterSettings()->vhf2Max().inMHz(), 174.0); QCOMPARE(decoded.settings()->anytoneExtension()->autoRepeaterSettings()->uhf2Min().inMHz(), 400.0); QCOMPARE(decoded.settings()->anytoneExtension()->autoRepeaterSettings()->uhf2Max().inMHz(), 480.0); } void D878UVTest::testRegressionVFOStep() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } config.settings()->setAnytoneExtension(new AnytoneSettingsExtension()); config.settings()->anytoneExtension()->setVFOStep(Frequency::fromkHz(8.33)); D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } QVERIFY(decoded.settings()->anytoneExtension()); QCOMPARE(decoded.settings()->anytoneExtension()->vfoStep().inkHz(), 8.33); } void D878UVTest::testEmptyAESKey() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } // Empty encryption key config.commercialExtension()->encryptionKeys()->add(new AESEncryptionKey()); D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone AT-D878UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(decoded.commercialExtension()->encryptionKeys()->count(), 0); } void D878UVTest::testChannelDataACK() { ErrorStack err; Config config; if (! config.readYAML(":/data/anytone_channel_data_ack.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->count(), 2); QVERIFY(config.channelList()->channel(0)->is()); QCOMPARE(config.channelList()->channel(0)->as() ->extended()->dataConfirm(), false); QVERIFY(config.channelList()->channel(1)->is()); QCOMPARE(config.channelList()->channel(1)->as() ->extended()->dataConfirm(), true); Codeplug::Flags flags; flags.setUpdateCodeplug(false); D878UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D878UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(testConfig.channelList()->channel(0)->as() ->extended()->dataConfirm(), false); QCOMPARE(testConfig.channelList()->channel(1)->as() ->extended()->dataConfirm(), true); } void D878UVTest::testSettingsDisplayVolumeChangePrompt() { ErrorStack err; Config config; if (! config.readYAML(":/data/anytone_settings_display.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.settings()->anytoneExtension()->displaySettings()->volumeChangePromptEnabled(), false); Codeplug::Flags flags; flags.setUpdateCodeplug(false); D878UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for AnyTone D878UV: %1") .arg(err.format()).toStdString().c_str()); } Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for AnyTone D878UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(testConfig.settings()->anytoneExtension()->displaySettings()->volumeChangePromptEnabled(), false); } void D878UVTest::testRadioLimits() { D878UVLimits limits({{Frequency::fromMHz(137),Frequency::fromMHz(150)}, {Frequency::fromMHz(400),Frequency::fromMHz(450)}}, {{Frequency::fromMHz(137),Frequency::fromMHz(150)}, {Frequency::fromMHz(400),Frequency::fromMHz(450)}}, "V101"); RadioLimitContext ctx; if (! limits.verifyConfig(&_basicConfig, ctx)) { QString issues; for (int i=0; i> pairs = {{1,1}, {2,1}, {3,1}, {4,3}, {5,3}, {6,5}, {7,5}, {8,7}, {9,7}, {10,10}}; for (auto pair: pairs) { config.settings()->audio()->setMicGain(Level::fromValue(pair.first)); encodeDecode(config, copy); QCOMPARE(copy.settings()->audio()->micGain(), Level::fromValue(pair.second)); } } void D878UVTest::testFixedLocation() { ErrorStack err; Config decoded, config; config.copy(_basicConfig); config.settings()->gnss()->setFixedPositionLocator("JO62kk45"); config.settings()->gnss()->enableFixedPosition(true); encodeDecode(config, decoded); QVERIFY(decoded.settings()->gnss()->fixedPositionEnabled()); QCOMPARE(decoded.settings()->gnss()->fixedPositionLocator(), QString("JO62kk45")); } QTEST_GUILESS_MAIN(D878UVTest) ================================================ FILE: test/d878uv_test.hh ================================================ #ifndef D878UVTEST_HH #define D878UVTEST_HH #include "libdmrconfigtest.hh" class D878UVTest : public UnitTestBase { Q_OBJECT public: explicit D878UVTest(QObject *parent = nullptr); protected: void encodeDecode(Config &input, Config &output); private slots: void initTestCase(); void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testAnalogMicGain(); void testRoaming(); void testHangTime(); void testKeyFunctions(); void testFMAPRSSettings(); void testAESEncryption(); void testARC4Encryption(); void testRegressionDefaultChannel(); void testRegressionAutoRepeater(); void testRegressionVFOStep(); void testEmptyAESKey(); ///< Regression test for #711 void testChannelDataACK(); ///< Regression test for #813 void testSettingsDisplayVolumeChangePrompt(); ///< Regression test for #813 void testRadioLimits(); ///< Regression test for #816 void testMicGain(); void testFixedLocation(); protected: Config _micGainConfig; QTextStream _stderr; }; #endif // D878UVTEST_HH ================================================ FILE: test/data/aes_encryption.yaml ================================================ --- version: 0.13.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: BB, ring: false, type: GroupCall, number: 2621} groupLists: - {id: grp1, name: Local, contacts: [cont1]} channels: - dmr: id: ch1 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 commercial: encryptionKey: key1 scanLists: - id: scan1 name: KW channels: [ch1] zones: - id: zone1 name: Zu Hause A: [ch1] commercial: encryptionKeys: - aes: id: key1 name: AES Key 1 key: 11223344556677889900AABBCCDDEEFF ... ================================================ FILE: test/data/am_channel_test.yaml ================================================ --- version: 0.14.0 settings: introLine1: "" introLine2: "" micLevel: 3 speech: false power: High squelch: 3 vox: off tot: infinite defaultID: id1 radioIDs: - dmr: {id: id1, name: Test, number: 12345} contacts: - dmr: {id: cont1, name: Test Contact, ring: false, type: PrivateCall, number: 123456} groupLists: [] channels: - am: id: ch1 name: AM Channel rxOnly: true admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow rxFrequency: 121.13 MHz txFrequency: ~ power: ! "" timeout: ! "" vox: ~ squelch: ! "" - fm: id: ch2 name: 2m Mobil rxOnly: false admit: Always rxTone: ~ txTone: ~ bandwidth: Narrow rxFrequency: 145.5 MHz txFrequency: 145.5 MHz power: ! "" timeout: ! "" vox: ~ squelch: ! "" zones: - id: zone1 name: Local A: [ch1, ch2] B: [] commercial: encryptionKeys: [] sms: format: DMR templates: [] ... ================================================ FILE: test/data/anytone_auto_repeater_extension.yaml ================================================ --- version: 0.9.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 anytone: autoRepeaterSettings: directionA: Negative directionB: Negative vhf: off0 uhf: off1 offsets: - {id: off0, name: VHF Offset, offset: 600 kHz} - {id: off1, name: UHF Offset, offset: 7.6 MHz} radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} groupLists: - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} - {id: grp2, name: DL, contacts: [cont4]} - {id: grp3, name: HamRadioVillage, contacts: [cont6]} channels: - dmr: id: ch1 name: L9 DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 - dmr: id: ch2 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont3 aprs: aprs1 power: High timeout: 0 vox: 0 - dmr: id: ch3 name: DL DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp2 contact: cont4 power: High timeout: 0 vox: 0 - dmr: id: ch4 name: HRV DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp3 contact: cont6 power: High timeout: 0 vox: 0 zones: - id: zone1 name: Zu Hause A: [ch1, ch2, ch4] B: [ch3] positioning: - dmr: id: aprs1 name: GPS System period: 300 contact: cont5 ... ================================================ FILE: test/data/anytone_call_hangtime.yaml ================================================ --- version: 0.9.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 dmr: privateCallHangTime: 4s groupCallHangTime: 5s anytone: dmrSettings: manualPrivateCallHangTime: 4s manualGroupCallHangTime: 5s radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} groupLists: - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} - {id: grp2, name: DL, contacts: [cont4]} - {id: grp3, name: HamRadioVillage, contacts: [cont6]} channels: - dmr: id: ch1 name: L9 DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 - dmr: id: ch2 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont3 aprs: aprs1 power: High timeout: 0 vox: 0 - dmr: id: ch3 name: DL DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp2 contact: cont4 power: High timeout: 0 vox: 0 - dmr: id: ch4 name: HRV DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp3 contact: cont6 power: High timeout: 0 vox: 0 zones: - id: zone1 name: Zu Hause A: [ch1, ch2, ch4] B: [ch3] positioning: - dmr: id: aprs1 name: GPS System period: 300 contact: cont5 ... ================================================ FILE: test/data/anytone_channel_data_ack.yaml ================================================ radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} channels: - dmr: id: ch1 name: "Test Channel" rxFrequency: 123.456 Mhz txFrequency: 123.456 Mhz extended: dataConfirm: false - dmr: id: ch2 name: "Test Channel 2" rxFrequency: 123.456 Mhz txFrequency: 123.456 Mhz extended: dataConfirm: true zones: - id: zone1 name: Test Zone A: [ch1, ch2] B: [] ================================================ FILE: test/data/anytone_key_function.yaml ================================================ --- version: 0.11.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 anytone: keySettings: funcKey1Short: ToggleMainChannel funcKey1Long: GPS funcKey2Short: ToggleVFO funcKey2Long: ChannelType funcKeyAShort: Off funcKeyALong: Encryption funcKeyBShort: Voltage funcKeyBLong: Call funcKeyCShort: Power funcKeyCLong: VOX radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} groupLists: - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} - {id: grp2, name: DL, contacts: [cont4]} - {id: grp3, name: HamRadioVillage, contacts: [cont6]} channels: - dmr: id: ch1 name: L9 DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 - dmr: id: ch2 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont3 aprs: aprs1 power: High timeout: 0 vox: 0 - dmr: id: ch3 name: DL DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp2 contact: cont4 power: High timeout: 0 vox: 0 - dmr: id: ch4 name: HRV DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp3 contact: cont6 power: High timeout: 0 vox: 0 zones: - id: zone1 name: Zu Hause A: [ch1, ch2, ch4] B: [ch3] positioning: - dmr: id: aprs1 name: GPS System period: 300 contact: cont5 ... ================================================ FILE: test/data/anytone_settings_display.yaml ================================================ settings: anytone: displaySettings: volumeChangePrompt: false ================================================ FILE: test/data/anytone_settings_roaming.yaml ================================================ settings: anytone: roaming: notificationCount: 1 ================================================ FILE: test/data/arc4_encryption.yaml ================================================ --- version: 0.13.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: BB, ring: false, type: GroupCall, number: 2621} groupLists: - {id: grp1, name: Local, contacts: [cont1]} channels: - dmr: id: ch1 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 commercial: encryptionKey: key1 scanLists: - id: scan1 name: KW channels: [ch1] zones: - id: zone1 name: Zu Hause A: [ch1] commercial: encryptionKeys: - rc4: id: key1 name: ARC4 Key 1 key: 1122334455 ... ================================================ FILE: test/data/audio_settings_extension.yaml ================================================ --- version: 0.15.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 audio: fmMicGain: 6 # Must differ from settings->micLevel radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} groupLists: - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} - {id: grp2, name: DL, contacts: [cont4]} - {id: grp3, name: HamRadioVillage, contacts: [cont6]} channels: - dmr: id: ch1 name: L9 DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 - dmr: id: ch2 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont3 aprs: aprs1 power: High timeout: 0 vox: 0 - dmr: id: ch3 name: DL DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp2 contact: cont4 power: High timeout: 0 vox: 0 - dmr: id: ch4 name: HRV DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp3 contact: cont6 power: High timeout: 0 vox: 0 zones: - id: zone1 name: Zu Hause A: [ch1, ch2, ch4] B: [ch3] positioning: - dmr: id: aprs1 name: GPS System period: 300 contact: cont5 ... ================================================ FILE: test/data/basic_encryption.yaml ================================================ --- version: 0.13.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: BB, ring: false, type: GroupCall, number: 2621} groupLists: - {id: grp1, name: Local, contacts: [cont1]} channels: - dmr: id: ch1 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 commercial: encryptionKey: key1 scanLists: - id: scan1 name: KW channels: [ch1] zones: - id: zone1 name: Zu Hause A: [ch1] commercial: encryptionKeys: - dmr: id: key1 name: DMR Key 1 key: 0123 ... ================================================ FILE: test/data/channel_frequency_test.yaml ================================================ radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} channels: - dmr: id: ch1 name: "Test Channel" rxFrequency: 123.4567801 MHz # <- Up to 10Hz precision -> 123.45678 MHz txFrequency: 999.99999 MHz # <- Max frequency for 8-digit BCD in 10Hz ================================================ FILE: test/data/chirp_bandwidth.csv ================================================ Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossMode,Mode,TStep,Skip,Power,Comment,URCALL,RPT1CALL,RPT2CALL,DVCODE 0,KD8BMI,147.075000,+,0.600000,Tone,103.5,77.0,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, 1,KD8BMI,147.075000,+,0.600000,Tone,103.5,77.0,023,NN,023,Tone->Tone,NFM,5.00,,50W,,,,, ================================================ FILE: test/data/chirp_cross.csv ================================================ Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossMode,Mode,TStep,Skip,Power,Comment,URCALL,RPT1CALL,RPT2CALL,DVCODE 0,DB0SP,144.600000,-,0.600000,Cross,67.0,67.0,023,NN,023,->Tone,FM,5.00,,50W,,,,, 1,DB0SP,144.600000,-,0.600000,Cross,67.0,67.0,023,NN,023,->DTCS,FM,5.00,,50W,,,,, 2,DB0SP,144.600000,-,0.600000,Cross,67.0,67.0,023,NN,023,Tone->,FM,5.00,,50W,,,,, 3,DB0SP,144.600000,-,0.600000,Cross,67.0,77.0,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, 4,DB0SP,144.600000,-,0.600000,Cross,67.0,77.0,023,NN,023,Tone->DTCS,FM,5.00,,50W,,,,, 5,DB0SP,144.600000,-,0.600000,Cross,67.0,77.0,023,NN,023,DTCS->,FM,5.00,,50W,,,,, 6,DB0SP,144.600000,-,0.600000,Cross,67.0,67.0,023,NN,023,DTCS->Tone,FM,5.00,,50W,,,,, 7,DB0SP,144.600000,-,0.600000,Cross,67.0,77.0,023,NN,032,DTCS->DTCS,FM,5.00,,50W,,,,, ================================================ FILE: test/data/chirp_ctcss.csv ================================================ Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossMode,Mode,TStep,Skip,Power,Comment,URCALL,RPT1CALL,RPT2CALL,DVCODE 0,DB0SP,144.600000,+,0.600000,,88.5,88.5,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, 1,DB0SP,144.600000,+,0.600000,Tone,67.0,88.5,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, 2,DB0SP,144.600000,+,0.600000,TSQL,67.0,77.0,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, ================================================ FILE: test/data/chirp_dcs.csv ================================================ Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossMode,Mode,TStep,Skip,Power,Comment,URCALL,RPT1CALL,RPT2CALL,DVCODE 0,DB0SP,144.600000,+,0.600000,DTCS,88.5,88.5,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, 1,DB0SP,144.600000,+,0.600000,DTCS,88.5,88.5,023,NR,023,Tone->Tone,FM,5.00,,50W,,,,, 2,DB0SP,144.600000,+,0.600000,DTCS,88.5,88.5,023,RN,023,Tone->Tone,FM,5.00,,50W,,,,, 3,DB0SP,144.600000,+,0.600000,DTCS,88.5,88.5,023,RR,023,Tone->Tone,FM,5.00,,50W,,,,, ================================================ FILE: test/data/chirp_simple.csv ================================================ Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossMode,Mode,TStep,Skip,Power,Comment,URCALL,RPT1CALL,RPT2CALL,DVCODE 0,KD8BMI,147.075000,+,0.600000,Tone,103.5,77.0,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, 1,W8CUL,146.760000,-,0.600000,Tone,103.5,103.5,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, 2,W8MWA,145.430000,-,0.600000,Tone,103.5,103.5,023,NN,023,Tone->Tone,FM,5.00,,50W,,,,, ================================================ FILE: test/data/config_test.yaml ================================================ --- version: 0.9.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} groupLists: - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} - {id: grp2, name: DL, contacts: [cont4]} - {id: grp3, name: HamRadioVillage, contacts: [cont6]} channels: - dmr: id: ch1 name: L9 DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 - dmr: id: ch2 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont3 aprs: aprs1 power: High timeout: 0 vox: 0 - dmr: id: ch3 name: DL DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp2 contact: cont4 power: High timeout: 0 vox: 0 - dmr: id: ch4 name: HRV DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp3 contact: cont6 power: High timeout: 0 vox: 0 scanLists: - id: scan1 name: KW channels: [ch1, ch2, ch3] zones: - id: zone1 name: Zu Hause A: [ch1, ch2, ch4] B: [ch3] - id: zone2 name: Zu Hause2 A: [ch3, ch2, ch4] B: [ch1] roamingChannels: - id: rc1 name: R DB0LDS rxFrequency: 439.5625 MHz txFrequency: 431.9625 MHz colorCode: 1 roamingZones: - id: roam1 name: Berlin/Brand channels: - rc1 positioning: - dmr: id: aprs1 name: GPS System period: 300 contact: cont5 ... ================================================ FILE: test/data/ctcss_copy_test.yaml ================================================ --- version: 0.12.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} groupLists: - {id: grp1, name: Local, contacts: [cont1]} channels: - fm: id: ch1 name: DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always rxTone: {ctcss: 67} txTone: {ctcss: 67} power: High timeout: 0 vox: 0 scanLists: - id: scan1 name: KW channels: [ch1] zones: - id: zone1 name: Zu Hause A: [ch1] B: [] ... ================================================ FILE: test/data/ctcss_null_test.yaml ================================================ --- version: 0.12.1 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} groupLists: - {id: grp1, name: Local, contacts: [cont1]} channels: - fm: id: ch1 name: DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always rxTone: ~ txTone: {ctcss: 67} power: High timeout: 0 vox: 0 - fm: id: ch2 name: DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always rxTone: {dcs: i037} txTone: {ctcss: 67.0 Hz} power: High timeout: 0 vox: 0 scanLists: - id: scan1 name: KW channels: [ch1] zones: - id: zone1 name: Zu Hause A: [ch1] B: [] ... ================================================ FILE: test/data/dtmf_contact.yaml ================================================ --- version: 0.13.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dtmf: {id: cont1, name: DTMF, ring: false, number: "*ABC123#"} channels: - fm: id: ch1 name: DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always rxTone: ~ txTone: {ctcss: 67} power: High timeout: 0 vox: 0 zones: - id: zone1 name: Zu Hause A: [ch1] B: [] ... ================================================ FILE: test/data/fm_aprs_test.yaml ================================================ --- version: 0.9.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} channels: - fm: id: aprs_ch name: 2m APRS rxFrequency: 145.8 MHz txFrequency: 145.8 MHz rxOnly: false admit: Always power: High timeout: 0 vox: 0 - fm: id: ch1 name: Some FM Channel rxFrequency: 145.1 MHz txFrequency: 145.1 MHz rxOnly: false admit: Always power: High timeout: 0 vox: 0 aprs: aprs zones: - id: zone1 name: Test A: [aprs_ch] B: [] positioning: - aprs: id: aprs name: APRS APAT81 period: 300 revert: aprs_ch icon: Jogger message: test message destination: APAT81-0 source: DM3MAT-7 path: [WIDE1-1, WIDE2-1] ... ================================================ FILE: test/data/multiple_radio_ids.yaml ================================================ --- version: 0.9.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} - dmr: {id: id2, name: DN3MAT, number: 2621371} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} groupLists: - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} - {id: grp2, name: DL, contacts: [cont4]} - {id: grp3, name: HamRadioVillage, contacts: [cont6]} channels: - dmr: id: ch1 name: L9 DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 - dmr: id: ch2 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont3 aprs: aprs1 power: High timeout: 0 vox: 0 - dmr: id: ch3 name: DL DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp2 contact: cont4 power: High timeout: 0 vox: 0 - dmr: id: ch4 name: HRV DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp3 contact: cont6 power: High timeout: 0 vox: 0 scanLists: - id: scan1 name: KW channels: [ch1, ch2, ch3] zones: - id: zone1 name: Zu Hause A: [ch1, ch2, ch4] B: [ch3] roamingChannels: - id: rc1 name: R DB0LDS rxFrequency: 439.5625 txFrequency: 431.96249999999998 colorCode: 1 roamingZones: - id: roam1 name: Berlin/Brand channels: - rc1 positioning: - dmr: id: aprs1 name: GPS System period: 300 contact: cont5 ... ================================================ FILE: test/data/opengd77_boot_melody.yaml ================================================ --- # Minimal settings +boot tone settings: defaultID: id1 tone: bootTone: true bootMelody: bpm: 100 melody: c4 e g c e g c e g c e g c4 d e g c1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} ... ================================================ FILE: test/data/opengd77_simple_config.yaml ================================================ version: 0.14.0 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} channels: - fm: id: ch1 name: "Test Channel" rxFrequency: 144.8 MHz txFrequency: 144.8 MHz ================================================ FILE: test/data/roaming_channel_test.yaml ================================================ --- version: 0.9.0 settings: introLine1: DM3MAT introLine2: qDMR micLevel: 3 speech: false power: High squelch: 1 vox: 0 tot: 0 defaultID: id1 radioIDs: - dmr: {id: id1, name: DM3MAT, number: 2621370} contacts: - dmr: {id: cont1, name: Local, ring: false, type: GroupCall, number: 9} - dmr: {id: cont2, name: Regional, ring: false, type: GroupCall, number: 8} - dmr: {id: cont3, name: BB, ring: false, type: GroupCall, number: 2621} - dmr: {id: cont4, name: DL, ring: false, type: GroupCall, number: 262} - dmr: {id: cont5, name: BM APRS, ring: false, type: PrivateCall, number: 262999} - dmr: {id: cont6, name: HamRadioVillage, ring: false, type: GroupCall, number: 3177826} groupLists: - {id: grp1, name: Local, contacts: [cont1, cont2, cont3]} - {id: grp2, name: DL, contacts: [cont4]} - {id: grp3, name: HamRadioVillage, contacts: [cont6]} channels: - dmr: id: ch1 name: L9 DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont1 power: High timeout: 0 vox: 0 - dmr: id: ch2 name: BB DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS2 groupList: grp1 contact: cont3 aprs: aprs1 power: High timeout: 0 vox: 0 - dmr: id: ch3 name: DL DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp2 contact: cont4 power: High timeout: 0 vox: 0 - dmr: id: ch4 name: HRV DB0LDS rxFrequency: 439.5625 txFrequency: 431.9625 rxOnly: false admit: Always colorCode: 1 timeSlot: TS1 groupList: grp3 contact: cont6 power: High timeout: 0 vox: 0 zones: - id: zone1 name: Zu Hause A: [ch1, ch2, ch4] B: [ch3] roamingChannels: - id: rc1 name: R DB0LDS rxFrequency: 439.5625 txFrequency: 431.96249999999998 colorCode: 1 - id: rc2 name: R DM0TZN rxFrequency: 438.82499999999999 txFrequency: 431.22500000000002 colorCode: 1 - id: rc3 name: R DM0TT rxFrequency: 439.08749999999998 txFrequency: 431.48750000000001 colorCode: 1 roamingZones: - id: roam1 name: RZ1 channels: [rc1, rc2] - id: roam2 name: RZ2 channels: [rc1, rc3] positioning: - dmr: id: aprs1 name: GPS System period: 300 contact: cont5 ... ================================================ FILE: test/dm1701_test.cc ================================================ #include "dm1701_test.hh" #include "config.hh" #include "dm1701_codeplug.hh" #include "errorstack.hh" #include #include DM1701Test::DM1701Test(QObject *parent) : UnitTestBase(parent) { // pass... } void DM1701Test::testBasicConfigEncoding() { ErrorStack err; DM1701Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH MD1701: %1") .arg(err.format()).toStdString().c_str()); } } void DM1701Test::testBasicConfigDecoding() { ErrorStack err; DM1701Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM1701: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DM1701: %1") .arg(err.format()).toStdString().c_str()); } } void DM1701Test::testChannelFrequency() { ErrorStack err; DM1701Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM1701: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DM1701: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } QTEST_GUILESS_MAIN(DM1701Test) ================================================ FILE: test/dm1701_test.hh ================================================ #ifndef DM1701TEST_HH #define DM1701TEST_HH #include "libdmrconfigtest.hh" class DM1701Test : public UnitTestBase { Q_OBJECT public: explicit DM1701Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); }; #endif // DM1701TEST_HH ================================================ FILE: test/dm32uv_test.cc ================================================ #include "dm32uv_test.hh" #include "config.hh" #include "dm32uv_codeplug.hh" #include "errorstack.hh" #include "logger.hh" #include DM32UVTest::DM32UVTest(QObject *parent) : UnitTestBase(parent) { } void DM32UVTest::testBasicConfigEncoding() { ErrorStack err; DM32UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } } void DM32UVTest::testBasicConfigDecoding() { ErrorStack err; DM32UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } } void DM32UVTest::testBasicConfigReencoding() { ErrorStack err; DM32UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } Config originalConfig; if (! codeplug.decode(&originalConfig, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } // original Config now contains all default values and extensions // re-encode it if (! codeplug.encode(&originalConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } // Decode complete config Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } // compare if (0 != originalConfig.compare(testConfig)) { QString orig, test; QTextStream origStream(&orig), testStream(&test); originalConfig.toYAML(origStream); testConfig.toYAML(testStream); QFAIL(QString("Decoded config of BTECH DM32UV does not match source:\n%1\n%2").arg(orig).arg(test).toStdString().c_str()); } } void DM32UVTest::testProstProcessingOfEmptyCodeplug() { Config config; DM32UVCodeplug codeplug; ErrorStack err; config.clear(); QVERIFY(codeplug.postprocess(&config, err)); } void DM32UVTest::testAMChannelReencoding() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/am_channel_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } DM32UVCodeplug codeplug; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(testConfig.channelList()->count(), config.channelList()->count()); QVERIFY(testConfig.channelList()->channel(0)->is()); QCOMPARE(testConfig.channelList()->channel(0)->rxFrequency(), Frequency::fromMHz(121.13)); QVERIFY(testConfig.channelList()->channel(0)->txFrequency().isZero()); } void DM32UVTest::testChannelBankEncoding() { static const uint32_t bank0=0x12000, bank1 = 0x13000, bank2 = 0x14000; Config config; config.radioIDs()->add(new DMRRadioID("test", 123456)); // Add 84 channels to fill bank 0 for (int i=0; i<84; i++) { auto ch = new FMChannel(); ch->setName(QString("Channel %1").arg(i)); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.0)); config.channelList()->add(ch); } { DM32UVCodeplug codeplug; ErrorStack err; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } QVERIFY(codeplug.isAllocated(bank0)); QVERIFY(!codeplug.isAllocated(bank1)); QVERIFY(!codeplug.isAllocated(bank2)); } // Add another 85 channel to fill bank 1 for (int i=0; i<85; i++) { auto ch = new FMChannel(); ch->setName(QString("Channel %1").arg(i)); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.0)); config.channelList()->add(ch); } { DM32UVCodeplug codeplug; ErrorStack err; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } QVERIFY(codeplug.isAllocated(bank0)); QVERIFY(codeplug.isAllocated(bank1)); QVERIFY(!codeplug.isAllocated(bank2)); } // Add yet another channel to allocate bank 2 auto ch = new FMChannel(); ch->setName(QString("Channel %1").arg(config.channelList()->count())); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.0)); config.channelList()->add(ch); { DM32UVCodeplug codeplug; ErrorStack err; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DM32UV: %1") .arg(err.format()).toStdString().c_str()); } QVERIFY(codeplug.isAllocated(bank0)); QVERIFY(codeplug.isAllocated(bank1)); QVERIFY(codeplug.isAllocated(bank2)); } } QTEST_GUILESS_MAIN(DM32UVTest) ================================================ FILE: test/dm32uv_test.hh ================================================ #ifndef DR1801TEST_HH #define DR1801TEST_HH #include "libdmrconfigtest.hh" class DM32UVTest : public UnitTestBase { Q_OBJECT public: explicit DM32UVTest(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testBasicConfigReencoding(); void testProstProcessingOfEmptyCodeplug(); void testAMChannelReencoding(); /** Regression test for #873 */ void testChannelBankEncoding(); }; #endif // DR1801TEST_HH ================================================ FILE: test/dmr6x2uv2_test.cc ================================================ #include "dmr6x2uv2_test.hh" #include DMR6X2UV2Test::DMR6X2UV2Test(QObject *parent) : UnitTestBase(parent) { // pass... } QTEST_GUILESS_MAIN(DMR6X2UV2Test) ================================================ FILE: test/dmr6x2uv2_test.hh ================================================ #ifndef DMR6X2UV2TEST_HH #define DMR6X2UV2TEST_HH #include "libdmrconfigtest.hh" class DMR6X2UV2Test : public UnitTestBase { Q_OBJECT public: explicit DMR6X2UV2Test(QObject *parent = nullptr); private slots: }; #endif // DMR6X2UV2TEST_HH ================================================ FILE: test/dmr6x2uv_test.cc ================================================ #include "dmr6x2uv_test.hh" #include "config.hh" #include "dmr6x2uv_codeplug.hh" #include "errorstack.hh" #include DMR6X2UVTest::DMR6X2UVTest(QObject *parent) : UnitTestBase(parent) { // pass... } void DMR6X2UVTest::encodeDecode(Config &input, Config &output) { ErrorStack err; D878UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&input, flags, err)) { QFAIL(QString("Cannot encode codeplug for BTECH DMR-6X2UV: %1") .arg(err.format()).toStdString().c_str()); } if (! codeplug.decode(&output, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DMR-6X2UV: %1") .arg(err.format()).toStdString().c_str()); } } void DMR6X2UVTest::testBasicConfigEncoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); DMR6X2UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for BTECH DMR-6X2UV: %1") .arg(err.format()).toStdString().c_str()); } } void DMR6X2UVTest::testBasicConfigDecoding() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); DMR6X2UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for BTECH DMR-6X2UV: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for BETCH DMR-6X2UV: %1") .arg(err.format()).toStdString().c_str()); } } void DMR6X2UVTest::testChannelFrequency() { ErrorStack err; Codeplug::Flags flags; flags.setUpdateCodeplug(false); D868UVCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, flags, err)) { QFAIL(QString("Cannot encode codeplug for BTECH DMR-6X2UV: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DMR-6X2UV: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void DMR6X2UVTest::testFMAPRSSettings() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/fm_aprs_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1\n") .arg(err.format()).toStdString().c_str()); } // Check config QCOMPARE(config.posSystems()->count(), 1); QVERIFY(config.posSystems()->get(0)->is()); FMAPRSSystem *aprs = config.posSystems()->get(0)->as(); QCOMPARE(aprs->source(), "DM3MAT"); QCOMPARE(aprs->srcSSID(), 7); QCOMPARE(aprs->destination(), "APAT81"); QCOMPARE(aprs->destSSID(), 0); QCOMPARE(aprs->path(), "WIDE1-1,WIDE2-1"); QCOMPARE(aprs->period(), Interval::fromMinutes(5)); QCOMPARE(config.channelList()->count(), 2); QVERIFY(config.channelList()->channel(0)->is()); QVERIFY(config.channelList()->channel(1)->is()); QVERIFY(! config.channelList()->channel(1)->as()->aprsRef()->isNull()); // Add extensions auto ch_ext = new AnytoneFMChannelExtension(); ch_ext->setAPRSPTT(AnytoneChannelExtension::APRSPTT::Start); config.channelList()->channel(1)->as()->setAnytoneChannelExtension(ch_ext); // test extension settings auto ext = new AnytoneFMAPRSSettingsExtension(); ext->setPreWaveDelay(Interval::fromMilliseconds(100)); ext->setTXDelay(Interval::fromMilliseconds(200)); aprs->setAnytoneExtension(ext); // Encode DMR6X2UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); Config *intermediate = codeplug.preprocess(&config, err); if (nullptr == intermediate) { QFAIL(QString("Cannot prepare codeplug for BTECH DMR-6X2UV: %1") .arg(err.format()).toStdString().c_str()); } if (! codeplug.encode(intermediate, flags, err)) { delete intermediate; QFAIL(QString("Cannot encode codeplug for BTECH DMR-6X2UV: %1") .arg(err.format()).toStdString().c_str()); } delete intermediate; // Decode Config comp_config; if (! codeplug.decode(&comp_config, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DMR-6X2UV: %1") .arg(err.format()).toStdString().c_str()); } // Check config QCOMPARE(comp_config.posSystems()->count(), 1); QVERIFY(comp_config.posSystems()->get(0)->is()); FMAPRSSystem *comp_aprs = comp_config.posSystems()->get(0)->as(); QCOMPARE(comp_aprs->source(), aprs->source()); QCOMPARE(comp_aprs->srcSSID(), aprs->srcSSID()); QCOMPARE(comp_aprs->destination(), aprs->destination()); QCOMPARE(comp_aprs->destSSID(), aprs->destSSID()); QCOMPARE(comp_aprs->path(), aprs->path()); QCOMPARE(comp_aprs->period(), aprs->period()); // check extension settings QVERIFY(nullptr != comp_aprs->anytoneExtension()); QCOMPARE(comp_aprs->anytoneExtension()->preWaveDelay().milliseconds(), 100); QCOMPARE(comp_aprs->anytoneExtension()->txDelay().milliseconds(), 200); // Check revert channel QCOMPARE(comp_config.channelList()->count(), 2); QVERIFY(comp_config.channelList()->channel(0)->is()); QCOMPARE(comp_config.channelList()->channel(0)->rxFrequency(), config.channelList()->channel(0)->rxFrequency()); QCOMPARE(comp_config.channelList()->channel(0)->txFrequency(), config.channelList()->channel(0)->txFrequency()); // Check channel extension properties QVERIFY(nullptr != comp_config.channelList()->channel(1)->as()->anytoneChannelExtension()); auto comp_ch_ext = comp_config.channelList()->channel(1)->as()->anytoneChannelExtension(); QCOMPARE(comp_ch_ext->aprsPTT(), ch_ext->aprsPTT()); } void DMR6X2UVTest::testAESEncryption() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/aes_encryption.yaml", err)) { QFAIL(QString("Cannot open codeplug file:\n%1") .arg(err.format(" ")).toStdString().c_str()); } // Check parser QVERIFY(nullptr != config.commercialExtension()); QCOMPARE(config.commercialExtension()->encryptionKeys()->count(), 1); QCOMPARE(config.commercialExtension()->encryptionKeys()->key(0)->key(), QByteArray::fromHex("11223344556677889900AABBCCDDEEFF")); QVERIFY(nullptr != config.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(config.channelList()->channel(0)->as()->commercialExtension()->encryptionKey(), config.commercialExtension()->encryptionKeys()->key(0)); DMR6X2UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for BETCH DMR6X2UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for BETCH DMR6X2UV: %1") .arg(err.format()).toStdString().c_str()); } // Verify existence of key QVERIFY(nullptr != decoded.commercialExtension()); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->count(), 1); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->key(0)->key(), QByteArray::fromHex("11223344556677889900AABBCCDDEEFF")); // Verify link to channel QVERIFY(nullptr != decoded.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(decoded.channelList()->channel(0)->as()->commercialExtension()->encryptionKey(), decoded.commercialExtension()->encryptionKeys()->key(0)); } void DMR6X2UVTest::testARC4Encryption() { ErrorStack err; // Load config from file Config config; if (! config.readYAML(":/data/arc4_encryption.yaml", err)) { QFAIL(QString("Cannot open codeplug file:\n%1") .arg(err.format(" ")).toStdString().c_str()); } DMR6X2UVCodeplug codeplug; Codeplug::Flags flags; flags.setUpdateCodeplug(false); if (! codeplug.encode(&config, flags, err)) { QFAIL(QString("Cannot encode codeplug for BETCH DMR6X2UV: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for BETCH DMR6X2UV: %1") .arg(err.format()).toStdString().c_str()); } // Verify existence of key QVERIFY(nullptr != decoded.commercialExtension()); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->count(), 1); QCOMPARE(decoded.commercialExtension()->encryptionKeys()->key(0)->key(), QByteArray::fromHex("1122334455")); // Verify link to channel QVERIFY(nullptr != decoded.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(decoded.channelList()->channel(0)->as()->commercialExtension()->encryptionKey(), decoded.commercialExtension()->encryptionKeys()->key(0)); } void DMR6X2UVTest::testMicGain() { ErrorStack err; Config copy, config; config.copy(_basicConfig); QList> pairs = {{1,1}, {2,1}, {3,1}, {4,3}, {5,3}, {6,5}, {7,5}, {8,7}, {9,7}, {10,10}}; for (auto pair: pairs) { config.settings()->audio()->setMicGain(Level::fromValue(pair.first)); encodeDecode(config, copy); QCOMPARE(copy.settings()->audio()->micGain(), Level::fromValue(pair.second)); } } QTEST_GUILESS_MAIN(DMR6X2UVTest) ================================================ FILE: test/dmr6x2uv_test.hh ================================================ #ifndef DMR6X2UVTEST_HH #define DMR6X2UVTEST_HH #include "libdmrconfigtest.hh" class DMR6X2UVTest : public UnitTestBase { Q_OBJECT public: explicit DMR6X2UVTest(QObject *parent = nullptr); protected: void encodeDecode(Config &input, Config &output); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testFMAPRSSettings(); void testAESEncryption(); void testARC4Encryption(); void testMicGain(); }; #endif // DMR6X2UVTEST_HH ================================================ FILE: test/dr1801_test.cc ================================================ #include "dr1801_test.hh" #include "config.hh" #include "dr1801uv_codeplug.hh" #include "errorstack.hh" #include #include DR1801Test::DR1801Test(QObject *parent) : UnitTestBase(parent) { // pass... } void DR1801Test::testBasicConfigEncoding() { ErrorStack err; DR1801UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DR1801UV: %1") .arg(err.format()).toStdString().c_str()); } } void DR1801Test::testBasicConfigDecoding() { ErrorStack err; DR1801UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DR1801UV: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DR1801UV: %1") .arg(err.format()).toStdString().c_str()); } } void DR1801Test::testBasicConfigReencoding() { ErrorStack err; DR1801UVCodeplug codeplug; if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DR1801UV: %1") .arg(err.format()).toStdString().c_str()); } Config originalConfig; if (! codeplug.decode(&originalConfig, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DR1801UV: %1") .arg(err.format()).toStdString().c_str()); } // original Config now contains all default values and extensions // re-encode it if (! codeplug.encode(&originalConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for BTECH DR1801UV: %1") .arg(err.format()).toStdString().c_str()); } // Decode complete config Config testConfig; if (! codeplug.decode(&testConfig, err)) { QFAIL(QString("Cannot decode codeplug for BTECH DR1801UV: %1") .arg(err.format()).toStdString().c_str()); } // compare if (0 != originalConfig.compare(testConfig)) { QFAIL("Decoded config of BTECH DR1801UV does not match source."); } } void DR1801Test::testProstProcessingOfEmptyCodeplug() { Config config; DR1801UVCodeplug codeplug; ErrorStack err; config.clear(); QVERIFY(codeplug.postprocess(&config, err)); } QTEST_GUILESS_MAIN(DR1801Test) ================================================ FILE: test/dr1801_test.hh ================================================ #ifndef DR1801TEST_HH #define DR1801TEST_HH #include "libdmrconfigtest.hh" class DR1801Test : public UnitTestBase { Q_OBJECT public: explicit DR1801Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testBasicConfigReencoding(); void testProstProcessingOfEmptyCodeplug(); }; #endif // DR1801TEST_HH ================================================ FILE: test/gd73_test.cc ================================================ #include "gd73_test.hh" #include "config.hh" #include "gd73.hh" #include "gd73_codeplug.hh" #include "gd73_limits.hh" #include "errorstack.hh" #include #include GD73Test::GD73Test(QObject *parent) : UnitTestBase(parent) { // pass... } void GD73Test::testBasicConfigEncoding() { ErrorStack err; GD73Codeplug codeplug; if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } } void GD73Test::testBasicConfigDecoding() { ErrorStack err; GD73Codeplug codeplug; if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } } void GD73Test::testChannelFrequency() { ErrorStack err; GD73Codeplug codeplug; if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void GD73Test::testSMSTemplates() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); config.smsExtension()->smsTemplates()->add(sms0); config.smsExtension()->smsTemplates()->add(sms1); ErrorStack err; GD73Codeplug codeplug; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); } void GD73Test::testFMSignaling() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); { FMChannel *ch = new FMChannel(); ch->setName("Channel 1"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); ch->setRXTone(SelectiveCall(67.0)); ch->setTXTone(SelectiveCall()); config.channelList()->add(ch); } { FMChannel *ch = new FMChannel(); ch->setName("Channel 2"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); ch->setRXTone(SelectiveCall()); ch->setTXTone(SelectiveCall(67.0)); config.channelList()->add(ch); } { FMChannel *ch = new FMChannel(); ch->setName("Channel 3"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); ch->setRXTone(SelectiveCall(23,false)); ch->setTXTone(SelectiveCall()); config.channelList()->add(ch); } { FMChannel *ch = new FMChannel(); ch->setName("Channel 4"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); ch->setRXTone(SelectiveCall()); ch->setTXTone(SelectiveCall(23, false)); config.channelList()->add(ch); } { FMChannel *ch = new FMChannel(); ch->setName("Channel 5"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); ch->setRXTone(SelectiveCall(23, true)); ch->setTXTone(SelectiveCall()); config.channelList()->add(ch); } { FMChannel *ch = new FMChannel(); ch->setName("Channel 6"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); ch->setRXTone(SelectiveCall()); ch->setTXTone(SelectiveCall(23, true)); config.channelList()->add(ch); } GD73Codeplug codeplug; ErrorStack err; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config compare; if (! codeplug.decode(&compare, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(compare.channelList()->count(), 6); for (int i=0; icount(); i++) { QVERIFY(compare.channelList()->channel(i)->is()); QCOMPARE(compare.channelList()->channel(i)->as()->rxTone(), config.channelList()->channel(i)->as()->rxTone()); QCOMPARE(compare.channelList()->channel(i)->as()->txTone(), config.channelList()->channel(i)->as()->txTone()); } } void GD73Test::testEncryption() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); DMRContact *cnt = new DMRContact(DMRContact::AllCall, "All Call", 0, false); config.contacts()->add(cnt); BasicEncryptionKey *key = new BasicEncryptionKey(); key->setName("Key 1"); key->fromHex("1234"); config.commercialExtension()->encryptionKeys()->add(key); QCOMPARE(key->toHex(), "1234"); auto *ext = new CommercialChannelExtension(); ext->setEncryptionKey(key); DMRChannel *ch = new DMRChannel(); ch->setName("Ch"); ch->setContact(cnt); ch->setTXFrequency(Frequency::fromMHz(440)); ch->setRXFrequency(Frequency::fromMHz(440)); ch->setCommercialExtension(ext); config.channelList()->add(ch); GD73Codeplug codeplug; ErrorStack err; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config compare; if (! codeplug.decode(&compare, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(compare.channelList()->count(), 1); QVERIFY(compare.channelList()->channel(0)->is()); QVERIFY(compare.channelList()->channel(0)->as()->commercialExtension()); QCOMPARE(compare.channelList()->channel(0)->as()->commercialExtension()->encryptionKey()->key(), config.channelList()->channel(0)->as()->commercialExtension()->encryptionKey()->key()); } void GD73Test::testEncryptionLimits() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); DMRContact *cnt = new DMRContact(DMRContact::GroupCall, "GroupCall", 1234, false); config.contacts()->add(cnt); RXGroupList *grpLst = new RXGroupList("GrpLst"); grpLst->addContact(cnt); config.rxGroupLists()->add(grpLst); BasicEncryptionKey *key = new BasicEncryptionKey(); key->setName("Key 1"); key->fromHex("1234"); config.commercialExtension()->encryptionKeys()->add(key); QCOMPARE(key->toHex(), "1234"); auto *ext = new CommercialChannelExtension(); ext->setEncryptionKey(key); DMRChannel *ch = new DMRChannel(); ch->setName("Ch"); ch->setContact(cnt); ch->setGroupList(grpLst); ch->setTXFrequency(Frequency::fromMHz(440)); ch->setRXFrequency(Frequency::fromMHz(440)); ch->setCommercialExtension(ext); config.channelList()->add(ch); Zone *zone = new Zone("Zone"); zone->A()->add(ch); config.zones()->add(zone); { RadioLimitContext issues; QVERIFY(GD73Limits().verifyConfig(&config, issues)); QStringList status; for (int i=0; isetName("Key 2"); key2->fromHex("1234567890abcdef1234567890abcdef"); config.commercialExtension()->encryptionKeys()->add(key2); { RadioLimitContext issues; GD73Limits().verifyConfig(&config, issues); QStringList status; for (int i=0; iencryptionKeys()->del(key2); BasicEncryptionKey *key3 = new BasicEncryptionKey(); key3->setName("Key 2"); key3->fromHex("1234567890"); config.commercialExtension()->encryptionKeys()->add(key3); QCOMPARE(key3->toHex(), "1234567890"); { RadioLimitContext issues; GD73Limits().verifyConfig(&config, issues); QStringList status; for (int i=0; iadd(new DMRRadioID("ID", 1234567)); { DMRChannel *ch = new DMRChannel(); ch->setName("Channel 1"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); config.channelList()->add(ch); } if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } new_config.radioIDs()->add(new DMRRadioID("ID", 1234567)); { FMChannel *ch = new FMChannel(); ch->setName("Channel 1"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); new_config.channelList()->add(ch); } // Re-encode in the same binary codeplug if (! codeplug.encode(&new_config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config compare; if (! codeplug.decode(&compare, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(compare.channelList()->count(), 1); QVERIFY(compare.channelList()->channel(0)->is()); } void GD73Test::testPowerEcoding() { Config config; GD73Codeplug codeplug; ErrorStack err; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); DMRChannel *ch = new DMRChannel(); ch->setName("Channel 1"); ch->setRXFrequency(Frequency::fromMHz(144.0)); ch->setTXFrequency(Frequency::fromMHz(144.6)); ch->setPower(Channel::Power::High); config.channelList()->add(ch); if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config compare; if (! codeplug.decode(&compare, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(compare.channelList()->count(), 1); QCOMPARE(compare.channelList()->channel(0)->power(), Channel::Power::High); } void GD73Test::testSquelchEcoding() { Config config; GD73Codeplug codeplug; ErrorStack err; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); config.settings()->audio()->setSquelch(Level::fromValue(1)); if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config compare; if (! codeplug.decode(&compare, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(compare.settings()->audio()->squelch().value(), 1); } QTEST_GUILESS_MAIN(GD73Test) ================================================ FILE: test/gd73_test.hh ================================================ #ifndef GD73TEST_HH #define GD73TEST_HH #include "libdmrconfigtest.hh" class GD73Test : public UnitTestBase { Q_OBJECT public: explicit GD73Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testSMSTemplates(); void testFMSignaling(); void testEncryption(); void testEncryptionLimits(); void testChannelTypeEcoding(); /// regression test #672 void testPowerEcoding(); /// regression test #672 void testSquelchEcoding(); /// regression test #676 }; #endif // GD73TEST_HH ================================================ FILE: test/gd77_test.cc ================================================ #include "gd77_test.hh" #include "config.hh" #include "gd77.hh" #include "gd77_codeplug.hh" #include "errorstack.hh" #include #include GD77Test::GD77Test(QObject *parent) : UnitTestBase(parent) { // pass... } void GD77Test::testBasicConfigEncoding() { ErrorStack err; GD77Codeplug codeplug; codeplug.clear(); auto intermediate = codeplug.preprocess(&_basicConfig, err); if (nullptr == intermediate) QFAIL(err.format().toLocal8Bit().constData()); if (! codeplug.encode(intermediate, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD77: %1") .arg(err.format()).toStdString().c_str()); } delete intermediate; } void GD77Test::testBasicConfigDecoding() { ErrorStack err; GD77Codeplug codeplug; codeplug.clear(); auto intermediate = codeplug.preprocess(&_basicConfig, err); if (nullptr == intermediate) QFAIL(err.format().toLocal8Bit().constData()); if (! codeplug.encode(intermediate, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD77: %1") .arg(err.format()).toStdString().c_str()); } delete intermediate; Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD77: %1") .arg(err.format()).toStdString().c_str()); } codeplug.clear(); } void GD77Test::testChannelFrequency() { ErrorStack err; GD77Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD77: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD77: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void GD77Test::testSMSTemplates() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); config.smsExtension()->smsTemplates()->add(sms0); config.smsExtension()->smsTemplates()->add(sms1); ErrorStack err; GD77Codeplug codeplug; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD77: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD77: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); } QTEST_GUILESS_MAIN(GD77Test) ================================================ FILE: test/gd77_test.hh ================================================ #ifndef GD77TEST_HH #define GD77TEST_HH #include "libdmrconfigtest.hh" class GD77Test : public UnitTestBase { Q_OBJECT public: explicit GD77Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testSMSTemplates(); }; #endif // GD77TEST_HH ================================================ FILE: test/labeltest.cc ================================================ #include "labeltest.hh" #include "configlabelingvisitor.hh" LabelTest::LabelTest(QObject *parent) : UnitTestBase(parent) { // pass... } void LabelTest::testLabelVisitor() { Config::Context ctx; ConfigLabelingVisitor labelPrinter(ctx); ErrorStack err; if (! labelPrinter.processItem(&_basicConfig, err)) { QFAIL(err.format().toLocal8Bit().constData()); } YAML::Node node = _basicConfig.serialize(ctx, err); if (node.IsNull()) { QFAIL(err.format().toLocal8Bit().constData()); } } QTEST_GUILESS_MAIN(LabelTest) ================================================ FILE: test/labeltest.hh ================================================ #ifndef LABELTEST_HH #define LABELTEST_HH #include "libdmrconfigtest.hh" class LabelTest: public UnitTestBase { Q_OBJECT public: explicit LabelTest(QObject *parent=nullptr); private slots: void testLabelVisitor(); }; #endif // LABETEST_HH ================================================ FILE: test/libdmrconfigtest.cc ================================================ #include "libdmrconfigtest.hh" UnitTestBase::UnitTestBase(QObject *parent) : QObject(parent) { // pass... } void UnitTestBase::initTestCase() { ErrorStack err; if (! _basicConfig.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } if (! _channelFrequencyConfig.readYAML(":/data/channel_frequency_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } if (! _roamingConfig.readYAML(":/data/roaming_channel_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); } } void UnitTestBase::cleanupTestCase() { // clear codeplug _basicConfig.clear(); _channelFrequencyConfig.clear(); } ================================================ FILE: test/libdmrconfigtest.hh ================================================ #ifndef LIBDMRCONFIGTEST_HH #define LIBDMRCONFIGTEST_HH #include #include "config.hh" #include class UnitTestBase : public QObject { Q_OBJECT public: explicit UnitTestBase(QObject *parent = nullptr); protected slots: virtual void initTestCase(); virtual void cleanupTestCase(); protected: Config _basicConfig; Config _channelFrequencyConfig; Config _roamingConfig; }; #endif // LIBDMRCONFIGTEST_HH ================================================ FILE: test/md2017_test.cc ================================================ #include "md2017_test.hh" #include "config.hh" #include "md2017_codeplug.hh" #include "errorstack.hh" #include #include MD2017Test::MD2017Test(QObject *parent) : UnitTestBase(parent) { // pass... } void MD2017Test::testBasicConfigEncoding() { ErrorStack err; MD2017Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT MD2017: %1") .arg(err.format()).toStdString().c_str()); } } void MD2017Test::testBasicConfigDecoding() { ErrorStack err; MD2017Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT MD2017: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for TyT MD2017: %1") .arg(err.format()).toStdString().c_str()); } } void MD2017Test::testChannelFrequency() { ErrorStack err; MD2017Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT MD2017: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for TyT MD2017: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } QTEST_GUILESS_MAIN(MD2017Test) ================================================ FILE: test/md2017_test.hh ================================================ #ifndef MD2017TEST_HH #define MD2017TEST_HH #include "libdmrconfigtest.hh" class MD2017Test : public UnitTestBase { Q_OBJECT public: explicit MD2017Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); }; #endif // MD2017TEST_HH ================================================ FILE: test/md390_test.cc ================================================ #include "md390_test.hh" #include "config.hh" #include "md390_codeplug.hh" #include "errorstack.hh" #include #include MD390Test::MD390Test(QObject *parent) : UnitTestBase(parent) { // pass... } void MD390Test::testBasicConfigEncoding() { ErrorStack err; MD390Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT MD390: {}") .arg(err.format()).toStdString().c_str()); } } void MD390Test::testBasicConfigDecoding() { ErrorStack err; MD390Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT MD390: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for TyT MD390: {}") .arg(err.format()).toStdString().c_str()); } } void MD390Test::testChannelFrequency() { ErrorStack err; MD390Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT MD390: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for TyT MD390: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void MD390Test::testSMSTemplates() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); config.smsExtension()->smsTemplates()->add(sms0); config.smsExtension()->smsTemplates()->add(sms1); ErrorStack err; MD390Codeplug codeplug; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity GD73: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); } QTEST_GUILESS_MAIN(MD390Test) ================================================ FILE: test/md390_test.hh ================================================ #ifndef MD390TEST_HH #define MD390TEST_HH #include "libdmrconfigtest.hh" class MD390Test : public UnitTestBase { Q_OBJECT public: explicit MD390Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testSMSTemplates(); }; #endif // MD390TEST_HH ================================================ FILE: test/mergetest.cc ================================================ #include "mergetest.hh" #include #include "config.hh" #include "configmergevisitor.hh" MergeTest::MergeTest(QObject *parent) : QObject{parent} { // pass... } void MergeTest::testMergeRadioIds() { Config *base = new Config(), *merging = new Config(); base->radioIDs()->add(new DMRRadioID("ID 0", 1234)); base->radioIDs()->add(new DMRRadioID("ID 1", 1234)); merging->radioIDs()->add(new DMRRadioID("ID 1", 2345)); merging->radioIDs()->add(new DMRRadioID("ID 2", 2345)); ErrorStack err; Config *merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->radioIDs()->count(), 3); QCOMPARE(merged->radioIDs()->get(0)->name(), "ID 0"); QCOMPARE(merged->radioIDs()->get(0)->as()->number(), 1234); QCOMPARE(merged->radioIDs()->get(1)->name(), "ID 1"); QCOMPARE(merged->radioIDs()->get(1)->as()->number(), 1234); QCOMPARE(merged->radioIDs()->get(2)->name(), "ID 2"); QCOMPARE(merged->radioIDs()->get(2)->as()->number(), 2345); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Override, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->radioIDs()->count(), 3); QCOMPARE(merged->radioIDs()->get(0)->name(), "ID 0"); QCOMPARE(merged->radioIDs()->get(0)->as()->number(), 1234); QCOMPARE(merged->radioIDs()->get(1)->name(), "ID 1"); QCOMPARE(merged->radioIDs()->get(1)->as()->number(), 2345); QCOMPARE(merged->radioIDs()->get(2)->name(), "ID 2"); QCOMPARE(merged->radioIDs()->get(2)->as()->number(), 2345); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Duplicate, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->radioIDs()->count(), 4); QCOMPARE(merged->radioIDs()->get(0)->name(), "ID 0"); QCOMPARE(merged->radioIDs()->get(0)->as()->number(), 1234); QCOMPARE(merged->radioIDs()->get(1)->name(), "ID 1"); QCOMPARE(merged->radioIDs()->get(1)->as()->number(), 1234); QCOMPARE(merged->radioIDs()->get(2)->name(), "ID 1 (copy)"); QCOMPARE(merged->radioIDs()->get(2)->as()->number(), 2345); QCOMPARE(merged->radioIDs()->get(3)->name(), "ID 2"); QCOMPARE(merged->radioIDs()->get(3)->as()->number(), 2345); } void MergeTest::testMergeContacts() { Config *base = new Config(), *merging = new Config(); base->contacts()->add(new DMRContact(DMRContact::PrivateCall, "ID 0", 1234)); base->contacts()->add(new DMRContact(DMRContact::PrivateCall, "ID 1", 1234)); merging->contacts()->add(new DMRContact(DMRContact::PrivateCall, "ID 1", 2345)); merging->contacts()->add(new DMRContact(DMRContact::PrivateCall, "ID 2", 2345)); ErrorStack err; Config *merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 3); QCOMPARE(merged->contacts()->get(0)->name(), "ID 0"); QCOMPARE(merged->contacts()->get(0)->as()->number(), 1234); QCOMPARE(merged->contacts()->get(1)->name(), "ID 1"); QCOMPARE(merged->contacts()->get(1)->as()->number(), 1234); QCOMPARE(merged->contacts()->get(2)->name(), "ID 2"); QCOMPARE(merged->contacts()->get(2)->as()->number(), 2345); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Override, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 3); QCOMPARE(merged->contacts()->get(0)->name(), "ID 0"); QCOMPARE(merged->contacts()->get(0)->as()->number(), 1234); QCOMPARE(merged->contacts()->get(1)->name(), "ID 1"); QCOMPARE(merged->contacts()->get(1)->as()->number(), 2345); QCOMPARE(merged->contacts()->get(2)->name(), "ID 2"); QCOMPARE(merged->contacts()->get(2)->as()->number(), 2345); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Duplicate, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 4); QCOMPARE(merged->contacts()->get(0)->name(), "ID 0"); QCOMPARE(merged->contacts()->get(0)->as()->number(), 1234); QCOMPARE(merged->contacts()->get(1)->name(), "ID 1"); QCOMPARE(merged->contacts()->get(1)->as()->number(), 1234); QCOMPARE(merged->contacts()->get(2)->name(), "ID 1 (copy)"); QCOMPARE(merged->contacts()->get(2)->as()->number(), 2345); QCOMPARE(merged->contacts()->get(3)->name(), "ID 2"); QCOMPARE(merged->contacts()->get(3)->as()->number(), 2345); } void MergeTest::testMergeGroupLists() { Config *base = new Config(), *merging = new Config(); base->contacts()->add(new DMRContact(DMRContact::PrivateCall, "ID 0", 1234)); base->contacts()->add(new DMRContact(DMRContact::PrivateCall, "ID 1", 1234)); { RXGroupList *lst = new RXGroupList("List 0"); lst->addContact(base->contacts()->contact(0)->as()); lst->addContact(base->contacts()->contact(1)->as()); base->rxGroupLists()->add(lst); } merging->contacts()->add(new DMRContact(DMRContact::PrivateCall, "ID 1", 2345)); merging->contacts()->add(new DMRContact(DMRContact::PrivateCall, "ID 2", 2345)); { RXGroupList *lst = new RXGroupList("List 0"); lst->addContact(merging->contacts()->contact(0)->as()); lst->addContact(merging->contacts()->contact(1)->as()); merging->rxGroupLists()->add(lst); } ErrorStack err; Config *merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 3); QCOMPARE(merged->rxGroupLists()->count(), 1); QCOMPARE(merged->rxGroupLists()->list(0)->count(), 2); QCOMPARE(merged->rxGroupLists()->list(0)->contact(0), merged->contacts()->contact(0)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(1), merged->contacts()->contact(1)); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Merge, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 3); QCOMPARE(merged->rxGroupLists()->count(), 1); QCOMPARE(merged->rxGroupLists()->list(0)->count(), 3); QCOMPARE(merged->rxGroupLists()->list(0)->contact(0), merged->contacts()->contact(0)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(1), merged->contacts()->contact(1)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(2), merged->contacts()->contact(2)); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Override, ConfigMergeVisitor::SetStrategy::Merge, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 3); QCOMPARE(merged->rxGroupLists()->count(), 1); QCOMPARE(merged->rxGroupLists()->list(0)->count(), 3); QCOMPARE(merged->rxGroupLists()->list(0)->contact(0), merged->contacts()->contact(0)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(1), merged->contacts()->contact(1)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(2), merged->contacts()->contact(2)); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Override, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 3); QCOMPARE(merged->rxGroupLists()->count(), 1); QCOMPARE(merged->rxGroupLists()->list(0)->count(), 2); QCOMPARE(merged->rxGroupLists()->list(0)->contact(0), merged->contacts()->contact(1)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(1), merged->contacts()->contact(2)); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Duplicate, ConfigMergeVisitor::SetStrategy::Merge, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 4); QCOMPARE(merged->rxGroupLists()->count(), 1); QCOMPARE(merged->rxGroupLists()->list(0)->count(), 4); QCOMPARE(merged->rxGroupLists()->list(0)->contact(0), merged->contacts()->contact(0)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(1), merged->contacts()->contact(1)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(2), merged->contacts()->contact(2)); QCOMPARE(merged->rxGroupLists()->list(0)->contact(3), merged->contacts()->contact(3)); } void MergeTest::testMergeChannels() { Config *base = new Config(), *merging = new Config(); base->contacts()->add(new DMRContact(DMRContact::GroupCall, "ID 0", 1234)); { RXGroupList *lst = new RXGroupList("List 0"); lst->addContact(base->contacts()->contact(0)->as()); base->rxGroupLists()->add(lst); FMChannel *ach = new FMChannel(); ach->setName("FM 0"); ach->setRXFrequency(Frequency::fromMHz(144.0)); ach->setTXFrequency(Frequency::fromMHz(144.0)); base->channelList()->add(ach); DMRChannel *dch = new DMRChannel(); dch->setName("DMR 0"); dch->setRXFrequency(Frequency::fromMHz(144.0)); dch->setTXFrequency(Frequency::fromMHz(144.0)); dch->setContact(base->contacts()->contact(0)->as()); dch->setGroupList(lst); base->channelList()->add(dch); } merging->contacts()->add(new DMRContact(DMRContact::GroupCall, "ID 0", 2345)); merging->contacts()->add(new DMRContact(DMRContact::GroupCall, "ID 1", 3456)); { RXGroupList *lst = new RXGroupList("List 0"); lst->addContact(merging->contacts()->contact(0)->as()); merging->rxGroupLists()->add(lst); FMChannel *ach = new FMChannel(); ach->setName("FM 0"); ach->setRXFrequency(Frequency::fromMHz(145.0)); ach->setTXFrequency(Frequency::fromMHz(145.0)); merging->channelList()->add(ach); DMRChannel *dch = new DMRChannel(); dch->setName("DMR 0"); dch->setRXFrequency(Frequency::fromMHz(145.0)); dch->setTXFrequency(Frequency::fromMHz(145.0)); dch->setContact(merging->contacts()->contact(0)->as()); dch->setGroupList(lst); merging->channelList()->add(dch); } ErrorStack err; Config *merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 2); QCOMPARE(merged->rxGroupLists()->count(), 1); QCOMPARE(merged->channelList()->count(), 2); QVERIFY(merged->channelList()->channel(0)->is()); QCOMPARE(merged->channelList()->channel(0)->rxFrequency().inMHz(), 144.0); QVERIFY(merged->channelList()->channel(1)->is()); QCOMPARE(merged->channelList()->channel(1)->rxFrequency().inMHz(), 144.0); QCOMPARE(merged->channelList()->channel(1)->as()->contact(), merged->contacts()->contact(0)->as()); QCOMPARE(merged->channelList()->channel(1)->as()->groupList(), merged->rxGroupLists()->list(0)); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Override, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->contacts()->count(), 2); QCOMPARE(merged->rxGroupLists()->count(), 1); QCOMPARE(merged->channelList()->count(), 2); QVERIFY(merged->channelList()->channel(0)->is()); QCOMPARE(merged->channelList()->channel(0)->rxFrequency().inMHz(), 145.0); QVERIFY(merged->channelList()->channel(1)->is()); QCOMPARE(merged->channelList()->channel(1)->rxFrequency().inMHz(), 145.0); QCOMPARE(merged->channelList()->channel(1)->as()->contact(), merged->contacts()->contact(0)->as()); QCOMPARE(merged->channelList()->channel(1)->as()->groupList(), merged->rxGroupLists()->list(0)); } void MergeTest::testMergeZones() { Config *base = new Config(), *merging = new Config(); base->contacts()->add(new DMRContact(DMRContact::GroupCall, "ID 0", 1234)); { RXGroupList *lst = new RXGroupList("List 0"); lst->addContact(base->contacts()->contact(0)->as()); base->rxGroupLists()->add(lst); FMChannel *ach = new FMChannel(); ach->setName("FM 0"); ach->setRXFrequency(Frequency::fromMHz(144.0)); ach->setTXFrequency(Frequency::fromMHz(144.0)); base->channelList()->add(ach); DMRChannel *dch = new DMRChannel(); dch->setName("DMR 0"); dch->setRXFrequency(Frequency::fromMHz(144.0)); dch->setTXFrequency(Frequency::fromMHz(144.0)); dch->setContact(base->contacts()->contact(0)->as()); dch->setGroupList(lst); base->channelList()->add(dch); Zone *zone = new Zone("Zone 0"); zone->A()->add(ach); zone->A()->add(dch); base->zones()->add(zone); } merging->contacts()->add(new DMRContact(DMRContact::GroupCall, "ID 0", 2345)); merging->contacts()->add(new DMRContact(DMRContact::GroupCall, "ID 1", 3456)); { RXGroupList *lst = new RXGroupList("List 0"); lst->addContact(merging->contacts()->contact(0)->as()); merging->rxGroupLists()->add(lst); FMChannel *ach = new FMChannel(); ach->setName("FM 0"); ach->setRXFrequency(Frequency::fromMHz(145.0)); ach->setTXFrequency(Frequency::fromMHz(145.0)); merging->channelList()->add(ach); DMRChannel *dch = new DMRChannel(); dch->setName("DMR 1"); dch->setRXFrequency(Frequency::fromMHz(145.0)); dch->setTXFrequency(Frequency::fromMHz(145.0)); dch->setContact(merging->contacts()->contact(0)->as()); dch->setGroupList(lst); merging->channelList()->add(dch); Zone *zone = new Zone("Zone 0"); zone->A()->add(ach); zone->A()->add(dch); merging->zones()->add(zone); } ErrorStack err; Config *merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Ignore, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->channelList()->count(), 3); QCOMPARE(merged->zones()->count(), 1); QCOMPARE(merged->zones()->zone(0)->A()->count(), 2); QCOMPARE(merged->zones()->zone(0)->A()->get(0)->as(), merged->channelList()->channel(0)); QCOMPARE(merged->zones()->zone(0)->A()->get(1)->as(), merged->channelList()->channel(1)); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Override, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->channelList()->count(), 3); QCOMPARE(merged->zones()->count(), 1); QCOMPARE(merged->zones()->zone(0)->A()->count(), 2); QCOMPARE(merged->zones()->zone(0)->A()->get(0)->as(), merged->channelList()->channel(0)); QCOMPARE(merged->zones()->zone(0)->A()->get(1)->as(), merged->channelList()->channel(2)); merged = ConfigMerge::merge(base, merging, ConfigMergeVisitor::ItemStrategy::Ignore, ConfigMergeVisitor::SetStrategy::Merge, err); if (nullptr == merged) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(merged->channelList()->count(), 3); QCOMPARE(merged->zones()->count(), 1); QCOMPARE(merged->zones()->zone(0)->A()->count(), 3); QCOMPARE(merged->zones()->zone(0)->A()->get(0)->as(), merged->channelList()->channel(0)); QCOMPARE(merged->zones()->zone(0)->A()->get(1)->as(), merged->channelList()->channel(1)); QCOMPARE(merged->zones()->zone(0)->A()->get(2)->as(), merged->channelList()->channel(2)); } QTEST_GUILESS_MAIN(MergeTest) ================================================ FILE: test/mergetest.hh ================================================ #ifndef MERGETEST_HH #define MERGETEST_HH #include class MergeTest : public QObject { Q_OBJECT public: explicit MergeTest(QObject *parent=nullptr); private slots: void testMergeRadioIds(); void testMergeContacts(); void testMergeGroupLists(); void testMergeChannels(); void testMergeZones(); }; #endif // MERGETEST_HH ================================================ FILE: test/opengd77_test.cc ================================================ #include "opengd77_test.hh" #include "opengd77_limits.hh" #include "config.hh" #include "opengd77_codeplug.hh" #include "errorstack.hh" #include #include #include "logger.hh" OpenGD77Test::OpenGD77Test(QObject *parent) : UnitTestBase(parent), _stderr(stderr) { Logger::get().addHandler(new StreamLogHandler(_stderr, LogMessage::DEBUG)); } void OpenGD77Test::testBasicConfigEncoding() { ErrorStack err; OpenGD77Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for OpenGD77: %1") .arg(err.format()).toStdString().c_str()); } } bool OpenGD77Test::encodeDecode(Config &config, Config &decoded, const ErrorStack &err) { OpenGD77Codeplug codeplug; codeplug.clear(); Codeplug::Flags flags; flags.setUpdateCodeplug(false); Config *intermediate = codeplug.preprocess(&config, err); if (nullptr == intermediate) { errMsg(err) << "Cannot encode codeplug for OpenGD77."; return false; } if (! codeplug.encode(intermediate, flags, err)) { errMsg(err) << "Cannot encode codeplug for OpenGD77."; return false; } delete intermediate; if (! codeplug.decode(&decoded, err)) { errMsg(err) << "Cannot decode codeplug for OpenGD77."; return false; } if (! codeplug.postprocess(&decoded, err)) { errMsg(err) << "Cannot decode codeplug for OpenGD77."; return false; } return true; } void OpenGD77Test::testBasicConfigDecoding() { ErrorStack err; Config decoded; if (! encodeDecode(_basicConfig, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); } void OpenGD77Test::testChannelFrequency() { ErrorStack err; Config config; if (! encodeDecode(_channelFrequencyConfig, config, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void OpenGD77Test::testChannelGroupList() { ErrorStack err; Config config, decoded; if (! config.readYAML(":/data/config_test.yaml", err)) QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); config.channelList()->channel(0)->as()->setGroupList(nullptr); config.channelList()->channel(0)->as()->setContact(nullptr); config.channelList()->channel(1)->as()->setContact(nullptr); config.channelList()->channel(2)->as()->setGroupList(nullptr); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QVERIFY(decoded.channelList()->channel(0)->as()->groupListRef()->isNull()); QVERIFY(decoded.channelList()->channel(0)->as()->contactRef()->isNull()); QVERIFY(! decoded.channelList()->channel(1)->as()->groupListRef()->isNull()); QVERIFY(decoded.channelList()->channel(1)->as()->contactRef()->isNull()); QVERIFY(decoded.channelList()->channel(2)->as()->groupListRef()->isNull()); QVERIFY(! decoded.channelList()->channel(2)->as()->contactRef()->isNull()); QVERIFY(! decoded.channelList()->channel(3)->as()->groupListRef()->isNull()); QVERIFY(decoded.channelList()->channel(3)->as()->contactRef()->isNull()); } void OpenGD77Test::testChannelPowerSettings() { ErrorStack err; Config config, decoded; if (! config.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } config.channelList()->channel(0)->setDefaultPower(); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.channelList()->channel(0)->defaultPower(), true); config.channelList()->channel(0)->setPower(Channel::Power::Low); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.channelList()->channel(0)->defaultPower(), false); QCOMPARE(decoded.channelList()->channel(0)->power(), Channel::Power::Low); config.channelList()->channel(0)->setPower(Channel::Power::High); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.channelList()->channel(0)->defaultPower(), false); QCOMPARE(decoded.channelList()->channel(0)->power(), Channel::Power::High); } void OpenGD77Test::testOverrideChannelRadioId() { ErrorStack err; Config config, decoded; if (! config.readYAML(":/data/config_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } // add another DMR ID and assign it to two channels auto id2 = new DMRRadioID("Test", 1234567); config.radioIDs()->add(id2); config.channelList()->channel(0)->as()->setRadioId(id2); config.channelList()->channel(1)->as()->setRadioId(id2); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.radioIDs()->count(), 2); QVERIFY(! decoded.channelList()->channel(0)->as()->radioIdRef()->isNull()); QCOMPARE(decoded.channelList()->channel(0)->as()->radioId()->number(), 1234567); QVERIFY(! decoded.channelList()->channel(1)->as()->radioIdRef()->isNull()); QCOMPARE(decoded.channelList()->channel(1)->as()->radioId()->number(), 1234567); } void OpenGD77Test::testChannelSubTones() { ErrorStack err; Config config, decoded; char enc[] = {0x00, 0x10}; uint16_t dec = *(quint16 *)enc; SelectiveCall decTone = OpenGD77BaseCodeplug::decodeSelectiveCall( qFromLittleEndian(dec)); QVERIFY(decTone.isValid()); QVERIFY(decTone.isCTCSS()); QCOMPARE(decTone.Hz(), 100.0); if (! config.readYAML(":/data/fm_aprs_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } config.channelList()->channel(0)->as()->setTXTone(SelectiveCall(67.0)); config.channelList()->channel(0)->as()->setRXTone(SelectiveCall(123.0)); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); SelectiveCall txTone = decoded.channelList()->channel(0)->as()->txTone(), rxTone = decoded.channelList()->channel(0)->as()->rxTone(); QVERIFY(txTone.isValid()); QVERIFY(txTone.isCTCSS()); QCOMPARE(txTone.Hz(), 67.0); QVERIFY(rxTone.isValid()); QVERIFY(rxTone.isCTCSS()); QCOMPARE(rxTone.Hz(), 123.0); } void OpenGD77Test::testChannelFixedLocation() { ErrorStack err; Config config, decoded; if (! config.readYAML(":/data/fm_aprs_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } auto ext = new OpenGD77ChannelExtension(); ext->setLocator("JO62jl24"); config.channelList()->channel(0)->setOpenGD77ChannelExtension(ext); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QVERIFY(decoded.channelList()->channel(0)->openGD77ChannelExtension()); QCOMPARE(decoded.channelList()->channel(0)->openGD77ChannelExtension()->locator(), "JO62jl24"); ext->setLocator("JO59gw73"); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.channelList()->channel(0)->openGD77ChannelExtension()->locator(), "JO59gw73"); } void OpenGD77Test::testAPRSSourceCall() { ErrorStack err; Config config, decoded; if (! config.readYAML(":/data/fm_aprs_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.posSystems()->count(), 1); auto sys = decoded.posSystems()->get(0)->as(); QCOMPARE(sys->source(), "DM3MAT"); FMChannel *channel = decoded.posSystems()->get(0)->as() ->revertChannelRef()->as(); QVERIFY(channel->name() == "2m APRS"); } void OpenGD77Test::testDTMFContacts() { ErrorStack err; Config config, decoded; if (! config.readYAML(":/data/dtmf_contact.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.contacts()->count(), 1); QVERIFY(decoded.contacts()->contact(0)->is()); auto cont = decoded.contacts()->contact(0)->as(); QCOMPARE(cont->name(), "DTMF"); QCOMPARE(cont->number(), "*ABC123#"); } void OpenGD77Test::testChannelTransmitTimeout() { ErrorStack err; Config config, decoded; if (! config.readYAML(":/data/fm_aprs_test.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } config.channelList()->channel(0)->setTimeout(Interval::fromSeconds(60)); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.channelList()->count(), 2); QVERIFY(! decoded.channelList()->channel(0)->timeoutDisabled()); QCOMPARE(decoded.channelList()->channel(0)->timeout(), Interval::fromSeconds(60)); QVERIFY(decoded.channelList()->channel(1)->timeoutDisabled()); } void OpenGD77Test::testConfigVerification() { ErrorStack err; Config config; if (! config.readYAML(":/data/opengd77_simple_config.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } RadioLimitContext ctx; OpenGD77Limits().verifyConfig(&config, ctx); QStringList messages; for (int i=0; itone()->bootMelody()->toLilypond(), "c4 e g c e g c e g c e g c d e g c1"); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.settings()->tone()->bootMelody()->toLilypond(), "c4 e g c e g c e g c e g c d e g c1"); } void OpenGD77Test::testBootMelodyPauses() { ErrorStack err; Config config, decoded; if (! config.readYAML(":/data/opengd77_boot_melody.yaml", err)) { QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toLocal8Bit().constData()); } config.settings()->tone()->bootMelody()->fromLilypond("c4 r16 c4 g4 e4 r4 c16 g16 e16 r16 c4"); if (! encodeDecode(config, decoded, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(decoded.settings()->tone()->bootMelody()->toLilypond(), "c4 r16 c4 g e r c16 g e r c4"); } QTEST_GUILESS_MAIN(OpenGD77Test) ================================================ FILE: test/opengd77_test.hh ================================================ #ifndef OPENGD77TEST_HH #define OPENGD77TEST_HH #include "libdmrconfigtest.hh" #include #include #include "config.hh" #include "errorstack.hh" class OpenGD77Test : public UnitTestBase { Q_OBJECT public: explicit OpenGD77Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); /** Regression test for #539. */ void testChannelGroupList(); /** Regression test for #507 */ void testChannelPowerSettings(); /** Regression test for #541. */ void testOverrideChannelRadioId(); /** Regression test for #554. */ void testAPRSSourceCall(); /** Regression test for #549. */ void testChannelSubTones(); /** Regression test for #556. */ void testChannelFixedLocation(); /** Regression test for #655. */ void testDTMFContacts(); /** Regression test for #697. */ void testChannelTransmitTimeout(); /** Regression test for #856. */ void testConfigVerification(); void testBootMelody(); /** Regression tests for #878. */ void testBootMelodyPauses(); protected: static bool encodeDecode(Config &config, Config &decoded, const ErrorStack &err=ErrorStack()); protected: QTextStream _stderr; }; #endif // OPENGD77TEST_HH ================================================ FILE: test/openuv380_test.cc ================================================ #include "openuv380_test.hh" #include "config.hh" #include "openuv380_codeplug.hh" #include "errorstack.hh" #include #include "logger.hh" OpenUV380Test::OpenUV380Test(QObject *parent) : UnitTestBase(parent), _stderr(stderr) { Logger::get().addHandler(new StreamLogHandler(_stderr, LogMessage::DEBUG)); } void OpenUV380Test::testBasicConfigEncoding() { ErrorStack err; OpenUV380Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for OpenGD77: %1") .arg(err.format()).toStdString().c_str()); } } void OpenUV380Test::testBasicConfigDecoding() { ErrorStack err; OpenUV380Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for OpenGD77: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for OpenGD77: %1") .arg(err.format()).toStdString().c_str()); } } void OpenUV380Test::testChannelFrequency() { ErrorStack err; OpenUV380Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for OpenGD77: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity RD5R: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } QTEST_GUILESS_MAIN(OpenUV380Test) ================================================ FILE: test/openuv380_test.hh ================================================ #ifndef OPENUV380TEST_HH #define OPENUV380TEST_HH #include "libdmrconfigtest.hh" #include #include #include "config.hh" class OpenUV380Test : public UnitTestBase { Q_OBJECT public: explicit OpenUV380Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); protected: QTextStream _stderr; }; #endif // OPENUV380TEST_HH ================================================ FILE: test/rd5r_test.cc ================================================ #include "rd5r_test.hh" #include "config.hh" #include "rd5r.hh" #include "rd5r_codeplug.hh" #include "errorstack.hh" #include #include RD5RTest::RD5RTest(QObject *parent) : UnitTestBase(parent) { // pass... } void RD5RTest::testBasicConfigEncoding() { ErrorStack err; RD5RCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity RD5R: %1") .arg(err.format()).toStdString().c_str()); } } void RD5RTest::testBasicConfigDecoding() { ErrorStack err; RD5RCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity RD5R: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity RD5R: %1") .arg(err.format()).toStdString().c_str()); } } void RD5RTest::testChannelFrequency() { ErrorStack err; RD5RCodeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity RD5R: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity RD5R: %1") .arg(err.format()).toStdString().c_str()); } /*QCOMPARE(config.channelList()->channel(0)->rxFrequency(), 123456780ULL); QCOMPARE(config.channelList()->channel(0)->txFrequency(), 1234567890ULL);*/ } void RD5RTest::testSMSTemplates() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); config.smsExtension()->smsTemplates()->add(sms0); config.smsExtension()->smsTemplates()->add(sms1); ErrorStack err; RD5RCodeplug codeplug; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for Radioddity RD5R: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for Radioddity RD5R: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); } QTEST_GUILESS_MAIN(RD5RTest) ================================================ FILE: test/rd5r_test.hh ================================================ #ifndef RD5RTEST_HH #define RD5RTEST_HH #include "libdmrconfigtest.hh" class RD5RTest : public UnitTestBase { Q_OBJECT public: explicit RD5RTest(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testSMSTemplates(); }; #endif // RD5RTEST_HH ================================================ FILE: test/resources.qrc ================================================ data/config_test.yaml data/channel_frequency_test.yaml data/anytone_auto_repeater_extension.yaml data/audio_settings_extension.yaml data/roaming_channel_test.yaml data/anytone_call_hangtime.yaml data/multiple_radio_ids.yaml data/ctcss_copy_test.yaml data/anytone_key_function.yaml data/chirp_simple.csv data/chirp_cross.csv data/chirp_ctcss.csv data/chirp_dcs.csv data/fm_aprs_test.yaml data/ctcss_null_test.yaml data/chirp_bandwidth.csv data/basic_encryption.yaml data/aes_encryption.yaml data/arc4_encryption.yaml data/dtmf_contact.yaml data/am_channel_test.yaml data/anytone_channel_data_ack.yaml data/anytone_settings_display.yaml data/anytone_settings_roaming.yaml data/opengd77_simple_config.yaml data/opengd77_boot_melody.yaml ================================================ FILE: test/smstemplatetest.cc ================================================ #include "smstemplatetest.hh" SMSTemplateTest::SMSTemplateTest(QObject *parent) : UnitTestBase{parent} { // pass... } void SMSTemplateTest::serializationTest() { Config config; auto msg1 = new SMSTemplate(); msg1->setName("Message 1"); msg1->setMessage("Some message"); config.smsExtension()->smsTemplates()->add(msg1); ErrorStack err; QString buffer; QTextStream stream(&buffer); if (! config.toYAML(stream, err)) QFAIL(err.format().toLocal8Bit().constData()); Config comp_config; Config::Context ctx; YAML::Node doc = YAML::Load(buffer.toStdString()); if (! comp_config.parse(doc, ctx, err)) QFAIL(err.format().toLocal8Bit().constData()); if (! comp_config.link(doc, ctx, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(comp_config.smsExtension()->smsTemplates()->count(), config.smsExtension()->smsTemplates()->count()); QCOMPARE(comp_config.smsExtension()->smsTemplates()->message(0)->name(), config.smsExtension()->smsTemplates()->message(0)->name()); QCOMPARE(comp_config.smsExtension()->smsTemplates()->message(0)->message(), config.smsExtension()->smsTemplates()->message(0)->message()); } void SMSTemplateTest::testMessageDuplication() { Config config; { auto msg1 = new SMSTemplate(); msg1->setName("Message 1"); msg1->setMessage("Some message"); config.smsExtension()->smsTemplates()->add(msg1); } ErrorStack err; QString buffer; QTextStream stream(&buffer); if (! config.toYAML(stream, err)) QFAIL(err.format().toLocal8Bit().constData()); config.clear(); Config::Context ctx; YAML::Node doc = YAML::Load(buffer.toStdString()); if (! config.parse(doc, ctx, err)) QFAIL(err.format().toLocal8Bit().constData()); if (! config.link(doc, ctx, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(config.smsExtension()->smsTemplates()->count(), 1); QCOMPARE(config.smsExtension()->smsTemplates()->message(0)->name(), "Message 1"); QCOMPARE(config.smsExtension()->smsTemplates()->message(0)->message(), "Some message"); } QTEST_GUILESS_MAIN(SMSTemplateTest) ================================================ FILE: test/smstemplatetest.hh ================================================ #ifndef SMSTEMPLATETEST_HH #define SMSTEMPLATETEST_HH #include "libdmrconfigtest.hh" class SMSTemplateTest : public UnitTestBase { Q_OBJECT public: explicit SMSTemplateTest(QObject *parent=nullptr); private slots: void serializationTest(); /** Regression test for #511. */ void testMessageDuplication(); }; #endif // SMSTEMPLATETEST_HH ================================================ FILE: test/tableformattest.cc ================================================ #include "tableformattest.hh" #include "config.hh" #include "csvreader.hh" #include TableFormatTest::TableFormatTest(QObject *parent) : QObject{parent} { // pass... } void TableFormatTest::testFrequencyParser() { Config config; QString data; QTextStream stream(&data); stream << "ID: 2621370" << Qt::endl << "Name: \"DM3MAT\"" << Qt::endl << "Digital Name Receive Transmit Power Scan TOT RO Admit CC TS RxGL TxC GPS Roam ID" << Qt::endl << "1 \"test 0\" 439.56250 -7.60000 High - - - Color 1 1 - - - + - # Local" << Qt::endl << "2 \"test 1\" 439.56250 +7.60000 High - - - Color 1 1 - - - + - # Sa/Th" << Qt::endl << "3 \"test 2\" 439.56250 439.56250 High - - - Color 1 2 - - - + - # Regional" << Qt::endl; QString errMsg; QVERIFY2(CSVReader::read(&config, stream, errMsg), errMsg.toStdString().c_str()); QCOMPARE(config.channelList()->count(), 3); QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromMHz(439.56250)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromMHz(431.96250)); QCOMPARE(config.channelList()->channel(1)->txFrequency(), Frequency::fromMHz(447.16250)); QCOMPARE(config.channelList()->channel(2)->txFrequency(), Frequency::fromMHz(439.56250)); } QTEST_GUILESS_MAIN(TableFormatTest) ================================================ FILE: test/tableformattest.hh ================================================ #ifndef TABLEFORMATTEST_HH #define TABLEFORMATTEST_HH #include class TableFormatTest : public QObject { Q_OBJECT public: explicit TableFormatTest(QObject *parent = nullptr); private slots: void testFrequencyParser(); }; #endif // TABLEFORMATTEST_HH ================================================ FILE: test/trafotest.cc ================================================ #include "trafotest.hh" #include "intermediaterepresentation.hh" #include "configcopyvisitor.hh" TrafoTest::TrafoTest(QObject *parent) : UnitTestBase(parent) { // pass... } void TrafoTest::testZoneSplitVisitor() { ErrorStack err; Config *copy = ConfigCopy::copy(&_basicConfig, err)->as(); if (nullptr == copy) QFAIL(err.format().toLocal8Bit().constData()); ZoneSplitVisitor splitter; if (! splitter.process(copy, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(copy->zones()->count(), 4); QVERIFY(copy->zones()->get(0)->name().endsWith(" A")); QVERIFY(copy->zones()->get(1)->name().endsWith(" B")); QCOMPARE(copy->zones()->get(0)->name().chopped(2), copy->zones()->get(1)->name().chopped(2)); QCOMPARE(copy->zones()->get(0)->as()->A()->count(), 3); QCOMPARE(copy->zones()->get(1)->as()->A()->count(), 1); } void TrafoTest::testZoneMergeVisitor() { ErrorStack err; Config *copy = ConfigCopy::copy(&_basicConfig, err)->as(); if (nullptr == copy) QFAIL(err.format().toLocal8Bit().constData()); ZoneSplitVisitor splitter; if (! splitter.process(copy, err)) QFAIL(err.format().toLocal8Bit().constData()); ZoneMergeVisitor merger; if (! merger.process(copy, err)) QFAIL(err.format().toLocal8Bit().constData()); QCOMPARE(_basicConfig.compare(*copy), 0); } void TrafoTest::testListElementRemoval() { ErrorStack err; Config *copy = ConfigCopy::copy(&_basicConfig, err)->as(); if (nullptr == copy) QFAIL(err.format().toLocal8Bit().constData()); ObjectFilterVisitor filter({DMRAPRSSystem::staticMetaObject}); if (! filter.process(copy, err)) QFAIL(err.format().toLocal8Bit().constData()); // Check filter QCOMPARE(copy->posSystems()->count(), 0); // Check references to deleted objects QCOMPARE(copy->channelList()->channel(1)->as()->aprs(), nullptr); } void TrafoTest::testPropertyRemoval() { ErrorStack err; Config config; if (! config.readYAML(":/data/anytone_call_hangtime.yaml", err)) QFAIL(QString("Cannot open codeplug file: %1") .arg(err.format()).toStdString().c_str()); QVERIFY2(config.settings()->anytoneExtension(), "Expected AnyTone settings extension."); ObjectFilterVisitor filter({AnytoneSettingsExtension::staticMetaObject}); if (! filter.process(&config, err)) QFAIL(err.format().toLocal8Bit().constData()); // Check filter auto settings = config.settings(); auto anytoneExt = settings->anytoneExtension(); QVERIFY(anytoneExt == nullptr); } QTEST_GUILESS_MAIN(TrafoTest) ================================================ FILE: test/trafotest.hh ================================================ #ifndef TRAFOTEST_HH #define TRAFOTEST_HH #include "libdmrconfigtest.hh" class TrafoTest: public UnitTestBase { Q_OBJECT public: explicit TrafoTest(QObject *parent=nullptr); private slots: void testZoneSplitVisitor(); void testZoneMergeVisitor(); void testListElementRemoval(); void testPropertyRemoval(); }; #endif // TRAFOTEST_HH ================================================ FILE: test/utilstest.cc ================================================ #include "utilstest.hh" #include #include #include "utils.hh" #include "frequency.hh" #include "chirpformat.hh" #include "config.hh" UtilsTest::UtilsTest(QObject *parent) : QObject{parent} { // pass... } void UtilsTest::initTestCase() { } void UtilsTest::testDecodeUnicode() { QString testString("abc123äöü"); QCOMPARE(decode_unicode((uint16_t *)testString.data(), testString.size()), testString); } void UtilsTest::testEncodeUnicode() { QString testString("abc123äöü"); QByteArray bufferTrue(32, 0x00), bufferTest(32, 0xff); memcpy(bufferTrue.data(), testString.data(), 18); encode_unicode((uint16_t *)bufferTest.data(), testString, 16); QCOMPARE(bufferTest, bufferTrue); } void UtilsTest::testEncodeASCII() { const char *testString = "abc"; QByteArray bufferTrue(16, 0xff), bufferTest(16,0x00); memcpy(bufferTrue.data(), testString, 3); encode_ascii((uint8_t *)bufferTest.data(), testString, 16, 0xff); QCOMPARE(bufferTest, bufferTrue); } void UtilsTest::testDecodeASCII() { const char *testString = "abc\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff"; QString res = decode_ascii((const uint8_t *)testString, 16, 0xff); QCOMPARE(res, QString("abc")); } void UtilsTest::testDecodeFrequency() { uint32_t bcd = 0x12345678U; double freq = 123.45678; QCOMPARE(decode_frequency(bcd), freq); } void UtilsTest::testEncodeFrequency() { uint32_t bcd = 0x12345678U; double freq = 123.45678; QCOMPARE(encode_frequency(freq), bcd); QCOMPARE(decode_frequency(encode_frequency(439.5630)), 439.5630); } void UtilsTest::testDecodeDMRID_bcd() { uint8_t bcd[4] = {0x12, 0x34, 0x56, 0x78}; uint32_t num = 12345678U; QCOMPARE(decode_dmr_id_bcd((uint8_t *)&bcd), num); } void UtilsTest::testEncodeDMRID_bcd() { const char bcd[4] = {0x12, 0x34, 0x56, 0x78}; uint32_t num = 12345678U; QByteArray res(4, 0); encode_dmr_id_bcd((uint8_t *)res.data(), num); QCOMPARE(res, QByteArray(bcd, 4)); } void UtilsTest::testFrequencyParser() { QCOMPARE(Frequency::fromString("100Hz").inHz(), 100ULL); QCOMPARE(Frequency::fromString("100 Hz").inHz(), 100ULL); QCOMPARE(Frequency::fromString("100kHz").inHz(), 100000ULL); QCOMPARE(Frequency::fromString("100 kHz").inHz(), 100000ULL); QCOMPARE(Frequency::fromString("100MHz").inHz(), 100000000ULL); QCOMPARE(Frequency::fromString("100 MHz").inHz(), 100000000ULL); QCOMPARE(Frequency::fromString("100GHz").inHz(), 100000000000ULL); QCOMPARE(Frequency::fromString("100 GHz").inHz(), 100000000000ULL); QCOMPARE(Frequency::fromString("100").inHz(), 100000000ULL); QCOMPARE(Frequency::fromString("100.0").inHz(), 100000000ULL); } void UtilsTest::testLocator() { QGeoCoordinate coor; coor = loc2deg("JO62"); QVERIFY(coor.isValid()); QCOMPARE(deg2loc(coor, 4), "JO62"); coor = loc2deg("JO62jl"); QVERIFY(coor.isValid()); QCOMPARE(deg2loc(coor, 6), "JO62jl"); coor = loc2deg("JO62jl55"); QVERIFY(coor.isValid()); QCOMPARE(deg2loc(coor, 8), "JO62jl55"); coor = loc2deg("JO62jl55jj"); QVERIFY(coor.isValid()); QCOMPARE(deg2loc(coor, 10), "JO62jl55jj"); } void UtilsTest::testEndianess() { char val1le[] = {0x34, 0x12}, val1be[] = {0x12,0x34}; QCOMPARE(qFromLittleEndian(*(quint16 *)val1le), 0x1234); QCOMPARE(qFromBigEndian(*(quint16 *)val1be), 0x1234); char val2le[] = {0x78, 0x56, 0x34, 0x12}, val2be[] = {0x12, 0x34, 0x56, 0x78}; QCOMPARE(qFromLittleEndian(*(quint32 *)val2le), 0x12345678); QCOMPARE(qFromBigEndian(*(quint32 *)val2be), 0x12345678); } void UtilsTest::testFrequencyNearestMap() { auto map = Frequency::MapNearest( { {Frequency::fromHz(100), 0}, {Frequency::fromHz(200), 1}, {Frequency::fromHz(300), 2}, {Frequency::fromHz(400), 3}, }); QCOMPARE(map.key(Frequency::fromHz(10)), Frequency::fromHz(100)); QCOMPARE(map.key(Frequency::fromHz(100)), Frequency::fromHz(100)); QCOMPARE(map.key(Frequency::fromHz(110)), Frequency::fromHz(100)); QCOMPARE(map.key(Frequency::fromHz(160)), Frequency::fromHz(200)); QCOMPARE(map.key(Frequency::fromHz(200)), Frequency::fromHz(200)); QCOMPARE(map.key(Frequency::fromHz(210)), Frequency::fromHz(200)); QCOMPARE(map.key(Frequency::fromHz(360)), Frequency::fromHz(400)); QCOMPARE(map.key(Frequency::fromHz(400)), Frequency::fromHz(400)); QCOMPARE(map.key(Frequency::fromHz(410)), Frequency::fromHz(400)); QCOMPARE(map.value(Frequency::fromHz(10)), 0); QCOMPARE(map.value(Frequency::fromHz(100)), 0); QCOMPARE(map.value(Frequency::fromHz(110)), 0); QCOMPARE(map.value(Frequency::fromHz(160)), 1); QCOMPARE(map.value(Frequency::fromHz(200)), 1); QCOMPARE(map.value(Frequency::fromHz(210)), 1); QCOMPARE(map.value(Frequency::fromHz(360)), 3); QCOMPARE(map.value(Frequency::fromHz(400)), 3); QCOMPARE(map.value(Frequency::fromHz(410)), 3); } QTEST_GUILESS_MAIN(UtilsTest) ================================================ FILE: test/utilstest.hh ================================================ #ifndef UTILSTEST_HH #define UTILSTEST_HH #include class UtilsTest : public QObject { Q_OBJECT public: explicit UtilsTest(QObject *parent = nullptr); private slots: void initTestCase(); void testDecodeUnicode(); void testEncodeUnicode(); void testDecodeASCII(); void testEncodeASCII(); void testDecodeFrequency(); void testEncodeFrequency(); void testDecodeDMRID_bcd(); void testEncodeDMRID_bcd(); void testFrequencyParser(); void testLocator(); void testEndianess(); void testFrequencyNearestMap(); }; #endif // UTILSTEST_HH ================================================ FILE: test/uv390_test.cc ================================================ #include "uv390_test.hh" #include "config.hh" #include "uv390_codeplug.hh" #include "errorstack.hh" #include #include UV390Test::UV390Test(QObject *parent) : UnitTestBase(parent) { // pass... } void UV390Test::testBasicConfigEncoding() { ErrorStack err; UV390Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT UV390: {}") .arg(err.format()).toStdString().c_str()); } } void UV390Test::testBasicConfigDecoding() { ErrorStack err; UV390Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_basicConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT UV390: %1") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for TyT UV390: %1") .arg(err.format()).toStdString().c_str()); } } void UV390Test::testChannelFrequency() { ErrorStack err; UV390Codeplug codeplug; codeplug.clear(); if (! codeplug.encode(&_channelFrequencyConfig, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT MD-UV390: {}") .arg(err.format()).toStdString().c_str()); } Config config; if (! codeplug.decode(&config, err)) { QFAIL(QString("Cannot decode codeplug for TyT MD-UV390: {}") .arg(err.format()).toStdString().c_str()); } QCOMPARE(config.channelList()->channel(0)->rxFrequency(), Frequency::fromHz(123456780ULL)); QCOMPARE(config.channelList()->channel(0)->txFrequency(), Frequency::fromHz(999999990ULL)); } void UV390Test::testSMSTemplates() { Config config; config.radioIDs()->add(new DMRRadioID("ID", 1234567)); SMSTemplate *sms0 = new SMSTemplate(); sms0->setName("SMS0"); sms0->setMessage("ABC"); SMSTemplate *sms1 = new SMSTemplate(); sms1->setName("SMS1"); sms1->setMessage("XYZ"); config.smsExtension()->smsTemplates()->add(sms0); config.smsExtension()->smsTemplates()->add(sms1); ErrorStack err; UV390Codeplug codeplug; if (! codeplug.encode(&config, Codeplug::Flags(), err)) { QFAIL(QString("Cannot encode codeplug for TyT MD-UV390: %1") .arg(err.format()).toStdString().c_str()); } Config decoded; if (! codeplug.decode(&decoded, err)) { QFAIL(QString("Cannot decode codeplug for TyT MD-UV390: %1") .arg(err.format()).toStdString().c_str()); } QCOMPARE(decoded.smsExtension()->smsTemplates()->count(), 2); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(0)->name(), "SMS0"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(0)->message(), "ABC"); //QCOMPARE_NE(decoded.smsExtension()->smsTemplates()->message(1)->name(), "SMS1"); QCOMPARE(decoded.smsExtension()->smsTemplates()->message(1)->message(), "XYZ"); } QTEST_GUILESS_MAIN(UV390Test) ================================================ FILE: test/uv390_test.hh ================================================ #ifndef UV390TEST_HH #define UV390TEST_HH #include "libdmrconfigtest.hh" class UV390Test : public UnitTestBase { Q_OBJECT public: explicit UV390Test(QObject *parent = nullptr); private slots: void testBasicConfigEncoding(); void testBasicConfigDecoding(); void testChannelFrequency(); void testSMSTemplates(); }; #endif // UV390TEST_HH