From dc6b29d625b56b578b5077a5a30c6bd0e7221d2a Mon Sep 17 00:00:00 2001 From: Jonathan Naylor Date: Thu, 10 May 2018 19:30:05 +0100 Subject: [PATCH] Initial commit. --- .gitignore | 10 + Arduino/RepeaterControl/RepeaterControl.ino | 104 + BUILD.txt | 77 + CHANGES.txt | 2088 +++++++++++++++++ COPYING.txt | 340 +++ Common/AMBEFEC.cpp | 638 +++++ Common/AMBEFEC.h | 37 + Common/AnnouncementCallback.h | 28 + Common/AnnouncementUnit.cpp | 149 ++ Common/AnnouncementUnit.h | 54 + Common/ArduinoController.cpp | 102 + Common/ArduinoController.h | 42 + Common/AudioCallback.h | 27 + Common/BeaconCallback.h | 26 + Common/BeaconUnit.cpp | 394 ++++ Common/BeaconUnit.h | 92 + Common/CCITTChecksum.cpp | 90 + Common/CCITTChecksum.h | 39 + Common/CCITTChecksumReverse.cpp | 92 + Common/CCITTChecksumReverse.h | 39 + Common/CallsignList.cpp | 76 + Common/CallsignList.h | 40 + Common/Common.vcxproj | 264 +++ Common/Common.vcxproj.filters | 326 +++ Common/DStarDefines.h | 198 ++ Common/DStarGMSKDemodulator.cpp | 120 + Common/DStarGMSKDemodulator.h | 46 + Common/DStarGMSKModulator.cpp | 62 + Common/DStarGMSKModulator.h | 33 + Common/DStarRepeaterConfig.cpp | 1906 +++++++++++++++ Common/DStarRepeaterConfig.h | 243 ++ Common/DStarScrambler.cpp | 139 ++ Common/DStarScrambler.h | 34 + Common/DVAPController.cpp | 1026 ++++++++ Common/DVAPController.h | 118 + Common/DVMegaController.cpp | 1149 +++++++++ Common/DVMegaController.h | 101 + Common/DVRPTRV1Controller.cpp | 1042 ++++++++ Common/DVRPTRV1Controller.h | 98 + Common/DVRPTRV2Controller.cpp | 841 +++++++ Common/DVRPTRV2Controller.h | 94 + Common/DVRPTRV3Controller.cpp | 841 +++++++ Common/DVRPTRV3Controller.h | 94 + Common/DVTOOLFileReader.cpp | 187 ++ Common/DVTOOLFileReader.h | 57 + Common/DVTOOLFileWriter.cpp | 346 +++ Common/DVTOOLFileWriter.h | 54 + Common/DummyController.cpp | 46 + Common/DummyController.h | 35 + Common/ExternalController.cpp | 135 ++ Common/ExternalController.h | 56 + Common/FIRFilter.cpp | 109 + Common/FIRFilter.h | 41 + Common/GMSKController.cpp | 374 +++ Common/GMSKController.h | 54 + Common/GMSKModem.cpp | 18 + Common/GMSKModem.h | 49 + Common/GMSKModemLibUsb.cpp | 443 ++++ Common/GMSKModemLibUsb.h | 90 + Common/GMSKModemWinUSB.cpp | 381 +++ Common/GMSKModemWinUSB.h | 60 + Common/GPIOController.cpp | 170 ++ Common/GPIOController.h | 46 + Common/GatewayProtocolHandler.cpp | 238 ++ Common/GatewayProtocolHandler.h | 54 + Common/Golay.cpp | 1092 +++++++++ Common/Golay.h | 31 + Common/HardwareController.cpp | 18 + Common/HardwareController.h | 32 + Common/HeaderData.cpp | 350 +++ Common/HeaderData.h | 92 + Common/K8055Controller.cpp | 412 ++++ Common/K8055Controller.h | 58 + Common/LogEvent.cpp | 45 + Common/LogEvent.h | 40 + Common/Logger.cpp | 122 + Common/Logger.h | 45 + Common/MMDVMController.cpp | 866 +++++++ Common/MMDVMController.h | 96 + Common/Modem.cpp | 94 + Common/Modem.h | 68 + Common/OutputQueue.cpp | 138 ++ Common/OutputQueue.h | 54 + Common/RepeaterProtocolHandler.cpp | 539 +++++ Common/RepeaterProtocolHandler.h | 72 + Common/RingBuffer.h | 143 ++ Common/SerialDataController.cpp | 464 ++++ Common/SerialDataController.h | 73 + Common/SerialLineController.cpp | 533 +++++ Common/SerialLineController.h | 73 + Common/SerialPortSelector.cpp | 144 ++ Common/SerialPortSelector.h | 31 + Common/SlowDataDecoder.cpp | 155 ++ Common/SlowDataDecoder.h | 49 + Common/SlowDataEncoder.cpp | 197 ++ Common/SlowDataEncoder.h | 42 + Common/SoundCardController.cpp | 1095 +++++++++ Common/SoundCardController.h | 96 + Common/SoundCardReaderWriter.cpp | 787 +++++++ Common/SoundCardReaderWriter.h | 128 + Common/SplitController.cpp | 747 ++++++ Common/SplitController.h | 107 + Common/TCPReaderWriter.cpp | 234 ++ Common/TCPReaderWriter.h | 57 + Common/Timer.cpp | 67 + Common/Timer.h | 89 + Common/UDPReaderWriter.cpp | 230 ++ Common/UDPReaderWriter.h | 58 + Common/UDRCController.cpp | 215 ++ Common/UDRCController.h | 33 + Common/URIUSBController.cpp | 411 ++++ Common/URIUSBController.h | 55 + Common/Utils.cpp | 61 + Common/Utils.h | 32 + Common/Version.h | 32 + DStarRepeater.sln | 61 + DStarRepeater/DStarRepeater.vcxproj | 204 ++ DStarRepeater/DStarRepeater.vcxproj.filters | 74 + DStarRepeater/DStarRepeaterApp.cpp | 694 ++++++ DStarRepeater/DStarRepeaterApp.h | 90 + DStarRepeater/DStarRepeaterD.cpp | 565 +++++ DStarRepeater/DStarRepeaterD.h | 52 + DStarRepeater/DStarRepeaterDefs.h | 39 + DStarRepeater/DStarRepeaterFrame.cpp | 586 +++++ DStarRepeater/DStarRepeaterFrame.h | 83 + DStarRepeater/DStarRepeaterLogger.cpp | 65 + DStarRepeater/DStarRepeaterLogger.h | 35 + DStarRepeater/DStarRepeaterRXThread.cpp | 503 ++++ DStarRepeater/DStarRepeaterRXThread.h | 98 + DStarRepeater/DStarRepeaterStatusData.cpp | 199 ++ DStarRepeater/DStarRepeaterStatusData.h | 106 + DStarRepeater/DStarRepeaterTRXThread.cpp | 1979 ++++++++++++++++ DStarRepeater/DStarRepeaterTRXThread.h | 223 ++ DStarRepeater/DStarRepeaterTXRXThread.cpp | 913 +++++++ DStarRepeater/DStarRepeaterTXRXThread.h | 132 ++ DStarRepeater/DStarRepeaterTXThread.cpp | 536 +++++ DStarRepeater/DStarRepeaterTXThread.h | 101 + DStarRepeater/DStarRepeaterThread.cpp | 23 + DStarRepeater/DStarRepeaterThread.h | 75 + .../DStarRepeaterConfig.vcxproj | 228 ++ .../DStarRepeaterConfig.vcxproj.filters | 146 ++ .../DStarRepeaterConfigActiveHangSet.cpp | 59 + .../DStarRepeaterConfigActiveHangSet.h | 38 + .../DStarRepeaterConfigAnnouncementSet.cpp | 130 + .../DStarRepeaterConfigAnnouncementSet.h | 51 + .../DStarRepeaterConfigApp.cpp | 93 + DStarRepeaterConfig/DStarRepeaterConfigApp.h | 47 + .../DStarRepeaterConfigBeaconSet.cpp | 123 + .../DStarRepeaterConfigBeaconSet.h | 47 + .../DStarRepeaterConfigCallsignSet.cpp | 334 +++ .../DStarRepeaterConfigCallsignSet.h | 60 + .../DStarRepeaterConfigControl1Set.cpp | 234 ++ .../DStarRepeaterConfigControl1Set.h | 74 + .../DStarRepeaterConfigControl2Set.cpp | 180 ++ .../DStarRepeaterConfigControl2Set.h | 71 + .../DStarRepeaterConfigControllerSet.cpp | 164 ++ .../DStarRepeaterConfigControllerSet.h | 47 + .../DStarRepeaterConfigDVAPSet.cpp | 170 ++ .../DStarRepeaterConfigDVAPSet.h | 48 + .../DStarRepeaterConfigDVMegaSet.cpp | 311 +++ .../DStarRepeaterConfigDVMegaSet.h | 57 + .../DStarRepeaterConfigDVRPTR1Set.cpp | 174 ++ .../DStarRepeaterConfigDVRPTR1Set.h | 50 + .../DStarRepeaterConfigDVRPTR2Set.cpp | 232 ++ .../DStarRepeaterConfigDVRPTR2Set.h | 58 + .../DStarRepeaterConfigDVRPTR3Set.cpp | 232 ++ .../DStarRepeaterConfigDVRPTR3Set.h | 58 + DStarRepeaterConfig/DStarRepeaterConfigDefs.h | 28 + .../DStarRepeaterConfigFrame.cpp | 294 +++ .../DStarRepeaterConfigFrame.h | 64 + .../DStarRepeaterConfigGMSKSet.cpp | 134 ++ .../DStarRepeaterConfigGMSKSet.h | 44 + .../DStarRepeaterConfigMMDVMSet.cpp | 187 ++ .../DStarRepeaterConfigMMDVMSet.h | 52 + .../DStarRepeaterConfigModemSet.cpp | 272 +++ .../DStarRepeaterConfigModemSet.h | 47 + .../DStarRepeaterConfigNetworkSet.cpp | 144 ++ .../DStarRepeaterConfigNetworkSet.h | 49 + .../DStarRepeaterConfigSoundCardSet.cpp | 206 ++ .../DStarRepeaterConfigSoundCardSet.h | 54 + .../DStarRepeaterConfigSplitSet.cpp | 162 ++ .../DStarRepeaterConfigSplitSet.h | 54 + .../DStarRepeaterConfigTimesSet.cpp | 71 + .../DStarRepeaterConfigTimesSet.h | 40 + DStarRepeaterConfig/Makefile | 92 + Data/Makefile | 22 + Data/de_DE.ambe | Bin 0 -> 21262 bytes Data/de_DE.indx | 44 + Data/dk_DK.ambe | Bin 0 -> 19669 bytes Data/dk_DK.indx | 44 + Data/en_GB.ambe | Bin 0 -> 22000 bytes Data/en_GB.indx | 44 + Data/en_US.ambe | Bin 0 -> 26734 bytes Data/en_US.indx | 44 + Data/es_ES.ambe | Bin 0 -> 22315 bytes Data/es_ES.indx | 44 + Data/fr_FR.ambe | Bin 0 -> 20137 bytes Data/fr_FR.indx | 44 + Data/it_IT.ambe | Bin 0 -> 22099 bytes Data/it_IT.indx | 44 + Data/no_NO.ambe | Bin 0 -> 20164 bytes Data/no_NO.indx | 44 + Data/pl_PL.ambe | Bin 0 -> 24169 bytes Data/pl_PL.indx | 44 + Data/se_SE.ambe | Bin 0 -> 24205 bytes Data/se_SE.indx | 44 + GUICommon/AddressTextCtrl.cpp | 29 + GUICommon/AddressTextCtrl.h | 37 + GUICommon/CallsignTextCtrl.cpp | 29 + GUICommon/CallsignTextCtrl.h | 37 + GUICommon/GUICommon.vcxproj | 151 ++ GUICommon/GUICommon.vcxproj.filters | 47 + GUICommon/MessageTextCtrl.cpp | 29 + GUICommon/MessageTextCtrl.h | 37 + GUICommon/PortTextCtrl.cpp | 29 + GUICommon/PortTextCtrl.h | 37 + GUICommon/RestrictedTextCtrl.cpp | 40 + GUICommon/RestrictedTextCtrl.h | 32 + LinuxUSB/Makefile.am | 3 + LinuxUSB/Makefile.in | 602 +++++ LinuxUSB/README.txt | 4 + LinuxUSB/rmk8055drv.c | 87 + LinuxUSB/rmuridrv.c | 85 + README.md | 13 + WindowsUSB/dvrptr_cdc.inf | 76 + WindowsUSB/gmsk.cat | Bin 0 -> 4061 bytes WindowsUSB/gmsk.inf | 181 ++ WindowsUSB/xDVRPTR-32-64-2.inf | 95 + debian/README.Debian | 6 + debian/README.source | 10 + debian/changelog | 5 + debian/compat | 1 + debian/control | 38 + debian/copyright | 34 + debian/docs | 5 + debian/dstarrepeater-udev.postinst | 52 + debian/dstarrepeater-udev.udev | 23 + debian/dstarrepeater.install | 2 + debian/dstarrepeater.logrotate | 7 + debian/dstarrepeaterd.dstarrepeaterd@.service | 13 + debian/dstarrepeaterd.examples | 1 + debian/dstarrepeaterd.install | 1 + debian/dstarrepeaterd.logrotate | 7 + debian/opendv-usb-utils.install | 2 + debian/rules | 41 + debian/source/format | 1 + linux/dstarrepeater_1.example | 114 + 247 files changed, 42555 insertions(+) create mode 100644 .gitignore create mode 100644 Arduino/RepeaterControl/RepeaterControl.ino create mode 100644 BUILD.txt create mode 100644 CHANGES.txt create mode 100644 COPYING.txt create mode 100644 Common/AMBEFEC.cpp create mode 100644 Common/AMBEFEC.h create mode 100644 Common/AnnouncementCallback.h create mode 100644 Common/AnnouncementUnit.cpp create mode 100644 Common/AnnouncementUnit.h create mode 100644 Common/ArduinoController.cpp create mode 100644 Common/ArduinoController.h create mode 100644 Common/AudioCallback.h create mode 100644 Common/BeaconCallback.h create mode 100644 Common/BeaconUnit.cpp create mode 100644 Common/BeaconUnit.h create mode 100644 Common/CCITTChecksum.cpp create mode 100644 Common/CCITTChecksum.h create mode 100644 Common/CCITTChecksumReverse.cpp create mode 100644 Common/CCITTChecksumReverse.h create mode 100644 Common/CallsignList.cpp create mode 100644 Common/CallsignList.h create mode 100644 Common/Common.vcxproj create mode 100644 Common/Common.vcxproj.filters create mode 100644 Common/DStarDefines.h create mode 100644 Common/DStarGMSKDemodulator.cpp create mode 100644 Common/DStarGMSKDemodulator.h create mode 100644 Common/DStarGMSKModulator.cpp create mode 100644 Common/DStarGMSKModulator.h create mode 100644 Common/DStarRepeaterConfig.cpp create mode 100644 Common/DStarRepeaterConfig.h create mode 100644 Common/DStarScrambler.cpp create mode 100644 Common/DStarScrambler.h create mode 100644 Common/DVAPController.cpp create mode 100644 Common/DVAPController.h create mode 100644 Common/DVMegaController.cpp create mode 100644 Common/DVMegaController.h create mode 100644 Common/DVRPTRV1Controller.cpp create mode 100644 Common/DVRPTRV1Controller.h create mode 100644 Common/DVRPTRV2Controller.cpp create mode 100644 Common/DVRPTRV2Controller.h create mode 100644 Common/DVRPTRV3Controller.cpp create mode 100644 Common/DVRPTRV3Controller.h create mode 100644 Common/DVTOOLFileReader.cpp create mode 100644 Common/DVTOOLFileReader.h create mode 100644 Common/DVTOOLFileWriter.cpp create mode 100644 Common/DVTOOLFileWriter.h create mode 100644 Common/DummyController.cpp create mode 100644 Common/DummyController.h create mode 100644 Common/ExternalController.cpp create mode 100644 Common/ExternalController.h create mode 100644 Common/FIRFilter.cpp create mode 100644 Common/FIRFilter.h create mode 100644 Common/GMSKController.cpp create mode 100644 Common/GMSKController.h create mode 100644 Common/GMSKModem.cpp create mode 100644 Common/GMSKModem.h create mode 100644 Common/GMSKModemLibUsb.cpp create mode 100644 Common/GMSKModemLibUsb.h create mode 100644 Common/GMSKModemWinUSB.cpp create mode 100644 Common/GMSKModemWinUSB.h create mode 100644 Common/GPIOController.cpp create mode 100644 Common/GPIOController.h create mode 100644 Common/GatewayProtocolHandler.cpp create mode 100644 Common/GatewayProtocolHandler.h create mode 100644 Common/Golay.cpp create mode 100644 Common/Golay.h create mode 100644 Common/HardwareController.cpp create mode 100644 Common/HardwareController.h create mode 100644 Common/HeaderData.cpp create mode 100644 Common/HeaderData.h create mode 100644 Common/K8055Controller.cpp create mode 100644 Common/K8055Controller.h create mode 100644 Common/LogEvent.cpp create mode 100644 Common/LogEvent.h create mode 100644 Common/Logger.cpp create mode 100644 Common/Logger.h create mode 100644 Common/MMDVMController.cpp create mode 100644 Common/MMDVMController.h create mode 100644 Common/Modem.cpp create mode 100644 Common/Modem.h create mode 100644 Common/OutputQueue.cpp create mode 100644 Common/OutputQueue.h create mode 100644 Common/RepeaterProtocolHandler.cpp create mode 100644 Common/RepeaterProtocolHandler.h create mode 100644 Common/RingBuffer.h create mode 100644 Common/SerialDataController.cpp create mode 100644 Common/SerialDataController.h create mode 100644 Common/SerialLineController.cpp create mode 100644 Common/SerialLineController.h create mode 100644 Common/SerialPortSelector.cpp create mode 100644 Common/SerialPortSelector.h create mode 100644 Common/SlowDataDecoder.cpp create mode 100644 Common/SlowDataDecoder.h create mode 100644 Common/SlowDataEncoder.cpp create mode 100644 Common/SlowDataEncoder.h create mode 100644 Common/SoundCardController.cpp create mode 100644 Common/SoundCardController.h create mode 100644 Common/SoundCardReaderWriter.cpp create mode 100644 Common/SoundCardReaderWriter.h create mode 100644 Common/SplitController.cpp create mode 100644 Common/SplitController.h create mode 100644 Common/TCPReaderWriter.cpp create mode 100644 Common/TCPReaderWriter.h create mode 100644 Common/Timer.cpp create mode 100644 Common/Timer.h create mode 100644 Common/UDPReaderWriter.cpp create mode 100644 Common/UDPReaderWriter.h create mode 100644 Common/UDRCController.cpp create mode 100644 Common/UDRCController.h create mode 100644 Common/URIUSBController.cpp create mode 100644 Common/URIUSBController.h create mode 100644 Common/Utils.cpp create mode 100644 Common/Utils.h create mode 100644 Common/Version.h create mode 100644 DStarRepeater.sln create mode 100644 DStarRepeater/DStarRepeater.vcxproj create mode 100644 DStarRepeater/DStarRepeater.vcxproj.filters create mode 100644 DStarRepeater/DStarRepeaterApp.cpp create mode 100644 DStarRepeater/DStarRepeaterApp.h create mode 100644 DStarRepeater/DStarRepeaterD.cpp create mode 100644 DStarRepeater/DStarRepeaterD.h create mode 100644 DStarRepeater/DStarRepeaterDefs.h create mode 100644 DStarRepeater/DStarRepeaterFrame.cpp create mode 100644 DStarRepeater/DStarRepeaterFrame.h create mode 100644 DStarRepeater/DStarRepeaterLogger.cpp create mode 100644 DStarRepeater/DStarRepeaterLogger.h create mode 100644 DStarRepeater/DStarRepeaterRXThread.cpp create mode 100644 DStarRepeater/DStarRepeaterRXThread.h create mode 100644 DStarRepeater/DStarRepeaterStatusData.cpp create mode 100644 DStarRepeater/DStarRepeaterStatusData.h create mode 100644 DStarRepeater/DStarRepeaterTRXThread.cpp create mode 100644 DStarRepeater/DStarRepeaterTRXThread.h create mode 100644 DStarRepeater/DStarRepeaterTXRXThread.cpp create mode 100644 DStarRepeater/DStarRepeaterTXRXThread.h create mode 100644 DStarRepeater/DStarRepeaterTXThread.cpp create mode 100644 DStarRepeater/DStarRepeaterTXThread.h create mode 100644 DStarRepeater/DStarRepeaterThread.cpp create mode 100644 DStarRepeater/DStarRepeaterThread.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfig.vcxproj create mode 100644 DStarRepeaterConfig/DStarRepeaterConfig.vcxproj.filters create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigActiveHangSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigActiveHangSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigAnnouncementSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigAnnouncementSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigApp.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigApp.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigBeaconSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigBeaconSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigCallsignSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigCallsignSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigControl1Set.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigControl1Set.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigControl2Set.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigControl2Set.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigControllerSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigControllerSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVAPSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVAPSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVMegaSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVMegaSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVRPTR1Set.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVRPTR1Set.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVRPTR2Set.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVRPTR2Set.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVRPTR3Set.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDVRPTR3Set.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigDefs.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigFrame.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigFrame.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigGMSKSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigGMSKSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigMMDVMSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigMMDVMSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigModemSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigModemSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigNetworkSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigNetworkSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigSoundCardSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigSoundCardSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigSplitSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigSplitSet.h create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigTimesSet.cpp create mode 100644 DStarRepeaterConfig/DStarRepeaterConfigTimesSet.h create mode 100644 DStarRepeaterConfig/Makefile create mode 100644 Data/Makefile create mode 100644 Data/de_DE.ambe create mode 100644 Data/de_DE.indx create mode 100644 Data/dk_DK.ambe create mode 100644 Data/dk_DK.indx create mode 100644 Data/en_GB.ambe create mode 100644 Data/en_GB.indx create mode 100644 Data/en_US.ambe create mode 100644 Data/en_US.indx create mode 100644 Data/es_ES.ambe create mode 100644 Data/es_ES.indx create mode 100644 Data/fr_FR.ambe create mode 100644 Data/fr_FR.indx create mode 100644 Data/it_IT.ambe create mode 100644 Data/it_IT.indx create mode 100644 Data/no_NO.ambe create mode 100644 Data/no_NO.indx create mode 100644 Data/pl_PL.ambe create mode 100644 Data/pl_PL.indx create mode 100644 Data/se_SE.ambe create mode 100644 Data/se_SE.indx create mode 100644 GUICommon/AddressTextCtrl.cpp create mode 100644 GUICommon/AddressTextCtrl.h create mode 100644 GUICommon/CallsignTextCtrl.cpp create mode 100644 GUICommon/CallsignTextCtrl.h create mode 100644 GUICommon/GUICommon.vcxproj create mode 100644 GUICommon/GUICommon.vcxproj.filters create mode 100644 GUICommon/MessageTextCtrl.cpp create mode 100644 GUICommon/MessageTextCtrl.h create mode 100644 GUICommon/PortTextCtrl.cpp create mode 100644 GUICommon/PortTextCtrl.h create mode 100644 GUICommon/RestrictedTextCtrl.cpp create mode 100644 GUICommon/RestrictedTextCtrl.h create mode 100644 LinuxUSB/Makefile.am create mode 100644 LinuxUSB/Makefile.in create mode 100644 LinuxUSB/README.txt create mode 100644 LinuxUSB/rmk8055drv.c create mode 100644 LinuxUSB/rmuridrv.c create mode 100644 README.md create mode 100644 WindowsUSB/dvrptr_cdc.inf create mode 100644 WindowsUSB/gmsk.cat create mode 100644 WindowsUSB/gmsk.inf create mode 100644 WindowsUSB/xDVRPTR-32-64-2.inf create mode 100644 debian/README.Debian create mode 100644 debian/README.source create mode 100644 debian/changelog create mode 100644 debian/compat create mode 100644 debian/control create mode 100644 debian/copyright create mode 100644 debian/docs create mode 100644 debian/dstarrepeater-udev.postinst create mode 100644 debian/dstarrepeater-udev.udev create mode 100644 debian/dstarrepeater.install create mode 100644 debian/dstarrepeater.logrotate create mode 100644 debian/dstarrepeaterd.dstarrepeaterd@.service create mode 100644 debian/dstarrepeaterd.examples create mode 100644 debian/dstarrepeaterd.install create mode 100644 debian/dstarrepeaterd.logrotate create mode 100644 debian/opendv-usb-utils.install create mode 100644 debian/rules create mode 100644 debian/source/format create mode 100644 linux/dstarrepeater_1.example diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0f42248 --- /dev/null +++ b/.gitignore @@ -0,0 +1,10 @@ +*.o +*.obj +x86 +Debug +Release +*.user +*~ +*.bak +.vs + diff --git a/Arduino/RepeaterControl/RepeaterControl.ino b/Arduino/RepeaterControl/RepeaterControl.ino new file mode 100644 index 0000000..ca82a04 --- /dev/null +++ b/Arduino/RepeaterControl/RepeaterControl.ino @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + + +// 0 and 1 are used for serial +const int RFTRANSMIT = 2; +const int EXTTRANSMIT = 3; +const int HEARTBEAT1 = 4; +const int HEARTBEAT2 = 13; +const int ACTIVE = 5; +const int OUTPUT1 = 6; +const int OUTPUT2 = 7; +const int OUTPUT3 = 8; +const int OUTPUT4 = 9; + + +const int RFSQUELCH1 = A0; +const int RFSQUELCH2 = A1; +const int EXTSQUELCH = A2; +const int BATTERY = A3; +const int DISABLE = A4; + + +void setup() { + // Set up the outut pins with pull ups + pinMode(RFSQUELCH1, INPUT); + pinMode(RFSQUELCH2, INPUT); + pinMode(EXTSQUELCH, INPUT); + pinMode(BATTERY, INPUT); + pinMode(DISABLE, INPUT); + + digitalWrite(RFSQUELCH1, HIGH); + digitalWrite(RFSQUELCH2, HIGH); + digitalWrite(EXTSQUELCH, HIGH); + digitalWrite(BATTERY, HIGH); + digitalWrite(DISABLE, HIGH); + + // Set up the output pins and set low + pinMode(RFTRANSMIT, OUTPUT); + pinMode(EXTTRANSMIT, OUTPUT); + pinMode(HEARTBEAT1, OUTPUT); + pinMode(HEARTBEAT2, OUTPUT); + pinMode(ACTIVE, OUTPUT); + pinMode(OUTPUT1, OUTPUT); + pinMode(OUTPUT2, OUTPUT); + pinMode(OUTPUT3, OUTPUT); + pinMode(OUTPUT4, OUTPUT); + + digitalWrite(RFTRANSMIT, LOW); + digitalWrite(EXTTRANSMIT, LOW); + digitalWrite(HEARTBEAT1, LOW); + digitalWrite(HEARTBEAT2, HIGH); + digitalWrite(ACTIVE, LOW); + digitalWrite(OUTPUT1, LOW); + digitalWrite(OUTPUT2, LOW); + digitalWrite(OUTPUT3, LOW); + digitalWrite(OUTPUT4, LOW); + + // Set up the serial port + Serial.begin(19200); +} + +void loop() { + if (Serial.available() > 0) { + int out = Serial.read(); + + digitalWrite(RFTRANSMIT, (out & 0x01) == 0x01 ? HIGH : LOW); + digitalWrite(EXTTRANSMIT, (out & 0x02) == 0x02 ? HIGH : LOW); + digitalWrite(HEARTBEAT1, (out & 0x04) == 0x04 ? HIGH : LOW); + digitalWrite(HEARTBEAT2, (out & 0x04) == 0x04 ? HIGH : LOW); + digitalWrite(ACTIVE, (out & 0x08) == 0x08 ? HIGH : LOW); + digitalWrite(OUTPUT1, (out & 0x10) == 0x10 ? HIGH : LOW); + digitalWrite(OUTPUT2, (out & 0x20) == 0x20 ? HIGH : LOW); + digitalWrite(OUTPUT3, (out & 0x40) == 0x40 ? HIGH : LOW); + digitalWrite(OUTPUT4, (out & 0x80) == 0x80 ? HIGH : LOW); + } + + byte val = 0x00; + val |= (digitalRead(RFSQUELCH1) == LOW) ? 0x01 : 0x00; + val |= (digitalRead(RFSQUELCH2) == LOW) ? 0x02 : 0x00; + val |= (digitalRead(EXTSQUELCH) == LOW) ? 0x04 : 0x00; + val |= (digitalRead(BATTERY) == LOW) ? 0x08 : 0x00; + val |= (digitalRead(DISABLE) == LOW) ? 0x10 : 0x00; + + Serial.write(val); + + delay(20); // 20ms +} + diff --git a/BUILD.txt b/BUILD.txt new file mode 100644 index 0000000..5140a51 --- /dev/null +++ b/BUILD.txt @@ -0,0 +1,77 @@ +Repeater - 20180510 +=================== + +Windows +------- + +To use the Repeater control software you will first need to build the latest +version of wxWidgets (http://www.wxwidgets.org), the version I used was 2.8.12, +I also installed it in the default location which is C:\wxWidgets-2.8.12. + +You will also need a copy of PortAudio (http://www.portaudio.com), I used the +latest stable version which is currently 20071207 and it appears to be fine. I +put that into the "Visual Studio 2008\Projects" folder alongside the source code +for Repeater itself. A skeleton version of PortAudio suitable for using to build +Repeater is available from the pcrepeatercontroller Yahoo group in the +Files/Windows Extras folder. + +For compiling I use Visual C++ 2008 Express Edition downloaded from Microsoft +for free. I recommend that you use the same. + +To build wxWidgets, you simply need to open Visual Studio 2008 using the File -> +Open -> Projects/Solutions and load the wx_dll.dsw file to be found in +wxWidgets-2.8.12\build\msw directory and then go into Batch Build and select the +DLL Unicode Debug and DLL Uncode Release entries for every one, this take a +little time! Then build them. + +Unfortunately building the software under Windows also requires the downloading +of a very large file from Microsoft, the WDK. Adding this to Visual Studio +causes a lot of problems, so I created an extract of the required files from the +kit and used that instead. If you do want to build this software from scratch, +then download the files HID.zip and WinUSB.zip from the Yahoo! group. Otherwise +simply use the supplied binaries. + +The path names for things like wxWidgets and PortAudio are embedded within the +Solution and Project preferences, and will need changing if anything other than +these default locations are used. The first pass through the compiler will no +doubt tell you all that you need to know if there are problems. + +Once you have built the executables, you will need to copy the +portaudio_x86.dll, wxbase28u_vc_custom.dll, wxmsw28u_adv_vc_custom.dll, +and wxmsw28u_core_vc_custom.dll to the same directory as your newly +built executables in order for it to run. If you are running in debug mode then +the required wxWidgets files have the names xxx28ud_xxxx instead. These can be +found in the wxWidgets-2.8.12\lib\vc_dll directory. + +It is also probable that you'll need to install a copy of the latest Visual C++ +run-time libraries from Microsoft, if you are not running the Repeater software +on the same machine as the development/compilation was done on. To do this you +need to copy and run the Vcredist_x86.exe file which is found at + + +Linux +----- + +You need to ensure that wxGTK, ALSA and libusb-1.0 are already +installed on your machine, under Ubuntu these are available from the standard +repositories, the version of wxWidgets is adequate. However you should get the +latest version of PortAudio from its home and build it from scratch. + +To install them from scratch, you need to get wxGTK from +, and libusb-1.0 from . +If you do a "make install" on all of them then they'll be installed in the +right places and nothing more needs to be done. + +On the Raspberry Pi you will need to build and install the wiringPi GPIO library +which is available from . +This will allow use the GPIO pins as controller pins for the different repeaters +if needed. + +The top level Makefile includes platform specific settings from the file +"settings.mk". The default file is for x86 Linux systems. Samples for other +platforms are provided by other developers, copy a required version to +"settings.mk" when needed. + +To actually build the software, type "make" in the same directory as this file +and all should build without errors, there may be a warning or two though. Once +compiled log in as root or use the sudo command, and do "make install". diff --git a/CHANGES.txt b/CHANGES.txt new file mode 100644 index 0000000..df1da31 --- /dev/null +++ b/CHANGES.txt @@ -0,0 +1,2088 @@ +Repeater - 20180510 +=================== + +20090530 +-------- + +Added audio filtering and optional pre- and de-emphasis. Also modified the +configuration to have less pages as well as add the new settings. + +20090607 +-------- + +Silly bug in the through audio fixed. +Added the option to use a sound file as an input, but only in a debug build. + +20090608 +-------- + +Added debugging information in the tone decoder. +Not generally released. + +20090609 +-------- + +Added a simple VOGAD with reporting in the GUI. +Reworked the filters and output levels. +Not generally released. + +20090610 +-------- + +Added the audio level value to the GUI. +Re-arranged the GUI layout. +Not generally released. + +20090613 +-------- +Added Linux support, but not tested. +Changed the name of the executable and directories. +Changed how the K8055 parameters are set. +Allow for COM ports higher than COM10. +Not generally released. + +20090616 +-------- + +Added the D-Star Repeater, but unfinished. +Optimised access to the K8055. +Not generally released. + +20090617 +-------- + +The D-Star Repeater now compiles cleanly on Windows and Linux, so a good time to +do a release. + +20090618 +-------- + +Removed the icons from the programs. +Increased the audio output for the external output. +Not generally released. + +20090619 +-------- + +Added a VOGAD unit, and made the VOGAD on both the Radio and External audio. +Not generally released. + +20090626 +-------- + +Converted the pre-emphasis, de-emphasis and audio filters to IIRs for efficiency +and better shapes. +Show VOGAD levels for the Radio and External on the main screen. +Not generally released. + +20090629 +-------- + +Switched off filters when not transmitting to reduce CPU load. +Moved CTCSS insertion after pre-emphasis filter. +Not generally released. + +200900630 +--------- + +Changed the main filter back to the FIR for performance reasons. +First version to be used on a real repeater, GB3IN on 2m. + +20090812 +-------- + +Fixed CTCSS output tones. +Widened the CTCSS detector from 2Hz to 5Hz and from 500ms to 200ms. +Numerous internal improvements. +Not generally released. + +20090813 +-------- + +Fixed audio out bug. +CTCSS output is now for the whole time the repeater is accessed. + +20090824 +-------- + +Audio levels are now reset before each received transmission instead of at the +Ack. +Added controls to toggle the unused output lines to control external hardware. +Delay renamed to Ack in the analogue repeater GUI. +Fixed bugs in the state machine causing timeout resets and a couple of missing +state transitions. + +20090909 +-------- + +Turn off the CTCSS early when the end callsign is enabled because of slow +responding decoders in radios allowing some of the callsign through. +Added an optional audio delay on the input to synchronise with the squelch +input(s). +Make GUI updates optional. + +20090910 +-------- + +Allow for an external CTCSS decoder. +Not generally released. + +20090911 +-------- + +Add a battery input and a change of Ack to signify battery operation. +Not generally released. + +20090916 +-------- + +The External Transmit is only set when relaying radio audio. +First version of the D-Star Controller. +Optimised the K8055 communication. + +20090917 +-------- + +Linux cleanups. +Changes to the D-Star Controller. +Not generally released. + +20090923 +-------- + +Validate the RPT2 callsign. +Fixed bit errors on the D-Star Ack. +Improved the documentation about the D-Star Repeater. +Added a PTT delay for D-Star. + +20091002 +-------- + +Removed RPT2 validation. +Added PTT and Squelch inversion for the D-Star Repeater. +Added G2 Gateway support. +Tightened the filter before the GMSK democulator. +Not generally released. + +20091008 +-------- + +Changes in the G2 protocol handler. +Not generally released. + +20091015 +-------- + +Allow for named configurations to allow multiple repeaters on one machine. +Increased the maximum PTT Delay to 500ms from 200ms. +Added the PTT Delay and PTT and Squelch inversion to the Analogue Repeater. +More changes to the G2 protocol handler. +Not generally released. + +20091016 +-------- + +Added a network watchdog timer to handle the loss of the end packet. + +20091017 +-------- + +Allow for the configuration of the local IP address to bind to. +Not generally released. + +20091020 +-------- + +Lots of Linux GUI cleanups. +Added the Watchdog timer to the D-Star GUI. +Added a simplex mode to the D-Star repeater. +Added an output level to the D-Star repeater to allow for sharing with the +analogue controller. +Not generally released. + +20091021 +-------- + +Removed the confirmation prompt when exiting the repeaters. +Not generally released. + +20091025 +-------- + +Changed to the GMSK modulator to be more conventional. +Added command line versions of the repeaters. +Major updates to the G2 protocol. +Not generally released. + +20091026 +-------- + +Added a new bandpass filter to the analogue repeater to remove the CTCSS tones. +More G2 changes. + +20091027 +-------- + +Removed RPT2 validation from network received headers. +More G2 changes. +Not generally released. + +20091029 +-------- + +Implemented the "Open G2" network protocol. +Not generally released. + +20091030 +-------- + +More changes to the "Open G2" network protocol. +Not generally released. + +20091031 +-------- + +More changes to the "Open G2" network protocol. +Allow network data relaying while waiting for the ack. +Not generally released. + +20091103 +-------- + +Move the log files to the users home directory. + +20091104 +-------- + +Changes to the protocol handlers to handle odd cases. +Not generally released. + +20091106 +-------- + +A cleanup of the 20091104 release because it works so well and deserves to be +released :-) + +20091117 +-------- + +Added support for serial ports as I/O ports for both repeaters. +Removed the ack when in simplex mode. +Added all the EIA CTCSS tones to the analogue repeater. +Changes to the "Open G2" network protocol. + +20091120 +-------- + +Merged the callsign and beacon timers in the analogue repeater. +Support the URI USB. + +20091125 +-------- + +Changes to the "Open G2" network protocol. +Added the network delay for the D-Star repeater. +Not generally released. + +20091126 +-------- + +Bug fix release for network delay. +Don't pass through RF data with the RPT2 entry set to blanks. +Not generally released. + +20091212 +-------- + +Added the Gateway mode to the D-Star repeater. +Don't pass through RF data with the RPT2 entry set to my callsign. +Brought the K8055 drivers internal for both Windows and Linux. +Put the ack back in simplex mode. +Added the callsign on latch to the analogue repeater. +Added a choice of timeout tones to the analogue repeater. + +20091219 +-------- + +Added DTMF and local commands to the analogue repeater. +Added remote and local commands to the D-Star repeater. +Added Callsign Holdoff to the analogue repeater. +Removed the RPT2 validation that was added for the last release. +Added new programs to configure the repeaters and removed configuration from the +main programs. +Added two new programs, rmk8055drv and rmuridrv, to remove the Linux usbhid +driver. +Fixed Simplex/Duplex choice bug. +Not generally released. + +20091226 +-------- + +Added Parrot mode to the D-Star repeater. +Added DTMF display to the analogue repeater. +Not generally released. + +20091228 +-------- + +Tweaks to the DTMF controller. + +20091229 +-------- + +Fixed Linux build issues with 20091228. +Included incomplete Parrot Controller. + +20091230 +-------- + +First version of the Parrot Controller. +Small change to the G2 protocols. +Not generally released. + +20091231 +-------- + +Fixed bugs in the Parrot Controller. +Added adjustable Turnaround Time to the Parrot Controller configuration. + +20100106 +-------- + +Added Watchdog timer to the Parrot Controller. +Added suffix D for 10m to the D-Star Repeater. +Added two external commands to both repeaters. +Added the four outputs to the D-Star control to match the analogue Repeater. +Not generally released. + +20100107 +-------- + +Added a command to be run at a period after the D-Star Repeater closes. +Not generally released. + +20100108 +-------- + +Fixed Linux compilation issues. + +20100111 +-------- + +Added menu commands to run Command 1 and 2 on the analogue repeater. +Removed the timed command added in 20100107. +Added a Disable input to shutdown and startup the repeaters. +Modified the Active output to use with the new Disable input to allow cross linking of repeaters leading to true dual-mode operation. +Fixed bug with K8055s with addresses greater than zero. +Not generally released. + +20100112 +-------- + +Fixed Active output bug in 20100111 in the D-Star repeater. +Added a Disable input to the URI USB. +Allows both external commands for Startup and Shutdown and the Disable input. +Not generally released. + +20100114 +-------- + +Bug fixes for 20100112. + +20100222 +-------- + +Added a gateway callsign to the D-Star repeater for messages. +Removed the Icom G2 Protocol Hander as it wasn't used. +Allow for a -nolog command line option to disable all logging. +Simplified the mode selection and added an ack option for D-Star. +Change network validation to only be on the IP address. +Not generally relased. + +20100223 +-------- + +Bug fixes for 20100222. + +20100412 +-------- + +Changed ack selection to free form text in the analogue repeater. +Removed the need for a squelch input or the audio delay setting for D-Star. +Now takes the header from slow data if the header was missed. +Removed the ack timer. +Better handling of missing sync bytes on both the radio and network sides. + +20100415 +-------- + +Re-added the ack timer, but it can now handle shorter times than before. +Allow for the manual setting of the D-Star gateway callsign. +Fixed a major bug in 20100412. + +20100419 +-------- + +Special test release for GB3IN. +Not generally released. + +20100423 +-------- + +Moved output controls to the menu bar instead of buttons. +Display the last three lines of the log on the repeater screen. +Not generally released. + +20100424 +-------- + +Threading bug in 20100423 fixed. +Not generally released. + +20100510 +-------- + +A bit of a stinker, best forgotten. +Not generally released. + +20100516 +-------- + +Extra D-Star header validation. + +20100603 +-------- + +Bug fixes. +Use the ack timer to pause the D-Star repeater in gateway mode between overs. +Added an optional audio delay to the D-Star output audio. +Not generally released. + +20100604 +-------- + +Bug fixes. +Re-implement the audio delay as negative PTT delay. +Not generally released. + +20100610 +-------- + +Added the end of transmission senc at the end of the network transmision. +Lengthened the D-Star transmit period. +Not generally released. + +20100712 +-------- + +Better handling of error conditions at startup on the repeaters. +Added new time to limit the timeout tones on the analogue repeater. +Change sound card handling times. +Not generally released. + +20100719 +-------- + +A bug fix on the previous release. + +20100720 +-------- + +More bug fixes. +CTCSS goes off before sending the callsign in lockout. + +20100726 +-------- + +Added new CTCSS output modes. +Changed the parameter of PortAudio which appears to make a big difference under +Linux. +Not generally released. + +20100808 +-------- + +Fix to the DVTool files. +Fixed shutdown command. +PortAudio parameter depends on the operating system, new parameter for Linux, +old for Windows. + +20100810 +-------- + +Added 79.7 Hz as a CTCSS tone. +Added an RX Level setting to the D-Star repeater for when running alongside +analogue for adjusting input levels. +Bug in the slow data decoder fixed. +Not generally released. + +20100811 +-------- + +Improvements to the slow data decoder. + +20100813 +-------- + +Added a beacon to the D-Star repeater. +Fixed bug in the new slow data decoder. + +20100815 +-------- + +Re-integrated the configuration for both repeaters back into the main +repeater programs. +Fix startup and shutdown when PTT inversion is set. +Not generally released. + +20100818 +-------- + +Added the GMSK repeater. +Not generally released. + +20100820 +-------- + +Added the DExtra Gateway. +Added hourly usage statistics to all of the repeaters. +Not generally released. + +20100823 +-------- + +Added the CTCSS hang timer. +Not generally released. + +20100825 +-------- + +First working version of the DExtra Gateway. +Not generally released. + +20100825a +--------- + +Bug fixes to the DExtra Gateway. +Not generally released. + +20100826 +-------- + +Linux support for the DExtra Gateway. +Written documentation for the DExtra Gateway. + +20100901 +-------- + +Added optional callsign restriction to both D-Star repeaters. +Added repeater callsign to the DExtra Gateway configuration, as this fixed a +bug. +Not generally relased. + +20100903 +-------- + +Modify the slow data when in gateway mode. +Not generally released. + +20100907 +-------- + +The GMSK Repeater now works on a Satoshi 7.08 system. +Not generally released. + +20100915 +-------- + +Allow for the choice of the GMSK modem type, Satoshi or Dutch*Star. Only the +Satoshi option works as expected. +Added text from the DExtra Reflector to appear in the repeater ack and beacon. +More changes for the GMSK Modem, it appears even better now. +A number of changes to the transmitted callsigns made. +Not generally released. + +20100917 +-------- + +More callsign changes. +More work on the GMSK Modem support. +Not generally released. + +20100922 +-------- + +More callsign changes in the repeaters and Parrot Controller. +Changes timings for the GMSK Modem. +Not generally released. + +20100924 +-------- + +Added display of the ack text to the repeater GUIs. +Use Fred PA4YBR's modem support library for GMSK Modems. + +20101016 +-------- + +Allow use of shared libraries on Linux. +Fixed header transmission for the GMSK Repeater. +Reject relayed gateway-ed data to stop confusing G2. + +20101026 +-------- + +Changed the way the header is received in the GMSK Repeater. +Increased the watchdog timer to two seconds from half second. + +20101104 +-------- + +Changes to the GMSK Repeater. +Pass Flag2 and Flag2 through the D-Star Repeater. +The GMSK Repeater can now build on an AMD64 Linux machine. + +20101107 +-------- + +More changes to the GMSK Repeater. +Add the -gui command line flag to all the programs. + +20101109 +-------- + +More changes to the GMSK Repeater. + +20101109a +--------- + +Plan B for getting full duplex working with the GMSK Repeater, a seperate thread for the transmitter. + +20101110 +-------- + +Another try, but still not working properly + +20101112 +-------- + +Logs are now written into the /var/log directory under Linux. +Removed the writer thread and gone back to single threading. +Added the Intel.o file for i386 to allow the GMSK Repeater to build. + +20101113 +-------- + +Changed the timing within the GMSK Repeater. +Fix a problem with receiving text from the latest ircDDB Gateway. + +20101121 +-------- + +Added the Active output for the URI USB at the expense of GUI Output 2. +Changed the end of transmission network packet to be more standard. +More changes to the GMSK Repeater. + +20101202 +-------- + +Fix the statistics for all the repeaters, hopefully. +Changed the callsigns in the acks to be Icom compatible. +Added more intelligent silence insertion. + +20101203 +-------- + +Another stab at full-duplex on the GMSK Modem. +Handle out-of-order data on the network traffic. + +20101203a +--------- + +Changed to wxWidgets 2.8.11. +Fixed half/full duplex modem setting. + +20101208 +-------- + +Converted the GMSK Repeater to use libusb instead of NAtools. +Changed to a new extension protocol to pass data to/from a gateway. +Collect extra statistics and put the AMBE BER on the GUI. +Added a statistics packet. + +20101211 +-------- + +The returned BER was wrong, it should have been the number of error bits. +The DExtra Gateway now reports the link status correctly to the repeaters. +Added French, German, Danish, Italian and Polish to the DExtra Gateway ack languages. +Added the XRF055 reflector. +Back to using NAtools. + +20101212 +-------- + +Test a new version of the libusb GMSK Repeater. +Clean up the GUI a little for the Flags and BER. + +20110105 +-------- + +Added the PTT Hang parameter to the D-Star Repeater. +Added the XRF019, XRF026, XRF027, and XRF044 reflectors. +Revert to using libusb. + +20110107 +-------- + +Removed the statistics from the GMSK Repeater. +Fixed the Linux compile bug from 20110105. +Added a PTT maintainer for the GMSK Repeater. + +20110107a +--------- + +A change to the PTT maintainer which may be more effective. +Increased the buffer size for RF data. + +20110107b +--------- + +Return to the PTT system of 20110107. +Increase the buffer size for the RF data again, it is now 50 frames. +Not generally released. + +20110108 +-------- + +Replaced the DExtra_Hosts.txt with the one from the ircDDB Gateway package. +More changes to buffering and timing in the GMSK Repeater. +Not generally released. + +20110108a +--------- + +More changes to buffering and timing in the GMSK Repeater. +Not generally released. + +20110109 +-------- + +Allow for the use of both the libUSB and NAtools interface. +Change how the internal timing operates. +Not generally released. + +20110109a +--------- + +Increase the buffer size and put in a cycle of silence to start it off. +Not generally released. + +20110114 +-------- + +Swap RPT1 and RPT2 for the libUSB modem driver. +Different GMSK modem buffer filling routines. +Not generally released. + +20110114a +--------- + +Same as 20110114 but with additional debugging information. +Not generally released. + +20110116 +-------- + +Swap RPT1 and RPT2 for the NAtools modem driver. +Increase the polling rate in the GMSK Repeater. +Not generally released. + +20110120 +-------- + +Added XRF000 for the DExtra Gateway. +DExtra protocol has been upgraded. +Another timing change for the GMSK Repeater receiver. + +20110121 +-------- + +More timing changes for the GMSK Repeater. + +20110123 +-------- + +More timing changes for the GMSK Repeater. + +20110205 +-------- + +Fixed the callsigns in the acks of the GMSK Repeater. +Change the timings, particularly when handling receive on the modem. +Change the default ports to match those of ircDDB Gateway. + +20110208 +-------- + +More timing changes to the GMSK Repeater. + +20110210 +-------- + +Handle errors in the GMSK modem better. +More timing changes to the GMSK Repeater. + +20110213 +-------- + +Internal restructuring of the GMSK Repeater. +Removed the RX State from the GMSK Repeater GUI. + +20110215 +-------- + +Added XRF020 to DExtra Gateway. +Added Dutch*Star 1.00.0 firmware compatibility again. + +20110217 +-------- + +Fixed bug in DExtraGateway with incoming headers disrupting communications. +Change the maximum Parrot Controller beacon timer to 20 minutes. +More GMSK Repeater internal changes. + +20110219 +-------- + +Swapped the callsigns in the D-Star Repeater to match the GMSK Repeater. +Removed support for Dutch*Star 1.00.0 firmware. + +20110219a +--------- + +Re-arranged the transmitted callsigns in both of the digital repeaters. +Validate the incoming network header for both digital repeaters. + +20110222 +-------- + +Added Swedish and Spanish translations to the DExtra Gateway. +Increased the USB timeout when using libUSB with the GMSK modem. +Increased the packet silence timeout from 30ms to 40ms. +Run the network processing from the timer thread in the GMSK Repeater. + +20110222a +--------- + +Small change to the GMSK Repeater to stop lock-ups. + +20110227 +-------- + +Added optional RPT1 validation. +Relaxed the timing when talking to the GMSK modem. + +20110301 +-------- + +Remove a delay added to the NAtools driver. +Fixed Linux compile error with the previous release. + +20110308 +-------- + +Added daily logs. +Added the -logdir command line option to the programs. +Added XRF007 and XRF021 to the DExtra_Hosts.txt file. +Added the sound card demodulator bias patch from Eric NS7DQ. + +20110308a +--------- + +Timing changes to the GMSK Repeater closer to the settings of 20110219a. + +20110327 +-------- + +Removed the bias patch which was added to the 20110308 release. +Removed the silly GMSK Repeater error messages that I added. +Updated the IP address of XRF008. +Added extra my callsign validation to the repeaters. + +20110329 +-------- + +Relax the header checksum testing under certain circumstances. +Check the incoming my call with a regular expression. +More buffering of network data in the GMSK Repeater. + +20110331 +-------- + +Timing changes in the GMSK Repeater. +Put the networking back into the second thread on the GMSK Repeater. + +20110402 +-------- + +Refinements to 20110331 based on feedback. + +20110403 +-------- + +Mutex locking in the last couple of releases was a little wrong. + +20110404 +-------- + +More mutex locking changes in the GMSK Repeater. +Timing changes in the GMSK Repeater. + +20110411 +-------- + +Different method of silence insertion used. + +20110413 +-------- + +A small tidy up of the previous release. + +20110417 +-------- + +Changed from wxWidgets 2.8.11 to 2.8.12. +Small internal timing tweak for the GMSK Repeater. + +20110418 +-------- + +Added XRF033. +Updated the IP address of XRF013. +Changed the timing in the main loop. +Not generally released. + +20110420 +-------- + +Changed the timing of feeding the data to the GMSK Modem. + +20110421 +-------- + +Fixed an obscure bug in the Parrot Controller. +Updated the IP address of XRF021. +Change timings and data feed to the GMSK Modem. + +20110506 +-------- + +Added XRF031. +Added the ack time to the minimum time for the analogue repeater controller. +Allow outgoing DExtra links to have random ports. +Change timings in the GMSK Repeater. + +20110508 +-------- + +Added -daemon option to create proper daemons on Linux. +Not generally released. + +20110510 +-------- + +Fundamental change to the GMSK Modem handling. +Cleaned up 20110508. + +20110512 +-------- + +Updated the USAGE.txt file to include the -daemon option. +The pid of the programs is written to /var/run for Linux versions. +Added the -confdir option to the daemon versions of the programs. + +20110525 +-------- + +Updated the IP address of XRF020. +Changed poll format to report the repeater type to the gateway. +Small change in timing of the GMSK Repeater. + +20110529 +-------- + +Tweaks to the software type/version reporting to the gateway. + +20110529a +--------- + +Small GMSK modem change, rolls back a change made in 20110529. + +20110601 +-------- + +Small GMSK modem change, rolls back another change made in 20110529. + +20110609 +-------- + +Add the cycle time setting to the GMSK modem settings. +Added different serial port configs to allow for different serial port settings. + +20110610 +-------- + +Added more cycle times for the GMSK modem. +Added an extra serial port config. + +20110613 +-------- + +Display cycle overruns more intelligently. +Display time taken for the networking to complete. +Stop polling the URI USB in the D-Star Repeater, it's not needed. + +20110621 +-------- + +Increased the max cycle time value to 20ms. +Now write 12 bytes per cycle instead of 8. + +20110621a +--------- + +Changed from the broken node_putdta to node_io. + +20110622 +-------- + +Clean ups and removal of some logging messages. +Added extra delays for libUSB. + +20110628 +-------- + +Remove the need to have NAtools installed on Windows. + +20110702 +-------- + +Remove the need to have LibUSB installed on Windows. + +20110703 +-------- + +Trying to handle unreasonable combinations of modem and USB chipset. + +20110704 +-------- + +Allow for compilation without NAtools being available. +Added XRF028. + +20110706 +-------- + +Changed timings when dealing with LibUSB and relaying from the modem. +Changed Modem Type to Firmware Type. + +20110708 +-------- + +Upgraded the LibUSB driver from libusb-0.1 to libusb-1.0 under Linux. +Upgraded the NAtools version under Windows. +Removed NAtools on the Linux side. + +20110710 +-------- + +Turned the logging upside down to match the gateway, and common sense. +Improvements to the NAtools drivers. + +20110711 +-------- + +Handle DUTCH*Star 0.1.00 firmware explicitely. + +20110712 +-------- + +Improve the clock accuracy in the GMSK Repeater. + +20110714 +-------- + +Insert a 100ms delay before sending the data after sending the header. +Updated XRF033. + +20110716 +-------- + +Added Nederlands as a language option to the DExtra Gateway. +Removed the cycle time option for the GMSK Repeater. +First version that is stable with DUTCH*Star 0.1.00 firmware. + +20110717 +-------- + +Cleans-ups in all the repeaters. +More work on 0.1.00 stability. + +20110719 +-------- + +Removed the Network Delay from the D-Star Repeater. +Added extra delay and buffering to the D-Star Repeater for network traffic. + +20110720 +-------- + +Fix PTT bug in 20110719. + +20110808 +-------- + +First version of the DVAP Node program. +Allow incoming network data during wait periods. +Clean up GMSK Repeater local repeating. + +20110808a +--------- + +Serial port selection bug fixed. + +20110808b +--------- + +Windows serial port changes. + +20110811 +-------- + +Minor internal changes to all of the repeaters. +Improved timing for the DExtra Gateway and Parrot Controller. +Added fuzzy matching for the frame sync to the D-Star Repeater. + +20110811a +--------- + +Fix for GMSK Repeater full duplex mode. +Identify the OS and wxWidgets version in the log for all programs. + +20110814 +-------- + +Added the noise squelch to the D-Star Repeater. +More work on GMSK full duplex mode. + +20110816 +-------- + +Allow the timers to handle long timeouts correctly. +The Squelch Level affects the D-Star Repeater in real-time. +Change the serial port handling for the Analogue and D-Star repeaters. + +20110818 +-------- + +The RX and TX Levels affect the D-Star Repeater in real-time. +The levels and thresholds in the Analogue Repeater operate in real-time. +Increased the Analogue Repeater hang timer maximum to 30 seconds. +All of the IDs and Acks in the Analogue Repeater can now be WAV files. + +20110822 +-------- + +The Battery Ack is now configurable in the Analogue Repeater. +Fix to the Windows SerialLineController.cpp +Add more buffering to the D-Star Repeater. + +20110823 +-------- + +Fixed a bug in the DExtra Gateway. +Allow the opening callsign of the Analogue Repeater to be different from the closing callsign. +Fixed the minimum time for ack timer in the Analogue Repeater. +Added XRF073. + +20110825 +-------- + +Added a noise level display to the D-Star Repeater. +Added the Squelch Mode to the D-Star Repeater. + +20110826 +-------- + +Allow for alternative GMSK Modem addresses in NAtools. +Use the new silence insertion in the D-Star Repeater. + +20110830 +-------- + +Fix Squelch and Noise display when transmitting in the D-Star Repeater. +First untested version of the DV-RPTR Repeater. + +20110831 +-------- + +More work on the DV-RPTR Repeater. + +20110902 +-------- + +Seperate the closdown ID from the periodic ID in the Analogue Repeater. +Lots of internal changes to the DVAP Node. +More work on the DV-RPTR Repeater. + +20110905 +-------- + +Considerable internal changes to the output queueing. +Bug in the silence inseration fixed. + +20110907 +-------- + +More internal changes to handle extreme situations. + +20110913 +-------- + +Use the official FTDI interface to access the DVAP on Windows. +Changed XRF002 IP address. + +20110915 +-------- + +Revert the FTDI change for the DVAP on Windows. +Changes to the queueing. +Enable the debug flag on Linux builds. +More changes to the DV-RPTR Repeater. + +20110923 +-------- + +More changes to the DV-RPTR Repeater. +Changed XRF073 IP address. + +20111004 +-------- + +Changed how data is fed to the DVAP and DV-RPTR. +Incorporated the new DV-RPTR data formats. + +20111016 +-------- + +Added XRF069. +Reverted external input handling for the Analogue Repeater. + +20111022 +-------- + +Changes to the DV-RPTR protocol. +Added packet logging for local traffic for all DV repeaters. +Pass the headers from the DV repeaters through properly. +Increase the size and number of the network queues. +Changed XRF069 IP address. +Changes to the repeater to gateway protocol. + +20111102 +-------- + +More changes to the DV-RPTR protocol. +The default log directory is now the home directory under Linux. +Bug fix to the DExtra Gateway. +Increased network security for the repeaters. +Added XRF123. + +20111109 +-------- + +Output buffer changes. +Added support for the reporting of the two status messages. + +20111112 +-------- + +Fix to the DExtra Gateway. +Minor bug fix. + +20111118 +-------- + +Increased the GUI width on Linux to accomodate the new status messages. +Allow for the DV-RPTR TX Delay up to 500ms from 250ms. +Fixed the config file name for the DV-RPTR daemon version. +Fixed the DV-RPTR communications protocol. +Fixed DV-RPTR version string. +Updated XRF069 IP address. +Added XRF038. + +20111120 +-------- + +Changes to the DVAP Node and DV-RPTR Repeater handling. + +20111122 +-------- + +Allow more choices for the analogue repeater Ack delay. +Cleaned up the DV-RPTR Modem settings. +Updated the Windows NATools DLL. +Updated XRF002 IP address. +DExtra Gateway fix. + +20111125 +-------- + +Handle startup of the DV-RPTR modem and DVAP better. + +20111126 +-------- + +Changes to the DV-RPTR modem startup for slow serial subsystem starts. +Dummy gateway handling added. + +20111204 +-------- + +Allow headers received when in full duplex and transmitting network data to be passed to the gateway. + +20111207 +-------- + +Acks changed from the link status to the transmission time and BER. +Allow up to five status messages. + +20111208 +-------- + +Fix bug in the D-Star Repeater for the new status messages. +Similar fix to the DVAP Node. +Small change to the new ack format. + +20111209 +-------- + +Change the ack text to include the reflector instead of the time. +Updated XRF019s IP address. + +20111212 +-------- + +In the Analogue Repeater send radio audio to the external port when relaying from the external port. +Removal of the USAGE.txt file. +Fix an ancient bug with the output switching in the D-Star Repeater. + +20111213 +-------- + +More work on the Analogue Repeater radio audio to the external port. + +20111215 +-------- + +More work on the Analogue Repeater radio audio to the external port. + +20111220 +-------- + +More work on the Analogue Repeater radio audio to the external port. +Updated XRF031s IP address. + +20111221 +-------- + +More work on the Analogue Repeater radio audio to the external port. + +20111222 +-------- + +Allow the ack on the D-Star repeaters to be selectable for BER or status. + +20111231 +-------- + +Added the upgraded Dummy Repeater to the repeater package. +Allow channel D in the DExtra Gateway. + +20120111 +-------- + +Increase maximum D-Star Repeater hang time to 3000ms from 2000ms. +Improve gateway mode rejection rules. +Allow the Dummy Repeater to be controlled from external hardware. +Added XRF119. + +20120116 +-------- + +Updated XRF033s IP addeess. +Fixed GUI PTT bug in the Dummy Repeater. +Add an audio resampler to the Dummy Repeater. + +20120125 +-------- + +Allow for the settings of the beacon text for the D-Star repeaters. +Equalised the audio levels in the Dummy Repeater; +Updated XRF019 IPs address. +Added XRF110. + +20120205 +-------- + +New configuration system for non-Windows platforms. +Added XRF040. + +20120206 +-------- + +Bug fixes for 20120205. + +20120210 +-------- + +Updated XRF021s IP address. +Small cleanups. + +20120213 +-------- + +Fixed D-Star Repeater Windows configuration bug. + +20120221 +-------- + +Changed the lower frequency limits of the analogue repeater callsign and ack. +Added the E (Echo) and I (Info) commands to the DExtra Gateway. +Updated XRF069s IP address. + +20120223 +-------- + +Blank any DTMF tones from being transmitted over RF. +Added audio delay and pre-, de-emphasis to the Analogue Repeater external port. + +20120228 +-------- + +Restore GUI window position on startup. +New repeater to gateway protocol implemented. +Allow background audio data to be transmitted. +Added a receive hang time to the Dummy Repeater. + +20120303 +-------- + +Added AMBE regeneration to background data. +Added receive-only and transmit-only modes to the repeaters. + +20120308 +-------- + +Changes to the DV-RPTR driver. +Updated XRF021s and XRF044s IP address. +Added XRF222, XRF353, and XRF777. +Not generally released. + +20120309 +-------- + +Changes to support the DCS reflector system. +More changes to the DV-RPTR driver. + +20120310 +-------- + +Updated the DV-RPTR driver again. +Binary only release for Windows users. + +20120310a +--------- + +Updated the DV-RPTR and DVAP drivers. + +20120312 +-------- + +Added CM119A product id for latest URI USB hardware. +Added K8055 support to the GMSK and DV-RPTR repeaters. + +20120320 +-------- + +Updated XRF021s IP address. +Added TX output to the K8055 with the GMSK and DV-RPTR repeaters. +Added the DCS Gateway. + +20120322 +-------- + +Added XRF030. +Added the Split Repeater. +Allow minimum ack time in the Analogue Repeater to be 100, 300, and 500ms. +Add up to 100ms of squelch delay in the Analogue Repeater. + +20120324 +-------- + +Added "TX and RX" mode to all of the D-Star repeaters except the DVAP Node. +More changes to the Split Repeater. +Added DCS009. + +20120326 +-------- + +Fixed bugs in the RF D-Star repeaters when used with the Split Repeater. +Fixed bugs in the use of the Split Repeater with the RF repeaters. +Added DCS005. + +20120327 +-------- + +Add extra logging to the Split Repeater for unknown IP addresses and ports. +Modify the Linux configuration system to fix a bug. +Updated XRF017s IP address. +Added DCS008. + +20120328 +-------- + +Fixed ack bug in the Split Repeater. +Added DCS006. + +20120330 +-------- + +Updated the DCS and DExtra hosts files to the latest versions. +Convert the main log files to use UTC instead of local time. +More changes to the Split Repeater. + +20120402 +-------- + +Better error handling of the DV-Dongle. +More changes to the Split Repeater. + +20120403 +-------- + +Sort the callsign entries in the Dummy Repeater. +Minor cleanups. + +20120411 +-------- + +Added Norwegian as a language to the DExtra and DCS Gateway. +Added an optional voice beacon to all of the D-Star repeaters. + +20120412 +-------- + +Fixed BER display on all of the repeater GUIs. +Fixed header transmission bug in diversity modes. +More changes to the Split Repeater. +Not generally released. + +20120414 +-------- + +Added DCS007. +Handle reopening of the GMSK modem if it disconnects. + +20120419 +-------- + +Allow a greater range of GMSK modem addresses. +Replaced NAtools with direct calls to WinUSB :-) +Handle reopening of the DV-RPTR modem if it disconnects, Linux only for now. + +20120420 +-------- + +Upgrade the reopening of the DV-RPTR modem on Linux. +Remove the first poll from the repeaters to the gateway. + +20120420a +--------- + +Tweaks to the GMSK modem LibUSB driver to reduce the logging. +Changes to the GMSK modem WinUSB driver. + +20120424 +-------- + +Remove the architecture setting in the top level Makefile. +Added DCS004. + +20120428 +-------- + +Small revision to the DVAP driver. +Add the -nolog command line option to the Dummy Repeater. + +20120511 +-------- + +Fix the -nolog option for the Dummy Repeater. + +20120524 +-------- + +Upgraded network error reporting. +Added DCS010. +Removed debug statements from the Slit Repeater. +Add One-Touch Reply to the Dummy Repeater. + +20120526 +-------- + +Run the GMSK Modem in a seperate thread. + +20120527 +-------- + +Improvements to the changes made in 20120526. + +20120527a +--------- + +Minor tweaks to 20120527 relating to Windows mostly. +Not generally released. + +20120602 +-------- + +Remove the LibUsb option for the GMSK modem in Windows. +Infill data gaps with the last transmitted audio or silence. +More GMSK repeater changes. + +20120603 +-------- + +Lot of debugging statements in the GMSK drivers. +More GMSK repeater changes. +Removed XRF009, XRF010, XRF011, XRF030, XRF038, and XRF110. +Updated XRF031s and XRF069s IP address. +Added DCS012. + +20120604 +-------- + +Remove the GMSK debugging statements. +More GMSK repeater changes. +Fix bug in TX and RX mode in all but the D-Star Repeater. + +20120615 +-------- + +Added DCS013. +Roll back GMSK repeater changes regarding driving the modem. +Fixed background data in the GMSK Repeater. + +20120706 +-------- + +More GMSK Repeater rollbacks. +Allow cross compilation to ARM processors. +Added DCS011. + +20120802 +-------- + +Added DCS014 and DCS015. +Removed XRF100. +Fixed ARM cross compilation location of data files. +Changed silence insertion parameter. +Allow for up to four commands. + +20120826 +-------- + +Upgraded to Visual C++ 2010 on Windows. +Platform dependent settings moved to Makefile includes. +Changed the D-Star Repeater to the Sound Card Repeater. +Upgraded to the latest version of the DCS protocol. +Increased the default TX delay in the DV-RPTR Repeater to 150ms from 30ms. + +20120826a +--------- + +Reverted to using Visual C++ 2008 on Windows. + +20120901 +-------- + +Allow module E on all of the repeaters. +Re-added Windows LibUSB support to the GMSK Repeater. + +20120920 +-------- + +Make LibUsb on both Windows and Linux more agressive at transferring data. +Remove the need for the extra thread in the Linux command line versions. +Initial support for the XDV-RPTR (V2) protocol. +Removed the DCS and DExtra Gateways. +Added SVN revsion version to the log file. +Add AMBE logging to non-GUI versions of the repeaters. +Add -audiodir command line option to change the location of the AMBE data logging. + +20121004 +-------- + +Put the DCS and DExtra Gateways back into the package. +Updated DCS protocol. +Added support for the 70cms DVAP. +Use the DVAP to validate its own frequency. +Add a pause in the GMSK Repeater before PTT is set to on. + +20121027 +-------- + +Added DCS016, DCS017, and DCS018. +Changed the announcement audio for B and D for UK English. +Removed the beacon in Gateway mode. + +20121112 +-------- + +Removed XRF018 and XRF250. +Updated DCS007s IP address. +Added new XDVRPTR data transfer mode for v0.72 firmware or later. + +20121220 +-------- + +Added DCS020. +Updated XRF008s IP address. +Add Ethernet support for the DVRPTR-V2 hardware. +Add support for the Raspberry Pi GPIO port. + +20130111 +-------- + +Make the frame wait time configurable in the Split Repeater. +Allow for empty beacons in the Analogue Repeater to suppress them. +Convert old DV-RPTR configuration values (Windows only). +Added DCS019. + +20130127 +-------- + +Allow for temporary ack text. +Convert the DCS hosts files to use hostnames. +Increase DVAP polling speed to increase reliability on slow machines. +Fixed bug in status reporting. + +20130131 +-------- + +Upgrade the DCS Gateway to the latest protocol version. +Revert DVAP polling change. + +20130203 +-------- + +Remove DVAP frequency offset setting. + +20130219 +-------- + +Updated XRF008s and XRF123s IP addresses. +Reset the DVAP into GMSK mode if FM data is received. +Fix for GMSK modems request for space. +Added DCS021. +Added XRF333. + +20130302 +-------- + +Make the DVAP initialisation less aggressive. +Updated to match ircDDB Gateway 20130302. +Added XRF150 + +20130309 +-------- + +Allow STN* callsigns as the my callsign. +Allow for switching DTMF blanking off for duplex. + +20130315 +-------- + +Added XRF038. +Allow for the new format of Danish callsigns. + +20130323 +-------- + +Updated XRF003s IP address. +Added the recording and playback of announcements. + +20130423 +-------- + +Change to the wiringPi library rather than bcm2835 library for controlling the RPi. +Added the generic D-Star Repeater and configuration program. +Allow incoming radio audio during the valid_wait period. +Don't reset the announcement timer on transmissions. +Added XRF851. + +20130503 +-------- + +Tweaks to the DVAP handling. +Made opening the serial port more aggressive for the DVAP. +Included the D-Star Repeater and Config program in the Windows package for the first time. + +20130508 +-------- + +Revert most of the serial port changes. + +20130515 +-------- + +Removed aggressive DVAP serial port opening. +Remove support for the DUTCH*Star 0.1.00 firmware. +Add GMSK modem support to the D-Star Repeater. +Added Arduino hardware support. + +20130523 +-------- + +Fixed GMSK Modem bug in the D-Star Repeater. +Updated XRF008s IP addresses. +Added XRF727. + +20130615 +-------- + +Added XRF250. +Add an optional startup delay for the DV-RPTR V1. +Add a sound card interface to the D-Star Repeater. + +20130622 +-------- + +Added XRF310. +Try overlapped serial I/O under Windows again. +Add the serial port PTT control to the D-Star Repeater. +Add /dev/ttyS* devices to the serial control list. +Added a receive level setting to the sound card driver in the D-Star Repeater. +Fixed bugs in the D-Star Repeater regarding end of incoming transmissions. + +20130702 +-------- + +Added daemon info to the log. +Changes to the internal buffering. +Remove the Analogue Repeater from the Repeater package. +Remove the DCSGateway, DExtraGateway, and ParrotController from the Repeater package. +Add a DC blocker to the sound card GMSK demodulator. +Implement GMSK PLL locking. + +20130726 +-------- + +Restore D-Star Repeater window position on startup. +Allow for the error reply to be disabled. + +20130831 +-------- + +Make DV-RPTR V2 handling better. +Catch exceptions and other C++ changes in the D-Star Repeater. +Add TX Delay to the DV-RPTR V2 modem in the D-Star Repeater. + +20130903 +-------- + +Removed the DC blocker from the sound card based repeaters. +Remove some of the C++ changes. + +20130904 +-------- + +Remove even more of the C++ changes. + +20131231 +-------- + +More cleanups. +Removed the white list functionality. +Added explicit support for the DV-RPTR V3 modem. + +20140117 +-------- + +Removed the DVAP Node and DV-RPTR Repeater. +Block Australian foundation class and French novice callsigns from using the repeaters. +Removed the optional delay on the DV-RPTR V1 modem. +Added the DVMEGA hardware. + +20140118 +-------- + +Fix endian-ness of the DVMEGA frequency command. + +20140128 +-------- + +Tweaks to the GMSK Modem controller in the D-Star Repeater. +More work on buffering in the D-Star Repeater for all modem types. + +20140129 +-------- + +Move the D-Star Repeater to its own package. +More GMSK Modem changes for better locking. + +20140208 +-------- + +GMSK Modem changes. +Improve the DVAP transmit handling. +Added the Split controller. + +20140217 +-------- + +Add extra logging to the Split driver. +Remove the FEC restoration in RX only and RX and TX modes. +Added TX Tail to the sound card driver. +Changed the timings in the GMSK driver. +Added GMSK driver modem reopen. +Write the configuration to a file under Windows. + +20140303 +-------- + +Experimental DC offset removal in the sound card driver. +Use names in the Split driver to allow for easier matching of addresses and ports. +Increase the number of receivers in the Split driver to five from three. +Added a modem watchdog to the DV-RPTR, DVAP and DVMEGA modem drivers. +Hold off writing the header until the TX is off for the DV-RPTR and DVMEGA modems. + +20140306 +-------- + +Removed the modem watchdog. +Fixed the DV-RPTR V2 and V3 modem drivers. +Fixed the DVMEGA driver. +Fix the network name going missing the config program. + +20140410 +-------- + +The statistics in the Split driver include all configured receivers. +Put the type of the repeater into the title bar of the GUI. +The first registration packet is after 10 seconds, and 30 seconds thereafter. +Fix many bugs in the Split driver. + +20140415 +-------- + +Change the DV-RPTR V2 & V3 handling back to the way it is done in the DV-RPTR Repeater. +Fixed watchdog timer handling in TX Only and TX and RX modes. +Reduce buffering in TX Only and TX and RX modes for local connections. + +20140424 +-------- + +Added /dev/ttyACM* for all serial devices under Linux. +Added PTT Inversion control for external hardware controllers. +Hold off the next D-Star transmission until driver is clear of previous data. + +20140520 +-------- + +Switch on internal and external speakers for XDVRPTR firmware/hardware. +DVMEGA messages say DVMega now. +Added power setting to the DVMEGA radio. +Add extra TX and RX entries for the Split driver, but not in the GUI. +Add two extra commands. + +20140521 +-------- + +Added DVAP error dump. + +20150208 +-------- + +Added ALSA for use on Linux, PortAudio is used elsewhere. +Dynamic serial port selection on Linux. +Allow for cross-band operating on the DVMEGA. +GPIO is potentially available on more platforms. +Assert the RTS pin for the DVMEGA at startup. +Getting the version at the startup of a DVMEGA is now more aggressive. +Added a sanity check to the reply from the DVMEGA. +Improved configuration and validation for the DVMEGA. + +20150210 +-------- + +Updated the dynamic serial port selection for /dev/ttyACM* +Updated the ALSA handing. + +20150213 +-------- + +Updated the ALSA handling once again. +Fixed a compile bug when defining GPIO. + +20150308 +-------- + +Added a .mk file for the Pi2. +Increased the network timeout from one second to two seconds. +More changes to the ALSA interface. + +20150404 +-------- + +Added method to ALSA interface for TX control in the sound card controller. + +20150615 +-------- + +Rearrange the source code slightly. +Allow a DVMEGA to be even slower at responding the first time. + +20150709 +-------- + +Allow for a late arriving data sync in the sound card repeater controller. +Add support for D-Star operation with the MMDVM. +Fix bugs in the DV-RPTR V1, V2, V3, and DVMEGA drivers. + +20151001 +-------- + +Added the RX and TX levels to the MMDVM driver and configuration. +Change the slow data header replacement in Gateway mode to be more intelligent. +Add sync regeneration to the sound card repeater driver. +Change the slipped sync detection in the sound card repeater driver. +Use a new matched filter in the GMSK modem. +More MMDVM driver changes. + +20151012 +-------- + +Change to the Windows serial controller for Windows 10. +Add overflow warnings for the MMDVM. + +2015xxxx +-------- + +Fix the TX Delay setting for the MMDVM. + +20180510 +-------- + +Port to wxWIdgets-3.0.x diff --git a/COPYING.txt b/COPYING.txt new file mode 100644 index 0000000..3912109 --- /dev/null +++ b/COPYING.txt @@ -0,0 +1,340 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc. + 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Library General Public License instead.) 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 +this service 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 make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. 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. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute 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 and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +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 +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the 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 a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, 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. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE 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. + + 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 +convey 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 2 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 St, Fifth Floor, Boston, MA 02110-1301 USA + + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision 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, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This 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 Library General +Public License instead of this License. diff --git a/Common/AMBEFEC.cpp b/Common/AMBEFEC.cpp new file mode 100644 index 0000000..a672b51 --- /dev/null +++ b/Common/AMBEFEC.cpp @@ -0,0 +1,638 @@ +/* + * Copyright (C) 2010,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "AMBEFEC.h" + +#include + +static const unsigned int PRNG_TABLE[] = { + 0x42CC47U, 0x19D6FEU, 0x304729U, 0x6B2CD0U, 0x60BF47U, 0x39650EU, 0x7354F1U, 0xEACF60U, 0x819C9FU, 0xDE25CEU, + 0xD7B745U, 0x8CC8B8U, 0x8D592BU, 0xF71257U, 0xBCA084U, 0xA5B329U, 0xEE6AFAU, 0xF7D9A7U, 0xBCC21CU, 0x4712D9U, + 0x4F2922U, 0x14FA37U, 0x5D43ECU, 0x564115U, 0x299A92U, 0x20A9EBU, 0x7B707DU, 0x3BE3A4U, 0x20D95BU, 0x6B085AU, + 0x5233A5U, 0x99A474U, 0xC0EDCBU, 0xCB5F12U, 0x918455U, 0xF897ECU, 0xE32E3BU, 0xAA7CC2U, 0xB1E7C9U, 0xFC561DU, + 0xA70DE6U, 0x8DBE73U, 0xD4F608U, 0x57658DU, 0x0E5E56U, 0x458DABU, 0x7E15B8U, 0x376645U, 0x2DFD86U, 0x64EC3BU, + 0x3F1F60U, 0x3481B4U, 0x4DA00FU, 0x067BCEU, 0x1B68B1U, 0xD19328U, 0xCA03FFU, 0xA31856U, 0xF8EB81U, 0xF9F2F8U, + 0xA26067U, 0xA91BB6U, 0xF19A59U, 0x9A6148U, 0x8372B6U, 0xC8E86FU, 0x9399DCU, 0x1A0291U, 0x619142U, 0x6DE9FFU, + 0x367A2CU, 0x7D2511U, 0x6484DAU, 0x2F1F0FU, 0x1E6DB4U, 0x55F6E1U, 0x0EA70AU, 0x061C96U, 0xDD0E45U, 0xB4D738U, + 0xAF64ABU, 0xE47F42U, 0xFDBE9DU, 0xB684ACU, 0xFE5773U, 0xC1E4A2U, 0x8AFD0DU, 0x932ED4U, 0xD814E3U, 0x81853AU, + 0x225EECU, 0x7A6945U, 0x31A112U, 0x2AB2EBU, 0x630974U, 0x785AB5U, 0x11E3CEU, 0x4A715BU, 0x402AA0U, 0x199B7DU, + 0x16C05EU, 0x6F5283U, 0xA4FB10U, 0xBFA8ECU, 0xF633B7U, 0xEC4012U, 0xADD8C9U, 0xD6EB1CU, 0xDD3027U, 0x84A1FAU, + 0xCF9E19U, 0xD64C80U, 0xBC4557U, 0xA7B62EU, 0x6E2DA1U, 0x311F50U, 0x38C68EU, 0x63D5BFU, 0x486E60U, 0x10BFE1U, + 0x5BAD1EU, 0x4A4647U, 0x0157F0U, 0x7ACC29U, 0x73BEEAU, 0x2825D7U, 0xA0940CU, 0xFBCFF9U, 0xB05C62U, 0x892426U, + 0xC6B3DDU, 0xDF3840U, 0x9449B3U, 0xCED3BEU, 0xE7804DU, 0xBC3B90U, 0xF5AA0BU, 0xE6D17EU, 0x2D43B5U, 0x345A04U, + 0x5EA9DBU, 0x07A202U, 0x0C7134U, 0x45C9FDU, 0x5EDA0AU, 0x310193U, 0x6830C4U, 0x62AA3DU, 0x3B59B2U, 0xB04043U, + 0xEB975CU, 0x82BCADU, 0x912E62U, 0xD8F7FBU, 0x82C489U, 0x895F54U, 0xF00FE7U, 0xFBBC2AU, 0xA2E771U, 0xE956C4U, + 0xF6CD1FU, 0x3F8FEAU, 0x0534E1U, 0x4C653CU, 0x17FE8FU, 0x1C4C52U, 0x4515A1U, 0x2E86A9U, 0x3FBD56U, 0x756C87U, + 0x6ED218U, 0x279179U, 0x7C0AA6U, 0xD53B17U, 0x8EE0C8U, 0x85F291U, 0xD94B36U, 0x9298EFU, 0xAB8318U, 0xE07301U, + 0xBB68DFU, 0xB2CB7CU, 0xE910A5U, 0xE101D2U, 0x92BB4BU, 0x59E8B4U, 0x407175U, 0x0B026AU, 0x12989BU, 0x792944U, + 0x2376EDU, 0x2EF5BAU, 0x758663U, 0x7C1ED5U, 0x078D0CU, 0x4EF6ABU, 0x5567F2U, 0x9F7C29U, 0xC68E9CU, 0xC51747U, + 0xBC6422U, 0xB7EFB9U, 0xECFD44U, 0xA50497U, 0xAF178AU, 0xD68C69U, 0xD97DB5U, 0x82670EU, 0xCBB45BU, 0x508D90U, + 0x190A25U, 0x63F0FEU, 0x68E3C7U, 0x317A10U, 0x3A09D9U, 0x6B926EU, 0x004237U, 0x1B79C8U, 0x53EA59U, 0x48B3B7U, + 0x811166U, 0xDE4A79U, 0xF5F988U, 0xAC6057U, 0xE733FEU, 0xFF89ADU, 0xB49830U, 0x8F4BC3U, 0xC6F00EU, 0x9DA135U, + 0x942FE0U, 0xC71C3BU, 0x4DC78FU, 0x3476C4U, 0x7F6C39U, 0x66BFAAU, 0x298657U, 0x725504U, 0x5B4E89U, 0x01FE72U, + 0x0835A3U, 0x53269CU, 0x189D4DU, 0x01CDC2U, 0xEA763BU, 0xF3A56DU, 0xB0BCD4U, 0xE80F13U, 0xE355CAU, 0x98C47DU, + 0x91AB24U, 0xCE38DBU, 0x87A35AU, 0x9CD3A5U, 0xD648F4U, 0xAF7B6FU, 0x24A292U, 0x7D3011U, 0x764B6DU, 0x2DDABEU, + 0x44D123U, 0x5E22D8U, 0x1FB09DU, 0x04A926U, 0x4F5AF3U, 0x064128U, 0x3DB105U, 0x70AAD6U, 0xAA392FU, 0xA1C4B8U, + 0xF8C7C0U, 0xD35D0FU, 0x8A2E9EU, 0xC1B761U, 0xDA44F0U, 0x925E8FU, 0x89CF4EU, 0xE8B4D1U, 0xB32728U, 0xB8FE7FU, + 0x61DCC6U, 0x2A4701U, 0x1614D8U, 0x5DADE2U, 0x46BE37U, 0x0F44DCU, 0x54D549U, 0x5D8E32U, 0x263DAFU, 0x2C237CU, + 0x75E291U, 0xBE5982U, 0xA74A7FU, 0xC493A4U, 0xDFA131U, 0x967A5AU, 0xCCCB8EU, 0xC1D835U, 0x9A02ECU, 0xF331BBU, + 0xE8B812U, 0xA3EBC5U, 0xBA507CU, 0x7080ABU, 0x099BC2U, 0x02285DU, 0x59718CU, 0x50C273U, 0x0B1862U, 0x4A1F8CU, + 0x70A655U, 0x3BF5C2U, 0x666FBBU, 0x6DDE68U, 0x3485C5U, 0x9F161EU, 0xC46F4BU, 0x8CFDF0U, 0x97C625U, 0xDE058EU, + 0xC59CD3U, 0xAEAE20U, 0xF775BCU, 0xFC647FU, 0xBD9F02U, 0xE70C91U, 0xCC1468U, 0x11E7B7U, 0x1AFC36U, 0x435B49U, + 0x080398U, 0x139027U, 0x7B63FEU, 0x607AF9U, 0x29E900U, 0x7293D6U, 0x79026FU, 0x00D930U, 0x0BEAF1U, 0xD3614EU, + 0x90119FU, 0x8B8AE4U, 0xC61969U, 0xBD609AU, 0xB4F247U, 0xEFA954U, 0xE518A9U, 0xBC0362U, 0xD7D0D6U, 0xCE7E8DU, + 0x856F18U, 0x1C94E3U, 0x578726U, 0x0D5F1DU, 0x24ECC0U, 0x7FF713U, 0x3E26AAU, 0x251D6DU, 0x6A8F14U, 0x53648BU, + 0x19757AU, 0x40AEB4U, 0xCB9CA5U, 0x90055AU, 0x9956C3U, 0xE2ED34U, 0xAB3C7DU, 0xB126EAU, 0xFA9513U, 0xA3D2C8U, + 0x886BFDU, 0xD9F836U, 0xD2A2E3U, 0x8D1359U, 0x454804U, 0x5EDBF7U, 0x37637AU, 0x2C3089U, 0x67ABD4U, 0x3E8847U, + 0x3551BAU, 0x4D6331U, 0x46B8C4U, 0x1D299FU, 0x54120EU, 0x5FC0E1U, 0x86D93BU, 0xE56A0EU, 0xFBB1D5U, 0xB2B600U, + 0xA94EABU, 0xE05DF6U, 0x9BE605U, 0x90B798U, 0xC92C6BU, 0xC3DE66U, 0x9AC7BDU, 0xD15448U, 0x6A3FD3U, 0x23ADA3U, + 0x78346CU, 0x7147F5U, 0x2BDC02U, 0x0EAD5BU, 0x553FFCU, 0x1EA425U, 0x07D5F2U, 0x4C4ECBU, 0x554C14U, 0x3EB3F5U, + 0xE4A26AU, 0xED799BU, 0xB6CA85U, 0xFFD25CU, 0xC421BFU, 0x8F3A22U, 0x96AB51U, 0xDC518CU, 0x895217U, 0x8289F2U, + 0xF9B8A9U, 0xF0231CU, 0x2BF1C7U, 0x62C80AU, 0x781B39U, 0x1320E5U, 0x4AB156U, 0x41EB8FU, 0x1848E0U, 0x13D771U, + 0x4886AEU, 0x203C5FU, 0x3B6F40U, 0x76F6A1U, 0xE5457EU, 0xAE1EE7U, 0xD7AC10U, 0xDCB549U, 0x8476EFU, 0x8FC536U, + 0xD49DE9U, 0x9D0ED8U, 0xA63513U, 0xEFE4A6U, 0xB4DF7DU, 0x3E0D00U, 0x779693U, 0x4CA75EU, 0x0568ADU, 0x527BB0U, + 0x59C34BU, 0x00109FU, 0x0A0B14U, 0x73FA61U, 0x38E0BAU, 0x23530FU, 0x6A88D4U, 0xB199DDU, 0x98322AU, 0xC260F3U, + 0xCBF944U, 0x908A0DU, 0xDB11F2U, 0xC28163U, 0xADFABDU, 0xBC694CU, 0xF65243U, 0xAD83BAU, 0xA40D6DU, 0x5F7EF4U, + 0x16E787U, 0x0DF44AU, 0x460EF1U, 0x5E1F24U, 0x15CC3FU, 0x6C77CAU, 0x676401U, 0x3C9CBDU, 0x359FEEU, 0x6A0413U, + 0x02F590U, 0x91EE4DU, 0xDA3C3EU, 0xC305A3U, 0x889658U, 0xF14D99U, 0xFA7F86U, 0xA1E677U, 0xE981E8U, 0xF21A10U, + 0xBB4BD7U, 0x80F1CEU, 0xCB6239U, 0x123BE0U, 0x1D885FU, 0x45921EU, 0x6641E1U, 0x3DE870U, 0x74BBAFU, 0x6F00C6U, + 0x261055U, 0x7DCBA8U, 0x57787AU, 0x0E2167U, 0x05B28CU, 0xCC8819U, 0x975BE2U, 0xBC52B7U, 0xE5E52CU, 0xEB37C9U, + 0xB20E12U, 0xF9DD2FU, 0xE8C6FCU, 0x837701U, 0xD8AD82U, 0xD1BE5AU, 0x0B0525U, 0x0244B4U, 0x79FE5BU, 0x322DCAU, + 0x2B3495U, 0x60876CU, 0x79DCFBU, 0x334C12U, 0x4C7745U, 0x45A4DCU, 0x1E3F23U, 0x175FF2U, 0xC4C0D8U, 0xAFF30DU, + 0xB72AF6U, 0xFCB96BU, 0xA5C338U, 0xAE5295U, 0xF54946U, 0xDCBABBU, 0x87A1A8U, 0xCF2165U, 0xD4DA9EU, 0x9FC90BU, + 0x223070U, 0x6922A4U, 0x30B92FU, 0x3348D6U, 0x695B01U, 0x20C038U, 0x1BB2EFU, 0x523B06U, 0x49EC99U, 0x02D7C8U, + 0x5B4777U, 0x713CA6U, 0xA8AF49U, 0xA3B650U, 0xF84586U, 0xB5DF7FU, 0xAE8CF8U, 0xC72581U, 0x9D3652U, 0x9EEDCFU, + 0xC75D34U, 0xCC0671U, 0xB5B5CAU, 0xFEAC1FU, 0x677EA4U, 0x2DC5F9U, 0x26D63AU, 0x7F1F86U, 0x142855U, 0x0DF2A8U, + 0x42E3B3U, 0x195872U, 0x108B8DU, 0x6AB31CU, 0x632063U, 0x307BAAU, 0xFBC83DU, 0xE201C4U, 0xA91393U, 0x90A82AU, + 0xDAF9E4U, 0x816A55U, 0x88D00AU, 0xD383DBU, 0xFA3A64U, 0xA569A5U, 0xEEE2DEU, 0x76D243U, 0x3D0D90U, 0x649E6DU, + 0x47E76EU, 0x1C7491U, 0x156E49U, 0x4E9DDEU, 0x0604B7U, 0x3D3720U, 0x76FDD9U, 0x6FEC06U, 0x2417B7U, 0xFD04F8U, + 0xF29D29U, 0x886F92U, 0xC1744FU, 0xDAC73CU, 0x939EB1U, 0x880C63U, 0xEBE79EU, 0xB2F285U, 0xB86970U, 0xE11ABBU, + 0xEA822EU, 0x311155U, 0x586AC0U, 0x43F92BU, 0x0A81F6U, 0x5412C5U, 0x5D111CU, 0x26E8CBU, 0x2D7B63U, 0x74213CU, + 0x3F90CDU, 0x2E8B52U, 0x645883U, 0xDFE36CU, 0x96F375U, 0xDD0882U, 0xC40B1BU, 0x8FD6CCU, 0xB464A5U, 0xFC7F3EU, + 0xA7AECBU, 0xAA9511U, 0xF10634U, 0xBA5CEFU, 0x83ED32U, 0x483681U, 0x5015DCU, 0x138D3FU, 0x48DEA2U, 0x616571U, + 0x3AF40CU, 0x33AF97U, 0x681D72U, 0x2246E9U, 0x3BD7B9U, 0x506C46U, 0x0D2FDFU, 0x869338U, 0xDDC061U, 0xD45BD6U, + 0xAF6A0FU, 0xE7B8C0U, 0xFC2371U, 0xBF102EU, 0xA6C9DFU, 0xEDDA40U, 0x943089U, 0x9FA1BFU, 0x459A66U, 0x0C4995U, + 0x175108U, 0x7AE243U, 0x6139B6U, 0x2A2A2DU, 0x73D3D8U, 0x79C183U, 0x204A26U, 0x0B3FFDU, 0x5AA420U, 0x111613U, + 0x8A4FDFU, 0xC3DC2CU, 0xF9A7B5U, 0xB034EAU, 0xEBAC5BU, 0xE0CF94U, 0xBD5465U, 0xF605FAU, 0xCFBEA3U, 0x85AC54U, + 0x9E55DDU, 0xD7C62AU, 0x0CDD73U, 0x252FCDU, 0x76361CU, 0x7DF5D3U, 0x3546E2U, 0x6E5B39U, 0x67A98CU, 0x1CB247U, + 0x57231AU, 0x4AD8A9U, 0x01CA74U, 0x191187U, 0xF2208AU, 0xA9AB50U, 0xA0F8A5U, 0xFB403EU, 0xF2D34BU, 0xA9A880U, + 0xCB393DU, 0xD262EEU, 0x99D0B7U, 0xC04B00U, 0xCB1AC9U, 0xB0B176U, 0x39E3A7U, 0x677EF8U, 0x2ECD58U, 0x359687U, + 0x7E277EU, 0x473D69U, 0x0CEEB0U, 0x55D557U, 0x5F04CEU, 0x0C8EBDU, 0x25BD60U, 0x7E64DBU, 0xB7771EU, 0xACCC05U, + 0xE51CF0U, 0xBF2F2AU, 0x90F497U, 0xC9E7D4U, 0xC25F09U, 0x9B9CBAU, 0xD08767U, 0xEB320CU, 0xA36999U, 0x38FB42U, + 0x7180B3U, 0x22112CU, 0x29AA45U, 0x50F9D2U, 0x1B610AU, 0x0202FDU, 0x4899E4U, 0x57080BU, 0x3E72DAU, 0x65E165U, + 0x6CFA34U, 0xB70BEBU, 0xBC104AU, 0xE4E295U, 0x8F7BECU, 0x96787FU, 0xD583B2U, 0x9E9740U, 0x870C5DU, 0xECFFA6U, + 0xF4E433U, 0xBF35F8U, 0xE00F8DU, 0x699C16U, 0x3265EBU, 0x1B6638U, 0x40F515U, 0x0A8DC6U, 0x131E1BU, 0x5845A0U, + 0x21F670U, 0x2A6E1FU, 0x791D8EU, 0x708651U, 0x2AD7E8U, 0xE37CAFU, 0xD8EE56U, 0x97B3C1U, 0x8E0018U, 0xC51B6FU, + 0x9CC9E6U, 0xB67019U, 0xEF23C8U, 0xE498F2U, 0xBF9927U, 0xF643ECU, 0xCD7051U, 0x04E902U, 0x563AFFU, 0x5D006CU, + 0x04D3A1U, 0x0FCA9AU, 0x72794FU, 0x39A2B4U, 0x228231U, 0x6A19EAU, 0x714E96U, 0x18F705U, 0x4324FCU, 0xC83E3BU, + 0x918D02U, 0xDADCD5U, 0xC2470CU, 0xA135B3U, 0xBABCF2U, 0xF30F4DU, 0xA8549EU, 0xA1C543U, 0xDEFF78U, 0xD42CBCU, + 0x0DB747U, 0x46C6D2U, 0x5F5C89U, 0x144F60U, 0x6FA6F7U, 0x66350EU, 0x2C0A59U, 0x35DAE0U, 0x7EC12FU, 0x0D32FEU, + 0x0429C1U, 0x5FB911U, 0xD642AEU, 0x895167U, 0xC3D8B0U, 0xFAAB89U, 0xB1315AU, 0xA8C0A7U, 0xE3DB24U, 0xB84879U, + 0x913382U, 0xCBA317U, 0x82F8FCU, 0x994BA9U, 0x50C213U, 0x4390CEU, 0x282F5DU, 0x713E30U, 0x7FCDE3U, 0x26565EU, + 0x2D0485U, 0x56BDD4U, 0x1FAE7BU, 0x0475AAU, 0x4DD555U, 0x17CE4CU, 0x9C1D9BU, 0xE52473U, 0xEEF7E4U, 0xB7CD1DU, + 0xF45E42U, 0xEF87E3U, 0x87B43CU, 0x986FADU, 0xD16FD2U, 0x8AD403U, 0x8103A8U, 0xD83A75U, 0x33A826U, 0x2BF39BU, + 0x604049U, 0x7B99A4U, 0x328ABFU, 0x49306AU, 0x407191U, 0x1BEA04U, 0x19D96FU, 0x4001F2U, 0x0FB201U, 0x36E9DCU, + 0xFD7ADFU, 0xE64326U, 0xAF91F9U, 0xF51249U, 0xDC2B16U, 0x87F8D7U, 0xCCE668U, 0xC517B1U, 0x9E8C46U, 0x97BF5FU, + 0xED6498U, 0xA67461U, 0x378FF6U, 0x788C8FU, 0x611514U, 0x0AE6F1U, 0x53FC2BU, 0x596F3EU, 0x0216C5U, 0x4B8508U, + 0x507FBBU, 0x396EE6U, 0x22F535U, 0xE99688U, 0xB10F43U, 0xBA1D36U, 0xC3E2ADU, 0xC07178U, 0x9B28C3U, 0xD69A8BU, + 0xCD817CU, 0x8570E5U, 0xFEEB12U, 0xF5E8CBU, 0xAC10C4U, 0x270335U, 0x7ED8EAU, 0x156B5BU, 0x0E7A14U, 0x46A0C5U, + 0x5D937AU, 0x144AA3U, 0x4F79D5U, 0x6CF35CU, 0x31228FU, 0x7A1932U, 0x628E69U, 0xA9D59CU, 0x926517U, 0xDBBEE2U, + 0x80ADB9U, 0x891424U, 0xD246D7U, 0xD8ED1AU, 0xA17C28U, 0xEA27F5U, 0xF3942EU, 0xB8CE8FU, 0xAB5FD0U, 0x466461U, + 0x1CB7BEU, 0x152F6FU, 0x4E1CC0U, 0x05D799U, 0x1CE66EU, 0x773DF7U, 0x7EAB00U, 0x249048U, 0x6D41D7U, 0x765A26U, + 0x1DA9F9U, 0x8431C8U, 0xCF0203U, 0x96C1DEU, 0x90D86DU, 0xCB6A30U, 0xA23193U, 0xB9A24EU, 0xF05B95U, 0xEB48A0U, + 0xA0D27AU, 0xD8A39FU, 0xD33804U, 0x0A9B79U, 0x01C3AAU, 0x5A5437U, 0x132FD4U, 0x28BC0DU, 0x60253AU, 0x3F57E3U, + 0x3CCC7CU, 0x65DD9DU, 0x4E26C2U, 0x172572U, 0xDCDDADU, 0xC64E64U, 0x8F5553U, 0x94A68AU, 0xFDBE7DU, 0xA66DE4U, + 0xADD68BU, 0xF4C75AU, 0xFE0CC1U, 0x873E34U, 0xC8A72FU, 0xDBD0C2U, 0x124B10U, 0x49998DU, 0x40A8FEU, 0x3A3323U, + 0x316088U, 0x68D95DU, 0x235B06U, 0x3A00B3U, 0x51B178U, 0x4AEA89U, 0x025816U, 0x59C36FU, 0xD092B8U, 0x8B2930U, + 0xE43AC7U, 0xF5E2DEU, 0xBEC121U, 0xA71AF0U, 0xED8B7FU, 0x94B40EU, 0x9F66D1U, 0xD45D68U, 0xCD8CBFU, 0x8617F6U, + 0x5F2545U, 0x75FC98U, 0x2EFF62U, 0x674467U, 0x7C959CU, 0x318F09U, 0x0A7CD2U, 0x4967AFU, 0x11D62CU, 0x1A8CD1U, + 0x431F02U, 0x48A69DU, 0xB3E5ECU, 0xFA7623U, 0xE10E9AU, 0xA99948U, 0xB20215U, 0xD971A6U, 0x80E86BU, 0x8BDA90U, + 0xD60185U, 0x9D907EU, 0x8FFBFBU, 0xE66920U, 0x7D705DU, 0x3483CEU, 0x6F9833U, 0x646BF1U, 0x1DF3E8U, 0x17E017U, + 0x4E1BC6U, 0x050A79U, 0x1E8038U, 0x5773E7U, 0x2C685EU, 0xA1BD89U, 0xFB86B0U, 0xF01477U, 0xA16D8EU, 0xCAFE19U, + 0xD365C1U, 0x9815AEU, 0x839E3FU, 0xCBCDC4U, 0x907611U, 0xB9E70AU, 0xE2BDE7U, 0x2B0E34U, 0x301789U, 0x7BE4DAU, + 0x477707U, 0x0C2FACU, 0x558C79U, 0x5E9743U, 0x0D4496U, 0x04786DU, 0x7FABE0U, 0x3730B3U, 0x3C014AU, 0xE7DADDU, + 0xEEE834U, 0x956163U, 0xDCB2FAU, 0xC78905U, 0x8D5BD4U, 0xD0427BU, 0xDBF12BU, 0xA22AB4U, 0xA93B4DU, 0xFA819AU, + 0xB3D2B3U, 0x287B64U, 0x40289DU, 0x5BB206U, 0x100153U, 0x495CB8U, 0x42CF2DU, 0x3BF4D6U, 0x70248BU, 0x6ABF19U, + 0x23CCF4U, 0x3C4527U, 0x75761AU, 0x8EACC1U, 0x853F44U, 0xD44EBFU, 0xDED5EEU, 0x87C751U, 0xEC3E80U, 0xF72D6FU, + 0xBEB676U, 0xE557A1U, 0xEC4D59U, 0xB6BECEU, 0x9DA527U, 0x443078U, 0x0BCAE9U, 0x12D916U, 0x594087U, 0x6033E8U, + 0x22A831U, 0x7948A2U, 0x70535FU, 0x2BC01CU, 0x62BBA1U, 0x592A7BU, 0x92308EU, 0x8AC395U, 0xC15A50U, 0x9809ABU, + 0xB3B336U, 0xECB245U, 0xE54998U, 0xBEDA1BU, 0xF681E6U, 0xED35F5U, 0x8E2E0CU, 0x87FDD3U, 0x5CC453U, 0x1556ACU, + 0x0E85FDU, 0x64AC42U, 0x3D7F8BU, 0x36447CU, 0x6FD665U, 0x640FB2U, 0x3B3C4BU, 0x52A7C4U, 0x48F7B5U, 0x014C2EU, + 0x9A9FFBU, 0xD19601U, 0xA0250CU, 0xAB7FFFU, 0xF2C822U, 0xB8D1B1U, 0xA302CCU, 0xEAB907U, 0xD1E9B2U, 0x987269U, + 0xC3411CU, 0xCC8897U, 0x141A42U, 0x3F61B8U, 0x66F2A1U, 0x2DCB56U, 0x3618DFU, 0x778208U, 0x2CB3F1U, 0x0468EEU, + 0x5F7B1FU, 0x5693D0U, 0x0D8041U, 0x461B3EU, 0xFFECE7U, 0xB4FD50U, 0xA94798U, 0xE314CFU, 0xB88D76U, 0xB17EADU, + 0xCA7508U, 0xC3E553U, 0x989EA6U, 0xDB0D3DU, 0xC396E8U, 0xA8E683U, 0x717D1EU, 0x7A0EEDU, 0x219730U, 0x288422U, + 0x736ECFU, 0x1BFF14U, 0x04A4A1U, 0x4F177AU, 0x56092BU, 0x1DD884U, 0x64635DU, 0xEF70EAU, 0xA589B3U, 0xF49B54U, + 0xFF50CDU, 0xA66312U, 0x8DFA62U, 0xD628FDU, 0x9F131CU, 0x8582C3U, 0xCCF9DAU, 0xF36A29U, 0xB8B2F4U, 0x618157U, + 0x6A020AU, 0x335999U, 0x79E864U, 0x4272BFU, 0x03259AU, 0x189C40U, 0x51CFB5U, 0x0A752EU, 0x216463U, 0x79BF90U, + 0x721C0DU, 0xAB47FEU, 0xE4D727U, 0xFDEC28U, 0x963FD9U, 0x8DA646U, 0xC594B7U, 0x9E4FE8U, 0x977E60U, 0xECA597U, + 0xAF264EU, 0xB61C79U, 0xFDCDA0U, 0x65D64FU, 0x2E61DCU, 0x553881U, 0x5CAA72U, 0x0351FBU, 0x0A400CU, 0x51FB55U, + 0x3BB9CAU, 0x22223AU, 0x6993B5U, 0x30C8C4U, 0x3B5B1BU, 0xE02B82U, 0xC1B075U, 0x9B23BCU, 0xD25A8BU, 0xC9C852U, + 0x82A3A9U, 0xBB303CU, 0xF42977U, 0xADDA82U, 0xA64418U, 0xFC55E5U, 0xB5AEE6U, 0x0EBD3BU, 0x4765C8U, 0x4CD655U, + 0x17DD2EU, 0x562EEBU, 0x6C3770U, 0x25A585U, 0x3E5EDEU, 0x754F6FU, 0x2C94A1U, 0x23A758U, 0x5A3F4FU, 0xD07C96U, + 0x8BC761U, 0xC254E8U, 0xD92C97U, 0xB0BF06U, 0xEBE0D9U, 0xE25138U, 0xB8CAA7U, 0xBB98DEU, 0xE22109U, 0x896291U, + 0x10F172U, 0x5BCB2FU, 0x401A94U, 0x0CA141U, 0x77B2BAU, 0x7E6BBFU, 0x255964U, 0x6E82D9U, 0x77130AU, 0x3C3877U, + 0x04EAF4U, 0x4FD129U, 0x9C40DBU, 0x959BC6U, 0xCEAC2DU, 0xE774FCU, 0xBC6763U, 0xF6DC12U, 0xEB8DCDU, 0xA00664U, + 0xF9F4B3U, 0xD2EF4AU, 0x895E5DU, 0x800584U, 0x5A972BU, 0x132EFBU, 0x287D84U, 0x63E615U, 0x7297CEU, 0x391D23U, + 0x608E30U, 0x6AF5CDU, 0x11641EU, 0x5C5E93U, 0x4789E0U, 0x0E903DU, 0x956386U, 0xFEF053U, 0xB6E879U, 0xAD0BACU, + 0xE41077U, 0xFF83CAU, 0xB47A99U, 0xCD6870U, 0xCE93E7U, 0x96823EU, 0x9D1941U, 0xC4EBD0U, 0x2BF23FU, 0x3031EEU, + 0x790A71U, 0x229909U, 0x2AC1CEU, 0x717677U, 0x5AEDA0U, 0x039C99U, 0x480646U, 0x515587U, 0x1AEC3CU, 0x296F69U, + 0xE13492U, 0xBA8607U, 0xB39FCCU, 0xEC4CB1U, 0xA77723U, 0x9EA7DEU, 0xD51C0DU, 0xCD0F00U, 0x86D4FBU, 0xDDF56EU, + 0xF46F95U, 0x2FBCD4U, 0x268D6BU, 0x7D52B2U, 0x374165U, 0x26F9DCU, 0x4D2A9BU, 0x141163U, 0x1FD2FCU, 0x40CA2DU, + 0x497952U, 0x3322D3U, 0x7AB32CU, 0xE108F5U, 0xAA5AE2U, 0xB3E31BU, 0xF8B098U, 0x812B65U, 0x8B8936U, 0xD0D08AU, + 0xD94341U, 0x8A7894U, 0xE3A9AFU, 0xF8377AU, 0xB74481U, 0x6FDD0CU, 0x64EE5FU, 0x3D35A2U, 0x163731U, 0x5F8ECCU, + 0x045DC7U, 0x0F4616U, 0x57B6E8U, 0x7CAD79U, 0x253E86U, 0x6EC7CFU, 0x7DD478U, 0xB426A1U, 0xCF2D76U, 0xC3BC5FU, + 0x984780U, 0x935571U, 0xCACCEEU, 0x81BBBFU, 0xB82054U, 0xF371C0U, 0xE9CB3BU, 0xA05826U, 0xFB33F5U, 0x52A218U, + 0x09B88BU, 0x424BF6U, 0x53D22DU, 0x198198U, 0x043A53U, 0x6F2A06U, 0x34F1BDU, 0x3DC260U, 0x664982U, 0x6FB81BU, + 0x15A24CU, 0xDE71F5U, 0xC7482AU, 0x8CDFCBU, 0x9505D4U, 0xDE3405U, 0xA5EFFAU, 0xA4FC63U, 0xFE5704U, 0xB387DDU, + 0xA8BC6AU, 0xC32FB2U, 0x5A7EE5U, 0x11C44CU, 0x489797U, 0x420E62U, 0x19BD79U, 0x30E6BCU, 0x6B6407U, 0x225DDAU, + 0x398EA9U, 0x703534U, 0x0A64F7U, 0x09FA0AU, 0xD4C910U, 0xDF10E5U, 0x86833EU, 0xCDB99BU, 0xE67A40U, 0xBE631BU, + 0xB590AEU, 0xEC8B75U, 0xA73BD0U, 0x9CE08BU, 0xD5F35EU, 0x8E0AE5U, 0x061828U, 0x5D835AU, 0x5660C7U, 0x277914U, + 0x68CAE9U, 0x7190E2U, 0x3A0113U, 0x20FECCU, 0x49ED7DU, 0x127522U, 0x1B06ABU, 0x40855CU, 0x8B9E85U, 0x926FB2U, + 0xF8F56AU, 0xE186A5U, 0xAA1F14U, 0xF10CCBU, 0xF0F7BAU, 0x8F6735U, 0x867CECU, 0xDC9F1FU, 0x978402U, 0x8E54F1U, + 0x45EF3CU, 0x7CFC8FU, 0x3705D2U, 0x6C1248U, 0x64C8BDU, 0x3FF976U, 0x566243U, 0x4DA198U, 0x069B45U, 0x1F0AF6U, + 0x5851BBU, 0x00E248U, 0xAB3BD1U, 0xF2090EU, 0xF9926FU, 0xA2C3F1U, 0xEB7800U, 0xD07B9FU, 0x98A1E6U, 0xC31021U, + 0xC84BB8U, 0x91D84FU, 0x9AEC96U, 0x6337A9U, 0x288468U, 0x369FB3U, 0x774E06U, 0x6C645DU, 0x05B7A9U, 0x4E2E22U, + 0x551DFFU, 0x1CC78CU, 0x47D611U, 0x4F2DF2U, 0x343E6FU, 0xBF8514U, 0xE655C1U, 0xAD5E5AU, 0xB4EDBFU, 0xDFB4E4U, + 0xC1265DU, 0x80DD8BU, 0xDBC852U, 0xD25375U, 0x8920ACU, 0xA2BA53U, 0xFB0BC2U, 0x31401DU, 0x28D33CU, 0x63AAE3U, + 0x18381AU, 0x11238DU, 0x4AD2E4U, 0x434933U, 0x195BABU, 0x56A058U, 0x6FB105U, 0x2C5AAEU, 0x35C97BU, 0xFED9A0U, + 0xA52295U, 0x8D314EU, 0xD6ECA3U, 0x9F5E30U, 0x84456DU, 0xCFB6DEU, 0xD6AF03U, 0xBD2CE9U, 0xE556FCU, 0xEEC707U, + 0xB71CD6U, 0x382F59U, 0x43B720U, 0x02E4F7U, 0x195F4EU, 0x51CC99U, 0x0AA550U, 0x013767U, 0x786CBEU, 0x73DD01U, + 0x2AC6D1U, 0x61159EU, 0x7BA92FU, 0x92BAF4U, 0x896109U, 0xC0521AU, 0x9F9AF7U, 0x942924U, 0xC532B9U, 0xEFE3C2U, + 0xA6D807U, 0xFD0ABCU, 0xF69369U, 0xAFA033U, 0x44738EU, 0x5D694DU, 0x17C8F0U, 0x0C93A3U, 0x45207AU, 0x1EF9C5U, + 0x37EB04U, 0x6850FBU, 0x6305EAU, 0x3B9E15U, 0x782DC4U, 0x41774BU, 0x8AF633U, 0xD18DE4U, 0xD81E5DU, 0x83A69AU, + 0x8AF583U, 0xF06E7CU, 0xBB5FADU, 0xA28416U, 0xE99653U, 0xF06D88U, 0x9FEC35U, 0xC4F7E6U, 0x4C059AU, 0x1F1C19U, + 0x56EFC4U, 0x4D743FU, 0x24612AU, 0x3F9BD1U, 0x748814U, 0x2C13AFU, 0x27F276U, 0x5EE861U, 0x553B88U, 0x0E0A5FU, + 0xC791E6U, 0xD8E2B0U, 0x907A69U, 0xABE9C6U, 0xE09217U, 0xB10168U, 0xBA48F9U, 0xE3FA26U, 0x8861CFU, 0x9230D8U, + 0xDB8B21U, 0xC099B2U, 0x09644FU, 0x52F704U, 0x79AC90U, 0x201F6BU, 0x2E17BEU, 0x77C495U, 0x3CFF48U, 0x172E9BU, + 0x4E9426U, 0x0D8775U, 0x145E98U, 0x5E6D03U, 0xC5F6D6U, 0xAC242DU, 0xF70D3CU, 0xFEDED2U, 0xA5C543U, 0xAE74BCU, + 0xD62EE5U, 0x9D9D72U, 0x80029BU, 0xCB534CU, 0x90E175U, 0x19BAAAU, 0x6A3B6BU, 0x6280D4U, 0x39D385U, 0x724B7AU, + 0x6B78E2U, 0x00A321U, 0x19101CU, 0x5248CFU, 0x0ADB30U, 0x01F0A9U, 0x5A21CEU, 0xB73A17U, 0xACC880U, 0xE55179U, + 0xFE42A6U, 0xB4B987U, 0xC5AF58U, 0xCE1688U, 0x97C533U, 0x9CCE76U, 0xC73F8DU, 0x8E2510U, 0xB4B6C3U, 0x7D4FFEU, + 0x665C3DU, 0x2DC7C0U, 0x70B55BU, 0x5B2C2EU, 0x025FF5U, 0x49D470U, 0x53448AU, 0x1A3FD7U, 0x09AC64U, 0x60BDBDU, + 0x3B467AU, 0xB0D043U, 0xE98B9CU, 0xE33A2DU, 0x9A21E2U, 0xD1C3B3U, 0xCA5A0CU, 0x8709DDU, 0xDCB222U, 0xF5A3AAU, + 0xBF79DDU, 0xA44A04U, 0xEDD193U, 0x3E006AU, 0x373B21U, 0x4CF994U, 0x47C04FU, 0x1F53DAU, 0x5488A1U, 0x4DB86CU, + 0x2623DFU, 0x7D7402U, 0x70CF50U, 0x2B9EFDU, 0x232426U, 0xF8A7D3U, 0x91FEC8U, 0x8A4D39U, 0xC117F6U, 0xD0866FU, + 0x9B3D18U, 0xE36EC1U, 0xE8F576U, 0xB3C5BFU, 0xBA1629U, 0xE1BD50U, 0xA8EC8FU, 0x17763EU, 0x5D45F1U, 0x049CA0U, + 0x0F8F1FU, 0x5630C6U, 0x7DE225U, 0x26FB38U, 0x6F08CBU, 0x7D0316U, 0x34B28DU, 0x2F68E9U, 0xC47B72U, 0x9DC287U, + 0x96915CU, 0xCF0B41U, 0x85F8A2U, 0xBAE17FU, 0xF372CCU, 0xE81991U, 0xA1894AU, 0xFAF2EBU, 0xF16134U, 0x89F845U, + 0x0A8ADBU, 0x53153AU, 0x1806E5U, 0x03FF7CU, 0x6A7C0BU, 0x312692U, 0x399775U, 0x628CACU, 0x6D7FB3U, 0x34EE42U, + 0x5FF49DU, 0x56073CU, 0x8D1C67U, 0x87CDBBU, 0xDEE708U, 0xB574D5U, 0xA4ADB6U, 0xEF9E2BU, 0xF605D0U, 0xBD7545U, + 0xE6EE0EU, 0xCE39FBU, 0x950260U, 0xD8929DU, 0x43D9CEU, 0x086A47U, 0x31B3B1U, 0x7AA068U, 0x221ADFU, 0x294B86U, + 0x72F049U, 0x73E3F8U, 0x083927U, 0x418856U, 0x5AC3C9U, 0x105020U, 0xC969B7U, 0xE2BBEEU, 0xBF2019U, 0xB41181U, + 0xEFCA6AU, 0xA6FD3FU, 0xBC27A4U, 0xD53651U, 0xCE9D9AU, 0x854EA7U, 0xDC5E74U, 0xDFE5A9U, 0x26B61AU, 0x6C0D57U, + 0x77DCECU, 0x3EC639U, 0x2575C3U, 0x682CD6U, 0x13AF1DU, 0x1855ECU, 0x404473U, 0x4BDF8AU, 0x12ACDDU, 0xF93754U, + 0xE207A3U, 0xABD87AU, 0xF04B45U, 0xF03284U, 0xABB05BU, 0x80ABEBU, 0xD95AB4U, 0x92C10DU, 0x8FD2CEU, 0xC42833U, + 0xEC3920U, 0x37C2FDU, 0x7C5106U, 0x654883U, 0x2EAAF8U, 0x37B12DU, 0x5C20B6U, 0x065B42U, 0x07C909U, 0x5C12B4U, + 0x152367U, 0x2EB4FAU, 0x65CF19U, 0xFC5F40U, 0xB294FFU, 0xEBA72EU, 0xE03ED1U, 0x9B6CD0U, 0x92D70FU, 0xC944F6U, + 0x801D60U, 0x9AAE19U, 0xF1F4DEU, 0xA85547U, 0xAB4EB8U, 0x729DE9U, 0x792456U, 0x223697U, 0x4BED0CU, 0x55DE71U, + 0x1C03A2U, 0x07910FU, 0x4CAADCU, 0x356BA0U, 0x3E5033U, 0x67C3EEU, 0x2D9B05U, 0xB62810U, 0xFFF3EBU, 0xC4E03EU, + 0x8558A5U, 0xDE0B48U, 0xD5905BU, 0x8D71A2U, 0xA26A75U, 0xFBD8ECU, 0xB08982U, 0xAB1253U, 0xE2A1ECU, 0x79FB3FU, + 0x116E52U, 0x4A15C9U, 0x43861CU, 0x188FE7U, 0x537DF2U, 0x62E619U, 0x29D7C0U, 0x310C57U, 0x7A1F2EU, 0x25E5B8U, + 0xAC7451U, 0xC76F86U, 0xDE9C9FU, 0x959460U, 0xCF27B1U, 0xC6FC1EU, 0xBDEDCFU, 0xF416B0U, 0xEF0429U, 0xA49FEEU, + 0xBDEA17U, 0xFF7104U, 0x06A3F8U, 0x0D8A63U, 0x5219A6U, 0x5B62DDU, 0x00F348U, 0x6969B3U, 0x731A6EU, 0x38816DU, + 0x61D090U, 0x6A6343U, 0x33F9FEU, 0x18B8A5U, 0xC30340U, 0x8B10DAU, 0x98E80BU, 0xD1FB74U, 0xEA20F5U, 0xA5930AU, + 0xFC8E93U, 0xF75CC4U, 0xAF673DU, 0xA4E6BAU, 0xDF3D43U, 0x960F9CU, 0x0DD68DU, 0x44E572U, 0x1F7EB2U, 0x35AD09U, + 0x6C9554U, 0x6746A7U, 0x365D3AU, 0x7DFCF9U, 0x64A6C4U, 0x0B351FU, 0x118CEAU, 0x58DF61U, 0x836434U, 0x8A36CFU, + 0xF1AB5BU, 0xBA18A0U, 0xA343EDU, 0xE8C27EU, 0xF0F887U, 0xBB2B50U, 0xC03A69U, 0xC9C1A6U, 0x9A5317U, 0x9368C8U, + 0x5CB919U, 0x26A226U, 0x2F01EFU, 0x74D919U, 0x3DCA80U, 0x2631D7U, 0x6D223EU, 0x54BAA1U, 0x1E4950U, 0x47520BU, + 0x4CA79EU, 0x97BC75U, 0xBE3EA8U, 0xED479BU, 0xA4D446U, 0xBA4FF5U, 0xF13C39U, 0xE8A46AU, 0x83D7D7U, 0xDA4C0CU, + 0xD1DDF9U, 0x8AA7F2U, 0xC22427U, 0x793DDCU, 0x30CE45U, 0x2B5522U, 0x6007FBU, 0x39BE6CU, 0x32AD95U, 0x42560BU, + 0x4D426AU, 0x16D1B5U, 0x5F3A04U, 0x442BDBU, 0x2DF082U, 0xF6C225U, 0xFE59FCU, 0xA5880FU, 0xAEB312U, 0xF761C9U, + 0x9C582CU, 0x85CBB7U, 0xCE00C3U, 0xD43118U, 0x9DAB9DU, 0xEAF866U, 0xE3437BU, 0x381288U, 0x738955U, 0x6A3BF6U, + 0x2066ABU, 0x19D570U, 0x52DEC1U, 0x090E1EU, 0x00B5FFU, 0x5BE6E1U, 0x727D38U, 0x284CCFU, 0x639656U, 0xFA8531U, + 0xBD3CA8U, 0xD4EF77U, 0xCFC586U, 0x841489U, 0x9C0F78U, 0xD7BCA7U, 0x8E671EU, 0xA5774DU, 0xFE8481U, 0xF79F32U, + 0xAC0AEFU, 0x65F09CU, 0x5FF301U, 0x144ACAU, 0x0D193FU, 0x468224U, 0x13F0D1U, 0x18694AU, 0x63FA87U, 0x2B81F4U, + 0x30106DU, 0x790A9BU, 0xE2E952U, 0x8970CDU, 0xD003BCU, 0xDB9963U, 0x838AD2U, 0x88731DU, 0xD1E064U, 0xBAFFF3U, + 0xA10F2AU, 0xEC049DU, 0xBFD7D4U, 0xB7EE2BU, 0x4C7CBBU, 0x478760U, 0x1E9415U, 0x554D9EU, 0x4C7E6BU, 0x07E4B0U, + 0x3D35ADU, 0x741E4EU, 0x2F8D93U, 0x26FC20U, 0x7D667DU, 0x16B586U, 0x8B8E02U, 0xC91FD9U, 0xD0456CU, 0x9BF237U, + 0xC0EBCEU, 0xE92849U, 0xB29390U, 0xBBC3E7U, 0xE1787EU, 0xAA6B81U, 0x93B040U, 0xD8005FU, 0x411BAEU, 0x0AC870U, + 0x51F1D1U, 0x5D328EU, 0x362837U, 0x6799E0U, 0x6C4239U, 0x37711AU, 0x3EABC7U, 0x45BA3CU, 0x0D01A9U, 0x16D6F2U, + 0xDDCF17U, 0xC46D8CU, 0x8F3670U, 0xF6A723U, 0xFD5CBCU, 0xA74F5DU, 0xEAF582U, 0xF1A43BU, 0x903768U, 0x8B0CC5U, + 0xC0DC16U, 0x9957CBU, 0x1324F0U, 0x4ABD25U, 0x61AECEU, 0x38545AU, 0x73C701U, 0x68FEF4U, 0x212D6FU, 0x5B3382U, + 0x52C2D1U, 0x09494CU, 0x065ABFU, 0xDFA126U, 0x9CB149U, 0xA56A98U, 0xEE5927U, 0xF4C0F6U, 0xBD33B8U, 0xE62901U, + 0xCFB8D6U, 0x94D32FU, 0x9F40B8U, 0xC69AF1U, 0x8CAB0EU, 0x15309FU, 0x7E6360U, 0x21DA31U, 0x2848BAU, 0x733747U, + 0x72A6D4U, 0x08EDA8U, 0x435F7BU, 0x5A4CD6U, 0x119505U, 0x082658U, 0x433DE3U, 0xB8ED26U, 0xB0D6DDU, 0xEB05C8U, + 0xA2BC13U, 0xA9BEEAU, 0xD6656DU, 0xDF5614U, 0x848F82U, 0xC41C5BU, 0xDF26A4U, 0x94F7A5U, 0xADCC5AU, 0x665B8BU, + 0x3F1234U, 0x34A0EDU, 0x6E7BAAU, 0x076813U, 0x1CD1C4U, 0x55833DU, 0x4E1836U, 0x03A9E2U, 0x58F219U, 0x72418CU, + 0x2B09F7U, 0xA89A72U, 0xF1A1A9U, 0xBA7254U, 0x81EA47U, 0xC899BAU, 0xD20279U, 0x9B13C4U, 0xC0E09FU, 0xCB7E4BU, + 0xB25FF0U, 0xF98431U, 0xE4974EU, 0x2E6CD7U, 0x35FC00U, 0x5CE7A9U, 0x07147EU, 0x060D07U, 0x5D9F98U, 0x56E449U, + 0x0E65A6U, 0x659EB7U, 0x7C8D49U, 0x371790U, 0x6C6623U, 0xE5FD6EU, 0x9E6EBDU, 0x921600U, 0xC985D3U, 0x82DAEEU, + 0x9B7B25U, 0xD0E0F0U, 0xE1924BU, 0xAA091EU, 0xF158F5U, 0xF9E369U, 0x22F1BAU, 0x4B28C7U, 0x509B54U, 0x1B80BDU, + 0x024162U, 0x497B53U, 0x01A88CU, 0x3E1B5DU, 0x7502F2U, 0x6CD12BU, 0x27EB1CU, 0x7E7AC5U, 0xDDA113U, 0x8596BAU, + 0xCE5EEDU, 0xD54D14U, 0x9CF68BU, 0x87A54AU, 0xEE1C31U, 0xB58EA4U, 0xBFD55FU, 0xE66482U, 0xE93FA1U, 0x90AD7CU, + 0x5B04EFU, 0x405713U, 0x09CC48U, 0x13BFEDU, 0x522736U, 0x2914E3U, 0x22CFD8U, 0x7B5E05U, 0x3061E6U, 0x29B37FU, + 0x43BAA8U, 0x5849D1U, 0x91D25EU, 0xCEE0AFU, 0xC73971U, 0x9C2A40U, 0xB7919FU, 0xEF401EU, 0xA452E1U, 0xB5B9B8U, + 0xFEA80FU, 0x8533D6U, 0x8C4115U, 0xD7DA28U, 0x5F6BF3U, 0x043006U, 0x4FA39DU, 0x76DBD9U, 0x394C22U, 0x20C7BFU, + 0x6BB64CU, 0x312C41U, 0x187FB2U, 0x43C46FU, 0x0A55F4U, 0x192E81U, 0xD2BC4AU, 0xCBA5FBU, 0xA15624U, 0xF85DFDU, + 0xF38ECBU, 0xBA3602U, 0xA125F5U, 0xCEFE6CU, 0x97CF3BU, 0x9D55C2U, 0xC4A64DU, 0x4FBFBCU, 0x1468A3U, 0x7D4352U, + 0x6ED19DU, 0x270804U, 0x7D3B76U, 0x76A0ABU, 0x0FF018U, 0x0443D5U, 0x5D188EU, 0x16A93BU, 0x0932E0U, 0xC07015U, + 0xFACB1EU, 0xB39AC3U, 0xE80170U, 0xE3B3ADU, 0xBAEA5EU, 0xD17956U, 0xC042A9U, 0x8A9378U, 0x912DE7U, 0xD86E86U, + 0x83F559U, 0x2AC4E8U, 0x711F37U, 0x7A0D6EU, 0x26B4C9U, 0x6D6710U, 0x547CE7U, 0x1F8CFEU, 0x449720U, 0x4D3483U, + 0x16EF5AU, 0x1EFE2DU, 0x6D44B4U, 0xA6174BU, 0xBF8E8AU, 0xF4FD95U, 0xED6764U, 0x86D6BBU, 0xDC8912U, 0xD10A45U, + 0x8A799CU, 0x83E12AU, 0xF872F3U, 0xB10954U, 0xAA980DU, 0x6083D6U, 0x397163U, 0x3AE8B8U, 0x439BDDU, 0x481046U, + 0x1302BBU, 0x5AFB68U, 0x50E875U, 0x297396U, 0x26824AU, 0x7D98F1U, 0x344BA4U, 0xAF726FU, 0xE6F5DAU, 0x9C0F01U, + 0x971C38U, 0xCE85EFU, 0xC5F626U, 0x946D91U, 0xFFBDC8U, 0xE48637U, 0xAC15A6U, 0xB74C48U, 0x7EEE99U, 0x21B586U, + 0x0A0677U, 0x539FA8U, 0x18CC01U, 0x007652U, 0x4B67CFU, 0x70B43CU, 0x390FF1U, 0x625ECAU, 0x6BD01FU, 0x38E3C4U, + 0xB23870U, 0xCB893BU, 0x8093C6U, 0x994055U, 0xD679A8U, 0x8DAAFBU, 0xA4B176U, 0xFE018DU, 0xF7CA5CU, 0xACD963U, + 0xE762B2U, 0xFE323DU, 0x1589C4U, 0x0C5A92U, 0x4F432BU, 0x17F0ECU, 0x1CAA35U, 0x673B82U, 0x6E54DBU, 0x31C724U, + 0x785CA5U, 0x632C5AU, 0x29B70BU, 0x508490U, 0xDB5D6DU, 0x82CFEEU, 0x89B492U, 0xD22541U, 0xBB2EDCU, 0xA1DD27U, + 0xE04F62U, 0xFB56D9U, 0xB0A50CU, 0xF9BED7U, 0xC24EFAU, 0x8F5529U, 0x55C6D0U, 0x5E3B47U, 0x07383FU, 0x2CA2F0U, + 0x75D161U, 0x3E489EU, 0x25BB0FU, 0x6DA170U, 0x7630B1U, 0x174B2EU, 0x4CD8D7U, 0x470180U, 0x9E2339U, 0xD5B8FEU, + 0xE9EB27U, 0xA2521DU, 0xB941C8U, 0xF0BB23U, 0xAB2AB6U, 0xA271CDU, 0xD9C250U, 0xD3DC83U, 0x8A1D6EU, 0x41A67DU, + 0x58B580U, 0x3B6C5BU, 0x205ECEU, 0x6985A5U, 0x333471U, 0x3E27CAU, 0x65FD13U, 0x0CCE44U, 0x1747EDU, 0x5C143AU, + 0x45AF83U, 0x8F7F54U, 0xF6643DU, 0xFDD7A2U, 0xA68E73U, 0xAF3D8CU, 0xF4E79DU, 0xB5E073U, 0x8F59AAU, 0xC40A3DU, + 0x999044U, 0x922197U, 0xCB7A3AU, 0x60E9E1U, 0x3B90B4U, 0x73020FU, 0x6839DAU, 0x21FA71U, 0x3A632CU, 0x5151DFU, + 0x088A43U, 0x039B80U, 0x4260FDU, 0x18F36EU, 0x33EB97U, 0xEE1848U, 0xE503C9U, 0xBCA4B6U, 0xF7FC67U, 0xEC6FD8U, + 0x849C01U, 0x9F8506U, 0xD616FFU, 0x8D6C29U, 0x86FD90U, 0xFF26CFU, 0xF4150EU, 0x2C9EB1U, 0x6FEE60U, 0x74751BU, + 0x39E696U, 0x429F65U, 0x4B0DB8U, 0x1056ABU, 0x1AE756U, 0x43FC9DU, 0x282F29U, 0x318172U, 0x7A90E7U, 0xE36B1CU, + 0xA878D9U, 0xF2A0E2U, 0xDB133FU, 0x8008ECU, 0xC1D955U, 0xDAE292U, 0x9570EBU, 0xAC9B74U, 0xE68A85U, 0xBF514BU, + 0x34635AU, 0x6FFAA5U, 0x66A93CU, 0x1D12CBU, 0x54C382U, 0x4ED915U, 0x056AECU, 0x5C2D37U, 0x779402U, 0x2607C9U, + 0x2D5D1CU, 0x72ECA6U, 0xBAB7FBU, 0xA12408U, 0xC89C85U, 0xD3CF76U, 0x98542BU, 0xC177B8U, 0xCAAE45U, 0xB29CCEU, + 0xB9473BU, 0xE2D660U, 0xABEDF1U, 0xA03F1EU, 0x7926C4U, 0x1A95F1U, 0x044E2AU, 0x4D49FFU, 0x56B154U, 0x1FA209U, + 0x6419FAU, 0x6F4867U, 0x36D394U, 0x3C2199U, 0x653842U, 0x2EABB7U, 0x95C02CU, 0xDC525CU, 0x87CB93U, 0x8EB80AU, + 0xD423FDU, 0xF152A4U, 0xAAC003U, 0xE15BDAU, 0xF82A0DU, 0xB3B134U, 0xAAB3EBU, 0xC14C0AU, 0x1B5D95U, 0x128664U, + 0x49357AU, 0x002DA3U, 0x3BDE40U, 0x70C5DDU, 0x6954AEU, 0x23AE73U, 0x76ADE8U, 0x7D760DU, 0x064756U, 0x0FDCE3U, + 0xD40E38U, 0x9D37F5U, 0x87E4C6U, 0xECDF1AU, 0xB54EA9U, 0xBE1470U, 0xE7B71FU, 0xEC288EU, 0xB77951U, 0xDFC3A0U, + 0xC490BFU, 0x89095EU, 0x1ABA81U, 0x51E118U, 0x2853EFU, 0x234AB6U, 0x7B8910U, 0x703AC9U, 0x2B6216U, 0x62F127U, + 0x59CAECU, 0x101B59U, 0x4B2082U, 0xC1F2FFU, 0x88696CU, 0xB358A1U, 0xFA9752U, 0xAD844FU, 0xA63CB4U, 0xFFEF60U, + 0xF5F4EBU, 0x8C059EU, 0xC71F45U, 0xDCACF0U, 0x95772BU, 0x4E6622U, 0x67CDD5U, 0x3D9F0CU, 0x3406BBU, 0x6F75F2U, + 0x24EE0DU, 0x3D7E9CU, 0x520542U, 0x4396B3U, 0x09ADBCU, 0x527C45U, 0x5BF292U, 0xA0810BU, 0xE91878U, 0xF20BB5U, + 0xB9F10EU, 0xA1E0DBU, 0xEA33C0U, 0x938835U, 0x989BFEU, 0xC36342U, 0xCA6011U, 0x95FBECU, 0xFD0A6FU, 0x6E11B2U, + 0x25C3C1U, 0x3CFA5CU, 0x7769A7U, 0x0EB266U, 0x058079U, 0x5E1988U, 0x167E17U, 0x0DE5EFU, 0x44B428U, 0x7F0E31U, + 0x349DC6U, 0xEDC41FU, 0xE277A0U, 0xBA6DE1U, 0x99BE1EU, 0xC2178FU, 0x8B4450U, 0x90FF39U, 0xD9EFAAU, 0x823457U, + 0xA88785U, 0xF1DE98U, 0xFA4D73U, 0x3377E6U, 0x68A41DU, 0x43AD48U, 0x1A1AD3U, 0x14C836U, 0x4DF1EDU, 0x0622D0U, + 0x173903U, 0x7C88FEU, 0x27527DU, 0x2E41A5U, 0xF4FADAU, 0xFDBB4BU, 0x8601A4U, 0xCDD235U, 0xD4CB6AU, 0x9F7893U, + 0x862304U, 0xCCB3EDU, 0xB388BAU, 0xBA5B23U, 0xE1C0DCU, 0xE8A00DU, 0x3B3F27U, 0x500CF2U, 0x48D509U, 0x034694U, + 0x5A3CC7U, 0x51AD6AU, 0x0AB6B9U, 0x234544U, 0x785E57U, 0x30DE9AU, 0x2B2561U, 0x6036F4U, 0xDDCF8FU, 0x96DD5BU, + 0xCF46D0U, 0xCCB729U, 0x96A4FEU, 0xDF3FC7U, 0xE44D10U, 0xADC4F9U, 0xB61366U, 0xFD2837U, 0xA4B888U, 0x8EC359U, + 0x5750B6U, 0x5C49AFU, 0x07BA79U, 0x4A2080U, 0x517307U, 0x38DA7EU, 0x62C9ADU, 0x611230U, 0x38A2CBU, 0x33F98EU, + 0x4A4A35U, 0x0153E0U, 0x98815BU, 0xD23A06U, 0xD929C5U, 0x80E079U, 0xEBD7AAU, 0xF20D57U, 0xBD1C4CU, 0xE6A78DU, + 0xEF7472U, 0x954CE3U, 0x9CDF9CU, 0xCF8455U, 0x0437C2U, 0x1DFE3BU, 0x56EC6CU, 0x6F57D5U, 0x25061BU, 0x7E95AAU, + 0x772FF5U, 0x2C7C24U, 0x05C59BU, 0x5A965AU, 0x111D21U, 0x892DBCU, 0xC2F26FU, 0x9B6192U, 0xB81891U, 0xE38B6EU, + 0xEA91B6U, 0xB16221U, 0xF9FB48U, 0xC2C8DFU, 0x890226U, 0x9013F9U, 0xDBE848U, 0x02FB07U, 0x0D62D6U, 0x77906DU, + 0x3E8BB0U, 0x2538C3U, 0x6C614EU, 0x77F39CU, 0x141861U, 0x4D0D7AU, 0x47968FU, 0x1EE544U, 0x157DD1U, 0xCEEEAAU, + 0xA7953FU, 0xBC06D4U, 0xF57E09U, 0xABED3AU, 0xA2EEE3U, 0xD91734U, 0xD2849CU, 0x8BDEC3U, 0xC06F32U, 0xD174ADU, + 0x9BA77CU, 0x201C93U, 0x690C8AU, 0x22F77DU, 0x3BF4E4U, 0x702933U, 0x4B9B5AU, 0x0380C1U, 0x585134U, 0x556AEEU, + 0x0EF9CBU, 0x45A310U, 0x7C12CDU, 0xB7C97EU, 0xAFEA23U, 0xEC72C0U, 0xB7215DU, 0x9E9A8EU, 0xC50BF3U, 0xCC5068U, + 0x97E28DU, 0xDDB916U, 0xC42846U, 0xAF93B9U, 0xF2D020U, 0x796CC7U, 0x223F9EU, 0x2BA429U, 0x5095F0U, 0x18473FU, + 0x03DC8EU, 0x40EFD1U, 0x593620U, 0x1225BFU, 0x6BCF76U, 0x605E40U, 0xBA6599U, 0xF3B66AU, 0xE8AEF7U, 0x851DBCU, + 0x9EC649U, 0xD5D5D2U, 0x8C2C27U, 0x863E7CU, 0xDFB5D9U, 0xF4C002U, 0xA55BDFU, 0xEEE9ECU, 0x75B020U, 0x3C23D3U, + 0x06584AU, 0x4FCB15U, 0x1453A4U, 0x1F306BU, 0x42AB9AU, 0x09FA05U, 0x30415CU, 0x7A53ABU, 0x61AA22U, 0x2839D5U, + 0xF3228CU, 0xDAD032U, 0x89C9E3U, 0x820A2CU, 0xCAB91DU, 0x91A4C6U, 0x985673U, 0xE34DB8U, 0xA8DCE5U, 0xB52756U, + 0xFE358BU, 0xE6EE78U, 0x0DDF75U, 0x5654AFU, 0x5F075AU, 0x04BFC1U, 0x0D2CB4U, 0x56577FU, 0x34C6C2U, 0x2D9D11U, + 0x662F48U, 0x3FB4FFU, 0x34E536U, 0x4F4E89U, 0xC61C58U, 0x988107U, 0xD132A7U, 0xCA6978U, 0x81D881U, 0xB8C296U, + 0xF3114FU, 0xAA2AA8U, 0xA0FB31U, 0xF37142U, 0xDA429FU, 0x819B24U, 0x4888E1U, 0x5333FAU, 0x1AE30FU, 0x40D0D5U, + 0x6F0B68U, 0x36182BU, 0x3DA0F6U, 0x646345U, 0x2F7898U, 0x14CDF3U, 0x5C9666U, 0xC704BDU, 0x8E7F4CU, 0xDDEED3U, + 0xD655BAU, 0xAF062DU, 0xE49EF5U, 0xFDFD02U, 0xB7661BU, 0xA8F7F4U, 0xC18D25U, 0x9A1E9AU, 0x9305CBU, 0x48F414U, + 0x43EFB5U, 0x1B1D6AU, 0x708413U, 0x698780U, 0x2A7C4DU, 0x6168BFU, 0x78F3A2U, 0x130059U, 0x0B1BCCU, 0x40CA07U, + 0x1FF072U, 0x9663E9U, 0xCD9A14U, 0xE499C7U, 0xBF0AEAU, 0xF57239U, 0xECE1E4U, 0xA7BA5FU, 0xDE098FU, 0xD591E0U, + 0x86E271U, 0x8F79AEU, 0xD52817U, 0x1C8350U, 0x2711A9U, 0x684C3EU, 0x71FFE7U, 0x3AE490U, 0x633619U, 0x498FE6U, + 0x10DC37U, 0x1B670DU, 0x4066D8U, 0x09BC13U, 0x328FAEU, 0xFB16FDU, 0xA9C500U, 0xA2FF93U, 0xFB2C5EU, 0xF03565U, + 0x8D86B0U, 0xC65D4BU, 0xDD7DCEU, 0x95E615U, 0x8EB169U, 0xE708FAU, 0xBCDB03U, 0x37C1C4U, 0x6E72FDU, 0x25232AU, + 0x3DB8F3U, 0x5ECA4CU, 0x45430DU, 0x0CF0B2U, 0x57AB61U, 0x5E3ABCU, 0x210087U, 0x2BD343U, 0xF248B8U, 0xB9392DU, + 0xA0A376U, 0xEBB09FU, 0x905908U, 0x99CAF1U, 0xD3F5A6U, 0xCA251FU, 0x813ED0U, 0xF2CD01U, 0xFBD63EU, 0xA046EEU, + 0x29BD51U, 0x76AE98U, 0x3C274FU, 0x055476U, 0x4ECEA5U, 0x573F58U, 0x1C24DBU, 0x47B786U, 0x6ECC7DU, 0x345CE8U, + 0x7D0703U, 0x66B456U, 0xAF3DECU, 0xBC6F31U, 0xD7D0A2U, 0x8EC1CFU, 0x80321CU, 0xD9A9A1U, 0xD2FB7AU, 0xA9422BU, + 0xE05184U, 0xFB8A55U, 0xB22AAAU, 0xE831B3U, 0x63E264U, 0x1ADB8CU, 0x11081BU, 0x4832E2U, 0x0BA1BDU, 0x10781CU, + 0x784BC3U, 0x679052U, 0x2E902DU, 0x752BFCU, 0x7EFC57U, 0x27C58AU, 0xCC57D9U, 0xD40C64U, 0x9FBFB6U, 0x84665BU, + 0xCD7540U, 0xB6CF95U, 0xBF8E6EU, 0xE415FBU, 0xE62690U, 0xBFFE0DU, 0xF04DFEU, 0xC91623U, 0x028520U, 0x19BCD9U, + 0x506E06U, 0x0AEDB6U, 0x23D4E9U, 0x780728U, 0x331997U, 0x3AE84EU, 0x6173B9U, 0x6840A0U, 0x129B67U, 0x598B9EU, + 0xC87009U, 0x877370U, 0x9EEAEBU, 0xF5190EU, 0xAC03D4U, 0xA690C1U, 0xFDE93AU, 0xB47AF7U, 0xAF8044U, 0xC69119U, + 0xDD0ACAU, 0x166977U, 0x4EF0BCU, 0x45E2C9U, 0x3C1D52U, 0x3F8E87U, 0x64D73CU, 0x296574U, 0x327E83U, 0x7A8F1AU, + 0x0114EDU, 0x0A1734U, 0x53EF3BU, 0xD8FCCAU, 0x812715U, 0xEA94A4U, 0xF185EBU, 0xB95F3AU, 0xA26C85U, 0xEBB55CU, + 0xB0862AU, 0x930CA3U, 0xCEDD70U, 0x85E6CDU, 0x9D7196U, 0x562A63U, 0x6D9AE8U, 0x24411DU, 0x7F5246U, 0x76EBDBU, + 0x2DB928U, 0x2712E5U, 0x5E83D7U, 0x15D80AU, 0x0C6BD1U, 0x473170U, 0x54A02FU, 0xB99B9EU, 0xE34841U, 0xEAD090U, + 0xB1E33FU, 0xFA2866U, 0xE31991U, 0x88C208U, 0x8154FFU, 0xDB6FB7U, 0x92BE28U, 0x89A5D9U, 0xE25606U, 0x7BCE37U, + 0x30FDFCU, 0x693E21U, 0x6F2792U, 0x3495CFU, 0x5DCE6CU, 0x465DB1U, 0x0FA46AU, 0x14B75FU, 0x5F2D85U, 0x275C60U, + 0x2CC7FBU, 0xF56486U, 0xFE3C55U, 0xA5ABC8U, 0xECD02BU, 0xD743F2U, 0x9FDAC5U, 0xC0A81CU, 0xC33383U, 0x9A2262U, + 0xB1D93DU, 0xE8DA8DU, 0x232252U, 0x39B19BU, 0x70AAACU, 0x6B5975U, 0x024182U, 0x59921BU, 0x522974U, 0x0B38A5U, + 0x01F33EU, 0x78C1CBU, 0x3758D0U, 0x242F3DU, 0xEDB4EFU, 0xB66672U, 0xBF5701U, 0xC5CCDCU, 0xCE9F77U, 0x9726A2U, + 0xDCA4F9U, 0xC5FF4CU, 0xAE4E87U, 0xB51576U, 0xFDA7E9U, 0xA63C90U, 0x2F6D47U, 0x74D6CFU, 0x1BC538U, 0x0A1D21U, + 0x413EDEU, 0x58E50FU, 0x127480U, 0x6B4BF1U, 0x60992EU, 0x2BA297U, 0x327340U, 0x79E809U, 0xA0DABAU, 0x8A0367U, + 0xD1009DU, 0x98BB98U, 0x836A63U, 0xCE70F6U, 0xF5832DU, 0xB69850U, 0xEE29D3U, 0xE5732EU, 0xBCE0FDU, 0xB75962U, + 0x4C1A13U, 0x0589DCU, 0x1EF165U, 0x5666B7U, 0x4DFDEAU, 0x268E59U, 0x7F1794U, 0x74256FU, 0x29FE7AU, 0x626F81U, + 0x700404U, 0x1996DFU, 0x828FA2U, 0xCB7C31U, 0x9067CCU, 0x9B940EU, 0xE20C17U, 0xE81FE8U, 0xB1E439U, 0xFAF586U, + 0xE17FC7U, 0xA88C18U, 0xD397A1U, 0x5E4276U, 0x04794FU, 0x0FEB88U, 0x5E9271U, 0x3501E6U, 0x2C9A3EU, 0x67EA51U, + 0x7C61C0U, 0x34323BU, 0x6F89EEU, 0x4618F5U, 0x1D4218U, 0xD4F1CBU, 0xCFE876U, 0x841B25U, 0xB888F8U, 0xF3D053U, + 0xAA7386U, 0xA168BCU, 0xF2BB69U, 0xFB8792U, 0x80541FU, 0xC8CF4CU, 0xC3FEB5U, 0x182522U, 0x1117CBU, 0x6A9E9CU, + 0x234D05U, 0x3876FAU, 0x72A42BU, 0x2FBD84U, 0x240ED4U, 0x5DD54BU, 0x56C4B2U, 0x057E65U, 0x4C2D4CU, 0xD7849BU, + 0xBFD762U, 0xA44DF9U, 0xEFFEACU, 0xB6A347U, 0xBD30D2U, 0xC40B29U, 0x8FDB74U, 0x9540E6U, 0xDC330BU, 0xC3BAD8U, + 0x8A89E5U, 0x71533EU, 0x7AC0BBU, 0x2BB140U, 0x212A11U, 0x7838AEU, 0x13C17FU, 0x08D290U, 0x414989U, 0x1AA85EU, + 0x13B2A6U, 0x494131U, 0x625AD8U, 0xBBCF87U, 0xF43516U, 0xED26E9U, 0xA6BF78U, 0x9FCC17U, 0xDD57CEU, 0x86B75DU, + 0x8FACA0U, 0xD43FE3U, 0x9D445EU, 0xA6D584U, 0x6DCF71U, 0x753C6AU, 0x3EA5AFU, 0x67F654U, 0x4C4CC9U, 0x134DBAU, + 0x1AB667U, 0x4125E4U, 0x097E19U, 0x12CA0AU, 0x71D1F3U, 0x78022CU, 0xA33BACU, 0xEAA953U, 0xF17A02U, 0x9B53BDU, + 0xC28074U, 0xC9BB83U, 0x90299AU, 0x9BF04DU, 0xC4C3B4U, 0xAD583BU, 0xB7084AU, 0xFEB3D1U, 0x656004U, 0x2E69FEU, + 0x5FDAF3U, 0x548000U, 0x0D37DDU, 0x472E4EU, 0x5CFD33U, 0x1546F8U, 0x2E164DU, 0x678D96U, 0x3CBEE3U, 0x337768U, + 0xEBE5BDU, 0xC09E47U, 0x990D5EU, 0xD234A9U, 0xC9E720U, 0x887DF7U, 0xD34C0EU, 0xFB9711U, 0xA084E0U, 0xA96C2FU, + 0xF27FBEU, 0xB9E4C1U, 0x001318U, 0x4B02AFU, 0x56B867U, 0x1CEB30U, 0x477289U, 0x4E8152U, 0x358AF7U, 0x3C1AACU, + 0x676159U, 0x24F2C2U, 0x3C6917U, 0x57197CU, 0x8E82E1U, 0x85F112U, 0xDE68CFU, 0xD77BDDU, 0x8C9130U, 0xE400EBU, + 0xFB5B5EU, 0xB0E885U, 0xA9F6D4U, 0xE2277BU, 0x9B9CA2U, 0x108F15U, 0x5A764CU, 0x0B64ABU, 0x00AF32U, 0x599CEDU, + 0x72059DU, 0x29D702U, 0x60ECE3U, 0x7A7D3CU, 0x330625U, 0x0C95D6U, 0x474D0BU, 0x9E7EA8U, 0x95FDF5U, 0xCCA666U, + 0x86179BU, 0xBD8D40U, 0xFCDA65U, 0xE763BFU, 0xAE304AU, 0xF58AD1U, 0xDE9B9CU, 0x86406FU, 0x8DE3F2U, 0x54B801U, + 0x1B28D8U, 0x0213D7U, 0x69C026U, 0x7259B9U, 0x3A6B48U, 0x61B017U, 0x68819FU, 0x135A68U, 0x50D9B1U, 0x49E386U, + 0x02325FU, 0x9A29B0U, 0xD19E23U, 0xAAC77EU, 0xA3558DU, 0xFCAE04U, 0xF5BFF3U, 0xAE04AAU, 0xC44635U, 0xDDDDC5U, + 0x966C4AU, 0xCF373BU, 0xC4A4E4U, 0x1FD47DU, 0x3E4F8AU, 0x64DC43U, 0x2DA574U, 0x3637ADU, 0x7D5C56U, 0x44CFC3U, + 0x0BD688U, 0x52257DU, 0x59BBE7U, 0x03AA1AU, 0x4A5119U, 0xF142C4U, 0xB89A37U, 0xB329AAU, 0xE822D1U, 0xA9D114U, + 0x93C88FU, 0xDA5A7AU, 0xC1A121U, 0x8AB090U, 0xD36B5EU, 0xDC58A7U, 0xA5C0B0U, 0x2F8369U, 0x74389EU, 0x3DAB17U, + 0x26D368U, 0x4F40F9U, 0x141F26U, 0x1DAEC7U, 0x473558U, 0x446721U, 0x1DDEF6U, 0x769D6EU, 0xEF0E8DU, 0xA434D0U, + 0xBFE56BU, 0xF35EBEU, 0x884D45U, 0x819440U, 0xDAA69BU, 0x917D26U, 0x88ECF5U, 0xC3C788U, 0xFB150BU, 0xB02ED6U, + 0x63BF24U, 0x6A6439U, 0x3153D2U, 0x188B03U, 0x43989CU, 0x0923EDU, 0x147232U, 0x5FF99BU, 0x060B4CU, 0x2D10B5U, + 0x76A1A2U, 0x7FFA7BU, 0xA568D4U, 0xECD104U, 0xD7827BU, 0x9C19EAU, 0x8D6831U, 0xC6E2DCU, 0x9F71CFU, 0x950A32U, + 0xEE9BE1U, 0xA3A16CU, 0xB8761FU, 0xF16FC2U, 0x6A9C79U, 0x010FACU, 0x491786U, 0x52F453U, 0x1BEF88U, 0x007C35U, + 0x4B8566U, 0x32978FU, 0x316C18U, 0x697DC1U, 0x62E6BEU, 0x3B142FU, 0xD40DC0U, 0xCFCE11U, 0x86F58EU, 0xDD66F6U, + 0xD53E31U, 0x8E8988U, 0xA5125FU, 0xFC6366U, 0xB7F9B9U, 0xAEAA78U, 0xE513C3U, 0xD69096U, 0x1ECB6DU, 0x4579F8U, + 0x4C6033U, 0x13B34EU, 0x5888DCU, 0x615821U, 0x2AE3F2U, 0x32F0FFU, 0x792B04U, 0x220A91U, 0x0B906AU, 0xD0432BU, + 0xD97294U, 0x82AD4DU, 0xC8BE9AU, 0xD90623U, 0xB2D564U, 0xEBEE9CU, 0xE02D03U, 0xBF35D2U, 0xB686ADU, 0xCCDD2CU, + 0x854CD3U, 0x1EF70AU, 0x55A51DU, 0x4C1CE4U, 0x074F67U, 0x7ED49AU, 0x7476C9U, 0x2F2F75U, 0x26BCBEU, 0x75876BU, + 0x1C5650U, 0x07C885U, 0x48BB7EU, 0x9022F3U, 0x9B11A0U, 0xC2CA5DU, 0xE9C8CEU, 0xA07133U, 0xFBA238U, 0xF0B9E9U, + 0xA84917U, 0x835286U, 0xDAC179U, 0x913830U, 0x822B87U, 0x4BD95EU, 0x30D289U, 0x3C43A0U, 0x67B87FU, 0x6CAA8EU, + 0x353311U, 0x7E4440U, 0x47DFABU, 0x0C8E3FU, 0x1634C4U, 0x5FA7D9U, 0x04CC0AU, 0xAD5DE7U, 0xF64774U, 0xBDB409U, + 0xAC2DD2U, 0xE67E67U, 0xFBC5ACU, 0x90D5F9U, 0xCB0E42U, 0xC23D9FU, 0x99B67DU, 0x9047E4U, 0xEA5DB3U, 0x218E0AU, + 0x38B7D5U, 0x732034U, 0x6AFA2BU, 0x21CBFAU, 0x5A1005U, 0x5B039CU, 0x01A8FBU, 0x4C7822U, 0x574395U, 0x3CD04DU, + 0xA5811AU, 0xEE3BB3U, 0xB76868U, 0xBDF19DU, 0xE64286U, 0xCF1943U, 0x949BF8U, 0xDDA225U, 0xC67156U, 0x8FCACBU, + 0xF59B08U, 0xF605F5U, 0x2B36EFU, 0x20EF1AU, 0x797CC1U, 0x324664U, 0x1985BFU, 0x419CE4U, 0x4A6F51U, 0x13748AU, + 0x58C42FU, 0x631F74U, 0x2A0CA1U, 0x71F51AU, 0xF9E7D7U, 0xA27CA5U, 0xA99F38U, 0xD886EBU, 0x973516U, 0x8E6F1DU, + 0xC5FEECU, 0xDF0133U, 0xB61282U, 0xED8ADDU, 0xE4F954U, 0xBF7AA3U, 0x74617AU, 0x6D904DU, 0x070A95U, 0x1E795AU, + 0x55E0EBU, 0x0EF334U, 0x0F0845U, 0x7098CAU, 0x798313U, 0x2360E0U, 0x687BFDU, 0x71AB0EU, 0xBA10C3U, 0x830370U, + 0xC8FA2DU, 0x93EDB7U, 0x9B3742U, 0xC00689U, 0xA99DBCU, 0xB25E67U, 0xF964BAU, 0xE0F509U, 0xA7AE44U, 0xFF1DB7U, + 0x54C42EU, 0x0DF6F1U, 0x066D90U, 0x5D3C0EU, 0x1487FFU, 0x2F8460U, 0x675E19U, 0x3CEFDEU, 0x37B447U, 0x6E27B0U, + 0x651369U, 0x9CC856U, 0xD77B97U, 0xC9604CU, 0x88B1F9U, 0x939BA2U, 0xFA4856U, 0xB1D1DDU, 0xAAE200U, 0xE33873U, + 0xB829EEU, 0xB0D20DU, 0xCBC190U, 0x407AEBU, 0x19AA3EU, 0x52A1A5U, 0x4B1240U, 0x204B1BU, 0x3ED9A2U, 0x7F2274U, + 0x2437ADU, 0x2DAC8AU, 0x76DF53U, 0x5D45ACU, 0x04F43DU, 0xCEBFE2U, 0xD72CC3U, 0x9C551CU, 0xE7C7E5U, 0xEEDC72U, + 0xB52D1BU, 0xBCB6CCU, 0xE6A454U, 0xA95FA7U, 0x904EFAU, 0xD3A551U, 0xCA3684U, 0x01265FU, 0x5ADD6AU, 0x72CEB1U, + 0x29135CU, 0x60A1CFU, 0x7BBA92U, 0x304921U, 0x2950FCU, 0x42D316U, 0x1AA903U, 0x1138F8U, 0x48E329U, 0xC7D0A6U, + 0xBC48DFU, 0xFD1B08U, 0xE6A0B1U, 0xAE3366U, 0xF55AAFU, 0xFEC898U, 0x879341U, 0x8C22FEU, 0xD5392EU, 0x9EEA61U, + 0x8456D0U, 0x6D450BU, 0x769EF6U, 0x3FADE5U, 0x606508U, 0x6BD6DBU, 0x3ACD46U, 0x101C3DU, 0x5927F8U, 0x02F543U, + 0x096C96U, 0x505FCCU, 0xBB8C71U, 0xA296B2U, 0xE8370FU, 0xF36C5CU, 0xBADF85U, 0xE1063AU, 0xC814FBU, 0x97AF04U, + 0x9CFA15U, 0xC461EAU, 0x87D23BU, 0xBE88B4U, 0x7509CCU, 0x2E721BU, 0x27E1A2U, 0x7C5965U, 0x750A7CU, 0x0F9183U, + 0x44A052U, 0x5D7BE9U, 0x1669ACU, 0x0F9277U, 0x6013CAU, 0x3B0819U, 0xB3FA65U, 0xE0E3E6U, 0xA9103BU, 0xB28BC0U, + 0xDB9ED5U, 0xC0642EU, 0x8B77EBU, 0xD3EC50U, 0xD80D89U, 0xA1179EU, 0xAAC477U, 0xF1F5A0U, 0x386E19U, 0x271D4FU, + 0x6F8596U, 0x541639U, 0x1F6DE8U, 0x4EFE97U, 0x45B706U, 0x1C05D9U, 0x779E30U, 0x6DCF27U, 0x2474DEU, 0x3F664DU, + 0xF69BB0U, 0xAD08FBU, 0x86536FU, 0xDFE094U, 0xD1E841U, 0x883B6AU, 0xC300B7U, 0xE8D164U, 0xB16BD9U, 0xF2788AU, + 0xEBA167U, 0xA192FCU, 0x3A0929U, 0x53DBD2U, 0x08F2C3U, 0x01212DU, 0x5A3ABCU, 0x518B43U, 0x29D11AU, 0x62628DU, + 0x7FFD64U, 0x34ACB3U, 0x6F1E8AU, 0xE64555U, 0x95C494U, 0x9D7F2BU, 0xC62C7AU, 0x8DB485U, 0x94871DU, 0xFF5CDEU, + 0xE6EFE3U, 0xADB730U, 0xF524CFU, 0xFE0F56U, 0xA5DE31U, 0x48C5E8U, 0x53377FU, 0x1AAE86U, 0x01BD59U, 0x4B4678U, + 0x3A50A7U, 0x31E977U, 0x683ACCU, 0x633189U, 0x38C072U, 0x71DAEFU, 0x4B493CU, 0x82B001U, 0x99A3C2U, 0xD2383FU, + 0x8F4AA4U, 0xA4D3D1U, 0xFDA00AU, 0xB62B8FU, 0xACBB75U, 0xE5C028U, 0xF6539BU, 0x9F4242U, 0xC4B985U, 0x4F2FBCU, + 0x167463U, 0x1CC5D2U, 0x65DE1DU, 0x2E3C4CU, 0x35A5F3U, 0x78F622U, 0x234DDDU, 0x0A5C55U, 0x408622U, 0x5BB5FBU, + 0x122E6CU, 0xC1FF95U, 0xC8C4DEU, 0xB3066BU, 0xB83FB0U, 0xE0AC25U, 0xAB775EU, 0xB24793U, 0xD9DC20U, 0x828BFDU, + 0x8F30AFU, 0xD46102U, 0xDCDBD9U, 0x07582CU, 0x6E0137U, 0x75B2C6U, 0x3EE809U, 0x2F7990U, 0x64C2E7U, 0x1C913EU, + 0x170A89U, 0x4C3A40U, 0x45E9D6U, 0x1E42AFU, 0x571370U, 0xE889C1U, 0xA2BA0EU, 0xFB635FU, 0xF070E0U, 0xA9CF39U, + 0x821DDAU, 0xD904C7U, 0x90F734U, 0x82FCE9U, 0xCB4D72U, 0xD09716U, 0x3B848DU, 0x623D78U, 0x696EA3U, 0x30F4BEU, + 0x7A075DU, 0x451E80U, 0x0C8D33U, 0x17E66EU, 0x5E76B5U, 0x050D14U, 0x0E9ECBU, 0x7607BAU, 0xF57524U, 0xACEAC5U, + 0xE7F91AU, 0xFC0083U, 0x9583F4U, 0xCED96DU, 0xC6688AU, 0x9D7353U, 0x92804CU, 0xCB11BDU, 0xA00B62U, 0xA9F8C3U, + 0x72E398U, 0x783244U, 0x2118F7U, 0x4A8B2AU, 0x5B5249U, 0x1061D4U, 0x09FA2FU, 0x428ABAU, 0x1911F1U, 0x31C604U, + 0x6AFD9FU, 0x276D62U, 0xBC2631U, 0xF795B8U, 0xCE4C4EU, 0x855F97U, 0xDDE520U, 0xD6B479U, 0x8D0FB6U, 0x8C1C07U, + 0xF7C6D8U, 0xBE77A9U, 0xA53C36U, 0xEFAFDFU, 0x369648U, 0x1D4411U, 0x40DFE6U, 0x4BEE7EU, 0x103595U, 0x5902C0U, + 0x43D85BU, 0x2AC9AEU, 0x316265U, 0x7AB158U, 0x23A18BU, 0x201A56U, 0xD949E5U, 0x93F2A8U, 0x882313U, 0xC139C6U, + 0xDA8A3CU, 0x97D329U, 0xEC50E2U, 0xE7AA13U, 0xBFBB8CU, 0xB42075U, 0xED5322U, 0x06C8ABU, 0x1DF85CU, 0x542785U, + 0x0FB4BAU, 0x0FCD7BU, 0x544FA4U, 0x7F5414U, 0x26A54BU, 0x6D3EF2U, 0x702D31U, 0x3BD7CCU, 0x13C6DFU, 0xC83D02U, + 0x83AEF9U, 0x9AB77CU, 0xD15507U, 0xC84ED2U, 0xA3DF49U, 0xF9A4BDU, 0xF836F6U, 0xA3ED4BU, 0xEADC98U, 0xD14B05U, + 0x9A30E6U, 0x03A0BFU, 0x4D6B00U, 0x1458D1U, 0x1FC12EU, 0x64932FU, 0x6D28F0U, 0x36BB09U, 0x7FE29FU, 0x6551E6U, + 0x0E0B21U, 0x57AAB8U, 0x54B147U, 0x8D6216U, 0x86DBA9U, 0xDDC968U, 0xB412F3U, 0xAA218EU, 0xE3FC5DU, 0xF86EF0U, + 0xB35523U, 0xCA945FU, 0xC1AFCCU, 0x983C11U, 0xD264FAU, 0x49D7EFU, 0x000C14U, 0x3B1FC1U, 0x7AA75AU, 0x21F4B7U, + 0x2A6FA4U, 0x728E5DU, 0x5D958AU, 0x042713U, 0x4F767DU, 0x54EDACU, 0x1D5E13U, 0x8604C0U, 0xEE91ADU, 0xB5EA36U, + 0xBC79E3U, 0xE77018U, 0xAC820DU, 0x9D19E6U, 0xD6283FU, 0xCEF3A8U, 0x85E0D1U, 0xDA1A47U, 0x538BAEU, 0x389079U, + 0x216360U, 0x6A6B9FU, 0x30D84EU, 0x3903E1U, 0x421230U, 0x0BE94FU, 0x10FBD6U, 0x5B6011U, 0x4215E8U, 0x008EFBU, + 0xF95C07U, 0xF2759CU, 0xADE659U, 0xA49D22U, 0xFF0CB7U, 0x96964CU, 0x8CE591U, 0xC77E92U, 0x9E2F6FU, 0x959CBCU, + 0xCC0601U, 0xE7475AU, 0x3CFCBFU, 0x74EF25U, 0x6717F4U, 0x2E048BU, 0x15DF0AU, 0x5A6CF5U, 0x03716CU, 0x08A33BU, + 0x5098C2U, 0x5B1945U, 0x20C2BCU, 0x69F063U, 0xF22972U, 0xBB1A8DU, 0xE0814DU, 0xCA52F6U, 0x936AABU, 0x98B958U, + 0xC9A2C5U, 0x820306U, 0x9B593BU, 0xF4CAE0U, 0xEE7315U, 0xA7209EU, 0x7C9BCBU, 0x75C930U, 0x0E54A4U, 0x45E75FU, + 0x5CBC12U, 0x173D81U, 0x0F0778U, 0x44D4AFU, 0x3FC596U, 0x363E59U, 0x65ACE8U, 0x6C9737U, 0xA346E6U, 0xD95DD9U, + 0xD0FE10U, 0x8B26E6U, 0xC2357FU, 0xD9CE28U, 0x92DDC1U, 0xAB455EU, 0xE1B6AFU, 0xB8ADF4U, 0xB35861U, 0x68438AU, + 0x41C157U, 0x12B864U, 0x5B2BB9U, 0x45B00AU, 0x0EC3C6U, 0x175B95U, 0x7C2828U, 0x25B3F3U, 0x2E2206U, 0x75580DU, + 0x3DDBD8U, 0x86C223U, 0xCF31BAU, 0xD4AADDU, 0x9FF804U, 0xC64193U, 0xCD526AU, 0xBDA9F4U, 0xB2BD95U, 0xE92E4AU, + 0xA0C5FBU, 0xBBD424U, 0xD20F7DU, 0x093DDAU, 0x01A603U, 0x5A77F0U, 0x514CEDU, 0x089E36U, 0x63A7D3U, 0x7A3448U, + 0x31FF3CU, 0x2BCEE7U, 0x625462U, 0x150799U, 0x1CBC84U, 0xC7ED77U, 0x8C76AAU, 0x95C409U, 0xDF9954U, 0xE62A8FU, + 0xAD213EU, 0xF6F1E1U, 0xFF4A00U, 0xA4191EU, 0x8D82C7U, 0xD7B330U, 0x9C69A9U, 0x057ACEU, 0x42C357U, 0x2B1088U, + 0x303A79U, 0x7BEB76U, 0x63F087U, 0x284358U, 0x7198E1U, 0x5A88B2U, 0x017B7EU, 0x0860CDU, 0x53F510U, 0x9A0F63U, + 0xA00CFEU, 0xEBB535U, 0xF2E6C0U, 0xB97DDBU, 0xEC0F2EU, 0xE796B5U, 0x9C0578U, 0xD47E0BU, 0xCFEF92U, 0x86F564U, + 0x1D16ADU, 0x768F32U, 0x2FFC43U, 0x24669CU, 0x7C752DU, 0x778CE2U, 0x2E1F9BU, 0x45000CU, 0x5EF0D5U, 0x13FB62U, + 0x40282BU, 0x4811D4U, 0xB38344U, 0xB8789FU, 0xE16BEAU, 0xAAB261U, 0xB38194U, 0xF81B4FU, 0xC2CA52U, 0x8BE1B1U, + 0xD0726CU, 0xD903DFU, 0x829982U, 0xE94A79U, 0x7471FDU, 0x36E026U, 0x2FBA93U, 0x640DC8U, 0x3F1431U, 0x16D7B6U, + 0x4D6C6FU, 0x443C18U, 0x1E8781U, 0x55947EU, 0x6C4FBFU, 0x27FFA0U, 0xBEE451U, 0xF5378FU, 0xAE0E2EU, 0xA2CD71U, + 0xC9D7C8U, 0x98661FU, 0x93BDC6U, 0xC88EE5U, 0xC15438U, 0xBA45C3U, 0xF2FE56U, 0xE9290DU, 0x2230E8U, 0x3B9273U, + 0x70C98FU, 0x0958DCU, 0x02A343U, 0x58B0A2U, 0x150A7DU, 0x0E5BC4U, 0x6FC897U, 0x74F33AU, 0x3F23E9U, 0x66A834U, + 0xECDB0FU, 0xB542DAU, 0x9E5131U, 0xC7ABA5U, 0x8C38FEU, 0x97010BU, 0xDED290U, 0xA4CC7DU, 0xAD3D2EU, 0xF6B6B3U, + 0xF9A540U, 0x205ED9U, 0x634EB6U, 0x5A9567U, 0x11A6D8U, 0x0B3F09U}; + +CAMBEFEC::CAMBEFEC() +{ +} + +CAMBEFEC::~CAMBEFEC() +{ +} + +unsigned int CAMBEFEC::regenerate(unsigned char* bytes) const +{ + wxASSERT(bytes != NULL); + + unsigned int a = ((bytes[0] & 0x80) ? 0x800000 : 0x000000) | ((bytes[0] & 0x02) ? 0x400000 : 0x000000) | + ((bytes[1] & 0x08) ? 0x200000 : 0x000000) | ((bytes[2] & 0x20) ? 0x100000 : 0x000000) | + ((bytes[3] & 0x80) ? 0x080000 : 0x000000) | ((bytes[3] & 0x02) ? 0x040000 : 0x000000) | + ((bytes[4] & 0x08) ? 0x020000 : 0x000000) | ((bytes[5] & 0x20) ? 0x010000 : 0x000000) | + ((bytes[6] & 0x80) ? 0x008000 : 0x000000) | ((bytes[6] & 0x02) ? 0x004000 : 0x000000) | + ((bytes[7] & 0x08) ? 0x002000 : 0x000000) | ((bytes[8] & 0x20) ? 0x001000 : 0x000000) | + ((bytes[0] & 0x40) ? 0x000800 : 0x000000) | ((bytes[0] & 0x01) ? 0x000400 : 0x000000) | + ((bytes[1] & 0x04) ? 0x000200 : 0x000000) | ((bytes[2] & 0x10) ? 0x000100 : 0x000000) | + ((bytes[3] & 0x40) ? 0x000080 : 0x000000) | ((bytes[3] & 0x01) ? 0x000040 : 0x000000) | + ((bytes[4] & 0x04) ? 0x000020 : 0x000000) | ((bytes[5] & 0x10) ? 0x000010 : 0x000000) | + ((bytes[6] & 0x40) ? 0x000008 : 0x000000) | ((bytes[6] & 0x01) ? 0x000004 : 0x000000) | + ((bytes[7] & 0x04) ? 0x000002 : 0x000000) | ((bytes[8] & 0x10) ? 0x000001 : 0x000000); + + unsigned int b = ((bytes[0] & 0x20) ? 0x800000 : 0x000000) | ((bytes[1] & 0x80) ? 0x400000 : 0x000000) | + ((bytes[1] & 0x02) ? 0x200000 : 0x000000) | ((bytes[2] & 0x08) ? 0x100000 : 0x000000) | + ((bytes[3] & 0x20) ? 0x080000 : 0x000000) | ((bytes[4] & 0x80) ? 0x040000 : 0x000000) | + ((bytes[4] & 0x02) ? 0x020000 : 0x000000) | ((bytes[5] & 0x08) ? 0x010000 : 0x000000) | + ((bytes[6] & 0x20) ? 0x008000 : 0x000000) | ((bytes[7] & 0x80) ? 0x004000 : 0x000000) | + ((bytes[7] & 0x02) ? 0x002000 : 0x000000) | ((bytes[8] & 0x08) ? 0x001000 : 0x000000) | + ((bytes[0] & 0x10) ? 0x000800 : 0x000000) | ((bytes[1] & 0x40) ? 0x000400 : 0x000000) | + ((bytes[1] & 0x01) ? 0x000200 : 0x000000) | ((bytes[2] & 0x04) ? 0x000100 : 0x000000) | + ((bytes[3] & 0x10) ? 0x000080 : 0x000000) | ((bytes[4] & 0x40) ? 0x000040 : 0x000000) | + ((bytes[4] & 0x01) ? 0x000020 : 0x000000) | ((bytes[5] & 0x04) ? 0x000010 : 0x000000) | + ((bytes[6] & 0x10) ? 0x000008 : 0x000000) | ((bytes[7] & 0x40) ? 0x000004 : 0x000000) | + ((bytes[7] & 0x01) ? 0x000002 : 0x000000) | ((bytes[8] & 0x04) ? 0x000001 : 0x000000); + + unsigned int c = ((bytes[0] & 0x08) ? 0x800000 : 0x000000) | ((bytes[1] & 0x20) ? 0x400000 : 0x000000) | + ((bytes[2] & 0x80) ? 0x200000 : 0x000000) | ((bytes[2] & 0x02) ? 0x100000 : 0x000000) | + ((bytes[3] & 0x08) ? 0x080000 : 0x000000) | ((bytes[4] & 0x20) ? 0x040000 : 0x000000) | + ((bytes[5] & 0x80) ? 0x020000 : 0x000000) | ((bytes[5] & 0x02) ? 0x010000 : 0x000000) | + ((bytes[6] & 0x08) ? 0x008000 : 0x000000) | ((bytes[7] & 0x20) ? 0x004000 : 0x000000) | + ((bytes[8] & 0x80) ? 0x002000 : 0x000000) | ((bytes[8] & 0x02) ? 0x001000 : 0x000000) | + ((bytes[0] & 0x04) ? 0x000800 : 0x000000) | ((bytes[1] & 0x10) ? 0x000400 : 0x000000) | + ((bytes[2] & 0x40) ? 0x000200 : 0x000000) | ((bytes[2] & 0x01) ? 0x000100 : 0x000000) | + ((bytes[3] & 0x04) ? 0x000080 : 0x000000) | ((bytes[4] & 0x10) ? 0x000040 : 0x000000) | + ((bytes[5] & 0x40) ? 0x000020 : 0x000000) | ((bytes[5] & 0x01) ? 0x000010 : 0x000000) | + ((bytes[6] & 0x04) ? 0x000008 : 0x000000) | ((bytes[7] & 0x10) ? 0x000004 : 0x000000) | + ((bytes[8] & 0x40) ? 0x000002 : 0x000000) | ((bytes[8] & 0x01) ? 0x000001 : 0x000000); + + unsigned int errors = regenerate(a, b, c); + + bytes[0] = ((a & 0x800000) ? 0x80 : 0x00) | ((a & 0x000800) ? 0x40 : 0x00) | ((b & 0x800000) ? 0x20 : 0x00) | + ((b & 0x000800) ? 0x10 : 0x00) | ((c & 0x800000) ? 0x08 : 0x00) | ((c & 0x000800) ? 0x04 : 0x00) | + ((a & 0x400000) ? 0x02 : 0x00) | ((a & 0x000400) ? 0x01 : 0x00); + + bytes[1] = ((b & 0x400000) ? 0x80 : 0x00) | ((b & 0x000400) ? 0x40 : 0x00) | ((c & 0x400000) ? 0x20 : 0x00) | + ((c & 0x000400) ? 0x10 : 0x00) | ((a & 0x200000) ? 0x08 : 0x00) | ((a & 0x000200) ? 0x04 : 0x00) | + ((b & 0x200000) ? 0x02 : 0x00) | ((b & 0x000200) ? 0x01 : 0x00); + + bytes[2] = ((c & 0x200000) ? 0x80 : 0x00) | ((c & 0x000200) ? 0x40 : 0x00) | ((a & 0x100000) ? 0x20 : 0x00) | + ((a & 0x000100) ? 0x10 : 0x00) | ((b & 0x100000) ? 0x08 : 0x00) | ((b & 0x000100) ? 0x04 : 0x00) | + ((c & 0x100000) ? 0x02 : 0x00) | ((c & 0x000100) ? 0x01 : 0x00); + + bytes[3] = ((a & 0x080000) ? 0x80 : 0x00) | ((a & 0x000080) ? 0x40 : 0x00) | ((b & 0x080000) ? 0x20 : 0x00) | + ((b & 0x000080) ? 0x10 : 0x00) | ((c & 0x080000) ? 0x08 : 0x00) | ((c & 0x000080) ? 0x04 : 0x00) | + ((a & 0x040000) ? 0x02 : 0x00) | ((a & 0x000040) ? 0x01 : 0x00); + + bytes[4] = ((b & 0x040000) ? 0x80 : 0x00) | ((b & 0x000040) ? 0x40 : 0x00) | ((c & 0x040000) ? 0x20 : 0x00) | + ((c & 0x000040) ? 0x10 : 0x00) | ((a & 0x020000) ? 0x08 : 0x00) | ((a & 0x000020) ? 0x04 : 0x00) | + ((b & 0x020000) ? 0x02 : 0x00) | ((b & 0x000020) ? 0x01 : 0x00); + + bytes[5] = ((c & 0x020000) ? 0x80 : 0x00) | ((c & 0x000020) ? 0x40 : 0x00) | ((a & 0x010000) ? 0x20 : 0x00) | + ((a & 0x000010) ? 0x10 : 0x00) | ((b & 0x010000) ? 0x08 : 0x00) | ((b & 0x000010) ? 0x04 : 0x00) | + ((c & 0x010000) ? 0x02 : 0x00) | ((c & 0x000010) ? 0x01 : 0x00); + + bytes[6] = ((a & 0x008000) ? 0x80 : 0x00) | ((a & 0x000008) ? 0x40 : 0x00) | ((b & 0x008000) ? 0x20 : 0x00) | + ((b & 0x000008) ? 0x10 : 0x00) | ((c & 0x008000) ? 0x08 : 0x00) | ((c & 0x000008) ? 0x04 : 0x00) | + ((a & 0x004000) ? 0x02 : 0x00) | ((a & 0x000004) ? 0x01 : 0x00); + + bytes[7] = ((b & 0x004000) ? 0x80 : 0x00) | ((b & 0x000004) ? 0x40 : 0x00) | ((c & 0x004000) ? 0x20 : 0x00) | + ((c & 0x000004) ? 0x10 : 0x00) | ((a & 0x002000) ? 0x08 : 0x00) | ((a & 0x000002) ? 0x04 : 0x00) | + ((b & 0x002000) ? 0x02 : 0x00) | ((b & 0x000002) ? 0x01 : 0x00); + + bytes[8] = ((c & 0x002000) ? 0x80 : 0x00) | ((c & 0x000002) ? 0x40 : 0x00) | ((a & 0x001000) ? 0x20 : 0x00) | + ((a & 0x000001) ? 0x10 : 0x00) | ((b & 0x001000) ? 0x08 : 0x00) | ((b & 0x000001) ? 0x04 : 0x00) | + ((c & 0x001000) ? 0x02 : 0x00) | ((c & 0x000001) ? 0x01 : 0x00); + + return errors; +} + +unsigned int CAMBEFEC::count(const unsigned char* bytes) const +{ + wxASSERT(bytes != NULL); + + unsigned int a = ((bytes[0] & 0x80) ? 0x800000 : 0x000000) | ((bytes[0] & 0x02) ? 0x400000 : 0x000000) | + ((bytes[1] & 0x08) ? 0x200000 : 0x000000) | ((bytes[2] & 0x20) ? 0x100000 : 0x000000) | + ((bytes[3] & 0x80) ? 0x080000 : 0x000000) | ((bytes[3] & 0x02) ? 0x040000 : 0x000000) | + ((bytes[4] & 0x08) ? 0x020000 : 0x000000) | ((bytes[5] & 0x20) ? 0x010000 : 0x000000) | + ((bytes[6] & 0x80) ? 0x008000 : 0x000000) | ((bytes[6] & 0x02) ? 0x004000 : 0x000000) | + ((bytes[7] & 0x08) ? 0x002000 : 0x000000) | ((bytes[8] & 0x20) ? 0x001000 : 0x000000) | + ((bytes[0] & 0x40) ? 0x000800 : 0x000000) | ((bytes[0] & 0x01) ? 0x000400 : 0x000000) | + ((bytes[1] & 0x04) ? 0x000200 : 0x000000) | ((bytes[2] & 0x10) ? 0x000100 : 0x000000) | + ((bytes[3] & 0x40) ? 0x000080 : 0x000000) | ((bytes[3] & 0x01) ? 0x000040 : 0x000000) | + ((bytes[4] & 0x04) ? 0x000020 : 0x000000) | ((bytes[5] & 0x10) ? 0x000010 : 0x000000) | + ((bytes[6] & 0x40) ? 0x000008 : 0x000000) | ((bytes[6] & 0x01) ? 0x000004 : 0x000000) | + ((bytes[7] & 0x04) ? 0x000002 : 0x000000) | ((bytes[8] & 0x10) ? 0x000001 : 0x000000); + + unsigned int b = ((bytes[0] & 0x20) ? 0x800000 : 0x000000) | ((bytes[1] & 0x80) ? 0x400000 : 0x000000) | + ((bytes[1] & 0x02) ? 0x200000 : 0x000000) | ((bytes[2] & 0x08) ? 0x100000 : 0x000000) | + ((bytes[3] & 0x20) ? 0x080000 : 0x000000) | ((bytes[4] & 0x80) ? 0x040000 : 0x000000) | + ((bytes[4] & 0x02) ? 0x020000 : 0x000000) | ((bytes[5] & 0x08) ? 0x010000 : 0x000000) | + ((bytes[6] & 0x20) ? 0x008000 : 0x000000) | ((bytes[7] & 0x80) ? 0x004000 : 0x000000) | + ((bytes[7] & 0x02) ? 0x002000 : 0x000000) | ((bytes[8] & 0x08) ? 0x001000 : 0x000000) | + ((bytes[0] & 0x10) ? 0x000800 : 0x000000) | ((bytes[1] & 0x40) ? 0x000400 : 0x000000) | + ((bytes[1] & 0x01) ? 0x000200 : 0x000000) | ((bytes[2] & 0x04) ? 0x000100 : 0x000000) | + ((bytes[3] & 0x10) ? 0x000080 : 0x000000) | ((bytes[4] & 0x40) ? 0x000040 : 0x000000) | + ((bytes[4] & 0x01) ? 0x000020 : 0x000000) | ((bytes[5] & 0x04) ? 0x000010 : 0x000000) | + ((bytes[6] & 0x10) ? 0x000008 : 0x000000) | ((bytes[7] & 0x40) ? 0x000004 : 0x000000) | + ((bytes[7] & 0x01) ? 0x000002 : 0x000000) | ((bytes[8] & 0x04) ? 0x000001 : 0x000000); + + unsigned int c = ((bytes[0] & 0x08) ? 0x800000 : 0x000000) | ((bytes[1] & 0x20) ? 0x400000 : 0x000000) | + ((bytes[2] & 0x80) ? 0x200000 : 0x000000) | ((bytes[2] & 0x02) ? 0x100000 : 0x000000) | + ((bytes[3] & 0x08) ? 0x080000 : 0x000000) | ((bytes[4] & 0x20) ? 0x040000 : 0x000000) | + ((bytes[5] & 0x80) ? 0x020000 : 0x000000) | ((bytes[5] & 0x02) ? 0x010000 : 0x000000) | + ((bytes[6] & 0x08) ? 0x008000 : 0x000000) | ((bytes[7] & 0x20) ? 0x004000 : 0x000000) | + ((bytes[8] & 0x80) ? 0x002000 : 0x000000) | ((bytes[8] & 0x02) ? 0x001000 : 0x000000) | + ((bytes[0] & 0x04) ? 0x000800 : 0x000000) | ((bytes[1] & 0x10) ? 0x000400 : 0x000000) | + ((bytes[2] & 0x40) ? 0x000200 : 0x000000) | ((bytes[2] & 0x01) ? 0x000100 : 0x000000) | + ((bytes[3] & 0x04) ? 0x000080 : 0x000000) | ((bytes[4] & 0x10) ? 0x000040 : 0x000000) | + ((bytes[5] & 0x40) ? 0x000020 : 0x000000) | ((bytes[5] & 0x01) ? 0x000010 : 0x000000) | + ((bytes[6] & 0x04) ? 0x000008 : 0x000000) | ((bytes[7] & 0x10) ? 0x000004 : 0x000000) | + ((bytes[8] & 0x40) ? 0x000002 : 0x000000) | ((bytes[8] & 0x01) ? 0x000001 : 0x000000); + + return count(a, b, c); +} + +unsigned int CAMBEFEC::regenerate(unsigned int& a, unsigned int& b, unsigned int&) const +{ + unsigned int old_a = a; + unsigned int old_b = b; + + unsigned int data = CGolay::decode24128(a); + + unsigned int new_a = CGolay::encode24128(data); + + // The PRNG + unsigned int p = PRNG_TABLE[data]; + + b ^= p; + + unsigned int datb = CGolay::decode24128(b); + + unsigned int new_b = CGolay::encode24128(datb); + + new_b ^= p; + + unsigned int errors = 0U; + unsigned int mask = 0x01; + for (unsigned int i = 0U; i < 24U; i++, mask <<= 1) { + if ((old_a & mask) != (new_a & mask)) + errors++; + if ((old_b & mask) != (new_b & mask)) + errors++; + } + + a = new_a; + b = new_b; + + return errors; +} + +unsigned int CAMBEFEC::count(unsigned int a, unsigned int b, unsigned int) const +{ + unsigned int old_a = a; + unsigned int old_b = b; + + unsigned int data = CGolay::decode24128(a); + + unsigned int new_a = CGolay::encode24128(data); + + // The PRNG + unsigned int p = PRNG_TABLE[data]; + + b ^= p; + + unsigned int datb = CGolay::decode24128(b); + + unsigned int new_b = CGolay::encode24128(datb); + + new_b ^= p; + + unsigned int errors = 0U; + unsigned int mask = 0x01; + for (unsigned int i = 0U; i < 24U; i++, mask <<= 1) { + if ((old_a & mask) != (new_a & mask)) + errors++; + if ((old_b & mask) != (new_b & mask)) + errors++; + } + + return errors; +} diff --git a/Common/AMBEFEC.h b/Common/AMBEFEC.h new file mode 100644 index 0000000..8f9cf8c --- /dev/null +++ b/Common/AMBEFEC.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2010,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef AMBEFEC_H +#define AMBEFEC_H + +#include "Golay.h" + +class CAMBEFEC { +public: + CAMBEFEC(); + ~CAMBEFEC(); + + unsigned int regenerate(unsigned char* bytes) const; + unsigned int count(const unsigned char* bytes) const; + +private: + unsigned int regenerate(unsigned int& a, unsigned int& b, unsigned int& c) const; + unsigned int count(unsigned int a, unsigned int b, unsigned int c) const; +}; + +#endif diff --git a/Common/AnnouncementCallback.h b/Common/AnnouncementCallback.h new file mode 100644 index 0000000..eef53f2 --- /dev/null +++ b/Common/AnnouncementCallback.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef AnnouncementCallback_H +#define AnnouncementCallback_H + +#include "HeaderData.h" + +class IAnnouncementCallback { +public: + virtual void transmitAnnouncementHeader(CHeaderData* header) = 0; + + virtual void transmitAnnouncementData(const unsigned char* data, unsigned int length, bool end) = 0; + +private: +}; + +#endif diff --git a/Common/AnnouncementUnit.cpp b/Common/AnnouncementUnit.cpp new file mode 100644 index 0000000..89430cf --- /dev/null +++ b/Common/AnnouncementUnit.cpp @@ -0,0 +1,149 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "AnnouncementUnit.h" + +#include +#include + +const wxString GLOBAL_FILE_NAME = wxT("Announce"); + +CAnnouncementUnit::CAnnouncementUnit(IAnnouncementCallback* handler, const wxString& callsign) : +m_handler(handler), +m_localFileName(), +m_reader(), +m_writer(), +m_time(), +m_out(0U), +m_sending(false) +{ + wxASSERT(handler != NULL); + + m_localFileName.Printf(wxT("Announce %s"), callsign.c_str()); +#if !defined(__WINDOWS__) + m_localFileName.Replace(wxT(" "), wxT("_")); +#endif + + m_writer.setDirectory(wxFileName::GetHomeDir()); +} + +CAnnouncementUnit::~CAnnouncementUnit() +{ +} + +bool CAnnouncementUnit::writeHeader(const CHeaderData& header) +{ + return m_writer.open(m_localFileName, header); +} + +bool CAnnouncementUnit::writeData(const unsigned char* data, unsigned int length, bool end) +{ + bool ret = m_writer.write(data, length); + if (!ret) + return false; + + if (end) + m_writer.close(); + + return true; +} + +void CAnnouncementUnit::deleteAnnouncement() +{ + wxFileName fileName(wxFileName::GetHomeDir(), m_localFileName, wxT("dvtool")); + + if (wxFile::Exists(fileName.GetFullPath())) + ::wxRemoveFile(fileName.GetFullPath()); +} + +void CAnnouncementUnit::startAnnouncement() +{ + wxFileName fileName1(wxFileName::GetHomeDir(), m_localFileName, wxT("dvtool")); + wxFileName fileName2(wxFileName::GetHomeDir(), GLOBAL_FILE_NAME, wxT("dvtool")); + + if (wxFile::Exists(fileName1.GetFullPath())) { + bool ret = m_reader.open(fileName1.GetFullPath()); + if (!ret) + return; + } else if (wxFile::Exists(fileName2.GetFullPath())) { + bool ret = m_reader.open(fileName2.GetFullPath()); + if (!ret) + return; + } else { + return; + } + + DVTFR_TYPE type = m_reader.read(); + if (type != DVTFR_HEADER) { + wxLogError(wxT("Invalid header element in the file - %d"), int(type)); + m_reader.close(); + return; + } + + CHeaderData* header = m_reader.readHeader(); + if (header == NULL) { + wxLogError(wxT("NULL header element in the file")); + m_reader.close(); + return; + } + + // Remove the repeater bit + header->setFlag1(header->getFlag1() & ~REPEATER_MASK); + + m_handler->transmitAnnouncementHeader(header); + + m_time.Start(); + + m_out = 0U; + m_sending = true; +} + +void CAnnouncementUnit::clock() +{ + if (!m_sending) + return; + + unsigned int needed = m_time.Time() / DSTAR_FRAME_TIME_MS; + + while (m_out < needed) { + DVTFR_TYPE type = m_reader.read(); + if (type != DVTFR_DATA) { + wxLogError(wxT("Invalid data element in the file - %d"), int(type)); + m_handler->transmitAnnouncementData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + m_reader.close(); + m_sending = false; + m_out = 0U; + return; + } + + unsigned char data[DV_FRAME_MAX_LENGTH_BYTES]; + bool end; + m_reader.readData(data, DV_FRAME_MAX_LENGTH_BYTES, end); + + m_handler->transmitAnnouncementData(data, DV_FRAME_LENGTH_BYTES, false); + m_out++; + + if (end) { + m_handler->transmitAnnouncementData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + m_reader.close(); + m_sending = false; + m_out = 0U; + return; + } + } +} diff --git a/Common/AnnouncementUnit.h b/Common/AnnouncementUnit.h new file mode 100644 index 0000000..0823214 --- /dev/null +++ b/Common/AnnouncementUnit.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef AnnouncementUnit_H +#define AnnouncementUnit_H + +#include "AnnouncementCallback.h" +#include "DVTOOLFileWriter.h" +#include "DVTOOLFileReader.h" +#include "DStarDefines.h" +#include "HeaderData.h" + +#include + +class CAnnouncementUnit { +public: + CAnnouncementUnit(IAnnouncementCallback* handler, const wxString& callsign); + ~CAnnouncementUnit(); + + bool writeHeader(const CHeaderData& header); + bool writeData(const unsigned char* data, unsigned int length, bool end); + + void deleteAnnouncement(); + + void startAnnouncement(); + + void clock(); + +private: + IAnnouncementCallback* m_handler; + wxString m_localFileName; + CDVTOOLFileReader m_reader; + CDVTOOLFileWriter m_writer; + wxStopWatch m_time; + unsigned int m_out; + bool m_sending; +}; + +#endif diff --git a/Common/ArduinoController.cpp b/Common/ArduinoController.cpp new file mode 100644 index 0000000..8b4ef04 --- /dev/null +++ b/Common/ArduinoController.cpp @@ -0,0 +1,102 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "ArduinoController.h" + + +const char IN_PORT1 = 0x01U; +const char IN_PORT2 = 0x02U; +const char IN_PORT3 = 0x04U; +const char IN_PORT4 = 0x08U; +const char IN_PORT5 = 0x10U; + +const char OUT_PORT1 = 0x01U; +const char OUT_PORT2 = 0x02U; +const char OUT_PORT3 = 0x04U; +const char OUT_PORT4 = 0x08U; +const char OUT_PORT5 = 0x10U; +const char OUT_PORT6 = 0x20U; +const char OUT_PORT7 = 0x40U; +const char OUT_PORT8 = 0x80U; + + +CArduinoController::CArduinoController(const wxString& port) : +m_serial(port, SERIAL_19200), +m_out(0x00U), +m_in(0x00U) +{ +} + +CArduinoController::~CArduinoController() +{ +} + +bool CArduinoController::open() +{ + bool ret = m_serial.open(); + if (!ret) + return false; + + unsigned char buffer; + while (m_serial.read(&buffer, 1U) == 1) + ; + + m_out = 0x00U; + m_in = 0x00U; + + return true; +} + +void CArduinoController::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + unsigned char buffer; + int ret = m_serial.read(&buffer, 1U); + if (ret == 1) { + m_in = buffer; + m_serial.write(&m_out, 1U); + } + + inp1 = (m_in & IN_PORT1) == IN_PORT1; + inp2 = (m_in & IN_PORT2) == IN_PORT2; + inp3 = (m_in & IN_PORT3) == IN_PORT3; + inp4 = (m_in & IN_PORT4) == IN_PORT4; + inp5 = (m_in & IN_PORT5) == IN_PORT5; +} + +void CArduinoController::setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8) +{ + m_out = 0x00U; + m_out |= outp1 ? OUT_PORT1 : 0x00U; + m_out |= outp2 ? OUT_PORT2 : 0x00U; + m_out |= outp3 ? OUT_PORT3 : 0x00U; + m_out |= outp4 ? OUT_PORT4 : 0x00U; + m_out |= outp5 ? OUT_PORT5 : 0x00U; + m_out |= outp6 ? OUT_PORT6 : 0x00U; + m_out |= outp7 ? OUT_PORT7 : 0x00U; + m_out |= outp8 ? OUT_PORT8 : 0x00U; + + unsigned char buffer; + int ret = m_serial.read(&buffer, 1U); + if (ret == 1) { + m_in = buffer; + m_serial.write(&m_out, 1U); + } +} + +void CArduinoController::close() +{ + unsigned char buffer = 0x00U; + m_serial.write(&buffer, 1U); + + m_serial.close(); +} diff --git a/Common/ArduinoController.h b/Common/ArduinoController.h new file mode 100644 index 0000000..bbf0889 --- /dev/null +++ b/Common/ArduinoController.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef ArduinoController_H +#define ArduinoController_H + +#include "SerialDataController.h" +#include "HardwareController.h" + +#include + + +class CArduinoController : public IHardwareController { +public: + CArduinoController(const wxString& port); + virtual ~CArduinoController(); + + virtual bool open(); + + virtual void getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5); + + virtual void setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8); + + virtual void close(); + +private: + CSerialDataController m_serial; + unsigned char m_out; + unsigned char m_in; +}; + +#endif diff --git a/Common/AudioCallback.h b/Common/AudioCallback.h new file mode 100644 index 0000000..fb37a1d --- /dev/null +++ b/Common/AudioCallback.h @@ -0,0 +1,27 @@ +/* + * Copyright (C) 2009,2015 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef AudioCallback_H +#define AudioCallback_H + +#include + +class IAudioCallback { +public: + virtual void readCallback(const wxFloat32* input, unsigned int nSamples, int id) = 0; + virtual void writeCallback(wxFloat32* output, unsigned int& nSamples, int id) = 0; + +private: +}; + +#endif diff --git a/Common/BeaconCallback.h b/Common/BeaconCallback.h new file mode 100644 index 0000000..2ba6aa8 --- /dev/null +++ b/Common/BeaconCallback.h @@ -0,0 +1,26 @@ +/* + * Copyright (C) 2012 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef BeaconCallback_H +#define BeaconCallback_H + +class IBeaconCallback { +public: + virtual void transmitBeaconHeader() = 0; + + virtual void transmitBeaconData(const unsigned char* data, unsigned int length, bool end) = 0; + +private: +}; + +#endif diff --git a/Common/BeaconUnit.cpp b/Common/BeaconUnit.cpp new file mode 100644 index 0000000..3b591ca --- /dev/null +++ b/Common/BeaconUnit.cpp @@ -0,0 +1,394 @@ +/* + * Copyright (C) 2011,2012 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "BeaconUnit.h" + +#include +#include +#include +#include + +const unsigned int MAX_FRAMES = 60U * DSTAR_FRAMES_PER_SEC; + +const unsigned int SILENCE_LENGTH = 10U; + +CBeaconUnit::CBeaconUnit(IBeaconCallback* handler, const wxString& callsign, const wxString& text, bool voice, TEXT_LANG language) : +m_ambe(NULL), +m_ambeLength(0U), +m_data(NULL), +m_dataLength(0U), +m_index(), +m_language(language), +m_handler(handler), +m_callsign(callsign), +m_encoder(), +m_in(0U), +m_out(0U), +m_seqNo(0U), +m_time(), +m_sending(false) +{ + wxASSERT(handler != NULL); + + wxString slowData = text; + slowData.resize(20U, wxT(' ')); + m_encoder.setTextData(slowData); + + m_data = new unsigned char[MAX_FRAMES * VOICE_FRAME_LENGTH_BYTES]; + ::memset(m_data, 0x00U, MAX_FRAMES * VOICE_FRAME_LENGTH_BYTES); + + if (!voice) + return; + + wxString ambeFileName; + wxString indxFileName; + + switch (m_language) { + case TL_DEUTSCH: + ambeFileName = wxT("de_DE.ambe"); + indxFileName = wxT("de_DE.indx"); + break; + case TL_DANSK: + ambeFileName = wxT("dk_DK.ambe"); + indxFileName = wxT("dk_DK.indx"); + break; + case TL_ITALIANO: + ambeFileName = wxT("it_IT.ambe"); + indxFileName = wxT("it_IT.indx"); + break; + case TL_FRANCAIS: + ambeFileName = wxT("fr_FR.ambe"); + indxFileName = wxT("fr_FR.indx"); + break; + case TL_ESPANOL: + ambeFileName = wxT("es_ES.ambe"); + indxFileName = wxT("es_ES.indx"); + break; + case TL_SVENSKA: + ambeFileName = wxT("se_SE.ambe"); + indxFileName = wxT("se_SE.indx"); + break; + case TL_POLSKI: + ambeFileName = wxT("pl_PL.ambe"); + indxFileName = wxT("pl_PL.indx"); + break; + case TL_ENGLISH_US: + ambeFileName = wxT("en_US.ambe"); + indxFileName = wxT("en_US.indx"); + break; + case TL_NORSK: + ambeFileName = wxT("no_NO.ambe"); + indxFileName = wxT("no_NO.indx"); + break; +// case TL_NEDERLANDS_NL: +// ambeFileName = wxT("nl_NL.ambe"); +// indxFileName = wxT("nl_NL.indx"); +// break; +// case TL_NEDERLANDS_BE: +// ambeFileName = wxT("nl_BE.ambe"); +// indxFileName = wxT("nl_BE.indx"); +// break; + default: + ambeFileName = wxT("en_GB.ambe"); + indxFileName = wxT("en_GB.indx"); + break; + } + + bool ret = readAMBE(ambeFileName); + if (!ret) + return; + + readIndex(indxFileName); +} + +CBeaconUnit::~CBeaconUnit() +{ + for (CIndexList_t::iterator it = m_index.begin(); it != m_index.end(); ++it) + delete it->second; + + delete[] m_ambe; + delete[] m_data; +} + +void CBeaconUnit::sendBeacon() +{ + m_handler->transmitBeaconHeader(); + + m_sending = true; + + m_time.Start(); + + m_in = 0U; + m_out = 0U; + m_seqNo = 0U; + m_dataLength = 0U; + + if (m_ambe == NULL) { + for (unsigned int i = 0U; i < 21U; i++) { + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + + if (i == 0U) { + m_encoder.sync(); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + } else { + unsigned char text[DATA_FRAME_LENGTH_BYTES]; + m_encoder.getTextData(text); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, text, DATA_FRAME_LENGTH_BYTES); + } + + ::memcpy(m_data + m_dataLength, buffer, DV_FRAME_LENGTH_BYTES); + m_dataLength += DV_FRAME_LENGTH_BYTES; + m_in++; + } + } else { + lookup(wxT(" ")); + lookup(wxT(" ")); + lookup(wxT(" ")); + lookup(wxT(" ")); + + spellCallsign(m_callsign); + + lookup(wxT(" ")); + lookup(wxT(" ")); + lookup(wxT(" ")); + lookup(wxT(" ")); + } +} + +void CBeaconUnit::clock() +{ + if (!m_sending) + return; + + unsigned int needed = m_time.Time() / DSTAR_FRAME_TIME_MS; + + while (m_out < needed) { + m_handler->transmitBeaconData(m_data + m_out * DV_FRAME_LENGTH_BYTES, DV_FRAME_LENGTH_BYTES, false); + m_out++; + + if (m_in == m_out) { + m_handler->transmitBeaconData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + m_sending = false; + m_in = 0U; + m_out = 0U; + return; + } + } +} + +bool CBeaconUnit::lookup(const wxString &id) +{ + CIndexRecord* info = m_index[id]; + if (info == NULL) { + // wxLogError(wxT("Cannot find the AMBE index for *%s*"), id.c_str()); + return false; + } + + unsigned int start = info->getStart(); + unsigned int length = info->getLength(); + + for (unsigned int i = 0U; i < length; i++) { + unsigned char* dataIn = m_ambe + (start + i) * VOICE_FRAME_LENGTH_BYTES; + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + ::memcpy(buffer + 0U, dataIn, VOICE_FRAME_LENGTH_BYTES); + + // Insert sync bytes when the sequence number is zero, slow data otherwise + if (m_seqNo == 0U) { + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + m_encoder.sync(); + } else { + m_encoder.getTextData(buffer + VOICE_FRAME_LENGTH_BYTES); + } + + ::memcpy(m_data + m_dataLength, buffer, DV_FRAME_LENGTH_BYTES); + m_dataLength += DV_FRAME_LENGTH_BYTES; + m_in++; + + m_seqNo++; + if (m_seqNo == 21U) + m_seqNo = 0U; + } + + return true; +} + +void CBeaconUnit::spellCallsign(const wxString &callsign) +{ + unsigned int length = callsign.Len(); + + for (unsigned int i = 0U; i < (length - 1U); i++) { + wxString c = callsign.Mid(i, 1U); + + if (!c.IsSameAs(wxT(" "))) + lookup(c); + } + + wxChar c = callsign.GetChar(length - 1U); + + switch (c) { + case wxT('A'): + lookup(wxT("alpha")); + break; + case wxT('B'): + lookup(wxT("bravo")); + break; + case wxT('C'): + lookup(wxT("charlie")); + break; + case wxT('D'): + lookup(wxT("delta")); + break; + default: + lookup(wxString(c)); + break; + } +} + +bool CBeaconUnit::readAMBE(const wxString& name) +{ + wxFileName fileName(wxFileName::GetHomeDir(), name); + + if (!fileName.IsFileReadable()) { + wxLogMessage(wxT("File %s not readable"), fileName.GetFullPath().c_str()); +#if defined(__WINDOWS__) + fileName.Assign(::wxGetCwd(), name); +#else + fileName.Assign(wxT(DATA_DIR), name); +#endif + if (!fileName.IsFileReadable()) { + wxLogMessage(wxT("File %s not readable"), fileName.GetFullPath().c_str()); + return false; + } + } + + wxFFile file; + + bool ret = file.Open(fileName.GetFullPath().c_str(), wxT("rb")); + if (!ret) { + wxLogMessage(wxT("Cannot open %s for reading"), fileName.GetFullPath().c_str()); + return false; + } + + wxLogMessage(wxT("Reading %s"), fileName.GetFullPath().c_str()); + + unsigned char buffer[VOICE_FRAME_LENGTH_BYTES]; + + size_t n = file.Read(buffer, 4U); + if (n != 4U) { + wxLogMessage(wxT("Unable to read the header from %s"), fileName.GetFullPath().c_str()); + file.Close(); + return false; + } + + if (::memcmp(buffer, "AMBE", 4U) != 0) { + wxLogMessage(wxT("Invalid header from %s"), fileName.GetFullPath().c_str()); + file.Close(); + return false; + } + + // Length of the file minus the header + unsigned int length = file.Length() - 4U; + + // Hold the file data plus silence at the end + m_ambe = new unsigned char[length + SILENCE_LENGTH * VOICE_FRAME_LENGTH_BYTES]; + m_ambeLength = length / VOICE_FRAME_LENGTH_BYTES; + + // Add silence to the beginning of the buffer + unsigned char* p = m_ambe; + for (unsigned int i = 0U; i < SILENCE_LENGTH; i++, p += VOICE_FRAME_LENGTH_BYTES) + ::memcpy(p, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + + n = file.Read(p, length); + if (n != length) { + wxLogMessage(wxT("Unable to read the AMBE data from %s"), fileName.GetFullPath().c_str()); + file.Close(); + delete[] m_ambe; + m_ambe = NULL; + return false; + } + + file.Close(); + + return true; +} + +bool CBeaconUnit::readIndex(const wxString& name) +{ + wxFileName fileName(wxFileName::GetHomeDir(), name); + + if (!fileName.IsFileReadable()) { + wxLogMessage(wxT("File %s not readable"), fileName.GetFullPath().c_str()); +#if defined(__WINDOWS__) + fileName.Assign(::wxGetCwd(), name); +#else + fileName.Assign(wxT(DATA_DIR), name); +#endif + if (!fileName.IsFileReadable()) { + wxLogMessage(wxT("File %s not readable"), fileName.GetFullPath().c_str()); + return false; + } + } + + wxTextFile file; + + bool ret = file.Open(fileName.GetFullPath()); + if (!ret) { + wxLogMessage(wxT("Cannot open %s for reading"), fileName.GetFullPath().c_str()); + return false; + } + + // Add a silence entry at the beginning + m_index[wxT(" ")] = new CIndexRecord(wxT(" "), 0U, SILENCE_LENGTH); + + wxLogMessage(wxT("Reading %s"), fileName.GetFullPath().c_str()); + + unsigned int nLines = file.GetLineCount(); + + for (unsigned int i = 0; i < nLines; i++) { + wxString line = file.GetLine(i); + + if (line.length() > 0 && line.GetChar(0) != wxT('#')) { + wxStringTokenizer t(line, wxT(" \t\r\n"), wxTOKEN_STRTOK); + wxString name = t.GetNextToken(); + wxString startTxt = t.GetNextToken(); + wxString lengthTxt = t.GetNextToken(); + + if (!name.IsEmpty() && !startTxt.IsEmpty() && !lengthTxt.IsEmpty()) { + unsigned long start; + startTxt.ToULong(&start); + + unsigned long length; + lengthTxt.ToULong(&length); + + if (start >= m_ambeLength || (start + length) >= m_ambeLength) + wxLogError(wxT("The start or end for *%s* is out of range, start: %lu, end: %lu"), name.c_str(), start, start + length); + else + m_index[name] = new CIndexRecord(name, start + SILENCE_LENGTH, length); + } + } + } + + file.Close(); + + return true; +} diff --git a/Common/BeaconUnit.h b/Common/BeaconUnit.h new file mode 100644 index 0000000..84f3cd5 --- /dev/null +++ b/Common/BeaconUnit.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2011,2012 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef BeaconUnit_H +#define BeaconUnit_H + +#include "SlowDataEncoder.h" +#include "BeaconCallback.h" +#include "DStarDefines.h" + +#include + +class CIndexRecord { +public: + CIndexRecord(const wxString& name, unsigned int start, unsigned int length) : + m_name(name), + m_start(start), + m_length(length) + { + } + + wxString getName() const + { + return m_name; + } + + unsigned int getStart() const + { + return m_start; + } + + unsigned int getLength() const + { + return m_length; + } + +private: + wxString m_name; + unsigned int m_start; + unsigned int m_length; +}; + +WX_DECLARE_STRING_HASH_MAP(CIndexRecord*, CIndexList_t); + +class CBeaconUnit { +public: + CBeaconUnit(IBeaconCallback* handler, const wxString& callsign, const wxString& text, bool voice, TEXT_LANG language); + ~CBeaconUnit(); + + void sendBeacon(); + + void clock(); + +private: + unsigned char* m_ambe; + unsigned int m_ambeLength; + unsigned char* m_data; + unsigned int m_dataLength; + CIndexList_t m_index; + TEXT_LANG m_language; + IBeaconCallback* m_handler; + wxString m_callsign; + CSlowDataEncoder m_encoder; + unsigned int m_in; + unsigned int m_out; + unsigned int m_seqNo; + wxStopWatch m_time; + bool m_sending; + + bool lookup(const wxString& id); + void spellCallsign(const wxString& callsign); + + bool readAMBE(const wxString& name); + bool readIndex(const wxString& name); +}; + +#endif diff --git a/Common/CCITTChecksum.cpp b/Common/CCITTChecksum.cpp new file mode 100644 index 0000000..62baf10 --- /dev/null +++ b/Common/CCITTChecksum.cpp @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2009 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "CCITTChecksum.h" + +#include "Utils.h" + +static const unsigned short ccittTab[] = { + 0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50A5, 0x60C6, 0x70E7, + 0x8108, 0x9129, 0xA14A, 0xB16B, 0xC18C, 0xD1AD, 0xE1CE, 0xF1EF, + 0x1231, 0x0210, 0x3273, 0x2252, 0x52B5, 0x4294, 0x72F7, 0x62D6, + 0x9339, 0x8318, 0xB37B, 0xA35A, 0xD3BD, 0xC39C, 0xF3FF, 0xE3DE, + 0x2462, 0x3443, 0x0420, 0x1401, 0x64E6, 0x74C7, 0x44A4, 0x5485, + 0xA56A, 0xB54B, 0x8528, 0x9509, 0xE5EE, 0xF5CF, 0xC5AC, 0xD58D, + 0x3653, 0x2672, 0x1611, 0x0630, 0x76D7, 0x66F6, 0x5695, 0x46B4, + 0xB75B, 0xA77A, 0x9719, 0x8738, 0xF7DF, 0xE7FE, 0xD79D, 0xC7BC, + 0x48C4, 0x58E5, 0x6886, 0x78A7, 0x0840, 0x1861, 0x2802, 0x3823, + 0xC9CC, 0xD9ED, 0xE98E, 0xF9AF, 0x8948, 0x9969, 0xA90A, 0xB92B, + 0x5AF5, 0x4AD4, 0x7AB7, 0x6A96, 0x1A71, 0x0A50, 0x3A33, 0x2A12, + 0xDBFD, 0xCBDC, 0xFBBF, 0xEB9E, 0x9B79, 0x8B58, 0xBB3B, 0xAB1A, + 0x6CA6, 0x7C87, 0x4CE4, 0x5CC5, 0x2C22, 0x3C03, 0x0C60, 0x1C41, + 0xEDAE, 0xFD8F, 0xCDEC, 0xDDCD, 0xAD2A, 0xBD0B, 0x8D68, 0x9D49, + 0x7E97, 0x6EB6, 0x5ED5, 0x4EF4, 0x3E13, 0x2E32, 0x1E51, 0x0E70, + 0xFF9F, 0xEFBE, 0xDFDD, 0xCFFC, 0xBF1B, 0xAF3A, 0x9F59, 0x8F78, + 0x9188, 0x81A9, 0xB1CA, 0xA1EB, 0xD10C, 0xC12D, 0xF14E, 0xE16F, + 0x1080, 0x00A1, 0x30C2, 0x20E3, 0x5004, 0x4025, 0x7046, 0x6067, + 0x83B9, 0x9398, 0xA3FB, 0xB3DA, 0xC33D, 0xD31C, 0xE37F, 0xF35E, + 0x02B1, 0x1290, 0x22F3, 0x32D2, 0x4235, 0x5214, 0x6277, 0x7256, + 0xB5EA, 0xA5CB, 0x95A8, 0x8589, 0xF56E, 0xE54F, 0xD52C, 0xC50D, + 0x34E2, 0x24C3, 0x14A0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405, + 0xA7DB, 0xB7FA, 0x8799, 0x97B8, 0xE75F, 0xF77E, 0xC71D, 0xD73C, + 0x26D3, 0x36F2, 0x0691, 0x16B0, 0x6657, 0x7676, 0x4615, 0x5634, + 0xD94C, 0xC96D, 0xF90E, 0xE92F, 0x99C8, 0x89E9, 0xB98A, 0xA9AB, + 0x5844, 0x4865, 0x7806, 0x6827, 0x18C0, 0x08E1, 0x3882, 0x28A3, + 0xCB7D, 0xDB5C, 0xEB3F, 0xFB1E, 0x8BF9, 0x9BD8, 0xABBB, 0xBB9A, + 0x4A75, 0x5A54, 0x6A37, 0x7A16, 0x0AF1, 0x1AD0, 0x2AB3, 0x3A92, + 0xFD2E, 0xED0F, 0xDD6C, 0xCD4D, 0xBDAA, 0xAD8B, 0x9DE8, 0x8DC9, + 0x7C26, 0x6C07, 0x5C64, 0x4C45, 0x3CA2, 0x2C83, 0x1CE0, 0x0CC1, + 0xEF1F, 0xFF3E, 0xCF5D, 0xDF7C, 0xAF9B, 0xBFBA, 0x8FD9, 0x9FF8, + 0x6E17, 0x7E36, 0x4E55, 0x5E74, 0x2E93, 0x3EB2, 0x0ED1, 0x1EF0}; + +CCCITTChecksum::CCCITTChecksum() : +m_crc16(0x0000U) +{ +} + +CCCITTChecksum::~CCCITTChecksum() +{ +} + +void CCCITTChecksum::update(const unsigned char* data, unsigned int length) +{ + wxASSERT(data != NULL); + + for (unsigned int i = 0U; i < length; i++) + m_crc16 = (wxUint16(m_crc8[0U]) << 8) ^ ccittTab[m_crc8[1U] ^ data[i]]; +} + +void CCCITTChecksum::result(unsigned char* data) +{ + wxASSERT(data != NULL); + + data[0U] = m_crc8[1U]; + data[1U] = m_crc8[0U]; +} + +bool CCCITTChecksum::check(const unsigned char* data) +{ + wxASSERT(data != NULL); + + unsigned char sum[2U]; + result(sum); + + return sum[0U] == data[0U] && sum[1U] == data[1U]; +} + +void CCCITTChecksum::reset() +{ + m_crc16 = 0x0000U; +} diff --git a/Common/CCITTChecksum.h b/Common/CCITTChecksum.h new file mode 100644 index 0000000..3e202cd --- /dev/null +++ b/Common/CCITTChecksum.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009,2011 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef CCITTChecksum_H +#define CCITTChecksum_H + +#include + +class CCCITTChecksum { +public: + CCCITTChecksum(); + ~CCCITTChecksum(); + + void update(const unsigned char* data, unsigned int length); + + void result(unsigned char* data); + + bool check(const unsigned char* data); + + void reset(); + +private: + union { + wxUint16 m_crc16; + wxUint8 m_crc8[2U]; + }; +}; + +#endif diff --git a/Common/CCITTChecksumReverse.cpp b/Common/CCITTChecksumReverse.cpp new file mode 100644 index 0000000..076d164 --- /dev/null +++ b/Common/CCITTChecksumReverse.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009,2011,2014 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "CCITTChecksumReverse.h" + +#include "Utils.h" + +static const unsigned short ccittTab[] = { + 0x0000,0x1189,0x2312,0x329b,0x4624,0x57ad,0x6536,0x74bf, + 0x8c48,0x9dc1,0xaf5a,0xbed3,0xca6c,0xdbe5,0xe97e,0xf8f7, + 0x1081,0x0108,0x3393,0x221a,0x56a5,0x472c,0x75b7,0x643e, + 0x9cc9,0x8d40,0xbfdb,0xae52,0xdaed,0xcb64,0xf9ff,0xe876, + 0x2102,0x308b,0x0210,0x1399,0x6726,0x76af,0x4434,0x55bd, + 0xad4a,0xbcc3,0x8e58,0x9fd1,0xeb6e,0xfae7,0xc87c,0xd9f5, + 0x3183,0x200a,0x1291,0x0318,0x77a7,0x662e,0x54b5,0x453c, + 0xbdcb,0xac42,0x9ed9,0x8f50,0xfbef,0xea66,0xd8fd,0xc974, + 0x4204,0x538d,0x6116,0x709f,0x0420,0x15a9,0x2732,0x36bb, + 0xce4c,0xdfc5,0xed5e,0xfcd7,0x8868,0x99e1,0xab7a,0xbaf3, + 0x5285,0x430c,0x7197,0x601e,0x14a1,0x0528,0x37b3,0x263a, + 0xdecd,0xcf44,0xfddf,0xec56,0x98e9,0x8960,0xbbfb,0xaa72, + 0x6306,0x728f,0x4014,0x519d,0x2522,0x34ab,0x0630,0x17b9, + 0xef4e,0xfec7,0xcc5c,0xddd5,0xa96a,0xb8e3,0x8a78,0x9bf1, + 0x7387,0x620e,0x5095,0x411c,0x35a3,0x242a,0x16b1,0x0738, + 0xffcf,0xee46,0xdcdd,0xcd54,0xb9eb,0xa862,0x9af9,0x8b70, + 0x8408,0x9581,0xa71a,0xb693,0xc22c,0xd3a5,0xe13e,0xf0b7, + 0x0840,0x19c9,0x2b52,0x3adb,0x4e64,0x5fed,0x6d76,0x7cff, + 0x9489,0x8500,0xb79b,0xa612,0xd2ad,0xc324,0xf1bf,0xe036, + 0x18c1,0x0948,0x3bd3,0x2a5a,0x5ee5,0x4f6c,0x7df7,0x6c7e, + 0xa50a,0xb483,0x8618,0x9791,0xe32e,0xf2a7,0xc03c,0xd1b5, + 0x2942,0x38cb,0x0a50,0x1bd9,0x6f66,0x7eef,0x4c74,0x5dfd, + 0xb58b,0xa402,0x9699,0x8710,0xf3af,0xe226,0xd0bd,0xc134, + 0x39c3,0x284a,0x1ad1,0x0b58,0x7fe7,0x6e6e,0x5cf5,0x4d7c, + 0xc60c,0xd785,0xe51e,0xf497,0x8028,0x91a1,0xa33a,0xb2b3, + 0x4a44,0x5bcd,0x6956,0x78df,0x0c60,0x1de9,0x2f72,0x3efb, + 0xd68d,0xc704,0xf59f,0xe416,0x90a9,0x8120,0xb3bb,0xa232, + 0x5ac5,0x4b4c,0x79d7,0x685e,0x1ce1,0x0d68,0x3ff3,0x2e7a, + 0xe70e,0xf687,0xc41c,0xd595,0xa12a,0xb0a3,0x8238,0x93b1, + 0x6b46,0x7acf,0x4854,0x59dd,0x2d62,0x3ceb,0x0e70,0x1ff9, + 0xf78f,0xe606,0xd49d,0xc514,0xb1ab,0xa022,0x92b9,0x8330, + 0x7bc7,0x6a4e,0x58d5,0x495c,0x3de3,0x2c6a,0x1ef1,0x0f78}; + +CCCITTChecksumReverse::CCCITTChecksumReverse() : +m_crc16(0xFFFFU) +{ +} + +CCCITTChecksumReverse::~CCCITTChecksumReverse() +{ +} + +void CCCITTChecksumReverse::update(const unsigned char* data, unsigned int length) +{ + wxASSERT(data != NULL); + + for (unsigned int i = 0U; i < length; i++) + m_crc16 = wxUint16(m_crc8[1U]) ^ ccittTab[m_crc8[0U] ^ data[i]]; +} + +void CCCITTChecksumReverse::result(unsigned char* data) +{ + wxASSERT(data != NULL); + + m_crc16 = ~m_crc16; + + data[0U] = m_crc8[0U]; + data[1U] = m_crc8[1U]; +} + +bool CCCITTChecksumReverse::check(const unsigned char* data) +{ + wxASSERT(data != NULL); + + unsigned char sum[2U]; + result(sum); + + return sum[0U] == data[0U] && sum[1U] == data[1U]; +} + +void CCCITTChecksumReverse::reset() +{ + m_crc16 = 0xFFFFU; +} diff --git a/Common/CCITTChecksumReverse.h b/Common/CCITTChecksumReverse.h new file mode 100644 index 0000000..febba08 --- /dev/null +++ b/Common/CCITTChecksumReverse.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2009,2011,2014 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef CCITTChecksumReverse_H +#define CCITTChecksumReverse_H + +#include + +class CCCITTChecksumReverse { +public: + CCCITTChecksumReverse(); + ~CCCITTChecksumReverse(); + + void update(const unsigned char* data, unsigned int length); + + void result(unsigned char* data); + + bool check(const unsigned char* data); + + void reset(); + +private: + union { + wxUint16 m_crc16; + wxUint8 m_crc8[2U]; + }; +}; + +#endif diff --git a/Common/CallsignList.cpp b/Common/CallsignList.cpp new file mode 100644 index 0000000..f55a1f5 --- /dev/null +++ b/Common/CallsignList.cpp @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2011 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CallsignList.h" +#include "DStarDefines.h" + +#include + +CCallsignList::CCallsignList(const wxString& filename) : +m_filename(filename), +m_callsigns() +{ +} + + +CCallsignList::~CCallsignList() +{ +} + +bool CCallsignList::load() +{ + wxTextFile file; + + bool res = file.Open(m_filename); + if (!res) + return false; + + unsigned int lines = file.GetLineCount(); + if (lines == 0U) { + file.Close(); + return true; + } + + m_callsigns.Alloc(lines); + + wxString callsign = file.GetFirstLine(); + + while (!file.Eof()) { + callsign.MakeUpper(); + callsign.Append(wxT(" ")); + callsign.Truncate(LONG_CALLSIGN_LENGTH); + + m_callsigns.Add(callsign); + + callsign = file.GetNextLine(); + } + + file.Close(); + + return true; +} + +unsigned int CCallsignList::getCount() const +{ + return m_callsigns.GetCount(); +} + +bool CCallsignList::isInList(const wxString& callsign) const +{ + return m_callsigns.Index(callsign) != wxNOT_FOUND; +} diff --git a/Common/CallsignList.h b/Common/CallsignList.h new file mode 100644 index 0000000..2a8d06c --- /dev/null +++ b/Common/CallsignList.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef CallsignList_H +#define CallsignList_H + +#include + +class CCallsignList { +public: + CCallsignList(const wxString& filename); + ~CCallsignList(); + + bool load(); + + unsigned int getCount() const; + + bool isInList(const wxString& callsign) const; + +private: + wxString m_filename; + wxArrayString m_callsigns; +}; + +#endif diff --git a/Common/Common.vcxproj b/Common/Common.vcxproj new file mode 100644 index 0000000..628c50c --- /dev/null +++ b/Common/Common.vcxproj @@ -0,0 +1,264 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {3753EF20-2330-415E-B933-2560A474498B} + Common + Win32Proj + 10.0.17134.0 + + + + StaticLibrary + v141 + Unicode + true + + + StaticLibrary + v141 + Unicode + true + + + StaticLibrary + v141 + Unicode + + + StaticLibrary + v141 + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.24720.0 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_dll\mswud;$(SolutionDir)..\libusb-win32-bin-1.2.6.0\include + + + $(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_x64_dll\mswud;$(SolutionDir)..\libusb-win32-bin-1.2.6.0\include + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_dll\mswu;$(SolutionDir)..\libusb-win32-bin-1.2.6.0\include + + + $(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_x64_dll\mswu;$(SolutionDir)..\libusb-win32-bin-1.2.6.0\include + + + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;__WXDEBUG__;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_LIB;%(PreprocessorDefinitions) + false + true + EnableFastChecks + MultiThreadedDebugDLL + + Level4 + EditAndContinue + $(SolutionDir)..\WinUSB\include;$(SolutionDir)..\portaudio\include;$(WXWIN)\include\msvc;$(WXWIN)\include;%(AdditionalIncludeDirectories) + + + + + + + + + Disabled + WIN32;_DEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;__WXDEBUG__;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_LIB;%(PreprocessorDefinitions) + false + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + ProgramDatabase + $(SolutionDir)..\WinUSB\include;$(SolutionDir)..\portaudio\include;$(WXWIN)\include\msvc;$(WXWIN)\include;%(AdditionalIncludeDirectories) + + + + + + + + MaxSpeed + true + $(SolutionDir)..\WinUSB\include;$(SolutionDir)..\HID;$(SolutionDir)..\portaudio\include;$(WXWIN)\include\msvc;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + Level3 + ProgramDatabase + + + + + + + + + MaxSpeed + true + $(SolutionDir)..\WinUSB\include;$(SolutionDir)..\HID;$(SolutionDir)..\portaudio\include;$(WXWIN)\include\msvc;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Common/Common.vcxproj.filters b/Common/Common.vcxproj.filters new file mode 100644 index 0000000..df153d8 --- /dev/null +++ b/Common/Common.vcxproj.filters @@ -0,0 +1,326 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/Common/DStarDefines.h b/Common/DStarDefines.h new file mode 100644 index 0000000..a21f17d --- /dev/null +++ b/Common/DStarDefines.h @@ -0,0 +1,198 @@ +/* + * Copyright (C) 2009-2015 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef DStarDefines_H +#define DStarDefines_H + +#include + +const unsigned int DSTAR_GMSK_SYMBOL_RATE = 4800U; +const float DSTAR_GMSK_BT = 0.5F; + +const unsigned char DATA_SYNC_BYTES[] = {0x55, 0x2D, 0x16}; + +const unsigned char END_PATTERN_BYTES[] = {0x55, 0x55, 0x55, 0x55, 0xC8, 0x7A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const unsigned int END_PATTERN_LENGTH_BYTES = 6U; + +const unsigned char NULL_AMBE_DATA_BYTES[] = {0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8}; + +// Note that these are already scrambled, 0x66 0x66 0x66 otherwise +const unsigned char NULL_SLOW_DATA_BYTES[] = {0x16, 0x29, 0xF5}; + +const unsigned char NULL_FRAME_DATA_BYTES[] = {0x9E, 0x8D, 0x32, 0x88, 0x26, 0x1A, 0x3F, 0x61, 0xE8, 0x16, 0x29, 0xF5}; + +const unsigned int VOICE_FRAME_LENGTH_BYTES = 9U; + +const unsigned int DATA_FRAME_LENGTH_BYTES = 3U; + +const unsigned int DV_FRAME_LENGTH_BYTES = VOICE_FRAME_LENGTH_BYTES + DATA_FRAME_LENGTH_BYTES; + +// The length of the end frame, three bytes extra +const unsigned int DV_FRAME_MAX_LENGTH_BYTES = DV_FRAME_LENGTH_BYTES + 3U; + +const unsigned int FEC_SECTION_LENGTH_BYTES = 83U; + +const unsigned int RADIO_HEADER_LENGTH_BYTES = 41U; + +const unsigned int DATA_BLOCK_SIZE_BYTES = 21U * DV_FRAME_LENGTH_BYTES; + +const unsigned int DSTAR_RADIO_SAMPLE_RATE = 48000U; +const unsigned int DSTAR_RADIO_BLOCK_SIZE = 960U; +const unsigned int DSTAR_AUDIO_BLOCK_SIZE = 160U; + +const unsigned int LOCATOR_LENGTH = 6U; +const unsigned int LONG_CALLSIGN_LENGTH = 8U; +const unsigned int SHORT_CALLSIGN_LENGTH = 4U; + +const unsigned char SLOW_DATA_TYPE_MASK = 0xF0U; +const unsigned char SLOW_DATA_TYPE_GPSDATA = 0x30U; +const unsigned char SLOW_DATA_TYPE_TEXT = 0x40U; +const unsigned char SLOW_DATA_TYPE_HEADER = 0x50U; +const unsigned char SLOW_DATA_TYPE_SQUELCH = 0xC0U; +const unsigned char SLOW_DATA_LENGTH_MASK = 0x0FU; + +const unsigned int SLOW_DATA_TEXT_LENGTH = 20U; + +const unsigned char SCRAMBLER_BYTE1 = 0x70U; +const unsigned char SCRAMBLER_BYTE2 = 0x4FU; +const unsigned char SCRAMBLER_BYTE3 = 0x93U; + +const unsigned char DATA_MASK = 0x80U; +const unsigned char REPEATER_MASK = 0x40U; +const unsigned char INTERRUPTED_MASK = 0x20U; +const unsigned char CONTROL_SIGNAL_MASK = 0x10U; +const unsigned char URGENT_MASK = 0x08U; + +const unsigned char REPEATER_CONTROL_MASK = 0x07U; +const unsigned char REPEATER_CONTROL = 0x07U; +const unsigned char AUTO_REPLY = 0x06U; +const unsigned char RESEND_REQUESTED = 0x04U; +const unsigned char ACK_FLAG = 0x03U; +const unsigned char NO_RESPONSE = 0x02U; +const unsigned char RELAY_UNAVAILABLE = 0x01U; + +const unsigned int DSTAR_RADIO_BIT_LENGTH = DSTAR_RADIO_SAMPLE_RATE / DSTAR_GMSK_SYMBOL_RATE; + +const unsigned int DSTAR_FRAME_TIME_MS = 20U; +const unsigned int DSTAR_FRAMES_PER_SEC = 50U; + +const unsigned int DSTAR_TICKS_PER_SEC = DSTAR_RADIO_SAMPLE_RATE / DSTAR_RADIO_BLOCK_SIZE; + +const unsigned int DEXTRA_PORT = 30001U; +const unsigned int DCS_PORT = 30051U; + +const unsigned int GMSK_MODEM_DATA_LENGTH = 8U; + +const unsigned int RADIO_RUN_FRAME_COUNT = 5U; +const unsigned int LOCAL_RUN_FRAME_COUNT = 1U; +const unsigned int NETWORK_RUN_FRAME_COUNT = 25U; + +const unsigned int DSTAR_BLEEP_FREQ = 2000U; +const unsigned int DSTAR_BLEEP_LENGTH = 100U; +const float DSTAR_BLEEP_AMPL = 0.5F; + +const unsigned int NETWORK_TIMEOUT = 2U; + +const unsigned int SPLIT_RX_GUI_COUNT = 5U; +const unsigned int SPLIT_RX_COUNT = 25U; +const unsigned int SPLIT_TX_GUI_COUNT = 3U; +const unsigned int SPLIT_TX_COUNT = 5U; + +enum DSTAR_RPT_STATE { + DSRS_SHUTDOWN, + DSRS_LISTENING, + DSRS_VALID, + DSRS_VALID_WAIT, + DSRS_INVALID, + DSRS_INVALID_WAIT, + DSRS_TIMEOUT, + DSRS_TIMEOUT_WAIT, + DSRS_NETWORK +}; + +enum NETWORK_TYPE { + NETWORK_NONE, + NETWORK_HEADER, + NETWORK_DATA, + NETWORK_TEXT, + NETWORK_TEMPTEXT, + NETWORK_STATUS1, + NETWORK_STATUS2, + NETWORK_STATUS3, + NETWORK_STATUS4, + NETWORK_STATUS5, + NETWORK_REGISTER +}; + +enum DSTAR_MODE { + MODE_DUPLEX, + MODE_SIMPLEX, + MODE_GATEWAY, + MODE_TXONLY, + MODE_RXONLY, + MODE_TXANDRX +}; + +enum LINK_STATUS { + LS_NONE, + LS_PENDING_IRCDDB, + LS_LINKING_LOOPBACK, + LS_LINKING_DEXTRA, + LS_LINKING_DPLUS, + LS_LINKING_DCS, + LS_LINKING_CCS, + LS_LINKED_LOOPBACK, + LS_LINKED_DEXTRA, + LS_LINKED_DPLUS, + LS_LINKED_DCS, + LS_LINKED_CCS +}; + +enum ACK_TYPE { + AT_NONE, + AT_BER, + AT_STATUS +}; + +enum TEXT_LANG { + TL_ENGLISH_UK, + TL_DEUTSCH, + TL_DANSK, + TL_FRANCAIS, + TL_ITALIANO, + TL_POLSKI, + TL_ESPANOL, + TL_SVENSKA, + TL_NEDERLANDS, + TL_ENGLISH_US, + TL_NORSK +}; + +enum CONNECTION_TYPE { + CT_USB, + CT_NETWORK +}; + +enum USB_INTERFACE { + UI_LIBUSB, + UI_WINUSB +}; + +enum DVMEGA_VARIANT { + DVMV_MODEM, + DVMV_RADIO_2M, + DVMV_RADIO_70CM, + DVMV_RADIO_2M_70CM +}; + +#endif diff --git a/Common/DStarGMSKDemodulator.cpp b/Common/DStarGMSKDemodulator.cpp new file mode 100644 index 0000000..2b67897 --- /dev/null +++ b/Common/DStarGMSKDemodulator.cpp @@ -0,0 +1,120 @@ +/* + * Copyright (C) 2009,2010,2013,2014,2015 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "DStarGMSKDemodulator.h" + +// Generated by gaussfir(0.5, 4, 10) +const wxFloat32 COEFFS_TABLE[] = { + 0.000000000000003F, 0.000000000000065F, 0.000000000001037F, 0.000000000014448F, 0.000000000174579F, + 0.000000001829471F, 0.000000016627294F, 0.000000131062698F, 0.000000895979719F, 0.000005312253663F, 0.000027316243802F, + 0.000121821714020F, 0.000471183399421F, 0.001580581180127F, 0.004598383433830F, 0.011602594308899F, 0.025390226926262F, + 0.048188078330624F, 0.079318443411643F, 0.113232294527059F, 0.140193533802410F, 0.150538369557851F, 0.140193533802410F, + 0.113232294527059F, 0.079318443411643F, 0.048188078330623F, 0.025390226926262F, 0.011602594308899F, 0.004598383433830F, + 0.001580581180127F, 0.000471183399421F, 0.000121821714020F, 0.000027316243802F, 0.000005312253663F, 0.000000895979719F, + 0.000000131062698F, 0.000000016627294F, 0.000000001829471F, 0.000000000174579F, 0.000000000014448F, 0.000000000001037F, + 0.000000000000065F, 0.000000000000003F}; + +const unsigned int COEFFS_LENGTH = 43U; + +const unsigned int DCOFFSET_COUNT = 4800U; // 5 seconds + +const unsigned int PLLMAX = 0x10000U; +const unsigned int PLLINC = PLLMAX / DSTAR_RADIO_BIT_LENGTH; +const unsigned int INC_LOCK = PLLINC / 64U; +const unsigned int INC_UNLOCK = PLLINC / 32U; + +CDStarGMSKDemodulator::CDStarGMSKDemodulator() : +m_filter(COEFFS_TABLE, COEFFS_LENGTH), +m_invert(false), +m_pll(0U), +m_prev(false), +m_inc(INC_UNLOCK), +m_locked(false), +m_offset(0.0F), +m_accum(0.0F), +m_count(0U) +{ +} + +CDStarGMSKDemodulator::~CDStarGMSKDemodulator() +{ +} + +TRISTATE CDStarGMSKDemodulator::decode(wxFloat32 val) +{ + TRISTATE state = STATE_UNKNOWN; + + // Calculate the DC offset when we are locked + if (m_locked) { + m_accum += val; + m_count++; + + if (m_count >= DCOFFSET_COUNT) { + m_accum /= wxFloat32(DCOFFSET_COUNT); + + m_offset = m_offset * 0.9F + m_accum * 0.1F; + + m_accum = 0.0F; + m_count = 0U; + } + } + + wxFloat32 out = m_filter.process(val - m_offset); + + bool bit = out > 0.0F; + + if (bit != m_prev) { + if (m_pll < (PLLMAX / 2U)) + m_pll += m_inc; + else + m_pll -= m_inc; + } + + m_prev = bit; + + m_pll += PLLINC; + + if (m_pll >= PLLMAX) { + if (m_invert) + state = bit ? STATE_TRUE : STATE_FALSE; + else + state = bit ? STATE_FALSE : STATE_TRUE; + + m_pll -= PLLMAX; + } + + return state; +} + +void CDStarGMSKDemodulator::reset() +{ + m_pll = 0U; + m_prev = false; + m_inc = INC_UNLOCK; + m_locked = false; +} + +void CDStarGMSKDemodulator::setInvert(bool set) +{ + m_invert = set; +} + +void CDStarGMSKDemodulator::lock(bool on) +{ + // Debugging only XXX + wxLogMessage(wxT("Current DC offset: %f"), m_offset); + + m_locked = on; + + m_inc = on ? INC_LOCK : INC_UNLOCK; +} diff --git a/Common/DStarGMSKDemodulator.h b/Common/DStarGMSKDemodulator.h new file mode 100644 index 0000000..38ba850 --- /dev/null +++ b/Common/DStarGMSKDemodulator.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009,2010,2013,2014 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef DStarGMSKDemodulator_H +#define DStarGMSKDemodulator_H + +#include "DStarDefines.h" +#include "FIRFilter.h" +#include "Utils.h" + +class CDStarGMSKDemodulator { +public: + CDStarGMSKDemodulator(); + ~CDStarGMSKDemodulator(); + + TRISTATE decode(wxFloat32 val); + + void setInvert(bool set); + + void reset(); + + void lock(bool on); + +private: + CFIRFilter m_filter; + bool m_invert; + unsigned int m_pll; + bool m_prev; + unsigned int m_inc; + bool m_locked; + wxFloat32 m_offset; + wxFloat32 m_accum; + unsigned int m_count; +}; + +#endif diff --git a/Common/DStarGMSKModulator.cpp b/Common/DStarGMSKModulator.cpp new file mode 100644 index 0000000..37c6c0d --- /dev/null +++ b/Common/DStarGMSKModulator.cpp @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2009,2015 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "DStarGMSKModulator.h" + +#include "DStarDefines.h" + +// Generated by gaussfir(0.5, 4, 10) +const wxFloat32 COEFFS_TABLE[] = { + 0.000000000000003F, 0.000000000000065F, 0.000000000001037F, 0.000000000014448F, 0.000000000174579F, + 0.000000001829471F, 0.000000016627294F, 0.000000131062698F, 0.000000895979719F, 0.000005312253663F, 0.000027316243802F, + 0.000121821714020F, 0.000471183399421F, 0.001580581180127F, 0.004598383433830F, 0.011602594308899F, 0.025390226926262F, + 0.048188078330624F, 0.079318443411643F, 0.113232294527059F, 0.140193533802410F, 0.150538369557851F, 0.140193533802410F, + 0.113232294527059F, 0.079318443411643F, 0.048188078330623F, 0.025390226926262F, 0.011602594308899F, 0.004598383433830F, + 0.001580581180127F, 0.000471183399421F, 0.000121821714020F, 0.000027316243802F, 0.000005312253663F, 0.000000895979719F, + 0.000000131062698F, 0.000000016627294F, 0.000000001829471F, 0.000000000174579F, 0.000000000014448F, 0.000000000001037F, + 0.000000000000065F, 0.000000000000003F}; + +const unsigned int COEFFS_LENGTH = 43U; + +CDStarGMSKModulator::CDStarGMSKModulator() : +m_filter(COEFFS_TABLE, COEFFS_LENGTH), +m_invert(false) +{ +} + +CDStarGMSKModulator::~CDStarGMSKModulator() +{ +} + +unsigned int CDStarGMSKModulator::code(bool bit, wxFloat32* buffer, unsigned int length) +{ + wxASSERT(buffer != NULL); + wxASSERT(length == DSTAR_RADIO_BIT_LENGTH); + + if (m_invert) + bit = !bit; + + for (unsigned int i = 0U; i < DSTAR_RADIO_BIT_LENGTH; i++) { + if (bit) + buffer[i] = m_filter.process(-0.5F); + else + buffer[i] = m_filter.process(0.5F); + } + + return DSTAR_RADIO_BIT_LENGTH; +} + +void CDStarGMSKModulator::setInvert(bool set) +{ + m_invert = set; +} diff --git a/Common/DStarGMSKModulator.h b/Common/DStarGMSKModulator.h new file mode 100644 index 0000000..1a1b401 --- /dev/null +++ b/Common/DStarGMSKModulator.h @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2009,2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef DStarGMSKModulator_H +#define DStarGMSKModulator_H + +#include "FIRFilter.h" + +class CDStarGMSKModulator { +public: + CDStarGMSKModulator(); + ~CDStarGMSKModulator(); + + unsigned int code(bool bit, wxFloat32* buffer, unsigned int length); + + void setInvert(bool set); + +private: + CFIRFilter m_filter; + bool m_invert; +}; + +#endif diff --git a/Common/DStarRepeaterConfig.cpp b/Common/DStarRepeaterConfig.cpp new file mode 100644 index 0000000..2a1fb33 --- /dev/null +++ b/Common/DStarRepeaterConfig.cpp @@ -0,0 +1,1906 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include + +#include "DStarRepeaterConfig.h" + +const wxString KEY_CALLSIGN = wxT("callsign"); +const wxString KEY_GATEWAY = wxT("gateway"); +const wxString KEY_MODE = wxT("mode"); +const wxString KEY_ACK = wxT("ack"); +const wxString KEY_RESTRICTION = wxT("restriction"); +const wxString KEY_RPT1_VALIDATION = wxT("rpt1Validation"); +const wxString KEY_DTMF_BLANKING = wxT("dtmfBlanking"); +const wxString KEY_ERROR_REPLY = wxT("errorReply"); +const wxString KEY_GATEWAY_ADDRESS = wxT("gatewayAddress"); +const wxString KEY_GATEWAY_PORT = wxT("gatewayPort"); +const wxString KEY_LOCAL_ADDRESS = wxT("localAddress"); +const wxString KEY_LOCAL_PORT = wxT("localPort"); +const wxString KEY_NETWORK_NAME = wxT("networkName"); +const wxString KEY_MODEM_TYPE = wxT("modemType"); +const wxString KEY_TIMEOUT = wxT("timeout"); +const wxString KEY_ACK_TIME = wxT("ackTime"); +const wxString KEY_BEACON_TIME = wxT("beaconTime"); +const wxString KEY_BEACON_TEXT = wxT("beaconText"); +const wxString KEY_BEACON_VOICE = wxT("beaconVoice"); +const wxString KEY_LANGUAGE = wxT("language"); +const wxString KEY_ANNOUNCEMENT_ENABLED = wxT("announcementEnabled"); +const wxString KEY_ANNOUNCEMENT_TIME = wxT("announcementTime"); +const wxString KEY_ANNOUNCEMENT_RECORD_RPT1 = wxT("announcementRecordRPT1"); +const wxString KEY_ANNOUNCEMENT_RECORD_RPT2 = wxT("announcementRecordRPT2"); +const wxString KEY_ANNOUNCEMENT_DELETE_RPT1 = wxT("announcementDeleteRPT1"); +const wxString KEY_ANNOUNCEMENT_DELETE_RPT2 = wxT("announcementDeleteRPT2"); +const wxString KEY_CONTROL_ENABLED = wxT("controlEnabled"); +const wxString KEY_CONTROL_RPT1 = wxT("controlRPT1"); +const wxString KEY_CONTROL_RPT2 = wxT("controlRPT2"); +const wxString KEY_CONTROL_SHUTDOWN = wxT("controlShutdown"); +const wxString KEY_CONTROL_STARTUP = wxT("controlStartup"); +const wxString KEY_CONTROL_STATUS1 = wxT("controlStatus1"); +const wxString KEY_CONTROL_STATUS2 = wxT("controlStatus2"); +const wxString KEY_CONTROL_STATUS3 = wxT("controlStatus3"); +const wxString KEY_CONTROL_STATUS4 = wxT("controlStatus4"); +const wxString KEY_CONTROL_STATUS5 = wxT("controlStatus5"); +const wxString KEY_CONTROL_COMMAND1 = wxT("controlCommand1"); +const wxString KEY_CONTROL_COMMAND1_LINE = wxT("controlCommand1Line"); +const wxString KEY_CONTROL_COMMAND2 = wxT("controlCommand2"); +const wxString KEY_CONTROL_COMMAND2_LINE = wxT("controlCommand2Line"); +const wxString KEY_CONTROL_COMMAND3 = wxT("controlCommand3"); +const wxString KEY_CONTROL_COMMAND3_LINE = wxT("controlCommand3Line"); +const wxString KEY_CONTROL_COMMAND4 = wxT("controlCommand4"); +const wxString KEY_CONTROL_COMMAND4_LINE = wxT("controlCommand4Line"); +const wxString KEY_CONTROL_COMMAND5 = wxT("controlCommand5"); +const wxString KEY_CONTROL_COMMAND5_LINE = wxT("controlCommand5Line"); +const wxString KEY_CONTROL_COMMAND6 = wxT("controlCommand6"); +const wxString KEY_CONTROL_COMMAND6_LINE = wxT("controlCommand6Line"); +const wxString KEY_CONTROL_OUTPUT1 = wxT("controlOutput1"); +const wxString KEY_CONTROL_OUTPUT2 = wxT("controlOutput2"); +const wxString KEY_CONTROL_OUTPUT3 = wxT("controlOutput3"); +const wxString KEY_CONTROL_OUTPUT4 = wxT("controlOutput4"); +const wxString KEY_CONTROLLER_TYPE = wxT("controllerType"); +const wxString KEY_SERIAL_CONFIG = wxT("serialConfig"); +const wxString KEY_PTT_INVERT = wxT("pttInvert"); +const wxString KEY_ACTIVE_HANG_TIME = wxT("activeHangTime"); +const wxString KEY_OUTPUT1 = wxT("output1"); +const wxString KEY_OUTPUT2 = wxT("output2"); +const wxString KEY_OUTPUT3 = wxT("output3"); +const wxString KEY_OUTPUT4 = wxT("output4"); +const wxString KEY_LOGGING = wxT("logging"); +const wxString KEY_WINDOW_X = wxT("windowX"); +const wxString KEY_WINDOW_Y = wxT("windowY"); + +const wxString KEY_DVAP_PORT = wxT("dvapPort"); +const wxString KEY_DVAP_FREQUENCY = wxT("dvapFrequency"); +const wxString KEY_DVAP_POWER = wxT("dvapPower"); +const wxString KEY_DVAP_SQUELCH = wxT("dvapSquelch"); + +const wxString KEY_GMSK_INTERFACE = wxT("gmskInterfaceType"); +const wxString KEY_GMSK_ADDRESS = wxT("gmskAddress"); + +const wxString KEY_DVRPTR1_PORT = wxT("dvrptr1Port"); +const wxString KEY_DVRPTR1_RXINVERT = wxT("dvrptr1RXInvert"); +const wxString KEY_DVRPTR1_TXINVERT = wxT("dvrptr1TXInvert"); +const wxString KEY_DVRPTR1_CHANNEL = wxT("dvrptr1Channel"); +const wxString KEY_DVRPTR1_MODLEVEL = wxT("dvrptr1ModLevel"); +const wxString KEY_DVRPTR1_TXDELAY = wxT("dvrptr1TXDelay"); + +const wxString KEY_DVRPTR2_CONNECTION = wxT("dvrptr2Connection"); +const wxString KEY_DVRPTR2_USBPORT = wxT("dvrptr2USBPort"); +const wxString KEY_DVRPTR2_ADDRESS = wxT("dvrptr2Address"); +const wxString KEY_DVRPTR2_PORT = wxT("dvrptr2Port"); +const wxString KEY_DVRPTR2_TXINVERT = wxT("dvrptr2TXInvert"); +const wxString KEY_DVRPTR2_MODLEVEL = wxT("dvrptr2ModLevel"); +const wxString KEY_DVRPTR2_TXDELAY = wxT("dvrptr2TXDelay"); + +const wxString KEY_DVRPTR3_CONNECTION = wxT("dvrptr3Connection"); +const wxString KEY_DVRPTR3_USBPORT = wxT("dvrptr3USBPort"); +const wxString KEY_DVRPTR3_ADDRESS = wxT("dvrptr3Address"); +const wxString KEY_DVRPTR3_PORT = wxT("dvrptr3Port"); +const wxString KEY_DVRPTR3_TXINVERT = wxT("dvrptr3TXInvert"); +const wxString KEY_DVRPTR3_MODLEVEL = wxT("dvrptr3ModLevel"); +const wxString KEY_DVRPTR3_TXDELAY = wxT("dvrptr3TXDelay"); + +const wxString KEY_DVMEGA_PORT = wxT("dvmegaPort"); +const wxString KEY_DVMEGA_VARIANT = wxT("dvmegaVariant"); +const wxString KEY_DVMEGA_RXINVERT = wxT("dvmegaRXInvert"); +const wxString KEY_DVMEGA_TXINVERT = wxT("dvmegaTXInvert"); +const wxString KEY_DVMEGA_TXDELAY = wxT("dvmegaTXDelay"); +const wxString KEY_DVMEGA_RXFREQUENCY = wxT("dvmegaRXFrequency"); +const wxString KEY_DVMEGA_TXFREQUENCY = wxT("dvmegaTXFrequency"); +const wxString KEY_DVMEGA_POWER = wxT("dvmegaPower"); + +const wxString KEY_MMDVM_PORT = wxT("mmdvmPort"); +const wxString KEY_MMDVM_RXINVERT = wxT("mmdvmRXInvert"); +const wxString KEY_MMDVM_TXINVERT = wxT("mmdvmTXInvert"); +const wxString KEY_MMDVM_PTTINVERT = wxT("mmdvmPTTInvert"); +const wxString KEY_MMDVM_TXDELAY = wxT("mmdvmTXDelay"); +const wxString KEY_MMDVM_RXLEVEL = wxT("mmdvmRXLevel"); +const wxString KEY_MMDVM_TXLEVEL = wxT("mmdvmTXLevel"); + +const wxString KEY_SOUNDCARD_RXDEVICE = wxT("soundCardRXDevice"); +const wxString KEY_SOUNDCARD_TXDEVICE = wxT("soundCardTXDevice"); +const wxString KEY_SOUNDCARD_RXINVERT = wxT("soundCardRXInvert"); +const wxString KEY_SOUNDCARD_TXINVERT = wxT("soundCardTXInvert"); +const wxString KEY_SOUNDCARD_RXLEVEL = wxT("soundCardRXLevel"); +const wxString KEY_SOUNDCARD_TXLEVEL = wxT("soundCardTXLevel"); +const wxString KEY_SOUNDCARD_TXDELAY = wxT("soundCardTXDelay"); +const wxString KEY_SOUNDCARD_TXTAIL = wxT("soundCardTXTail"); + +const wxString KEY_SPLIT_LOCALADDRESS = wxT("splitLocalAddress"); +const wxString KEY_SPLIT_LOCALPORT = wxT("splitLocalPort"); +const wxString KEY_SPLIT_TXNAME = wxT("splitTXName"); +const wxString KEY_SPLIT_RXNAME = wxT("splitRXName"); +const wxString KEY_SPLIT_TIMEOUT = wxT("splitTimeout"); + + +const wxString DEFAULT_CALLSIGN = wxT("GB3IN C"); +const wxString DEFAULT_GATEWAY = wxEmptyString; +const DSTAR_MODE DEFAULT_MODE = MODE_DUPLEX; +const ACK_TYPE DEFAULT_ACK = AT_BER; +const bool DEFAULT_RESTRICTION = false; +const bool DEFAULT_RPT1_VALIDATION = true; +const bool DEFAULT_DTMF_BLANKING = true; +const bool DEFAULT_ERROR_REPLY = true; +const wxString DEFAULT_GATEWAY_ADDRESS = wxT("127.0.0.1"); +const unsigned int DEFAULT_GATEWAY_PORT = 20010U; +const wxString DEFAULT_LOCAL_ADDRESS = wxT("127.0.0.1"); +const unsigned int DEFAULT_LOCAL_PORT = 20011U; +const wxString DEFAULT_NETWORK_NAME = wxEmptyString; +const wxString DEFAULT_MODEM_TYPE = wxT("DVAP"); +const unsigned int DEFAULT_TIMEOUT = 180U; +const unsigned int DEFAULT_ACK_TIME = 500U; +const unsigned int DEFAULT_BEACON_TIME = 600U; +const wxString DEFAULT_BEACON_TEXT = wxT("D-Star Repeater"); +const bool DEFAULT_BEACON_VOICE = false; +const TEXT_LANG DEFAULT_LANGUAGE = TL_ENGLISH_UK; +const bool DEFAULT_ANNOUNCEMENT_ENABLED = false; +const unsigned int DEFAULT_ANNOUNCEMENT_TIME = 500U; +const wxString DEFAULT_ANNOUNCEMENT_RECORD_RPT1 = wxEmptyString; +const wxString DEFAULT_ANNOUNCEMENT_RECORD_RPT2 = wxEmptyString; +const wxString DEFAULT_ANNOUNCEMENT_DELETE_RPT1 = wxEmptyString; +const wxString DEFAULT_ANNOUNCEMENT_DELETE_RPT2 = wxEmptyString; +const bool DEFAULT_CONTROL_ENABLED = false; +const wxString DEFAULT_CONTROL_RPT1 = wxEmptyString; +const wxString DEFAULT_CONTROL_RPT2 = wxEmptyString; +const wxString DEFAULT_CONTROL_SHUTDOWN = wxEmptyString; +const wxString DEFAULT_CONTROL_STARTUP = wxEmptyString; +const wxString DEFAULT_CONTROL_STATUS = wxEmptyString; +const wxString DEFAULT_CONTROL_COMMAND = wxEmptyString; +const wxString DEFAULT_CONTROL_COMMAND_LINE = wxEmptyString; +const wxString DEFAULT_CONTROL_OUTPUT = wxEmptyString; +const wxString DEFAULT_CONTROLLER_TYPE = wxEmptyString; +const unsigned int DEFAULT_SERIAL_CONFIG = 1U; +const bool DEFAULT_PTT_INVERT = false; +const unsigned int DEFAULT_ACTIVE_HANG_TIME = 0U; +const bool DEFAULT_OUTPUT = false; +const bool DEFAULT_LOGGING = false; +const int DEFAULT_WINDOW_X = -1; +const int DEFAULT_WINDOW_Y = -1; + +const wxString DEFAULT_DVAP_PORT = wxEmptyString; +const unsigned int DEFAULT_DVAP_FREQUENCY = 145500000U; +const int DEFAULT_DVAP_POWER = 10; +const int DEFAULT_DVAP_SQUELCH = -100; + +#if defined(WIN32) +const USB_INTERFACE DEFAULT_GMSK_INTERFACE = UI_WINUSB; +#else +const USB_INTERFACE DEFAULT_GMSK_INTERFACE = UI_LIBUSB; +#endif +const unsigned int DEFAULT_GMSK_ADDRESS = 0x0300U; + +const wxString DEFAULT_DVRPTR1_PORT = wxEmptyString; +const bool DEFAULT_DVRPTR1_RXINVERT = false; +const bool DEFAULT_DVRPTR1_TXINVERT = false; +const bool DEFAULT_DVRPTR1_CHANNEL = false; +const unsigned int DEFAULT_DVRPTR1_MODLEVEL = 20U; +const unsigned int DEFAULT_DVRPTR1_TXDELAY = 150U; + +const CONNECTION_TYPE DEFAULT_DVRPTR2_CONNECTION = CT_USB; +const wxString DEFAULT_DVRPTR2_USBPORT = wxEmptyString; +const wxString DEFAULT_DVRPTR2_ADDRESS = wxT("127.0.0.1"); +const unsigned int DEFAULT_DVRPTR2_PORT = 0U; +const bool DEFAULT_DVRPTR2_TXINVERT = false; +const unsigned int DEFAULT_DVRPTR2_MODLEVEL = 20U; +const unsigned int DEFAULT_DVRPTR2_TXDELAY = 150U; + +const CONNECTION_TYPE DEFAULT_DVRPTR3_CONNECTION = CT_USB; +const wxString DEFAULT_DVRPTR3_USBPORT = wxEmptyString; +const wxString DEFAULT_DVRPTR3_ADDRESS = wxT("127.0.0.1"); +const unsigned int DEFAULT_DVRPTR3_PORT = 0U; +const bool DEFAULT_DVRPTR3_TXINVERT = false; +const unsigned int DEFAULT_DVRPTR3_MODLEVEL = 20U; +const unsigned int DEFAULT_DVRPTR3_TXDELAY = 150U; + +const wxString DEFAULT_DVMEGA_PORT = wxEmptyString; +const DVMEGA_VARIANT DEFAULT_DVMEGA_VARIANT = DVMV_MODEM; +const bool DEFAULT_DVMEGA_RXINVERT = false; +const bool DEFAULT_DVMEGA_TXINVERT = false; +const unsigned int DEFAULT_DVMEGA_TXDELAY = 150U; +const unsigned int DEFAULT_DVMEGA_RXFREQUENCY = 145500000U; +const unsigned int DEFAULT_DVMEGA_TXFREQUENCY = 145500000U; +const unsigned int DEFAULT_DVMEGA_POWER = 100U; + +const wxString DEFAULT_MMDVM_PORT = wxEmptyString; +const bool DEFAULT_MMDVM_RXINVERT = false; +const bool DEFAULT_MMDVM_TXINVERT = false; +const bool DEFAULT_MMDVM_PTTINVERT = false; +const unsigned int DEFAULT_MMDVM_TXDELAY = 50U; +const unsigned int DEFAULT_MMDVM_RXLEVEL = 100U; +const unsigned int DEFAULT_MMDVM_TXLEVEL = 100U; + +const wxString DEFAULT_SOUNDCARD_RXDEVICE = wxEmptyString; +const wxString DEFAULT_SOUNDCARD_TXDEVICE = wxEmptyString; +const bool DEFAULT_SOUNDCARD_RXINVERT = false; +const bool DEFAULT_SOUNDCARD_TXINVERT = false; +const wxFloat32 DEFAULT_SOUNDCARD_RXLEVEL = 1.0F; +const wxFloat32 DEFAULT_SOUNDCARD_TXLEVEL = 1.0F; +const unsigned int DEFAULT_SOUNDCARD_TXDELAY = 150U; +const unsigned int DEFAULT_SOUNDCARD_TXTAIL = 50U; + +const wxString DEFAULT_SPLIT_LOCALADDRESS = wxEmptyString; +const unsigned int DEFAULT_SPLIT_LOCALPORT = 0U; +const wxString DEFAULT_SPLIT_TXNAME = wxEmptyString; +const wxString DEFAULT_SPLIT_RXNAME = wxEmptyString; +const unsigned int DEFAULT_SPLIT_TIMEOUT = 0U; + +#if defined(__WINDOWS__) + +CDStarRepeaterConfig::CDStarRepeaterConfig(wxConfigBase* config, const wxString& dir, const wxString& configName, const wxString& name) : +m_config(config), +m_name(wxT("/")), +m_fileName(), +m_callsign(DEFAULT_CALLSIGN), +m_gateway(DEFAULT_GATEWAY), +m_mode(DEFAULT_MODE), +m_ack(DEFAULT_ACK), +m_restriction(DEFAULT_RESTRICTION), +m_rpt1Validation(DEFAULT_RPT1_VALIDATION), +m_dtmfBlanking(DEFAULT_DTMF_BLANKING), +m_errorReply(DEFAULT_ERROR_REPLY), +m_gatewayAddress(DEFAULT_GATEWAY_ADDRESS), +m_gatewayPort(DEFAULT_GATEWAY_PORT), +m_localAddress(DEFAULT_LOCAL_ADDRESS), +m_localPort(DEFAULT_LOCAL_PORT), +m_networkName(DEFAULT_NETWORK_NAME), +m_modemType(DEFAULT_MODEM_TYPE), +m_timeout(DEFAULT_TIMEOUT), +m_ackTime(DEFAULT_ACK_TIME), +m_beaconTime(DEFAULT_BEACON_TIME), +m_beaconText(DEFAULT_BEACON_TEXT), +m_beaconVoice(DEFAULT_BEACON_VOICE), +m_language(DEFAULT_LANGUAGE), +m_announcementEnabled(DEFAULT_ANNOUNCEMENT_ENABLED), +m_announcementTime(DEFAULT_ANNOUNCEMENT_TIME), +m_announcementRecordRPT1(DEFAULT_ANNOUNCEMENT_RECORD_RPT1), +m_announcementRecordRPT2(DEFAULT_ANNOUNCEMENT_RECORD_RPT2), +m_announcementDeleteRPT1(DEFAULT_ANNOUNCEMENT_DELETE_RPT1), +m_announcementDeleteRPT2(DEFAULT_ANNOUNCEMENT_DELETE_RPT2), +m_controlEnabled(DEFAULT_CONTROL_ENABLED), +m_controlRpt1Callsign(DEFAULT_CONTROL_RPT1), +m_controlRpt2Callsign(DEFAULT_CONTROL_RPT2), +m_controlShutdown(DEFAULT_CONTROL_SHUTDOWN), +m_controlStartup(DEFAULT_CONTROL_STARTUP), +m_controlStatus1(DEFAULT_CONTROL_STATUS), +m_controlStatus2(DEFAULT_CONTROL_STATUS), +m_controlStatus3(DEFAULT_CONTROL_STATUS), +m_controlStatus4(DEFAULT_CONTROL_STATUS), +m_controlStatus5(DEFAULT_CONTROL_STATUS), +m_controlCommand1(DEFAULT_CONTROL_COMMAND), +m_controlCommand1Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand2(DEFAULT_CONTROL_COMMAND), +m_controlCommand2Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand3(DEFAULT_CONTROL_COMMAND), +m_controlCommand3Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand4(DEFAULT_CONTROL_COMMAND), +m_controlCommand4Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand5(DEFAULT_CONTROL_COMMAND), +m_controlCommand5Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand6(DEFAULT_CONTROL_COMMAND), +m_controlCommand6Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlOutput1(DEFAULT_CONTROL_OUTPUT), +m_controlOutput2(DEFAULT_CONTROL_OUTPUT), +m_controlOutput3(DEFAULT_CONTROL_OUTPUT), +m_controlOutput4(DEFAULT_CONTROL_OUTPUT), +m_controllerType(DEFAULT_CONTROLLER_TYPE), +m_serialConfig(DEFAULT_SERIAL_CONFIG), +m_pttInvert(DEFAULT_PTT_INVERT), +m_activeHangTime(DEFAULT_ACTIVE_HANG_TIME), +m_output1(DEFAULT_OUTPUT), +m_output2(DEFAULT_OUTPUT), +m_output3(DEFAULT_OUTPUT), +m_output4(DEFAULT_OUTPUT), +m_logging(DEFAULT_LOGGING), +m_x(DEFAULT_WINDOW_X), +m_y(DEFAULT_WINDOW_Y), +m_dvapPort(DEFAULT_DVAP_PORT), +m_dvapFrequency(DEFAULT_DVAP_FREQUENCY), +m_dvapPower(DEFAULT_DVAP_POWER), +m_dvapSquelch(DEFAULT_DVAP_SQUELCH), +m_gmskInterface(DEFAULT_GMSK_INTERFACE), +m_gmskAddress(DEFAULT_GMSK_ADDRESS), +m_dvrptr1Port(DEFAULT_DVRPTR1_PORT), +m_dvrptr1RXInvert(DEFAULT_DVRPTR1_RXINVERT), +m_dvrptr1TXInvert(DEFAULT_DVRPTR1_TXINVERT), +m_dvrptr1Channel(DEFAULT_DVRPTR1_CHANNEL), +m_dvrptr1ModLevel(DEFAULT_DVRPTR1_MODLEVEL), +m_dvrptr1TXDelay(DEFAULT_DVRPTR1_TXDELAY), +m_dvrptr2Connection(DEFAULT_DVRPTR2_CONNECTION), +m_dvrptr2USBPort(DEFAULT_DVRPTR2_USBPORT), +m_dvrptr2Address(DEFAULT_DVRPTR2_ADDRESS), +m_dvrptr2Port(DEFAULT_DVRPTR2_PORT), +m_dvrptr2TXInvert(DEFAULT_DVRPTR2_TXINVERT), +m_dvrptr2ModLevel(DEFAULT_DVRPTR2_MODLEVEL), +m_dvrptr2TXDelay(DEFAULT_DVRPTR2_TXDELAY), +m_dvrptr3Connection(DEFAULT_DVRPTR3_CONNECTION), +m_dvrptr3USBPort(DEFAULT_DVRPTR3_USBPORT), +m_dvrptr3Address(DEFAULT_DVRPTR3_ADDRESS), +m_dvrptr3Port(DEFAULT_DVRPTR3_PORT), +m_dvrptr3TXInvert(DEFAULT_DVRPTR3_TXINVERT), +m_dvrptr3ModLevel(DEFAULT_DVRPTR3_MODLEVEL), +m_dvrptr3TXDelay(DEFAULT_DVRPTR3_TXDELAY), +m_dvmegaPort(DEFAULT_DVMEGA_PORT), +m_dvmegaVariant(DEFAULT_DVMEGA_VARIANT), +m_dvmegaRXInvert(DEFAULT_DVMEGA_RXINVERT), +m_dvmegaTXInvert(DEFAULT_DVMEGA_TXINVERT), +m_dvmegaTXDelay(DEFAULT_DVMEGA_TXDELAY), +m_dvmegaRXFrequency(DEFAULT_DVMEGA_RXFREQUENCY), +m_dvmegaTXFrequency(DEFAULT_DVMEGA_TXFREQUENCY), +m_dvmegaPower(DEFAULT_DVMEGA_POWER), +m_mmdvmPort(DEFAULT_MMDVM_PORT), +m_mmdvmRXInvert(DEFAULT_MMDVM_RXINVERT), +m_mmdvmTXInvert(DEFAULT_MMDVM_TXINVERT), +m_mmdvmPTTInvert(DEFAULT_MMDVM_PTTINVERT), +m_mmdvmTXDelay(DEFAULT_MMDVM_TXDELAY), +m_mmdvmRXLevel(DEFAULT_MMDVM_RXLEVEL), +m_mmdvmTXLevel(DEFAULT_MMDVM_TXLEVEL), +m_soundCardRXDevice(DEFAULT_SOUNDCARD_RXDEVICE), +m_soundCardTXDevice(DEFAULT_SOUNDCARD_TXDEVICE), +m_soundCardRXInvert(DEFAULT_SOUNDCARD_RXINVERT), +m_soundCardTXInvert(DEFAULT_SOUNDCARD_TXINVERT), +m_soundCardRXLevel(DEFAULT_SOUNDCARD_RXLEVEL), +m_soundCardTXLevel(DEFAULT_SOUNDCARD_TXLEVEL), +m_soundCardTXDelay(DEFAULT_SOUNDCARD_TXDELAY), +m_soundCardTXTail(DEFAULT_SOUNDCARD_TXTAIL), +m_splitLocalAddress(DEFAULT_SPLIT_LOCALADDRESS), +m_splitLocalPort(DEFAULT_SPLIT_LOCALPORT), +m_splitTXNames(), +m_splitRXNames(), +m_splitTimeout(DEFAULT_SPLIT_TIMEOUT) +{ + wxASSERT(config != NULL); + wxASSERT(!dir.IsEmpty()); + + wxString fileName = configName; + + if (!name.IsEmpty()) { + fileName = configName + wxT("_") + name; + m_name = wxT("/") + name + wxT("/"); + } + + m_fileName.Assign(dir, fileName); + + long temp; + double temp1; + + m_config->Read(m_name + KEY_CALLSIGN, &m_callsign, DEFAULT_CALLSIGN); + + m_config->Read(m_name + KEY_GATEWAY, &m_gateway, DEFAULT_GATEWAY); + + m_config->Read(m_name + KEY_MODE, &temp, long(DEFAULT_MODE)); + m_mode = DSTAR_MODE(temp); + + m_config->Read(m_name + KEY_ACK, &temp, long(DEFAULT_ACK)); + m_ack = ACK_TYPE(temp); + + m_config->Read(m_name + KEY_RESTRICTION, &m_restriction, DEFAULT_RESTRICTION); + + m_config->Read(m_name + KEY_RPT1_VALIDATION, &m_rpt1Validation, DEFAULT_RPT1_VALIDATION); + + m_config->Read(m_name + KEY_DTMF_BLANKING, &m_dtmfBlanking, DEFAULT_DTMF_BLANKING); + + m_config->Read(m_name + KEY_ERROR_REPLY, &m_errorReply, DEFAULT_ERROR_REPLY); + + m_config->Read(m_name + KEY_GATEWAY_ADDRESS, &m_gatewayAddress, DEFAULT_GATEWAY_ADDRESS); + + m_config->Read(m_name + KEY_GATEWAY_PORT, &temp, long(DEFAULT_GATEWAY_PORT)); + m_gatewayPort = (unsigned int)temp; + + m_config->Read(m_name + KEY_LOCAL_ADDRESS, &m_localAddress, DEFAULT_LOCAL_ADDRESS); + + m_config->Read(m_name + KEY_LOCAL_PORT, &temp, long(DEFAULT_LOCAL_PORT)); + m_localPort = (unsigned int)temp; + + m_config->Read(m_name + KEY_NETWORK_NAME, &m_networkName, DEFAULT_NETWORK_NAME); + + m_config->Read(m_name + KEY_MODEM_TYPE, &m_modemType, DEFAULT_MODEM_TYPE); + + m_config->Read(m_name + KEY_TIMEOUT, &temp, long(DEFAULT_TIMEOUT)); + m_timeout = (unsigned int)temp; + + m_config->Read(m_name + KEY_ACK_TIME, &temp, long(DEFAULT_ACK_TIME)); + m_ackTime = (unsigned int)temp; + + m_config->Read(m_name + KEY_BEACON_TIME, &temp, long(DEFAULT_BEACON_TIME)); + m_beaconTime = (unsigned int)temp; + + m_config->Read(m_name + KEY_BEACON_TEXT, &m_beaconText, DEFAULT_BEACON_TEXT); + + m_config->Read(m_name + KEY_BEACON_VOICE, &m_beaconVoice, DEFAULT_BEACON_VOICE); + + m_config->Read(m_name + KEY_LANGUAGE, &temp, long(DEFAULT_LANGUAGE)); + m_language = TEXT_LANG(temp); + + m_config->Read(m_name + KEY_ANNOUNCEMENT_ENABLED, &m_announcementEnabled, DEFAULT_ANNOUNCEMENT_ENABLED); + + m_config->Read(m_name + KEY_ANNOUNCEMENT_TIME, &temp, long(DEFAULT_ANNOUNCEMENT_TIME)); + m_announcementTime = (unsigned int)temp; + + m_config->Read(m_name + KEY_ANNOUNCEMENT_RECORD_RPT1, &m_announcementRecordRPT1, DEFAULT_ANNOUNCEMENT_RECORD_RPT1); + + m_config->Read(m_name + KEY_ANNOUNCEMENT_RECORD_RPT2, &m_announcementRecordRPT2, DEFAULT_ANNOUNCEMENT_RECORD_RPT2); + + m_config->Read(m_name + KEY_ANNOUNCEMENT_DELETE_RPT1, &m_announcementDeleteRPT1, DEFAULT_ANNOUNCEMENT_DELETE_RPT1); + + m_config->Read(m_name + KEY_ANNOUNCEMENT_DELETE_RPT2, &m_announcementDeleteRPT2, DEFAULT_ANNOUNCEMENT_DELETE_RPT2); + + m_config->Read(m_name + KEY_CONTROL_ENABLED, &m_controlEnabled, DEFAULT_CONTROL_ENABLED); + + m_config->Read(m_name + KEY_CONTROL_RPT1, &m_controlRpt1Callsign, DEFAULT_CONTROL_RPT1); + + m_config->Read(m_name + KEY_CONTROL_RPT2, &m_controlRpt2Callsign, DEFAULT_CONTROL_RPT2); + + m_config->Read(m_name + KEY_CONTROL_SHUTDOWN, &m_controlShutdown, DEFAULT_CONTROL_SHUTDOWN); + + m_config->Read(m_name + KEY_CONTROL_STARTUP, &m_controlStartup, DEFAULT_CONTROL_STARTUP); + + m_config->Read(m_name + KEY_CONTROL_STATUS1, &m_controlStatus1, DEFAULT_CONTROL_STATUS); + + m_config->Read(m_name + KEY_CONTROL_STATUS2, &m_controlStatus2, DEFAULT_CONTROL_STATUS); + + m_config->Read(m_name + KEY_CONTROL_STATUS3, &m_controlStatus3, DEFAULT_CONTROL_STATUS); + + m_config->Read(m_name + KEY_CONTROL_STATUS4, &m_controlStatus4, DEFAULT_CONTROL_STATUS); + + m_config->Read(m_name + KEY_CONTROL_STATUS5, &m_controlStatus5, DEFAULT_CONTROL_STATUS); + + m_config->Read(m_name + KEY_CONTROL_COMMAND1, &m_controlCommand1, DEFAULT_CONTROL_COMMAND); + + m_config->Read(m_name + KEY_CONTROL_COMMAND1_LINE, &m_controlCommand1Line, DEFAULT_CONTROL_COMMAND_LINE); + + m_config->Read(m_name + KEY_CONTROL_COMMAND2, &m_controlCommand2, DEFAULT_CONTROL_COMMAND); + + m_config->Read(m_name + KEY_CONTROL_COMMAND2_LINE, &m_controlCommand2Line, DEFAULT_CONTROL_COMMAND_LINE); + + m_config->Read(m_name + KEY_CONTROL_COMMAND3, &m_controlCommand3, DEFAULT_CONTROL_COMMAND); + + m_config->Read(m_name + KEY_CONTROL_COMMAND3_LINE, &m_controlCommand3Line, DEFAULT_CONTROL_COMMAND_LINE); + + m_config->Read(m_name + KEY_CONTROL_COMMAND4, &m_controlCommand4, DEFAULT_CONTROL_COMMAND); + + m_config->Read(m_name + KEY_CONTROL_COMMAND4_LINE, &m_controlCommand4Line, DEFAULT_CONTROL_COMMAND_LINE); + + m_config->Read(m_name + KEY_CONTROL_COMMAND5, &m_controlCommand5, DEFAULT_CONTROL_COMMAND); + + m_config->Read(m_name + KEY_CONTROL_COMMAND5_LINE, &m_controlCommand5Line, DEFAULT_CONTROL_COMMAND_LINE); + + m_config->Read(m_name + KEY_CONTROL_COMMAND6, &m_controlCommand6, DEFAULT_CONTROL_COMMAND); + + m_config->Read(m_name + KEY_CONTROL_COMMAND6_LINE, &m_controlCommand6Line, DEFAULT_CONTROL_COMMAND_LINE); + + m_config->Read(m_name + KEY_CONTROL_OUTPUT1, &m_controlOutput1, DEFAULT_CONTROL_OUTPUT); + + m_config->Read(m_name + KEY_CONTROL_OUTPUT2, &m_controlOutput2, DEFAULT_CONTROL_OUTPUT); + + m_config->Read(m_name + KEY_CONTROL_OUTPUT3, &m_controlOutput3, DEFAULT_CONTROL_OUTPUT); + + m_config->Read(m_name + KEY_CONTROL_OUTPUT4, &m_controlOutput4, DEFAULT_CONTROL_OUTPUT); + + m_config->Read(m_name + KEY_CONTROLLER_TYPE, &m_controllerType, DEFAULT_CONTROLLER_TYPE); + + m_config->Read(m_name + KEY_SERIAL_CONFIG, &temp, long(DEFAULT_SERIAL_CONFIG)); + m_serialConfig = (unsigned int)temp; + + m_config->Read(m_name + KEY_PTT_INVERT, &m_pttInvert, DEFAULT_PTT_INVERT); + + m_config->Read(m_name + KEY_ACTIVE_HANG_TIME, &temp, long(DEFAULT_ACTIVE_HANG_TIME)); + m_activeHangTime = (unsigned int)temp; + + m_config->Read(m_name + KEY_OUTPUT1, &m_output1, DEFAULT_OUTPUT); + + m_config->Read(m_name + KEY_OUTPUT2, &m_output2, DEFAULT_OUTPUT); + + m_config->Read(m_name + KEY_OUTPUT3, &m_output3, DEFAULT_OUTPUT); + + m_config->Read(m_name + KEY_OUTPUT4, &m_output4, DEFAULT_OUTPUT); + + m_config->Read(m_name + KEY_LOGGING, &m_logging, DEFAULT_LOGGING); + + m_config->Read(m_name + KEY_WINDOW_X, &temp, long(DEFAULT_WINDOW_X)); + m_x = int(temp); + + m_config->Read(m_name + KEY_WINDOW_Y, &temp, long(DEFAULT_WINDOW_Y)); + m_y = int(temp); + + m_config->Read(m_name + KEY_DVAP_PORT, &m_dvapPort, DEFAULT_DVAP_PORT); + + m_config->Read(m_name + KEY_DVAP_FREQUENCY, &temp, long(DEFAULT_DVAP_FREQUENCY)); + m_dvapFrequency = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVAP_POWER, &temp, long(DEFAULT_DVAP_POWER)); + m_dvapPower = int(temp); + + m_config->Read(m_name + KEY_DVAP_SQUELCH, &temp, long(DEFAULT_DVAP_SQUELCH)); + m_dvapSquelch = int(temp); + + m_config->Read(m_name + KEY_GMSK_INTERFACE, &temp, long(DEFAULT_GMSK_INTERFACE)); + m_gmskInterface = USB_INTERFACE(temp); + + m_config->Read(m_name + KEY_GMSK_ADDRESS, &temp, long(DEFAULT_GMSK_ADDRESS)); + m_gmskAddress = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVRPTR1_PORT, &m_dvrptr1Port, DEFAULT_DVRPTR1_PORT); + + m_config->Read(m_name + KEY_DVRPTR1_RXINVERT, &m_dvrptr1RXInvert, DEFAULT_DVRPTR1_RXINVERT); + + m_config->Read(m_name + KEY_DVRPTR1_TXINVERT, &m_dvrptr1TXInvert, DEFAULT_DVRPTR1_TXINVERT); + + m_config->Read(m_name + KEY_DVRPTR1_CHANNEL, &m_dvrptr1Channel, DEFAULT_DVRPTR1_CHANNEL); + + m_config->Read(m_name + KEY_DVRPTR1_MODLEVEL, &temp, long(DEFAULT_DVRPTR1_MODLEVEL)); + m_dvrptr1ModLevel = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVRPTR1_TXDELAY, &temp, long(DEFAULT_DVRPTR1_TXDELAY)); + m_dvrptr1TXDelay = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVRPTR2_CONNECTION, &temp, long(DEFAULT_DVRPTR2_CONNECTION)); + m_dvrptr2Connection = CONNECTION_TYPE(temp); + + m_config->Read(m_name + KEY_DVRPTR2_USBPORT, &m_dvrptr2USBPort, DEFAULT_DVRPTR2_USBPORT); + + m_config->Read(m_name + KEY_DVRPTR2_ADDRESS, &m_dvrptr2Address, DEFAULT_DVRPTR2_ADDRESS); + + m_config->Read(m_name + KEY_DVRPTR2_PORT, &temp, long(DEFAULT_DVRPTR2_PORT)); + m_dvrptr2Port = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVRPTR2_TXINVERT, &m_dvrptr2TXInvert, DEFAULT_DVRPTR2_TXINVERT); + + m_config->Read(m_name + KEY_DVRPTR2_MODLEVEL, &temp, long(DEFAULT_DVRPTR2_MODLEVEL)); + m_dvrptr2ModLevel = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVRPTR2_TXDELAY, &temp, long(DEFAULT_DVRPTR2_TXDELAY)); + m_dvrptr2TXDelay = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVRPTR3_CONNECTION, &temp, long(DEFAULT_DVRPTR3_CONNECTION)); + m_dvrptr3Connection = CONNECTION_TYPE(temp); + + m_config->Read(m_name + KEY_DVRPTR3_USBPORT, &m_dvrptr3USBPort, DEFAULT_DVRPTR3_USBPORT); + + m_config->Read(m_name + KEY_DVRPTR3_ADDRESS, &m_dvrptr3Address, DEFAULT_DVRPTR3_ADDRESS); + + m_config->Read(m_name + KEY_DVRPTR3_PORT, &temp, long(DEFAULT_DVRPTR3_PORT)); + m_dvrptr3Port = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVRPTR3_TXINVERT, &m_dvrptr3TXInvert, DEFAULT_DVRPTR3_TXINVERT); + + m_config->Read(m_name + KEY_DVRPTR3_MODLEVEL, &temp, long(DEFAULT_DVRPTR3_MODLEVEL)); + m_dvrptr3ModLevel = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVRPTR3_TXDELAY, &temp, long(DEFAULT_DVRPTR3_TXDELAY)); + m_dvrptr3TXDelay = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVMEGA_PORT, &m_dvmegaPort, DEFAULT_DVMEGA_PORT); + + m_config->Read(m_name + KEY_DVMEGA_VARIANT, &temp, long(DEFAULT_DVMEGA_VARIANT)); + m_dvmegaVariant = DVMEGA_VARIANT(temp); + + m_config->Read(m_name + KEY_DVMEGA_RXINVERT, &m_dvmegaRXInvert, DEFAULT_DVMEGA_RXINVERT); + + m_config->Read(m_name + KEY_DVMEGA_TXINVERT, &m_dvmegaTXInvert, DEFAULT_DVMEGA_TXINVERT); + + m_config->Read(m_name + KEY_DVMEGA_TXDELAY, &temp, long(DEFAULT_DVMEGA_TXDELAY)); + m_dvmegaTXDelay = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVMEGA_RXFREQUENCY, &temp, long(DEFAULT_DVMEGA_RXFREQUENCY)); + m_dvmegaRXFrequency = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVMEGA_TXFREQUENCY, &temp, long(DEFAULT_DVMEGA_TXFREQUENCY)); + m_dvmegaTXFrequency = (unsigned int)temp; + + m_config->Read(m_name + KEY_DVMEGA_POWER, &temp, long(DEFAULT_DVMEGA_POWER)); + m_dvmegaPower = (unsigned int)temp; + + m_config->Read(m_name + KEY_MMDVM_PORT, &m_mmdvmPort, DEFAULT_MMDVM_PORT); + + m_config->Read(m_name + KEY_MMDVM_RXINVERT, &m_mmdvmRXInvert, DEFAULT_MMDVM_RXINVERT); + + m_config->Read(m_name + KEY_MMDVM_TXINVERT, &m_mmdvmTXInvert, DEFAULT_MMDVM_TXINVERT); + + m_config->Read(m_name + KEY_MMDVM_PTTINVERT, &m_mmdvmPTTInvert, DEFAULT_MMDVM_PTTINVERT); + + m_config->Read(m_name + KEY_MMDVM_TXDELAY, &temp, long(DEFAULT_MMDVM_TXDELAY)); + m_mmdvmTXDelay = (unsigned int)temp; + + m_config->Read(m_name + KEY_MMDVM_RXLEVEL, &temp, long(DEFAULT_MMDVM_RXLEVEL)); + m_mmdvmRXLevel = (unsigned int)temp; + + m_config->Read(m_name + KEY_MMDVM_TXLEVEL, &temp, long(DEFAULT_MMDVM_TXLEVEL)); + m_mmdvmTXLevel = (unsigned int)temp; + + m_config->Read(m_name + KEY_SOUNDCARD_RXDEVICE, &m_soundCardRXDevice, DEFAULT_SOUNDCARD_RXDEVICE); + + m_config->Read(m_name + KEY_SOUNDCARD_TXDEVICE, &m_soundCardTXDevice, DEFAULT_SOUNDCARD_TXDEVICE); + + m_config->Read(m_name + KEY_SOUNDCARD_RXINVERT, &m_soundCardRXInvert, DEFAULT_SOUNDCARD_RXINVERT); + + m_config->Read(m_name + KEY_SOUNDCARD_TXINVERT, &m_soundCardTXInvert, DEFAULT_SOUNDCARD_TXINVERT); + + m_config->Read(m_name + KEY_SOUNDCARD_RXLEVEL, &temp1, double(DEFAULT_SOUNDCARD_RXLEVEL)); + m_soundCardRXLevel = wxFloat32(temp1); + + m_config->Read(m_name + KEY_SOUNDCARD_TXLEVEL, &temp1, double(DEFAULT_SOUNDCARD_TXLEVEL)); + m_soundCardTXLevel = wxFloat32(temp1); + + m_config->Read(m_name + KEY_SOUNDCARD_TXDELAY, &temp, long(DEFAULT_SOUNDCARD_TXDELAY)); + m_soundCardTXDelay = (unsigned int)temp; + + m_config->Read(m_name + KEY_SOUNDCARD_TXTAIL, &temp, long(DEFAULT_SOUNDCARD_TXTAIL)); + m_soundCardTXTail = (unsigned int)temp; + + m_config->Read(m_name + KEY_SPLIT_LOCALADDRESS, &m_splitLocalAddress, DEFAULT_SPLIT_LOCALADDRESS); + + m_config->Read(m_name + KEY_SPLIT_LOCALPORT, &temp, long(DEFAULT_SPLIT_LOCALPORT)); + m_splitLocalPort = (unsigned int)temp; + + for (unsigned int i = 0U; i < SPLIT_TX_COUNT; i++) { + wxString name; + name.Printf(wxT("%s%u"), KEY_SPLIT_TXNAME.c_str(), i); + + wxString value; + m_config->Read(m_name + name, &value, DEFAULT_SPLIT_TXNAME); + m_splitTXNames.Add(value); + } + + for (unsigned int i = 0U; i < SPLIT_RX_COUNT; i++) { + wxString name; + name.Printf(wxT("%s%u"), KEY_SPLIT_RXNAME.c_str(), i); + + wxString value; + m_config->Read(m_name + name, &value, DEFAULT_SPLIT_RXNAME); + m_splitRXNames.Add(value); + } + + m_config->Read(m_name + KEY_SPLIT_TIMEOUT, &temp, long(DEFAULT_SPLIT_TIMEOUT)); + m_splitTimeout = (unsigned int)temp; +} + +CDStarRepeaterConfig::~CDStarRepeaterConfig() +{ + delete m_config; +} + +#else + +CDStarRepeaterConfig::CDStarRepeaterConfig(const wxString& dir, const wxString& configName, const wxString& name, const bool mustExist) : +m_fileName(), +m_callsign(DEFAULT_CALLSIGN), +m_gateway(DEFAULT_GATEWAY), +m_mode(DEFAULT_MODE), +m_ack(DEFAULT_ACK), +m_restriction(DEFAULT_RESTRICTION), +m_rpt1Validation(DEFAULT_RPT1_VALIDATION), +m_dtmfBlanking(DEFAULT_DTMF_BLANKING), +m_errorReply(DEFAULT_ERROR_REPLY), +m_gatewayAddress(DEFAULT_GATEWAY_ADDRESS), +m_gatewayPort(DEFAULT_GATEWAY_PORT), +m_localAddress(DEFAULT_LOCAL_ADDRESS), +m_localPort(DEFAULT_LOCAL_PORT), +m_networkName(DEFAULT_NETWORK_NAME), +m_modemType(DEFAULT_MODEM_TYPE), +m_timeout(DEFAULT_TIMEOUT), +m_ackTime(DEFAULT_ACK_TIME), +m_beaconTime(DEFAULT_BEACON_TIME), +m_beaconText(DEFAULT_BEACON_TEXT), +m_beaconVoice(DEFAULT_BEACON_VOICE), +m_language(DEFAULT_LANGUAGE), +m_announcementEnabled(DEFAULT_ANNOUNCEMENT_ENABLED), +m_announcementTime(DEFAULT_ANNOUNCEMENT_TIME), +m_announcementRecordRPT1(DEFAULT_ANNOUNCEMENT_RECORD_RPT1), +m_announcementRecordRPT2(DEFAULT_ANNOUNCEMENT_RECORD_RPT2), +m_announcementDeleteRPT1(DEFAULT_ANNOUNCEMENT_DELETE_RPT1), +m_announcementDeleteRPT2(DEFAULT_ANNOUNCEMENT_DELETE_RPT2), +m_controlEnabled(DEFAULT_CONTROL_ENABLED), +m_controlRpt1Callsign(DEFAULT_CONTROL_RPT1), +m_controlRpt2Callsign(DEFAULT_CONTROL_RPT2), +m_controlShutdown(DEFAULT_CONTROL_SHUTDOWN), +m_controlStartup(DEFAULT_CONTROL_STARTUP), +m_controlStatus1(DEFAULT_CONTROL_STATUS), +m_controlStatus2(DEFAULT_CONTROL_STATUS), +m_controlStatus3(DEFAULT_CONTROL_STATUS), +m_controlStatus4(DEFAULT_CONTROL_STATUS), +m_controlStatus5(DEFAULT_CONTROL_STATUS), +m_controlCommand1(DEFAULT_CONTROL_COMMAND), +m_controlCommand1Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand2(DEFAULT_CONTROL_COMMAND), +m_controlCommand2Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand3(DEFAULT_CONTROL_COMMAND), +m_controlCommand3Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand4(DEFAULT_CONTROL_COMMAND), +m_controlCommand4Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand5(DEFAULT_CONTROL_COMMAND), +m_controlCommand5Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlCommand6(DEFAULT_CONTROL_COMMAND), +m_controlCommand6Line(DEFAULT_CONTROL_COMMAND_LINE), +m_controlOutput1(DEFAULT_CONTROL_OUTPUT), +m_controlOutput2(DEFAULT_CONTROL_OUTPUT), +m_controlOutput3(DEFAULT_CONTROL_OUTPUT), +m_controlOutput4(DEFAULT_CONTROL_OUTPUT), +m_controllerType(DEFAULT_CONTROLLER_TYPE), +m_serialConfig(DEFAULT_SERIAL_CONFIG), +m_pttInvert(DEFAULT_PTT_INVERT), +m_activeHangTime(DEFAULT_ACTIVE_HANG_TIME), +m_output1(DEFAULT_OUTPUT), +m_output2(DEFAULT_OUTPUT), +m_output3(DEFAULT_OUTPUT), +m_output4(DEFAULT_OUTPUT), +m_logging(DEFAULT_LOGGING), +m_x(DEFAULT_WINDOW_X), +m_y(DEFAULT_WINDOW_Y), +m_dvapPort(DEFAULT_DVAP_PORT), +m_dvapFrequency(DEFAULT_DVAP_FREQUENCY), +m_dvapPower(DEFAULT_DVAP_POWER), +m_dvapSquelch(DEFAULT_DVAP_SQUELCH), +m_gmskInterface(DEFAULT_GMSK_INTERFACE), +m_gmskAddress(DEFAULT_GMSK_ADDRESS), +m_dvrptr1Port(DEFAULT_DVRPTR1_PORT), +m_dvrptr1RXInvert(DEFAULT_DVRPTR1_RXINVERT), +m_dvrptr1TXInvert(DEFAULT_DVRPTR1_TXINVERT), +m_dvrptr1Channel(DEFAULT_DVRPTR1_CHANNEL), +m_dvrptr1ModLevel(DEFAULT_DVRPTR1_MODLEVEL), +m_dvrptr1TXDelay(DEFAULT_DVRPTR1_TXDELAY), +m_dvrptr2Connection(DEFAULT_DVRPTR2_CONNECTION), +m_dvrptr2USBPort(DEFAULT_DVRPTR2_USBPORT), +m_dvrptr2Address(DEFAULT_DVRPTR2_ADDRESS), +m_dvrptr2Port(DEFAULT_DVRPTR2_PORT), +m_dvrptr2TXInvert(DEFAULT_DVRPTR2_TXINVERT), +m_dvrptr2ModLevel(DEFAULT_DVRPTR2_MODLEVEL), +m_dvrptr2TXDelay(DEFAULT_DVRPTR2_TXDELAY), +m_dvrptr3Connection(DEFAULT_DVRPTR3_CONNECTION), +m_dvrptr3USBPort(DEFAULT_DVRPTR3_USBPORT), +m_dvrptr3Address(DEFAULT_DVRPTR3_ADDRESS), +m_dvrptr3Port(DEFAULT_DVRPTR3_PORT), +m_dvrptr3TXInvert(DEFAULT_DVRPTR3_TXINVERT), +m_dvrptr3ModLevel(DEFAULT_DVRPTR3_MODLEVEL), +m_dvrptr3TXDelay(DEFAULT_DVRPTR3_TXDELAY), +m_dvmegaPort(DEFAULT_DVMEGA_PORT), +m_dvmegaVariant(DEFAULT_DVMEGA_VARIANT), +m_dvmegaRXInvert(DEFAULT_DVMEGA_RXINVERT), +m_dvmegaTXInvert(DEFAULT_DVMEGA_TXINVERT), +m_dvmegaTXDelay(DEFAULT_DVMEGA_TXDELAY), +m_dvmegaRXFrequency(DEFAULT_DVMEGA_RXFREQUENCY), +m_dvmegaTXFrequency(DEFAULT_DVMEGA_TXFREQUENCY), +m_dvmegaPower(DEFAULT_DVMEGA_POWER), +m_mmdvmPort(DEFAULT_MMDVM_PORT), +m_mmdvmRXInvert(DEFAULT_MMDVM_RXINVERT), +m_mmdvmTXInvert(DEFAULT_MMDVM_TXINVERT), +m_mmdvmPTTInvert(DEFAULT_MMDVM_PTTINVERT), +m_mmdvmTXDelay(DEFAULT_MMDVM_TXDELAY), +m_mmdvmRXLevel(DEFAULT_MMDVM_RXLEVEL), +m_mmdvmTXLevel(DEFAULT_MMDVM_TXLEVEL), +m_soundCardRXDevice(DEFAULT_SOUNDCARD_RXDEVICE), +m_soundCardTXDevice(DEFAULT_SOUNDCARD_TXDEVICE), +m_soundCardRXInvert(DEFAULT_SOUNDCARD_RXINVERT), +m_soundCardTXInvert(DEFAULT_SOUNDCARD_TXINVERT), +m_soundCardRXLevel(DEFAULT_SOUNDCARD_RXLEVEL), +m_soundCardTXLevel(DEFAULT_SOUNDCARD_TXLEVEL), +m_soundCardTXDelay(DEFAULT_SOUNDCARD_TXDELAY), +m_soundCardTXTail(DEFAULT_SOUNDCARD_TXTAIL), +m_splitLocalAddress(DEFAULT_SPLIT_LOCALADDRESS), +m_splitLocalPort(DEFAULT_SPLIT_LOCALPORT), +m_splitTXNames(), +m_splitRXNames(), +m_splitTimeout(DEFAULT_SPLIT_TIMEOUT) +{ + wxASSERT(!dir.IsEmpty()); + + wxString fileName = configName; + if (!name.IsEmpty()) + fileName = configName + wxT("_") + name; + + m_fileName.Assign(dir, fileName); + + wxTextFile file(m_fileName.GetFullPath()); + + if (!file.Exists()) + if(mustExist) + throw std::runtime_error("Configuration file does not exist"); + else + return; + + if (!file.Open()) + throw std::runtime_error("Cannot open the configuration file"); + + wxString* splitTXName = new wxString[SPLIT_TX_COUNT]; + wxString* splitRXName = new wxString[SPLIT_RX_COUNT]; + + long temp1; + unsigned long temp2; + double temp3; + + for (wxString str = file.GetFirstLine(); + !file.Eof(); + str = file.GetNextLine()) { + if (str.GetChar(0U) == wxT('#')) { + str = file.GetNextLine(); + continue; + } + + int n = str.Find(wxT('=')); + if (n == wxNOT_FOUND) { + str = file.GetNextLine(); + continue; + } + + wxString key = str.Left(n); + wxString val = str.Mid(n + 1U); + + if (key.IsSameAs(KEY_CALLSIGN)) { + m_callsign = val; + } else if (key.IsSameAs(KEY_GATEWAY)) { + m_gateway = val; + } else if (key.IsSameAs(KEY_MODE)) { + val.ToLong(&temp1); + m_mode = DSTAR_MODE(temp1); + } else if (key.IsSameAs(KEY_ACK)) { + val.ToLong(&temp1); + m_ack = ACK_TYPE(temp1); + } else if (key.IsSameAs(KEY_RESTRICTION)) { + val.ToLong(&temp1); + m_restriction = temp1 == 1L; + } else if (key.IsSameAs(KEY_RPT1_VALIDATION)) { + val.ToLong(&temp1); + m_rpt1Validation = temp1 == 1L; + } else if (key.IsSameAs(KEY_DTMF_BLANKING)) { + val.ToLong(&temp1); + m_dtmfBlanking = temp1 == 1L; + } else if (key.IsSameAs(KEY_ERROR_REPLY)) { + val.ToLong(&temp1); + m_errorReply = temp1 == 1L; + } else if (key.IsSameAs(KEY_GATEWAY_ADDRESS)) { + m_gatewayAddress = val; + } else if (key.IsSameAs(KEY_GATEWAY_PORT)) { + val.ToULong(&temp2); + m_gatewayPort = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_LOCAL_ADDRESS)) { + m_localAddress = val; + } else if (key.IsSameAs(KEY_LOCAL_PORT)) { + val.ToULong(&temp2); + m_localPort = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_NETWORK_NAME)) { + m_networkName = val; + } else if (key.IsSameAs(KEY_MODEM_TYPE)) { + m_modemType = val; + } else if (key.IsSameAs(KEY_TIMEOUT)) { + val.ToULong(&temp2); + m_timeout = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_ACK_TIME)) { + val.ToULong(&temp2); + m_ackTime = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_BEACON_TIME)) { + val.ToULong(&temp2); + m_beaconTime = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_BEACON_TEXT)) { + m_beaconText = val; + } else if (key.IsSameAs(KEY_BEACON_VOICE)) { + val.ToLong(&temp1); + m_beaconVoice = temp1 == 1L; + } else if (key.IsSameAs(KEY_LANGUAGE)) { + val.ToLong(&temp1); + m_language = TEXT_LANG(temp1); + } else if (key.IsSameAs(KEY_ANNOUNCEMENT_ENABLED)) { + val.ToLong(&temp1); + m_announcementEnabled = temp1 == 1L; + } else if (key.IsSameAs(KEY_ANNOUNCEMENT_TIME)) { + val.ToULong(&temp2); + m_announcementTime = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_ANNOUNCEMENT_RECORD_RPT1)) { + m_announcementRecordRPT1 = val; + } else if (key.IsSameAs(KEY_ANNOUNCEMENT_RECORD_RPT2)) { + m_announcementRecordRPT2 = val; + } else if (key.IsSameAs(KEY_ANNOUNCEMENT_DELETE_RPT1)) { + m_announcementDeleteRPT1 = val; + } else if (key.IsSameAs(KEY_ANNOUNCEMENT_DELETE_RPT2)) { + m_announcementDeleteRPT2 = val; + } else if (key.IsSameAs(KEY_CONTROL_ENABLED)) { + val.ToLong(&temp1); + m_controlEnabled = temp1 == 1L; + } else if (key.IsSameAs(KEY_CONTROL_RPT1)) { + m_controlRpt1Callsign = val; + } else if (key.IsSameAs(KEY_CONTROL_RPT2)) { + m_controlRpt2Callsign = val; + } else if (key.IsSameAs(KEY_CONTROL_SHUTDOWN)) { + m_controlShutdown = val; + } else if (key.IsSameAs(KEY_CONTROL_STARTUP)) { + m_controlStartup = val; + } else if (key.IsSameAs(KEY_CONTROL_STATUS1)) { + m_controlStatus1 = val; + } else if (key.IsSameAs(KEY_CONTROL_STATUS2)) { + m_controlStatus2 = val; + } else if (key.IsSameAs(KEY_CONTROL_STATUS3)) { + m_controlStatus3 = val; + } else if (key.IsSameAs(KEY_CONTROL_STATUS4)) { + m_controlStatus4 = val; + } else if (key.IsSameAs(KEY_CONTROL_STATUS5)) { + m_controlStatus5 = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND1)) { + m_controlCommand1 = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND1_LINE)) { + m_controlCommand1Line = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND2)) { + m_controlCommand2 = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND2_LINE)) { + m_controlCommand2Line = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND3)) { + m_controlCommand3 = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND3_LINE)) { + m_controlCommand3Line = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND4)) { + m_controlCommand4 = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND4_LINE)) { + m_controlCommand4Line = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND5)) { + m_controlCommand5 = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND5_LINE)) { + m_controlCommand5Line = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND6)) { + m_controlCommand6 = val; + } else if (key.IsSameAs(KEY_CONTROL_COMMAND6_LINE)) { + m_controlCommand6Line = val; + } else if (key.IsSameAs(KEY_CONTROL_OUTPUT1)) { + m_controlOutput1 = val; + } else if (key.IsSameAs(KEY_CONTROL_OUTPUT2)) { + m_controlOutput2 = val; + } else if (key.IsSameAs(KEY_CONTROL_OUTPUT3)) { + m_controlOutput3 = val; + } else if (key.IsSameAs(KEY_CONTROL_OUTPUT4)) { + m_controlOutput4 = val; + } else if (key.IsSameAs(KEY_CONTROLLER_TYPE)) { + m_controllerType = val; + } else if (key.IsSameAs(KEY_SERIAL_CONFIG)) { + val.ToULong(&temp2); + m_serialConfig = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_PTT_INVERT)) { + val.ToLong(&temp1); + m_pttInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_ACTIVE_HANG_TIME)) { + val.ToULong(&temp2); + m_activeHangTime = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_OUTPUT1)) { + val.ToLong(&temp1); + m_output1 = temp1 == 1L; + } else if (key.IsSameAs(KEY_OUTPUT2)) { + val.ToLong(&temp1); + m_output2 = temp1 == 1L; + } else if (key.IsSameAs(KEY_OUTPUT3)) { + val.ToLong(&temp1); + m_output3 = temp1 == 1L; + } else if (key.IsSameAs(KEY_OUTPUT4)) { + val.ToLong(&temp1); + m_output4 = temp1 == 1L; + } else if (key.IsSameAs(KEY_LOGGING)) { + val.ToLong(&temp1); + m_logging = temp1 == 1L; + } else if (key.IsSameAs(KEY_WINDOW_X)) { + val.ToLong(&temp1); + m_x = int(temp1); + } else if (key.IsSameAs(KEY_WINDOW_Y)) { + val.ToLong(&temp1); + m_y = int(temp1); + } else if (key.IsSameAs(KEY_DVAP_PORT)) { + m_dvapPort = val; + } else if (key.IsSameAs(KEY_DVAP_FREQUENCY)) { + val.ToULong(&temp2); + m_dvapFrequency = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVAP_POWER)) { + val.ToLong(&temp1); + m_dvapPower = int(temp1); + } else if (key.IsSameAs(KEY_DVAP_SQUELCH)) { + val.ToLong(&temp1); + m_dvapSquelch = int(temp1); + } else if (key.IsSameAs(KEY_GMSK_ADDRESS)) { + val.ToULong(&temp2); + m_gmskAddress = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVRPTR1_PORT)) { + m_dvrptr1Port = val; + } else if (key.IsSameAs(KEY_DVRPTR1_RXINVERT)) { + val.ToLong(&temp1); + m_dvrptr1RXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_DVRPTR1_TXINVERT)) { + val.ToLong(&temp1); + m_dvrptr1TXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_DVRPTR1_CHANNEL)) { + val.ToLong(&temp1); + m_dvrptr1Channel = temp1 == 1L; + } else if (key.IsSameAs(KEY_DVRPTR1_MODLEVEL)) { + val.ToULong(&temp2); + m_dvrptr1ModLevel = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVRPTR1_TXDELAY)) { + val.ToULong(&temp2); + m_dvrptr1TXDelay = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVRPTR2_CONNECTION)) { + val.ToLong(&temp1); + m_dvrptr2Connection = CONNECTION_TYPE(temp1); + } else if (key.IsSameAs(KEY_DVRPTR2_USBPORT)) { + m_dvrptr2USBPort = val; + } else if (key.IsSameAs(KEY_DVRPTR2_ADDRESS)) { + m_dvrptr2Address = val; + } else if (key.IsSameAs(KEY_DVRPTR2_PORT)) { + val.ToULong(&temp2); + m_dvrptr2Port = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVRPTR2_TXINVERT)) { + val.ToLong(&temp1); + m_dvrptr2TXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_DVRPTR2_MODLEVEL)) { + val.ToULong(&temp2); + m_dvrptr2ModLevel = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVRPTR2_TXDELAY)) { + val.ToULong(&temp2); + m_dvrptr2TXDelay = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVRPTR3_CONNECTION)) { + val.ToLong(&temp1); + m_dvrptr3Connection = CONNECTION_TYPE(temp1); + } else if (key.IsSameAs(KEY_DVRPTR3_USBPORT)) { + m_dvrptr3USBPort = val; + } else if (key.IsSameAs(KEY_DVRPTR3_ADDRESS)) { + m_dvrptr3Address = val; + } else if (key.IsSameAs(KEY_DVRPTR3_PORT)) { + val.ToULong(&temp2); + m_dvrptr3Port = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVRPTR3_TXINVERT)) { + val.ToLong(&temp1); + m_dvrptr3TXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_DVRPTR3_MODLEVEL)) { + val.ToULong(&temp2); + m_dvrptr3ModLevel = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVRPTR3_TXDELAY)) { + val.ToULong(&temp2); + m_dvrptr3TXDelay = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVMEGA_PORT)) { + m_dvmegaPort = val; + } else if (key.IsSameAs(KEY_DVMEGA_VARIANT)) { + val.ToLong(&temp1); + m_dvmegaVariant = DVMEGA_VARIANT(temp1); + } else if (key.IsSameAs(KEY_DVMEGA_RXINVERT)) { + val.ToLong(&temp1); + m_dvmegaRXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_DVMEGA_TXINVERT)) { + val.ToLong(&temp1); + m_dvmegaTXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_DVMEGA_TXDELAY)) { + val.ToULong(&temp2); + m_dvmegaTXDelay = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVMEGA_RXFREQUENCY)) { + val.ToULong(&temp2); + m_dvmegaRXFrequency = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVMEGA_TXFREQUENCY)) { + val.ToULong(&temp2); + m_dvmegaTXFrequency = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_DVMEGA_POWER)) { + val.ToULong(&temp2); + m_dvmegaPower = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_MMDVM_PORT)) { + m_mmdvmPort = val; + } else if (key.IsSameAs(KEY_MMDVM_RXINVERT)) { + val.ToLong(&temp1); + m_mmdvmRXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_MMDVM_TXINVERT)) { + val.ToLong(&temp1); + m_mmdvmTXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_MMDVM_PTTINVERT)) { + val.ToLong(&temp1); + m_mmdvmPTTInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_MMDVM_TXDELAY)) { + val.ToULong(&temp2); + m_mmdvmTXDelay = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_MMDVM_RXLEVEL)) { + val.ToULong(&temp2); + m_mmdvmRXLevel = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_MMDVM_TXLEVEL)) { + val.ToULong(&temp2); + m_mmdvmTXLevel = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_SOUNDCARD_RXDEVICE)) { + m_soundCardRXDevice = val; + } else if (key.IsSameAs(KEY_SOUNDCARD_TXDEVICE)) { + m_soundCardTXDevice = val; + } else if (key.IsSameAs(KEY_SOUNDCARD_RXINVERT)) { + val.ToLong(&temp1); + m_soundCardRXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_SOUNDCARD_TXINVERT)) { + val.ToLong(&temp1); + m_soundCardTXInvert = temp1 == 1L; + } else if (key.IsSameAs(KEY_SOUNDCARD_RXLEVEL)) { + val.ToDouble(&temp3); + m_soundCardRXLevel = wxFloat32(temp3); + } else if (key.IsSameAs(KEY_SOUNDCARD_TXLEVEL)) { + val.ToDouble(&temp3); + m_soundCardTXLevel = wxFloat32(temp3); + } else if (key.IsSameAs(KEY_SOUNDCARD_TXDELAY)) { + val.ToULong(&temp2); + m_soundCardTXDelay = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_SOUNDCARD_TXTAIL)) { + val.ToULong(&temp2); + m_soundCardTXTail = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_SPLIT_LOCALADDRESS)) { + m_splitLocalAddress = val; + } else if (key.IsSameAs(KEY_SPLIT_LOCALPORT)) { + val.ToULong(&temp2); + m_splitLocalPort = (unsigned int)temp2; + } else if (key.IsSameAs(KEY_SPLIT_TIMEOUT)) { + val.ToULong(&temp2); + m_splitTimeout = (unsigned int)temp2; + } else { + for (unsigned int i = 0U; i < SPLIT_TX_COUNT; i++) { + wxString name; + name.Printf(wxT("%s%u"), KEY_SPLIT_TXNAME.c_str(), i); + if (key.IsSameAs(name)) + splitTXName[i] = val; + } + + for (unsigned int i = 0U; i < SPLIT_RX_COUNT; i++) { + wxString name; + name.Printf(wxT("%s%u"), KEY_SPLIT_RXNAME.c_str(), i); + if (key.IsSameAs(name)) + splitRXName[i] = val; + } + } + } + + file.Close(); + + // Move the array of wxStrings into the wxArrayString + for (unsigned int i = 0U; i < SPLIT_TX_COUNT; i++) + m_splitTXNames.Add(splitTXName[i]); + for (unsigned int i = 0U; i < SPLIT_RX_COUNT; i++) + m_splitRXNames.Add(splitRXName[i]); + + delete[] splitTXName; + delete[] splitRXName; +} + +CDStarRepeaterConfig::~CDStarRepeaterConfig() +{ +} + +#endif + +void CDStarRepeaterConfig::getCallsign(wxString& callsign, wxString& gateway, DSTAR_MODE& mode, ACK_TYPE& ack, bool& restriction, bool& rpt1Validation, bool& dtmfBlanking, bool& errorReply) const +{ + callsign = m_callsign; + gateway = m_gateway; + mode = m_mode; + ack = m_ack; + restriction = m_restriction; + rpt1Validation = m_rpt1Validation; + dtmfBlanking = m_dtmfBlanking; + errorReply = m_errorReply; +} + +void CDStarRepeaterConfig::setCallsign(const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply) +{ + m_callsign = callsign; + m_gateway = gateway; + m_mode = mode; + m_ack = ack; + m_restriction = restriction; + m_rpt1Validation = rpt1Validation; + m_dtmfBlanking = dtmfBlanking; + m_errorReply = errorReply; +} + +void CDStarRepeaterConfig::getNetwork(wxString& gatewayAddress, unsigned int& gatewayPort, wxString& localAddress, unsigned int& localPort, wxString& name) const +{ + gatewayAddress = m_gatewayAddress; + gatewayPort = m_gatewayPort; + localAddress = m_localAddress; + localPort = m_localPort; + name = m_networkName; +} + +void CDStarRepeaterConfig::setNetwork(const wxString& gatewayAddress, unsigned int gatewayPort, const wxString& localAddress, unsigned int localPort, const wxString& name) +{ + m_gatewayAddress = gatewayAddress; + m_gatewayPort = gatewayPort; + m_localAddress = localAddress; + m_localPort = localPort; + m_networkName = name; +} + +void CDStarRepeaterConfig::getModem(wxString& type) const +{ + type = m_modemType; +} + +void CDStarRepeaterConfig::setModem(const wxString& type) +{ + m_modemType = type; +} + +void CDStarRepeaterConfig::getTimes(unsigned int& timeout, unsigned int& ackTime) const +{ + timeout = m_timeout; + ackTime = m_ackTime; +} + +void CDStarRepeaterConfig::setTimes(unsigned int timeout, unsigned int ackTime) +{ + m_timeout = timeout; + m_ackTime = ackTime; +} + +void CDStarRepeaterConfig::getBeacon(unsigned int& time, wxString& text, bool& voice, TEXT_LANG& language) const +{ + time = m_beaconTime; + text = m_beaconText; + voice = m_beaconVoice; + language = m_language; +} + +void CDStarRepeaterConfig::setBeacon(unsigned int time, const wxString& text, bool voice, TEXT_LANG language) +{ + m_beaconTime = time; + m_beaconText = text; + m_beaconVoice = voice; + m_language = language; +} + +void CDStarRepeaterConfig::getAnnouncement(bool& enabled, unsigned int& time, wxString& recordRPT1, wxString& recordRPT2, wxString& deleteRPT1, wxString& deleteRPT2) const +{ + enabled = m_announcementEnabled; + time = m_announcementTime; + recordRPT1 = m_announcementRecordRPT1; + recordRPT2 = m_announcementRecordRPT2; + deleteRPT1 = m_announcementDeleteRPT1; + deleteRPT2 = m_announcementDeleteRPT2; +} + +void CDStarRepeaterConfig::setAnnouncement(bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2) +{ + m_announcementEnabled = enabled; + m_announcementTime = time; + m_announcementRecordRPT1 = recordRPT1; + m_announcementRecordRPT2 = recordRPT2; + m_announcementDeleteRPT1 = deleteRPT1; + m_announcementDeleteRPT2 = deleteRPT2; +} + +void CDStarRepeaterConfig::getControl(bool& enabled, wxString& rpt1Callsign, wxString& rpt2Callsign, wxString& shutdown, wxString& startup, wxString& status1, wxString& status2, wxString& status3, wxString& status4, wxString& status5, wxString& command1, wxString& command1Line, wxString& command2, wxString& command2Line, wxString& command3, wxString& command3Line, wxString& command4, wxString& command4Line, wxString& command5, wxString& command5Line, wxString& command6, wxString& command6Line, wxString& output1, wxString& output2, wxString& output3, wxString& output4) const +{ + enabled = m_controlEnabled; + rpt1Callsign = m_controlRpt1Callsign; + rpt2Callsign = m_controlRpt2Callsign; + shutdown = m_controlShutdown; + startup = m_controlStartup; + + status1 = m_controlStatus1; + status2 = m_controlStatus2; + status3 = m_controlStatus3; + status4 = m_controlStatus4; + status5 = m_controlStatus5; + + command1 = m_controlCommand1; + command1Line = m_controlCommand1Line; + command2 = m_controlCommand2; + command2Line = m_controlCommand2Line; + command3 = m_controlCommand3; + command3Line = m_controlCommand3Line; + command4 = m_controlCommand4; + command4Line = m_controlCommand4Line; + command5 = m_controlCommand5; + command5Line = m_controlCommand5Line; + command6 = m_controlCommand6; + command6Line = m_controlCommand6Line; + + output1 = m_controlOutput1; + output2 = m_controlOutput2; + output3 = m_controlOutput3; + output4 = m_controlOutput4; +} + +void CDStarRepeaterConfig::setControl(bool enabled, const wxString& rpt1Callsign, const wxString& rpt2Callsign, const wxString& shutdown, const wxString& startup, const wxString& status1, const wxString& status2, const wxString& status3, const wxString& status4, const wxString& status5, const wxString& command1, const wxString& command1Line, const wxString& command2, const wxString& command2Line, const wxString& command3, const wxString& command3Line, const wxString& command4, const wxString& command4Line, const wxString& command5, const wxString& command5Line, const wxString& command6, const wxString& command6Line, const wxString& output1, const wxString& output2, const wxString& output3, const wxString& output4) +{ + m_controlEnabled = enabled; + m_controlRpt1Callsign = rpt1Callsign; + m_controlRpt2Callsign = rpt2Callsign; + m_controlShutdown = shutdown; + m_controlStartup = startup; + m_controlStatus1 = status1; + m_controlStatus2 = status2; + m_controlStatus3 = status3; + m_controlStatus4 = status4; + m_controlStatus5 = status5; + m_controlCommand1 = command1; + m_controlCommand1Line = command1Line; + m_controlCommand2 = command2; + m_controlCommand2Line = command2Line; + m_controlCommand3 = command3; + m_controlCommand3Line = command3Line; + m_controlCommand4 = command4; + m_controlCommand4Line = command4Line; + m_controlCommand5 = command5; + m_controlCommand5Line = command5Line; + m_controlCommand6 = command6; + m_controlCommand6Line = command6Line; + m_controlOutput1 = output1; + m_controlOutput2 = output2; + m_controlOutput3 = output3; + m_controlOutput4 = output4; +} + +void CDStarRepeaterConfig::getController(wxString& type, unsigned int& serialConfig, bool& pttInvert, unsigned int& activeHangTime) const +{ + type = m_controllerType; + serialConfig = m_serialConfig; + pttInvert = m_pttInvert; + activeHangTime = m_activeHangTime; +} + +void CDStarRepeaterConfig::setController(const wxString& type, unsigned int serialConfig, bool pttInvert, unsigned int activeHangTime) +{ + m_controllerType = type; + m_serialConfig = serialConfig; + m_pttInvert = pttInvert; + m_activeHangTime = activeHangTime; +} + +void CDStarRepeaterConfig::getOutputs(bool& out1, bool& out2, bool& out3, bool& out4) const +{ + out1 = m_output1; + out2 = m_output2; + out3 = m_output3; + out4 = m_output4; +} + +void CDStarRepeaterConfig::setOutputs(bool out1, bool out2, bool out3, bool out4) +{ + m_output1 = out1; + m_output2 = out2; + m_output3 = out3; + m_output4 = out4; +} + +void CDStarRepeaterConfig::getLogging(bool& logging) const +{ + logging = m_logging; +} + +void CDStarRepeaterConfig::setLogging(bool logging) +{ + m_logging = logging; +} + +void CDStarRepeaterConfig::getPosition(int& x, int& y) const +{ + x = m_x; + y = m_y; +} + +void CDStarRepeaterConfig::setPosition(int x, int y) +{ + m_x = x; + m_y = y; +} + +void CDStarRepeaterConfig::getDVAP(wxString& port, unsigned int& frequency, int& power, int& squelch) const +{ + port = m_dvapPort; + frequency = m_dvapFrequency; + power = m_dvapPower; + squelch = m_dvapSquelch; +} + +void CDStarRepeaterConfig::setDVAP(const wxString& port, unsigned int frequency, int power, int squelch) +{ + m_dvapPort = port; + m_dvapFrequency = frequency; + m_dvapPower = power; + m_dvapSquelch = squelch; +} + +void CDStarRepeaterConfig::getGMSK(USB_INTERFACE& type, unsigned int& address) const +{ + type = m_gmskInterface; + address = m_gmskAddress; +} + +void CDStarRepeaterConfig::setGMSK(USB_INTERFACE type, unsigned int address) +{ + m_gmskInterface = type; + m_gmskAddress = address; +} + +void CDStarRepeaterConfig::getDVRPTR1(wxString& port, bool& rxInvert, bool& txInvert, bool& channel, unsigned int& modLevel, unsigned int& txDelay) const +{ + port = m_dvrptr1Port; + rxInvert = m_dvrptr1RXInvert; + txInvert = m_dvrptr1TXInvert; + channel = m_dvrptr1Channel; + modLevel = m_dvrptr1ModLevel; + txDelay = m_dvrptr1TXDelay; +} + +void CDStarRepeaterConfig::setDVRPTR1(const wxString& port, bool rxInvert, bool txInvert, bool channel, unsigned int modLevel, unsigned int txDelay) +{ + m_dvrptr1Port = port; + m_dvrptr1RXInvert = rxInvert; + m_dvrptr1TXInvert = txInvert; + m_dvrptr1Channel = channel; + m_dvrptr1ModLevel = modLevel; + m_dvrptr1TXDelay = txDelay; +} + +void CDStarRepeaterConfig::getDVRPTR2(CONNECTION_TYPE& connection, wxString& usbPort, wxString& address, unsigned int& port, bool& txInvert, unsigned int& modLevel, unsigned int& txDelay) const +{ + connection = m_dvrptr2Connection; + usbPort = m_dvrptr2USBPort; + address = m_dvrptr2Address; + port = m_dvrptr2Port; + txInvert = m_dvrptr2TXInvert; + modLevel = m_dvrptr2ModLevel; + txDelay = m_dvrptr2TXDelay; +} + +void CDStarRepeaterConfig::setDVRPTR2(CONNECTION_TYPE connection, const wxString& usbPort, const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, unsigned int txDelay) +{ + m_dvrptr2Connection = connection; + m_dvrptr2USBPort = usbPort; + m_dvrptr2Address = address; + m_dvrptr2Port = port; + m_dvrptr2TXInvert = txInvert; + m_dvrptr2ModLevel = modLevel; + m_dvrptr2TXDelay = txDelay; +} + +void CDStarRepeaterConfig::getDVRPTR3(CONNECTION_TYPE& connection, wxString& usbPort, wxString& address, unsigned int& port, bool& txInvert, unsigned int& modLevel, unsigned int& txDelay) const +{ + connection = m_dvrptr3Connection; + usbPort = m_dvrptr3USBPort; + address = m_dvrptr3Address; + port = m_dvrptr3Port; + txInvert = m_dvrptr3TXInvert; + modLevel = m_dvrptr3ModLevel; + txDelay = m_dvrptr3TXDelay; +} + +void CDStarRepeaterConfig::setDVRPTR3(CONNECTION_TYPE connection, const wxString& usbPort, const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, unsigned int txDelay) +{ + m_dvrptr3Connection = connection; + m_dvrptr3USBPort = usbPort; + m_dvrptr3Address = address; + m_dvrptr3Port = port; + m_dvrptr3TXInvert = txInvert; + m_dvrptr3ModLevel = modLevel; + m_dvrptr3TXDelay = txDelay; +} + +void CDStarRepeaterConfig::getDVMEGA(wxString& port, DVMEGA_VARIANT& variant, bool& rxInvert, bool& txInvert, unsigned int& txDelay, unsigned int& rxFrequency, unsigned int& txFrequency, unsigned int& power) const +{ + port = m_dvmegaPort; + variant = m_dvmegaVariant; + rxInvert = m_dvmegaRXInvert; + txInvert = m_dvmegaTXInvert; + txDelay = m_dvmegaTXDelay; + rxFrequency = m_dvmegaRXFrequency; + txFrequency = m_dvmegaTXFrequency; + power = m_dvmegaPower; +} + +void CDStarRepeaterConfig::setDVMEGA(const wxString& port, DVMEGA_VARIANT variant, bool rxInvert, bool txInvert, unsigned int txDelay, unsigned int rxFrequency, unsigned int txFrequency, unsigned int power) +{ + m_dvmegaPort = port; + m_dvmegaVariant = variant; + m_dvmegaRXInvert = rxInvert; + m_dvmegaTXInvert = txInvert; + m_dvmegaTXDelay = txDelay; + m_dvmegaRXFrequency = rxFrequency; + m_dvmegaTXFrequency = txFrequency; + m_dvmegaPower = power; +} + +void CDStarRepeaterConfig::getMMDVM(wxString& port, bool& rxInvert, bool& txInvert, bool& pttInvert, unsigned int& txDelay, unsigned int& rxLevel, unsigned int& txLevel) const +{ + port = m_mmdvmPort; + rxInvert = m_mmdvmRXInvert; + txInvert = m_mmdvmTXInvert; + pttInvert = m_mmdvmPTTInvert; + txDelay = m_mmdvmTXDelay; + rxLevel = m_mmdvmRXLevel; + txLevel = m_mmdvmTXLevel; +} + +void CDStarRepeaterConfig::setMMDVM(const wxString& port, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int rxLevel, unsigned int txLevel) +{ + m_mmdvmPort = port; + m_mmdvmRXInvert = rxInvert; + m_mmdvmTXInvert = txInvert; + m_mmdvmPTTInvert = pttInvert; + m_mmdvmTXDelay = txDelay; + m_mmdvmRXLevel = rxLevel; + m_mmdvmTXLevel = txLevel; +} + +void CDStarRepeaterConfig::getSoundCard(wxString& rxDevice, wxString& txDevice, bool& rxInvert, bool& txInvert, wxFloat32& rxLevel, wxFloat32& txLevel, unsigned int& txDelay, unsigned int& txTail) const +{ + rxDevice = m_soundCardRXDevice; + txDevice = m_soundCardTXDevice; + rxInvert = m_soundCardRXInvert; + txInvert = m_soundCardTXInvert; + rxLevel = m_soundCardRXLevel; + txLevel = m_soundCardTXLevel; + txDelay = m_soundCardTXDelay; + txTail = m_soundCardTXTail; +} + +void CDStarRepeaterConfig::setSoundCard(const wxString& rxDevice, const wxString& txDevice, bool rxInvert, bool txInvert, wxFloat32 rxLevel, wxFloat32 txLevel, unsigned int txDelay, unsigned int txTail) +{ + m_soundCardRXDevice = rxDevice; + m_soundCardTXDevice = txDevice; + m_soundCardRXInvert = rxInvert; + m_soundCardTXInvert = txInvert; + m_soundCardRXLevel = rxLevel; + m_soundCardTXLevel = txLevel; + m_soundCardTXDelay = txDelay; + m_soundCardTXTail = txTail; +} + +void CDStarRepeaterConfig::getSplit(wxString& localAddress, unsigned int& localPort, wxArrayString& transmitterNames, wxArrayString& receiverNames, unsigned int& timeout) const +{ + localAddress = m_splitLocalAddress; + localPort = m_splitLocalPort; + transmitterNames = m_splitTXNames; + receiverNames = m_splitRXNames; + timeout = m_splitTimeout; +} + +void CDStarRepeaterConfig::setSplit(const wxString& localAddress, unsigned int localPort, const wxArrayString& transmitterNames, const wxArrayString& receiverNames, unsigned int timeout) +{ + m_splitLocalAddress = localAddress; + m_splitLocalPort = localPort; + m_splitTXNames = transmitterNames; + m_splitRXNames = receiverNames; + m_splitTimeout = timeout; +} + +bool CDStarRepeaterConfig::write() +{ +#if defined(__WINDOWS__) + m_config->Write(m_name + KEY_CALLSIGN, m_callsign); + m_config->Write(m_name + KEY_GATEWAY, m_gateway); + m_config->Write(m_name + KEY_MODE, long(m_mode)); + m_config->Write(m_name + KEY_ACK, long(m_ack)); + m_config->Write(m_name + KEY_RESTRICTION, m_restriction); + m_config->Write(m_name + KEY_RPT1_VALIDATION, m_rpt1Validation); + m_config->Write(m_name + KEY_DTMF_BLANKING, m_dtmfBlanking); + m_config->Write(m_name + KEY_ERROR_REPLY, m_errorReply); + m_config->Write(m_name + KEY_GATEWAY_ADDRESS, m_gatewayAddress); + m_config->Write(m_name + KEY_GATEWAY_PORT, long(m_gatewayPort)); + m_config->Write(m_name + KEY_LOCAL_ADDRESS, m_localAddress); + m_config->Write(m_name + KEY_LOCAL_PORT, long(m_localPort)); + m_config->Write(m_name + KEY_NETWORK_NAME, m_networkName); + m_config->Write(m_name + KEY_MODEM_TYPE, m_modemType); + m_config->Write(m_name + KEY_TIMEOUT, long(m_timeout)); + m_config->Write(m_name + KEY_ACK_TIME, long(m_ackTime)); + m_config->Write(m_name + KEY_BEACON_TIME, long(m_beaconTime)); + m_config->Write(m_name + KEY_BEACON_TEXT, m_beaconText); + m_config->Write(m_name + KEY_BEACON_VOICE, m_beaconVoice); + m_config->Write(m_name + KEY_LANGUAGE, long(m_language)); + m_config->Write(m_name + KEY_ANNOUNCEMENT_ENABLED, m_announcementEnabled); + m_config->Write(m_name + KEY_ANNOUNCEMENT_TIME, long(m_announcementTime)); + m_config->Write(m_name + KEY_ANNOUNCEMENT_RECORD_RPT1, m_announcementRecordRPT1); + m_config->Write(m_name + KEY_ANNOUNCEMENT_RECORD_RPT2, m_announcementRecordRPT2); + m_config->Write(m_name + KEY_ANNOUNCEMENT_DELETE_RPT1, m_announcementDeleteRPT1); + m_config->Write(m_name + KEY_ANNOUNCEMENT_DELETE_RPT2, m_announcementDeleteRPT2); + m_config->Write(m_name + KEY_CONTROL_ENABLED, m_controlEnabled); + m_config->Write(m_name + KEY_CONTROL_RPT1, m_controlRpt1Callsign); + m_config->Write(m_name + KEY_CONTROL_RPT2, m_controlRpt2Callsign); + m_config->Write(m_name + KEY_CONTROL_SHUTDOWN, m_controlShutdown); + m_config->Write(m_name + KEY_CONTROL_STARTUP, m_controlStartup); + m_config->Write(m_name + KEY_CONTROL_STATUS1, m_controlStatus1); + m_config->Write(m_name + KEY_CONTROL_STATUS2, m_controlStatus2); + m_config->Write(m_name + KEY_CONTROL_STATUS3, m_controlStatus3); + m_config->Write(m_name + KEY_CONTROL_STATUS4, m_controlStatus4); + m_config->Write(m_name + KEY_CONTROL_STATUS5, m_controlStatus5); + m_config->Write(m_name + KEY_CONTROL_COMMAND1, m_controlCommand1); + m_config->Write(m_name + KEY_CONTROL_COMMAND1_LINE, m_controlCommand1Line); + m_config->Write(m_name + KEY_CONTROL_COMMAND2, m_controlCommand2); + m_config->Write(m_name + KEY_CONTROL_COMMAND2_LINE, m_controlCommand2Line); + m_config->Write(m_name + KEY_CONTROL_COMMAND3, m_controlCommand3); + m_config->Write(m_name + KEY_CONTROL_COMMAND3_LINE, m_controlCommand3Line); + m_config->Write(m_name + KEY_CONTROL_COMMAND4, m_controlCommand4); + m_config->Write(m_name + KEY_CONTROL_COMMAND4_LINE, m_controlCommand4Line); + m_config->Write(m_name + KEY_CONTROL_COMMAND5, m_controlCommand5); + m_config->Write(m_name + KEY_CONTROL_COMMAND5_LINE, m_controlCommand5Line); + m_config->Write(m_name + KEY_CONTROL_COMMAND6, m_controlCommand6); + m_config->Write(m_name + KEY_CONTROL_COMMAND6_LINE, m_controlCommand6Line); + m_config->Write(m_name + KEY_CONTROL_OUTPUT1, m_controlOutput1); + m_config->Write(m_name + KEY_CONTROL_OUTPUT2, m_controlOutput2); + m_config->Write(m_name + KEY_CONTROL_OUTPUT3, m_controlOutput3); + m_config->Write(m_name + KEY_CONTROL_OUTPUT4, m_controlOutput4); + m_config->Write(m_name + KEY_CONTROLLER_TYPE, m_controllerType); + m_config->Write(m_name + KEY_SERIAL_CONFIG, long(m_serialConfig)); + m_config->Write(m_name + KEY_PTT_INVERT, m_pttInvert); + m_config->Write(m_name + KEY_ACTIVE_HANG_TIME, long(m_activeHangTime)); + m_config->Write(m_name + KEY_OUTPUT1, m_output1); + m_config->Write(m_name + KEY_OUTPUT2, m_output2); + m_config->Write(m_name + KEY_OUTPUT3, m_output3); + m_config->Write(m_name + KEY_OUTPUT4, m_output4); + m_config->Write(m_name + KEY_LOGGING, m_logging); + m_config->Write(m_name + KEY_WINDOW_X, long(m_x)); + m_config->Write(m_name + KEY_WINDOW_Y, long(m_y)); + + m_config->Write(m_name + KEY_DVAP_PORT, m_dvapPort); + m_config->Write(m_name + KEY_DVAP_FREQUENCY, long(m_dvapFrequency)); + m_config->Write(m_name + KEY_DVAP_POWER, long(m_dvapPower)); + m_config->Write(m_name + KEY_DVAP_SQUELCH, long(m_dvapSquelch)); + + m_config->Write(m_name + KEY_GMSK_INTERFACE, long(m_gmskInterface)); + m_config->Write(m_name + KEY_GMSK_ADDRESS, long(m_gmskAddress)); + + m_config->Write(m_name + KEY_DVRPTR1_PORT, m_dvrptr1Port); + m_config->Write(m_name + KEY_DVRPTR1_RXINVERT, m_dvrptr1RXInvert); + m_config->Write(m_name + KEY_DVRPTR1_TXINVERT, m_dvrptr1TXInvert); + m_config->Write(m_name + KEY_DVRPTR1_CHANNEL, m_dvrptr1Channel); + m_config->Write(m_name + KEY_DVRPTR1_MODLEVEL, long(m_dvrptr1ModLevel)); + m_config->Write(m_name + KEY_DVRPTR1_TXDELAY, long(m_dvrptr1TXDelay)); + + m_config->Write(m_name + KEY_DVRPTR2_CONNECTION, long(m_dvrptr2Connection)); + m_config->Write(m_name + KEY_DVRPTR2_USBPORT, m_dvrptr2USBPort); + m_config->Write(m_name + KEY_DVRPTR2_ADDRESS, m_dvrptr2Address); + m_config->Write(m_name + KEY_DVRPTR2_PORT, long(m_dvrptr2Port)); + m_config->Write(m_name + KEY_DVRPTR2_TXINVERT, m_dvrptr2TXInvert); + m_config->Write(m_name + KEY_DVRPTR2_MODLEVEL, long(m_dvrptr2ModLevel)); + m_config->Write(m_name + KEY_DVRPTR2_TXDELAY, long(m_dvrptr2TXDelay)); + + m_config->Write(m_name + KEY_DVRPTR3_CONNECTION, long(m_dvrptr3Connection)); + m_config->Write(m_name + KEY_DVRPTR3_USBPORT, m_dvrptr3USBPort); + m_config->Write(m_name + KEY_DVRPTR3_ADDRESS, m_dvrptr3Address); + m_config->Write(m_name + KEY_DVRPTR3_PORT, long(m_dvrptr3Port)); + m_config->Write(m_name + KEY_DVRPTR3_TXINVERT, m_dvrptr3TXInvert); + m_config->Write(m_name + KEY_DVRPTR3_MODLEVEL, long(m_dvrptr3ModLevel)); + m_config->Write(m_name + KEY_DVRPTR3_TXDELAY, long(m_dvrptr3TXDelay)); + + m_config->Write(m_name + KEY_DVMEGA_PORT, m_dvmegaPort); + m_config->Write(m_name + KEY_DVMEGA_VARIANT, long(m_dvmegaVariant)); + m_config->Write(m_name + KEY_DVMEGA_RXINVERT, m_dvmegaRXInvert); + m_config->Write(m_name + KEY_DVMEGA_TXINVERT, m_dvmegaTXInvert); + m_config->Write(m_name + KEY_DVMEGA_TXDELAY, long(m_dvmegaTXDelay)); + m_config->Write(m_name + KEY_DVMEGA_RXFREQUENCY, long(m_dvmegaRXFrequency)); + m_config->Write(m_name + KEY_DVMEGA_TXFREQUENCY, long(m_dvmegaTXFrequency)); + m_config->Write(m_name + KEY_DVMEGA_POWER, long(m_dvmegaPower)); + + m_config->Write(m_name + KEY_MMDVM_PORT, m_mmdvmPort); + m_config->Write(m_name + KEY_MMDVM_RXINVERT, m_mmdvmRXInvert); + m_config->Write(m_name + KEY_MMDVM_TXINVERT, m_mmdvmTXInvert); + m_config->Write(m_name + KEY_MMDVM_PTTINVERT, m_mmdvmPTTInvert); + m_config->Write(m_name + KEY_MMDVM_TXDELAY, long(m_mmdvmTXDelay)); + m_config->Write(m_name + KEY_MMDVM_RXLEVEL, long(m_mmdvmRXLevel)); + m_config->Write(m_name + KEY_MMDVM_TXLEVEL, long(m_mmdvmTXLevel)); + + m_config->Write(m_name + KEY_SOUNDCARD_RXDEVICE, m_soundCardRXDevice); + m_config->Write(m_name + KEY_SOUNDCARD_TXDEVICE, m_soundCardTXDevice); + m_config->Write(m_name + KEY_SOUNDCARD_RXINVERT, m_soundCardRXInvert); + m_config->Write(m_name + KEY_SOUNDCARD_TXINVERT, m_soundCardTXInvert); + m_config->Write(m_name + KEY_SOUNDCARD_RXLEVEL, double(m_soundCardRXLevel)); + m_config->Write(m_name + KEY_SOUNDCARD_TXLEVEL, double(m_soundCardTXLevel)); + m_config->Write(m_name + KEY_SOUNDCARD_TXDELAY, long(m_soundCardTXDelay)); + m_config->Write(m_name + KEY_SOUNDCARD_TXTAIL, long(m_soundCardTXTail)); + + m_config->Write(m_name + KEY_SPLIT_LOCALADDRESS, m_splitLocalAddress); + m_config->Write(m_name + KEY_SPLIT_LOCALPORT, long(m_splitLocalPort)); + + for (unsigned int i = 0U; i < m_splitTXNames.GetCount(); i++) { + wxString name; + name.Printf(wxT("%s%u"), KEY_SPLIT_TXNAME.c_str(), i); + + m_config->Write(m_name + name, m_splitTXNames.Item(i)); + } + + for (unsigned int i = 0U; i < m_splitRXNames.GetCount(); i++) { + wxString name; + name.Printf(wxT("%s%u"), KEY_SPLIT_RXNAME.c_str(), i); + + m_config->Write(m_name + name, m_splitRXNames.Item(i)); + } + + m_config->Write(m_name + KEY_SPLIT_TIMEOUT, long(m_splitTimeout)); + + m_config->Flush(); +#endif + + wxTextFile file(m_fileName.GetFullPath()); + + bool exists = file.Exists(); + if (exists) { + bool ret = file.Open(); + if (!ret) { + wxLogError(wxT("Cannot open the config file - %s"), m_fileName.GetFullPath().c_str()); + return false; + } + + // Remove the existing file entries + file.Clear(); + } else { + bool ret = file.Create(); + if (!ret) { + wxLogError(wxT("Cannot create the config file - %s"), m_fileName.GetFullPath().c_str()); + return false; + } + } + + wxString buffer; + buffer.Printf(wxT("%s=%s"), KEY_CALLSIGN.c_str(), m_callsign.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_GATEWAY.c_str(), m_gateway.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_MODE.c_str(), int(m_mode)); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_ACK.c_str(), int(m_ack)); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_RESTRICTION.c_str(), m_restriction ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_RPT1_VALIDATION.c_str(), m_rpt1Validation ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DTMF_BLANKING.c_str(), m_dtmfBlanking ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_ERROR_REPLY.c_str(), m_errorReply ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_GATEWAY_ADDRESS.c_str(), m_gatewayAddress.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_GATEWAY_PORT.c_str(), m_gatewayPort); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_LOCAL_ADDRESS.c_str(), m_localAddress.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_LOCAL_PORT.c_str(), m_localPort); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_NETWORK_NAME.c_str(), m_networkName.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_MODEM_TYPE.c_str(), m_modemType.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_TIMEOUT.c_str(), m_timeout); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_ACK_TIME.c_str(), m_ackTime); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_BEACON_TIME.c_str(), m_beaconTime); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_BEACON_TEXT.c_str(), m_beaconText.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_BEACON_VOICE.c_str(), m_beaconVoice ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_LANGUAGE.c_str(), int(m_language)); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_ANNOUNCEMENT_ENABLED.c_str(), m_announcementEnabled ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_ANNOUNCEMENT_TIME.c_str(), m_announcementTime); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_ANNOUNCEMENT_RECORD_RPT1.c_str(), m_announcementRecordRPT1.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_ANNOUNCEMENT_RECORD_RPT2.c_str(), m_announcementRecordRPT2.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_ANNOUNCEMENT_DELETE_RPT1.c_str(), m_announcementDeleteRPT1.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_ANNOUNCEMENT_DELETE_RPT2.c_str(), m_announcementDeleteRPT2.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_CONTROL_ENABLED.c_str(), m_controlEnabled ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_RPT1.c_str(), m_controlRpt1Callsign.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_RPT2.c_str(), m_controlRpt2Callsign.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_SHUTDOWN.c_str(), m_controlShutdown.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_STARTUP.c_str(), m_controlStartup.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_STATUS1.c_str(), m_controlStatus1.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_STATUS2.c_str(), m_controlStatus2.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_STATUS3.c_str(), m_controlStatus3.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_STATUS4.c_str(), m_controlStatus4.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_STATUS5.c_str(), m_controlStatus5.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND1.c_str(), m_controlCommand1.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND1_LINE.c_str(), m_controlCommand1Line.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND2.c_str(), m_controlCommand2.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND2_LINE.c_str(), m_controlCommand2Line.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND3.c_str(), m_controlCommand3.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND3_LINE.c_str(), m_controlCommand3Line.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND4.c_str(), m_controlCommand4.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND4_LINE.c_str(), m_controlCommand4Line.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND5.c_str(), m_controlCommand5.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND5_LINE.c_str(), m_controlCommand5Line.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND6.c_str(), m_controlCommand6.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_COMMAND6_LINE.c_str(), m_controlCommand6Line.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_OUTPUT1.c_str(), m_controlOutput1.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_OUTPUT2.c_str(), m_controlOutput2.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_OUTPUT3.c_str(), m_controlOutput3.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROL_OUTPUT4.c_str(), m_controlOutput4.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_CONTROLLER_TYPE.c_str(), m_controllerType.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_SERIAL_CONFIG.c_str(), m_serialConfig); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_PTT_INVERT.c_str(), m_pttInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_ACTIVE_HANG_TIME.c_str(), m_activeHangTime); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_OUTPUT1.c_str(), m_output1 ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_OUTPUT2.c_str(), m_output2 ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_OUTPUT3.c_str(), m_output3 ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_OUTPUT4.c_str(), m_output4 ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_LOGGING.c_str(), m_logging ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_WINDOW_X.c_str(), m_x); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_WINDOW_Y.c_str(), m_y); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%s"), KEY_DVAP_PORT.c_str(), m_dvapPort.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVAP_FREQUENCY.c_str(), m_dvapFrequency); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVAP_POWER.c_str(), m_dvapPower); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVAP_SQUELCH.c_str(), m_dvapSquelch); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%u"), KEY_GMSK_ADDRESS.c_str(), m_gmskAddress); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%s"), KEY_DVRPTR1_PORT.c_str(), m_dvrptr1Port.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVRPTR1_RXINVERT.c_str(), m_dvrptr1RXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVRPTR1_TXINVERT.c_str(), m_dvrptr1TXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVRPTR1_CHANNEL.c_str(), m_dvrptr1Channel ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVRPTR1_MODLEVEL.c_str(), m_dvrptr1ModLevel); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVRPTR1_TXDELAY.c_str(), m_dvrptr1TXDelay); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%d"), KEY_DVRPTR2_CONNECTION.c_str(), int(m_dvrptr2Connection)); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_DVRPTR2_USBPORT.c_str(), m_dvrptr2USBPort.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_DVRPTR2_ADDRESS.c_str(), m_dvrptr2Address.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVRPTR2_PORT.c_str(), m_dvrptr2Port); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVRPTR2_TXINVERT.c_str(), m_dvrptr2TXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVRPTR2_MODLEVEL.c_str(), m_dvrptr2ModLevel); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVRPTR2_TXDELAY.c_str(), m_dvrptr2TXDelay); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%d"), KEY_DVRPTR3_CONNECTION.c_str(), int(m_dvrptr3Connection)); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_DVRPTR3_USBPORT.c_str(), m_dvrptr3USBPort.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_DVRPTR3_ADDRESS.c_str(), m_dvrptr3Address.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVRPTR3_PORT.c_str(), m_dvrptr3Port); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVRPTR3_TXINVERT.c_str(), m_dvrptr3TXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVRPTR3_MODLEVEL.c_str(), m_dvrptr3ModLevel); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVRPTR3_TXDELAY.c_str(), m_dvrptr3TXDelay); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%s"), KEY_DVMEGA_PORT.c_str(), m_dvmegaPort.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVMEGA_VARIANT.c_str(), int(m_dvmegaVariant)); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVMEGA_RXINVERT.c_str(), m_dvmegaRXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_DVMEGA_TXINVERT.c_str(), m_dvmegaTXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVMEGA_TXDELAY.c_str(), m_dvmegaTXDelay); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVMEGA_RXFREQUENCY.c_str(), m_dvmegaRXFrequency); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVMEGA_TXFREQUENCY.c_str(), m_dvmegaTXFrequency); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_DVMEGA_POWER.c_str(), m_dvmegaPower); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%s"), KEY_MMDVM_PORT.c_str(), m_mmdvmPort.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_MMDVM_RXINVERT.c_str(), m_mmdvmRXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_MMDVM_TXINVERT.c_str(), m_mmdvmTXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_MMDVM_PTTINVERT.c_str(), m_mmdvmPTTInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_MMDVM_TXDELAY.c_str(), m_mmdvmTXDelay); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_MMDVM_RXLEVEL.c_str(), m_mmdvmRXLevel); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_MMDVM_TXLEVEL.c_str(), m_mmdvmTXLevel); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%s"), KEY_SOUNDCARD_RXDEVICE.c_str(), m_soundCardRXDevice.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%s"), KEY_SOUNDCARD_TXDEVICE.c_str(), m_soundCardTXDevice.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_SOUNDCARD_RXINVERT.c_str(), m_soundCardRXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%d"), KEY_SOUNDCARD_TXINVERT.c_str(), m_soundCardTXInvert ? 1 : 0); file.AddLine(buffer); + buffer.Printf(wxT("%s=%.4f"), KEY_SOUNDCARD_RXLEVEL.c_str(), m_soundCardRXLevel); file.AddLine(buffer); + buffer.Printf(wxT("%s=%.4f"), KEY_SOUNDCARD_TXLEVEL.c_str(), m_soundCardTXLevel); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_SOUNDCARD_TXDELAY.c_str(), m_soundCardTXDelay); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_SOUNDCARD_TXTAIL.c_str(), m_soundCardTXTail); file.AddLine(buffer); + + buffer.Printf(wxT("%s=%s"), KEY_SPLIT_LOCALADDRESS.c_str(), m_splitLocalAddress.c_str()); file.AddLine(buffer); + buffer.Printf(wxT("%s=%u"), KEY_SPLIT_LOCALPORT.c_str(), m_splitLocalPort); file.AddLine(buffer); + + for (unsigned int i = 0U; i < m_splitTXNames.GetCount(); i++) { + wxString name; + name.Printf(wxT("%s%u"), KEY_SPLIT_TXNAME.c_str(), i); + buffer.Printf(wxT("%s=%s"), name.c_str(), m_splitTXNames.Item(i).c_str()); + file.AddLine(buffer); + } + + for (unsigned int i = 0U; i < m_splitRXNames.GetCount(); i++) { + wxString name; + name.Printf(wxT("%s%u"), KEY_SPLIT_RXNAME.c_str(), i); + buffer.Printf(wxT("%s=%s"), name.c_str(), m_splitRXNames.Item(i).c_str()); + file.AddLine(buffer); + } + + buffer.Printf(wxT("%s=%u"), KEY_SPLIT_TIMEOUT.c_str(), m_splitTimeout); file.AddLine(buffer); + + bool ret = file.Write(); + if (!ret) { + file.Close(); + wxLogError(wxT("Cannot write the config file - %s"), m_fileName.GetFullPath().c_str()); + return false; + } + + file.Close(); + + return true; +} diff --git a/Common/DStarRepeaterConfig.h b/Common/DStarRepeaterConfig.h new file mode 100644 index 0000000..2f36d98 --- /dev/null +++ b/Common/DStarRepeaterConfig.h @@ -0,0 +1,243 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfig_H +#define DStarRepeaterConfig_H + +#include "DStarDefines.h" + +#include +#include +#include + +class CDStarRepeaterConfig { +public: +#if defined(__WINDOWS__) + CDStarRepeaterConfig(wxConfigBase* config, const wxString& dir, const wxString& configName, const wxString& name); +#else + CDStarRepeaterConfig(const wxString& dir, const wxString& configName, const wxString& name, const bool mustExist=false); +#endif + ~CDStarRepeaterConfig(); + + void getCallsign(wxString& callsign, wxString& gateway, DSTAR_MODE& mode, ACK_TYPE& ack, bool& restriction, bool& rpt1Validation, bool& dtmfBlanking, bool& errorReply) const; + void setCallsign(const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply); + + void getNetwork(wxString& gatewayAddress, unsigned int& gatewayPort, wxString& localAddress, unsigned int& localPort, wxString& name) const; + void setNetwork(const wxString& gatewayAddress, unsigned int gatewayPort, const wxString& localAddress, unsigned int localPort, const wxString& name); + + void getModem(wxString& type) const; + void setModem(const wxString& type); + + void getTimes(unsigned int& timeout, unsigned int& ackTime) const; + void setTimes(unsigned int timeout, unsigned int ackTime); + + void getBeacon(unsigned int& time, wxString& text, bool& voice, TEXT_LANG& language) const; + void setBeacon(unsigned int time, const wxString& text, bool voice, TEXT_LANG language); + + void getAnnouncement(bool& enabled, unsigned int& time, wxString& recordRPT1, wxString& recordRPT2, wxString& deleteRPT1, wxString& deleteRPT2) const; + void setAnnouncement(bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2); + + void getControl(bool& enabled, wxString& rpt1Callsign, wxString& rpt2Callsign, wxString& shutdown, wxString& startup, wxString& status1, wxString& status2, wxString& status3, wxString& status4, wxString& status5, wxString& command1, wxString& command1Line, wxString& command2, wxString& command2Line, wxString& command5, wxString& command5Line, wxString& command6, wxString& command6Line, wxString& command3, wxString& command3Line, wxString& command4, wxString& command4Line, wxString& output1, wxString& output2, wxString& output3, wxString& output4) const; + + void setControl(bool enabled, const wxString& rpt1Callsign, const wxString& rpt2Callsign, const wxString& shutdown, const wxString& startup, const wxString& status1, const wxString& status2, const wxString& status3, const wxString& status4, const wxString& status5, const wxString& command1, const wxString& command1Line, const wxString& command2, const wxString& command2Line, const wxString& command3, const wxString& command3Line, const wxString& command4, const wxString& command4Line, const wxString& command5, const wxString& command5Line, const wxString& command6, const wxString& command6Line, const wxString& output1, const wxString& output2, const wxString& output3, const wxString& output4); + + void getController(wxString& type, unsigned int& serialConfig, bool& pttInvert, unsigned int& activeHangTime) const; + void setController(const wxString& type, unsigned int serialConfig, bool pttInvert, unsigned int activeHangTime); + + void getOutputs(bool& out1, bool& out2, bool& out3, bool& out4) const; + void setOutputs(bool out1, bool out2, bool out3, bool out4); + + void getLogging(bool& logging) const; + void setLogging(bool logging); + + void getPosition(int& x, int& y) const; + void setPosition(int x, int y); + + void getDVAP(wxString& port, unsigned int& frequency, int& power, int& squelch) const; + void setDVAP(const wxString& port, unsigned int frequency, int power, int squelch); + + void getGMSK(USB_INTERFACE& type, unsigned int& address) const; + void setGMSK(USB_INTERFACE type, unsigned int address); + + void getDVRPTR1(wxString& port, bool& rxInvert, bool& txInvert, bool& channel, unsigned int& modLevel, unsigned int& txDelay) const; + void setDVRPTR1(const wxString& port, bool rxInvert, bool txInvert, bool channel, unsigned int modLevel, unsigned int txDelay); + + void getDVRPTR2(CONNECTION_TYPE& connectionType, wxString& usbPort, wxString& address, unsigned int& port, bool& txInvert, unsigned int& modLevel, unsigned int& txDelay) const; + void setDVRPTR2(CONNECTION_TYPE connectionType, const wxString& usbPort, const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, unsigned int txDelay); + + void getDVRPTR3(CONNECTION_TYPE& connectionType, wxString& usbPort, wxString& address, unsigned int& port, bool& txInvert, unsigned int& modLevel, unsigned int& txDelay) const; + void setDVRPTR3(CONNECTION_TYPE connectionType, const wxString& usbPort, const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, unsigned int txDelay); + + void getDVMEGA(wxString& port, DVMEGA_VARIANT& variant, bool& rxInvert, bool& txInvert, unsigned int& txDelay, unsigned int& rxFrequency, unsigned int& txFrequency, unsigned int& power) const; + void setDVMEGA(const wxString& port, DVMEGA_VARIANT variant, bool rxInvert, bool txInvert, unsigned int txDelay, unsigned int rxFrequency, unsigned int txFrequency, unsigned int power); + + void getMMDVM(wxString& port, bool& rxInvert, bool& txInvert, bool& pttInvert, unsigned int& txDelay, unsigned int& rxLevel, unsigned int& txLevel) const; + void setMMDVM(const wxString& port, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int rxLevel, unsigned int txLevel); + + void getSoundCard(wxString& rxDevice, wxString& txDevice, bool& rxInvert, bool& txInvert, wxFloat32& rxLevel, wxFloat32& txLevel, unsigned int& txDelay, unsigned int& txTail) const; + void setSoundCard(const wxString& rxDevice, const wxString& txDevice, bool rxInvert, bool txInvert, wxFloat32 rxLevel, wxFloat32 txLevel, unsigned int txDelay, unsigned int txTail); + + void getSplit(wxString& localAddress, unsigned int& localPort, wxArrayString& transmitterNames, wxArrayString& receiverNames, unsigned int& timeout) const; + void setSplit(const wxString& localAddress, unsigned int localPort, const wxArrayString& transmitterNames, const wxArrayString& receiverNames, unsigned int timeout); + + bool write(); + +private: +#if defined(__WINDOWS__) + wxConfigBase* m_config; + wxString m_name; +#endif + wxFileName m_fileName; + wxString m_callsign; + wxString m_gateway; + DSTAR_MODE m_mode; + ACK_TYPE m_ack; + bool m_restriction; + bool m_rpt1Validation; + bool m_dtmfBlanking; + bool m_errorReply; + wxString m_gatewayAddress; + unsigned int m_gatewayPort; + wxString m_localAddress; + unsigned int m_localPort; + wxString m_networkName; + wxString m_modemType; + unsigned int m_timeout; + unsigned int m_ackTime; + unsigned int m_beaconTime; + wxString m_beaconText; + bool m_beaconVoice; + TEXT_LANG m_language; + bool m_announcementEnabled; + unsigned int m_announcementTime; + wxString m_announcementRecordRPT1; + wxString m_announcementRecordRPT2; + wxString m_announcementDeleteRPT1; + wxString m_announcementDeleteRPT2; + bool m_controlEnabled; + wxString m_controlRpt1Callsign; + wxString m_controlRpt2Callsign; + wxString m_controlShutdown; + wxString m_controlStartup; + wxString m_controlStatus1; + wxString m_controlStatus2; + wxString m_controlStatus3; + wxString m_controlStatus4; + wxString m_controlStatus5; + wxString m_controlCommand1; + wxString m_controlCommand1Line; + wxString m_controlCommand2; + wxString m_controlCommand2Line; + wxString m_controlCommand3; + wxString m_controlCommand3Line; + wxString m_controlCommand4; + wxString m_controlCommand4Line; + wxString m_controlCommand5; + wxString m_controlCommand5Line; + wxString m_controlCommand6; + wxString m_controlCommand6Line; + wxString m_controlOutput1; + wxString m_controlOutput2; + wxString m_controlOutput3; + wxString m_controlOutput4; + wxString m_controllerType; + unsigned int m_serialConfig; + bool m_pttInvert; + unsigned int m_activeHangTime; + bool m_output1; + bool m_output2; + bool m_output3; + bool m_output4; + bool m_logging; + int m_x; + int m_y; + + // DVAP + wxString m_dvapPort; + unsigned int m_dvapFrequency; + int m_dvapPower; + int m_dvapSquelch; + + // GMSK + USB_INTERFACE m_gmskInterface; + unsigned int m_gmskAddress; + + // DV-RPTR 1 + wxString m_dvrptr1Port; + bool m_dvrptr1RXInvert; + bool m_dvrptr1TXInvert; + bool m_dvrptr1Channel; + unsigned int m_dvrptr1ModLevel; + unsigned int m_dvrptr1TXDelay; + + // DV-RPTR 2 + CONNECTION_TYPE m_dvrptr2Connection; + wxString m_dvrptr2USBPort; + wxString m_dvrptr2Address; + unsigned int m_dvrptr2Port; + bool m_dvrptr2TXInvert; + unsigned int m_dvrptr2ModLevel; + unsigned int m_dvrptr2TXDelay; + + // DV-RPTR 3 + CONNECTION_TYPE m_dvrptr3Connection; + wxString m_dvrptr3USBPort; + wxString m_dvrptr3Address; + unsigned int m_dvrptr3Port; + bool m_dvrptr3TXInvert; + unsigned int m_dvrptr3ModLevel; + unsigned int m_dvrptr3TXDelay; + + // DVMEGA + wxString m_dvmegaPort; + DVMEGA_VARIANT m_dvmegaVariant; + bool m_dvmegaRXInvert; + bool m_dvmegaTXInvert; + unsigned int m_dvmegaTXDelay; + unsigned int m_dvmegaRXFrequency; + unsigned int m_dvmegaTXFrequency; + unsigned int m_dvmegaPower; + + // MMDVM + wxString m_mmdvmPort; + bool m_mmdvmRXInvert; + bool m_mmdvmTXInvert; + bool m_mmdvmPTTInvert; + unsigned int m_mmdvmTXDelay; + unsigned int m_mmdvmRXLevel; + unsigned int m_mmdvmTXLevel; + + // Sound Card + wxString m_soundCardRXDevice; + wxString m_soundCardTXDevice; + bool m_soundCardRXInvert; + bool m_soundCardTXInvert; + wxFloat32 m_soundCardRXLevel; + wxFloat32 m_soundCardTXLevel; + unsigned int m_soundCardTXDelay; + unsigned int m_soundCardTXTail; + + // Split + wxString m_splitLocalAddress; + unsigned int m_splitLocalPort; + wxArrayString m_splitTXNames; + wxArrayString m_splitRXNames; + unsigned int m_splitTimeout; +}; + +#endif diff --git a/Common/DStarScrambler.cpp b/Common/DStarScrambler.cpp new file mode 100644 index 0000000..ad1c735 --- /dev/null +++ b/Common/DStarScrambler.cpp @@ -0,0 +1,139 @@ +/* + * Copyright (C) 2009 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "DStarScrambler.h" + +#include + +static const bool SCRAMBLER_TABLE_BITS[] = { + false, false, false, false, true, true, true, false, true, true, true, true, false, false, true, false, + true, true, false, false, true, false, false, true, false, false, false, false, false, false, true, false, + false, false, true, false, false, true, true, false, false, false, true, false, true, true, true, false, + true, false, true, true, false, true, true, false, false, false, false, false, true, true, false, false, + true, true, false, true, false, true, false, false, true, true, true, false, false, true, true, true, + true, false, true, true, false, true, false, false, false, false, true, false, true, false, true, false, + true, true, true, true, true, false, true, false, false, true, false, true, false, false, false, true, + true, false, true, true, true, false, false, false, true, true, true, true, true, true, true, false, + false, false, false, true, true, true, false, true, true, true, true, false, false, true, false, true, + true, false, false, true, false, false, true, false, false, false, false, false, false, true, false, false, + false, true, false, false, true, true, false, false, false, true, false, true, true, true, false, true, + false, true, true, false, true, true, false, false, false, false, false, true, true, false, false, true, + true, false, true, false, true, false, false, true, true, true, false, false, true, true, true, true, + false, true, true, false, true, false, false, false, false, true, false, true, false, true, false, true, + true, true, true, true, false, true, false, false, true, false, true, false, false, false, true, true, + false, true, true, true, false, false, false, true, true, true, true, true, true, true, false, false, + false, false, true, true, true, false, true, true, true, true, false, false, true, false, true, true, + false, false, true, false, false, true, false, false, false, false, false, false, true, false, false, false, + true, false, false, true, true, false, false, false, true, false, true, true, true, false, true, false, + true, true, false, true, true, false, false, false, false, false, true, true, false, false, true, true, + false, true, false, true, false, false, true, true, true, false, false, true, true, true, true, false, + true, true, false, true, false, false, false, false, true, false, true, false, true, false, true, true, + true, true, true, false, true, false, false, true, false, true, false, false, false, true, true, false, + true, true, true, false, false, false, true, true, true, true, true, true, true, false, false, false, + false, true, true, true, false, true, true, true, true, false, false, true, false, true, true, false, + false, true, false, false, true, false, false, false, false, false, false, true, false, false, false, true, + false, false, true, true, false, false, false, true, false, true, true, true, false, true, false, true, + true, false, true, true, false, false, false, false, false, true, true, false, false, true, true, false, + true, false, true, false, false, true, true, true, false, false, true, true, true, true, false, true, + true, false, true, false, false, false, false, true, false, true, false, true, false, true, true, true, + true, true, false, true, false, false, true, false, true, false, false, false, true, true, false, true, + true, true, false, false, false, true, true, true, true, true, true, true, false, false, false, false, + true, true, true, false, true, true, true, true, false, false, true, false, true, true, false, false, + true, false, false, true, false, false, false, false, false, false, true, false, false, false, true, false, + false, true, true, false, false, false, true, false, true, true, true, false, true, false, true, true, + false, true, true, false, false, false, false, false, true, true, false, false, true, true, false, true, + false, true, false, false, true, true, true, false, false, true, true, true, true, false, true, true, + false, true, false, false, false, false, true, false, true, false, true, false, true, true, true, true, + true, false, true, false, false, true, false, true, false, false, false, true, true, false, true, true, + true, false, false, false, true, true, true, true, true, true, true, false, false, false, false, true, + true, true, false, true, true, true, true, false, false, true, false, true, true, false, false, true, + false, false, true, false, false, false, false, false, false, true, false, false, false, true, false, false, + true, true, false, false, false, true, false, true, true, true, false, true, false, true, true, false, + true, true, false, false, false, false, false, true, true, false, false, true, true, false, true, false, + true, false, false, true, true, true, false, false, true, true, true, true, false, true, true, false}; + +static const unsigned int SCRAMBLER_TABLE_BITS_LENGTH = 720U; + +static const unsigned char SCRAMBLER_TABLE_BYTES[] = { + 0x0e, 0xf2, 0xc9, 0x02, 0x26, 0x2e, 0xb6, 0x0c, 0xd4, 0xe7, 0xb4, 0x2a, 0xfa, 0x51, 0xb8, 0xfe, + 0x1d, 0xe5, 0x92, 0x04, 0x4c, 0x5d, 0x6c, 0x19, 0xa9, 0xcf, 0x68, 0x55, 0xf4, 0xa3, 0x71, 0xfc, + 0x3b, 0xcb, 0x24, 0x08, 0x98, 0xba, 0xd8, 0x33, 0x53, 0x9e, 0xd0, 0xab, 0xe9, 0x46, 0xe3, 0xf8, + 0x77, 0x96, 0x48, 0x11, 0x31, 0x75, 0xb0, 0x66, 0xa7, 0x3d, 0xa1, 0x57, 0xd2, 0x8d, 0xc7, 0xf0, + 0xef, 0x2c, 0x90, 0x22, 0x62, 0xeb, 0x60, 0xcd, 0x4e, 0x7b, 0x42, 0xaf, 0xa5, 0x1b, 0x8f, 0xe1, + 0xde, 0x59, 0x20, 0x44, 0xc5, 0xd6, 0xc1, 0x9a, 0x9c, 0xf6}; + +static const unsigned int SCRAMBLER_TABLE_BYTES_LENGTH = 90U; + +CDStarScrambler::CDStarScrambler() : +m_count(0U) +{ +} + +CDStarScrambler::~CDStarScrambler() +{ +} + +void CDStarScrambler::process(bool* inOut, unsigned int length) +{ + wxASSERT(inOut != 0); + + for (unsigned int i = 0U; i < length; i++) { + inOut[i] ^= SCRAMBLER_TABLE_BITS[m_count++]; + + if (m_count >= SCRAMBLER_TABLE_BITS_LENGTH) + m_count = 0U; + } +} + +void CDStarScrambler::process(const bool* in, bool* out, unsigned int length) +{ + wxASSERT(in != 0); + wxASSERT(out != 0); + + for (unsigned int i = 0U; i < length; i++) { + out[i] = in[i] ^ SCRAMBLER_TABLE_BITS[m_count++]; + + if (m_count >= SCRAMBLER_TABLE_BITS_LENGTH) + m_count = 0U; + } +} + +void CDStarScrambler::process(unsigned char* inOut, unsigned int length) +{ + wxASSERT(inOut != 0); + + for (unsigned int i = 0U; i < length; i++) { + inOut[i] ^= SCRAMBLER_TABLE_BYTES[m_count++]; + + if (m_count >= SCRAMBLER_TABLE_BYTES_LENGTH) + m_count = 0U; + } +} + +void CDStarScrambler::process(const unsigned char* in, unsigned char* out, unsigned int length) +{ + wxASSERT(in != 0); + wxASSERT(out != 0); + + for (unsigned int i = 0U; i < length; i++) { + out[i] = in[i] ^ SCRAMBLER_TABLE_BYTES[m_count++]; + + if (m_count >= SCRAMBLER_TABLE_BYTES_LENGTH) + m_count = 0U; + } +} + +void CDStarScrambler::reset() +{ + m_count = 0U; +} diff --git a/Common/DStarScrambler.h b/Common/DStarScrambler.h new file mode 100644 index 0000000..40db775 --- /dev/null +++ b/Common/DStarScrambler.h @@ -0,0 +1,34 @@ +/* + * Copyright (C) 2009,2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef DStarScrambler_H +#define DStarScrambler_H + +class CDStarScrambler { +public: + CDStarScrambler(); + ~CDStarScrambler(); + + void process(bool* inOut, unsigned int length); + void process(const bool* in, bool* out, unsigned int length); + + void process(unsigned char* inOut, unsigned int length); + void process(const unsigned char* in, unsigned char* out, unsigned int length); + + void reset(); + +private: + unsigned int m_count; +}; + +#endif diff --git a/Common/DVAPController.cpp b/Common/DVAPController.cpp new file mode 100644 index 0000000..e8a0fee --- /dev/null +++ b/Common/DVAPController.cpp @@ -0,0 +1,1026 @@ +/* + * Copyright (C) 2011-2014,2016 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CCITTChecksumReverse.h" +#include "DVAPController.h" +#include "DStarDefines.h" +#include "Timer.h" + +const unsigned char DVAP_REQ_NAME[] = {0x04, 0x20, 0x01, 0x00}; +const unsigned int DVAP_REQ_NAME_LEN = 4U; + +const unsigned char DVAP_RESP_NAME[] = {0x10, 0x00, 0x01, 0x00, 'D', 'V', 'A', 'P', ' ', 'D', 'o', 'n', 'g', 'l', 'e', 0x00}; +const unsigned int DVAP_RESP_NAME_LEN = 16U; + +const unsigned char DVAP_REQ_SERIAL[] = {0x04, 0x20, 0x02, 0x00}; +const unsigned int DVAP_REQ_SERIAL_LEN = 4U; + +const unsigned char DVAP_RESP_SERIAL[] = {0x0C, 0x00, 0x02, 0x00}; +const unsigned int DVAP_RESP_SERIAL_LEN = 4U; + +const unsigned char DVAP_REQ_FIRMWARE[] = {0x05, 0x20, 0x04, 0x00, 0x01}; +const unsigned int DVAP_REQ_FIRMWARE_LEN = 5U; + +const unsigned char DVAP_RESP_FIRMWARE[] = {0x07, 0x00, 0x04, 0x00, 0x01, 0x00, 0x00}; +const unsigned int DVAP_RESP_FIRMWARE_LEN = 7U; + +const unsigned char DVAP_REQ_MODULATION[] = {0x05, 0x00, 0x28, 0x00, 0x01}; +const unsigned int DVAP_REQ_MODULATION_LEN = 5U; + +const unsigned char DVAP_RESP_MODULATION[] = {0x05, 0x00, 0x28, 0x00, 0x01}; +const unsigned int DVAP_RESP_MODULATION_LEN = 5U; + +const unsigned char DVAP_REQ_MODE[] = {0x05, 0x00, 0x2A, 0x00, 0x00}; +const unsigned int DVAP_REQ_MODE_LEN = 5U; + +const unsigned char DVAP_RESP_MODE[] = {0x05, 0x00, 0x2A, 0x00, 0x00}; +const unsigned int DVAP_RESP_MODE_LEN = 5U; + +const unsigned char DVAP_REQ_SQUELCH[] = {0x05, 0x00, 0x80, 0x00, 0x00}; +const unsigned int DVAP_REQ_SQUELCH_LEN = 5U; + +const unsigned char DVAP_RESP_SQUELCH[] = {0x05, 0x00, 0x80, 0x00, 0x00}; +const unsigned int DVAP_RESP_SQUELCH_LEN = 5U; + +const unsigned char DVAP_REQ_POWER[] = {0x06, 0x00, 0x38, 0x01, 0x00, 0x00}; +const unsigned int DVAP_REQ_POWER_LEN = 6U; + +const unsigned char DVAP_RESP_POWER[] = {0x06, 0x00, 0x38, 0x01, 0x00, 0x00}; +const unsigned int DVAP_RESP_POWER_LEN = 6U; + +const unsigned char DVAP_REQ_FREQUENCY[] = {0x08, 0x00, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00}; +const unsigned int DVAP_REQ_FREQUENCY_LEN = 8U; + +const unsigned char DVAP_RESP_FREQUENCY[] = {0x08, 0x00, 0x20, 0x02, 0x00, 0x00, 0x00, 0x00}; +const unsigned int DVAP_RESP_FREQUENCY_LEN = 8U; + +const unsigned char DVAP_REQ_FREQLIMITS[] = {0x04, 0x20, 0x30, 0x02}; +const unsigned int DVAP_REQ_FREQLIMITS_LEN = 4U; + +const unsigned char DVAP_RESP_FREQLIMITS[] = {0x0C, 0x00, 0x30, 0x02}; +const unsigned int DVAP_RESP_FREQLIMITS_LEN = 4U; + +const unsigned char DVAP_REQ_START[] = {0x05, 0x00, 0x18, 0x00, 0x01}; +const unsigned int DVAP_REQ_START_LEN = 5U; + +const unsigned char DVAP_RESP_START[] = {0x05, 0x00, 0x18, 0x00, 0x01}; +const unsigned int DVAP_RESP_START_LEN = 5U; + +const unsigned char DVAP_REQ_STOP[] = {0x05, 0x00, 0x18, 0x00, 0x00}; +const unsigned int DVAP_REQ_STOP_LEN = 5U; + +const unsigned char DVAP_RESP_STOP[] = {0x05, 0x00, 0x18, 0x00, 0x00}; +const unsigned int DVAP_RESP_STOP_LEN = 5U; + +const unsigned char DVAP_HEADER[] = {0x2F, 0xA0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const unsigned int DVAP_HEADER_LEN = 47U; + +const unsigned char DVAP_RESP_HEADER[] = {0x2F, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const unsigned int DVAP_RESP_HEADER_LEN = 47U; + +const unsigned char DVAP_RESP_PTT[] = {0x05, 0x20, 0x18, 0x01, 0x00}; +const unsigned int DVAP_RESP_PTT_LEN = 5U; + +const unsigned char DVAP_GMSK_DATA[] = {0x12, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +const unsigned int DVAP_GMSK_DATA_LEN = 18U; + +const unsigned char DVAP_STATUS[] = {0x07, 0x20, 0x90, 0x00, 0x00, 0x00, 0x00}; +const unsigned int DVAP_STATUS_LEN = 7U; + +const unsigned char DVAP_ACK[] = {0x03, 0x60, 0x00}; +const unsigned int DVAP_ACK_LEN = 3U; + +const unsigned char DVAP_FM_DATA[] = {0x42, 0x81}; +const unsigned int DVAP_FM_DATA_LEN = 2U; + +const unsigned int DVAP_HEADER_LENGTH = 2U; + +const unsigned int MAX_RESPONSES = 20U; + +const unsigned int BUFFER_LENGTH = 200U; + +const unsigned int DVAP_DUMP_LENGTH = 30U; + +CDVAPController::CDVAPController(const wxString& port, unsigned int frequency, int power, int squelch) : +CModem(), +m_serial(port, SERIAL_230400), +m_frequency(frequency), +m_power(power), +m_squelch(squelch), +m_squelchOpen(false), +m_signal(0), +m_buffer(NULL), +m_streamId(0U), +m_framePos(0U), +m_seq(0U), +m_txData(1000U) +#if defined(DVAP_DUMP) +, +m_dvapData(NULL), +m_dvapLength(NULL), +m_dvapIndex(0U) +#endif +{ + wxASSERT(!port.IsEmpty()); + wxASSERT((frequency >= 144000000U && frequency <= 148000000U) || + (frequency >= 220000000U && frequency <= 225000000U) || + (frequency >= 420000000U && frequency <= 450000000U)); + wxASSERT(power >= -12 && power <= 10); + wxASSERT(squelch >= -128 && squelch <= -45); + + m_buffer = new unsigned char[BUFFER_LENGTH]; + +#if defined(DVAP_DUMP) + m_dvapData = new unsigned char*[DVAP_DUMP_LENGTH]; + for (unsigned int i = 0U; i < DVAP_DUMP_LENGTH; i++) + m_dvapData[i] = new unsigned char[100U]; + + m_dvapLength = new unsigned int[DVAP_DUMP_LENGTH]; + for (unsigned int i = 0U; i < DVAP_DUMP_LENGTH; i++) + m_dvapLength[i] = 0U; +#endif +} + +CDVAPController::~CDVAPController() +{ + delete[] m_buffer; + +#if defined(DVAP_DUMP) + for (unsigned int i = 0U; i < DVAP_DUMP_LENGTH; i++) + delete[] m_dvapData[i]; + delete[] m_dvapData; + + delete[] m_dvapLength; +#endif +} + +bool CDVAPController::start() +{ + bool res = m_serial.open(); + if (!res) + return false; + + res = getName(); + if (!res) { + m_serial.close(); + return false; + } + + res = getFirmware(); + if (!res) { + m_serial.close(); + return false; + } + + res = getSerial(); + if (!res) { + m_serial.close(); + return false; + } + + res = setModulation(); + if (!res) { + m_serial.close(); + return false; + } + + res = setMode(); + if (!res) { + m_serial.close(); + return false; + } + + res = setSquelch(); + if (!res) { + m_serial.close(); + return false; + } + + res = setPower(); + if (!res) { + m_serial.close(); + return false; + } + + res = setFrequency(); + if (!res) { + m_serial.close(); + return false; + } + + res = startDVAP(); + if (!res) { + m_serial.close(); + return false; + } + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CDVAPController::Entry() +{ + wxLogMessage(wxT("Starting DVAP Controller thread")); + + // Clock every 5ms-ish + CTimer pollTimer(200U, 2U); + pollTimer.start(); + + unsigned char writeLength = 0U; + unsigned char* writeBuffer = new unsigned char[BUFFER_LENGTH]; + + unsigned int space = 0U; + + while (!m_stopped) { + // Poll the modem every 2s + if (pollTimer.hasExpired()) { + writePoll(); + pollTimer.start(); + } + + unsigned int length; + RESP_TYPE type = getResponse(m_buffer, length); + + switch (type) { + case RT_TIMEOUT: + break; + case RT_ERROR: + wxLogMessage(wxT("Stopping DVAP Controller thread")); +#if defined(DVAP_DUMP) + dumpPackets(); +#endif + m_serial.close(); + return NULL; + case RT_STATE: + m_signal = int(m_buffer[4U]) - 256; + m_squelchOpen = m_buffer[5U] == 0x01U; + space = m_buffer[6U]; + break; + case RT_PTT: + m_tx = m_buffer[4U] == 0x01U; + break; + case RT_START: + break; + case RT_STOP: + wxLogWarning(wxT("DVAP has stopped, restarting")); +#if defined(DVAP_DUMP) + dumpPackets(); +#endif + startDVAP(); + break; + case RT_HEADER: { + wxMutexLocker locker(m_mutex); + + unsigned char hdr[2U]; + hdr[0U] = DSMTT_HEADER; + hdr[1U] = RADIO_HEADER_LENGTH_BYTES; + m_rxData.addData(hdr, 2U); + + m_rxData.addData(m_buffer + 6U, RADIO_HEADER_LENGTH_BYTES); + } + break; + case RT_HEADER_ACK: + break; + case RT_GMSK_DATA: { + wxMutexLocker locker(m_mutex); + + bool end = (m_buffer[4U] & 0x40U) == 0x40U; + if (end) { + unsigned char hdr[2U]; + hdr[0U] = DSMTT_EOT; + hdr[1U] = 0U; + m_rxData.addData(hdr, 2U); + } else { + unsigned char hdr[2U]; + hdr[0U] = DSMTT_DATA; + hdr[1U] = length - 6U; + m_rxData.addData(hdr, 2U); + + m_rxData.addData(m_buffer + 6U, length - 6U); + } + } + break; + case RT_FM_DATA: + wxLogWarning(wxT("The DVAP has gone into FM mode, restarting the DVAP")); +#if defined(DVAP_DUMP) + dumpPackets(); +#endif + stopDVAP(); + setModulation(); + startDVAP(); + break; + default: + wxLogMessage(wxT("Unknown message")); + CUtils::dump(wxT("Buffer dump"), m_buffer, length); +#if defined(DVAP_DUMP) + dumpPackets(); +#endif + break; + } + + // Use the status packet every 20ms to trigger the sending of data to the DVAP + if (space > 0U && type == RT_STATE) { + if (writeLength == 0U && m_txData.hasData()) { + wxMutexLocker locker(m_mutex); + + m_txData.getData(&writeLength, 1U); + m_txData.getData(writeBuffer, writeLength); + } + + // Only send the header when the TX is off + if (!m_tx && writeLength == DVAP_HEADER_LEN) { + // CUtils::dump(wxT("Write Header"), writeBuffer, writeLength); + +#if defined(DVAP_DUMP) + storePacket(writeBuffer, writeLength); +#endif + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing the header to the DVAP")); + + writeLength = 0U; + space--; + } + + if (writeLength == DVAP_GMSK_DATA_LEN) { + // CUtils::dump(wxT("Write Data"), writeBuffer, writeLength); + +#if defined(DVAP_DUMP) + storePacket(writeBuffer, writeLength); +#endif + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing data to the DVAP")); + + writeLength = 0U; + space--; + } + } + + Sleep(5UL); + + pollTimer.clock(); + } + + wxLogMessage(wxT("Stopping DVAP Controller thread")); + + stopDVAP(); + + delete[] writeBuffer; + + m_serial.close(); + + return NULL; +} + +bool CDVAPController::writeHeader(const CHeaderData& header) +{ + bool ret = m_txData.hasSpace(DVAP_HEADER_LEN + 1U); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + m_streamId++; + + unsigned char buffer[50U]; + + ::memcpy(buffer, DVAP_HEADER, DVAP_HEADER_LEN); + + wxUint16 sid = wxUINT16_SWAP_ON_BE(m_streamId); + ::memcpy(buffer + 2U, &sid, sizeof(wxUint16)); + + buffer[4U] = 0x80U; + buffer[5U] = 0U; + + ::memset(buffer + 6U, ' ', RADIO_HEADER_LENGTH_BYTES); + + buffer[6U] = header.getFlag1(); + buffer[7U] = header.getFlag2(); + buffer[8U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 9U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 17U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 25U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 33U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer[i + 41U] = my2.GetChar(i); + + CCCITTChecksumReverse cksum; + cksum.update(buffer + 6U, RADIO_HEADER_LENGTH_BYTES - 2U); + cksum.result(buffer + 45U); + + m_framePos = 0U; + m_seq = 0U; + + wxMutexLocker locker(m_mutex); + + unsigned char len = DVAP_HEADER_LEN; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, DVAP_HEADER_LEN); + + return true; +} + +bool CDVAPController::writeData(const unsigned char* data, unsigned int, bool end) +{ + bool ret = m_txData.hasSpace(DVAP_GMSK_DATA_LEN + 1U); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + unsigned char buffer[20U]; + + ::memcpy(buffer + 0U, DVAP_GMSK_DATA, DVAP_GMSK_DATA_LEN); + + if (::memcmp(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES) == 0) + m_framePos = 0U; + + wxUint16 sid = wxUINT16_SWAP_ON_BE(m_streamId); + ::memcpy(buffer + 2U, &sid, sizeof(wxUint16)); + + buffer[4U] = m_framePos; + buffer[5U] = m_seq; + + if (end) + buffer[4U] |= 0x40U; + + ::memcpy(buffer + 6U, data, DV_FRAME_LENGTH_BYTES); + + wxMutexLocker locker(m_mutex); + + unsigned char len = DVAP_GMSK_DATA_LEN; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, DVAP_GMSK_DATA_LEN); + + m_framePos++; + m_seq++; + + return true; +} + +unsigned int CDVAPController::getSpace() +{ + return m_txData.freeSpace() / (DVAP_GMSK_DATA_LEN + 1U); +} + +bool CDVAPController::isTXReady() +{ + if (m_tx) + return false; + + return m_txData.isEmpty(); +} + +bool CDVAPController::getSquelch() const +{ + return m_squelchOpen; +} + +int CDVAPController::getSignal() const +{ + return m_signal; +} + +void CDVAPController::writePoll() +{ +#if defined(DVAP_DUMP) + storePacket(DVAP_ACK, DVAP_ACK_LEN); +#endif + m_serial.write(DVAP_ACK, DVAP_ACK_LEN); +} + +bool CDVAPController::getName() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + int ret = m_serial.write(DVAP_REQ_NAME, DVAP_REQ_NAME_LEN); + if (ret != int(DVAP_REQ_NAME_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_NAME) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The Dongle is not replying with its name")); + return false; + } + } + } while (resp != RT_NAME); + + bool cmp = ::memcmp(m_buffer, DVAP_RESP_NAME, length) == 0; + if (!cmp) { + wxLogError(wxT("The Dongle is not responding as a DVAP")); + return false; + } + + return true; +} + +bool CDVAPController::getFirmware() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + int ret = m_serial.write(DVAP_REQ_FIRMWARE, DVAP_REQ_FIRMWARE_LEN); + if (ret != int(DVAP_REQ_FIRMWARE_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_FIRMWARE) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding with its firmware version")); + return false; + } + } + } while (resp != RT_FIRMWARE); + + unsigned int version = m_buffer[6U] * 256U + m_buffer[5U]; + wxLogInfo(wxT("DVAP Firmware version: %u.%u"), version / 100U, version % 100U); + + return true; +} + +bool CDVAPController::getSerial() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + int ret = m_serial.write(DVAP_REQ_SERIAL, DVAP_REQ_SERIAL_LEN); + if (ret != int(DVAP_REQ_SERIAL_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_SERIAL) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding with its serial number")); + return false; + } + } + } while (resp != RT_SERIAL); + + wxString serial((char*)(m_buffer + 4U), wxConvLocal, length - 5U); + wxLogInfo(wxT("DVAP Serial number: %s"), serial.c_str()); + + return true; +} + +bool CDVAPController::startDVAP() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + int ret = m_serial.write(DVAP_REQ_START, DVAP_REQ_START_LEN); + if (ret != int(DVAP_REQ_START_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_START) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding to the start command")); + return false; + } + } + } while (resp != RT_START); + + return true; +} + +bool CDVAPController::stopDVAP() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + int ret = m_serial.write(DVAP_REQ_STOP, DVAP_REQ_STOP_LEN); + if (ret != int(DVAP_REQ_STOP_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_STOP) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding to the stop command")); + return false; + } + } + } while (resp != RT_STOP); + + return true; +} + +bool CDVAPController::setModulation() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + int ret = m_serial.write(DVAP_REQ_MODULATION, DVAP_REQ_MODULATION_LEN); + if (ret != int(DVAP_REQ_MODULATION_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_MODULATION) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding to the modulation command")); + return false; + } + } + } while (resp != RT_MODULATION); + + return true; +} + +bool CDVAPController::setMode() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + int ret = m_serial.write(DVAP_REQ_MODE, DVAP_REQ_MODE_LEN); + if (ret != int(DVAP_REQ_MODE_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_MODE) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding to the mode command")); + return false; + } + } + } while (resp != RT_MODE); + + return true; +} + +bool CDVAPController::setSquelch() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + unsigned char buffer[10U]; + ::memcpy(buffer, DVAP_REQ_SQUELCH, DVAP_REQ_SQUELCH_LEN); + ::memcpy(buffer + 4U, &m_squelch, sizeof(wxInt8)); + + int ret = m_serial.write(buffer, DVAP_REQ_SQUELCH_LEN); + if (ret != int(DVAP_REQ_SQUELCH_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_SQUELCH) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding to the squelch command")); + return false; + } + } + } while (resp != RT_SQUELCH); + + return true; +} + +bool CDVAPController::setPower() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + unsigned char buffer[10U]; + ::memcpy(buffer, DVAP_REQ_POWER, DVAP_REQ_POWER_LEN); + + wxInt16 power = wxINT16_SWAP_ON_BE(m_power); + ::memcpy(buffer + 4U, &power, sizeof(wxInt16)); + + int ret = m_serial.write(buffer, DVAP_REQ_POWER_LEN); + if (ret != int(DVAP_REQ_POWER_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_POWER) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding to the power command")); + return false; + } + } + } while (resp != RT_POWER); + + return true; +} + +bool CDVAPController::setFrequency() +{ + unsigned int count = 0U; + unsigned int length; + RESP_TYPE resp; + do { + int ret = m_serial.write(DVAP_REQ_FREQLIMITS, DVAP_REQ_FREQLIMITS_LEN); + if (ret != int(DVAP_REQ_FREQLIMITS_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_FREQLIMITS) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding to the frequency limits command")); + return false; + } + } + } while (resp != RT_FREQLIMITS); + + wxUint32* pFreq1 = (wxUint32*)(m_buffer + 4U); + wxUint32* pFreq2 = (wxUint32*)(m_buffer + 8U); + + wxUint32 lower = wxUINT32_SWAP_ON_BE(*pFreq1); + wxUint32 upper = wxUINT32_SWAP_ON_BE(*pFreq2); + + wxLogInfo(wxT("DVAP frequency limits are %u Hz to %u Hz"), lower, upper); + + if (m_frequency < lower || m_frequency > upper) { + wxLogError(wxT("The required frequency is out of the range of the DVAP hardware")); + m_serial.close(); + return false; + } + + count = 0U; + do { + unsigned char buffer[10U]; + ::memcpy(buffer, DVAP_REQ_FREQUENCY, DVAP_REQ_FREQUENCY_LEN); + + wxUint32 frequency = wxUINT32_SWAP_ON_BE(m_frequency); + ::memcpy(buffer + 4U, &frequency, sizeof(wxUint32)); + + int ret = m_serial.write(buffer, DVAP_REQ_FREQUENCY_LEN); + if (ret != int(DVAP_REQ_FREQUENCY_LEN)) { + m_serial.close(); + return false; + } + + ::wxMilliSleep(50UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT_FREQUENCY) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVAP is not responding to the frequency command")); + return false; + } + } + } while (resp != RT_FREQUENCY); + + return true; +} + +RESP_TYPE CDVAPController::getResponse(unsigned char *buffer, unsigned int& length) +{ + int ret = m_serial.read(buffer, DVAP_HEADER_LENGTH); + if (ret == 0) + return RT_TIMEOUT; + if (ret != int(DVAP_HEADER_LENGTH)) + return RT_ERROR; + + unsigned int offset = DVAP_HEADER_LENGTH; + + // First byte has gone missing from the operation status message, fix it + if (buffer[0U] == 0x20U && buffer[1U] == 0x90U) { + buffer[0U] = 0x07U; + buffer[1U] = 0x20U; + buffer[2U] = 0x90U; + offset = DVAP_HEADER_LENGTH + 1U; + } + + length = buffer[0U] + (buffer[1U] & 0x1FU) * 256U; + + // Check for silliness + if (length > 50U) { + CUtils::dump(wxT("Bad DVAP header"), buffer, DVAP_HEADER_LENGTH); +#if defined(DVAP_DUMP) + dumpPackets(); +#endif + resync(); + return RT_TIMEOUT; + } + + while (offset < length) { + ret = m_serial.read(buffer + offset, length - offset); + if (ret < 0) + return RT_ERROR; + if (ret > 0) + offset += ret; + if (ret == 0) + Sleep(5UL); + } + + // CUtils::dump(wxT("Received"), buffer, length); + + if (::memcmp(buffer, DVAP_STATUS, 4U) == 0) + return RT_STATE; + else if (::memcmp(buffer, DVAP_GMSK_DATA, 2U) == 0) + return RT_GMSK_DATA; + else if (::memcmp(buffer, DVAP_HEADER, 2U) == 0) + return RT_HEADER; + else if (::memcmp(buffer, DVAP_RESP_HEADER, 2U) == 0) + return RT_HEADER_ACK; + else if (::memcmp(buffer, DVAP_RESP_PTT, 4U) == 0) + return RT_PTT; + else if (::memcmp(buffer, DVAP_ACK, DVAP_ACK_LEN) == 0) + return RT_ACK; + else if (::memcmp(buffer, DVAP_FM_DATA, 2U) == 0) + return RT_FM_DATA; + else if (::memcmp(buffer, DVAP_RESP_START, DVAP_RESP_START_LEN) == 0) + return RT_START; + else if (::memcmp(buffer, DVAP_RESP_STOP, DVAP_RESP_STOP_LEN) == 0) + return RT_STOP; + else if (::memcmp(buffer, DVAP_RESP_NAME, 4U) == 0) + return RT_NAME; + else if (::memcmp(buffer + 1U, DVAP_RESP_SERIAL + 1U, 3U) == 0) + return RT_SERIAL; + else if (::memcmp(buffer, DVAP_RESP_FIRMWARE, 5U) == 0) + return RT_FIRMWARE; + else if (::memcmp(buffer, DVAP_RESP_FREQUENCY, 4U) == 0) + return RT_FREQUENCY; + else if (::memcmp(buffer, DVAP_RESP_FREQLIMITS, 4U) == 0) + return RT_FREQLIMITS; + else if (::memcmp(buffer, DVAP_RESP_MODULATION, DVAP_RESP_MODULATION_LEN) == 0) + return RT_MODULATION; + else if (::memcmp(buffer, DVAP_RESP_MODE, DVAP_RESP_MODE_LEN) == 0) + return RT_MODE; + else if (::memcmp(buffer, DVAP_RESP_POWER, 4U) == 0) + return RT_POWER; + else if (::memcmp(buffer, DVAP_RESP_SQUELCH, 4U) == 0) + return RT_SQUELCH; + else { + CUtils::dump(wxT("Bad DVAP data"), buffer, length); +#if defined(DVAP_DUMP) + dumpPackets(); +#endif + resync(); + return RT_TIMEOUT; + } +} + +void CDVAPController::resync() +{ + wxLogWarning(wxT("Resynchronising the DVAP data stream")); + + unsigned char data[DVAP_STATUS_LEN]; + ::memset(data, 0x00U, DVAP_STATUS_LEN); + + while (::memcmp(data, DVAP_STATUS, 4U) != 0) { + unsigned char c; + int n = m_serial.read(&c, 1U); + if (n > 0) { + data[0U] = data[1U]; + data[1U] = data[2U]; + data[2U] = data[3U]; + data[3U] = data[4U]; + data[4U] = data[5U]; + data[5U] = data[6U]; + data[6U] = c; + + // CUtils::dump(wxT("Resync buffer"), data, DVAP_STATUS_LEN); + } + } + + wxLogMessage(wxT("End resynchronising")); +} + +#if defined(DVAP_DUMP) + +void CDVAPController::storePacket(const unsigned char* data, unsigned int length) +{ + ::memcpy(m_dvapData[m_dvapIndex], data, length); + m_dvapLength[m_dvapIndex] = length; + + m_dvapIndex++; + if (m_dvapIndex >= DVAP_DUMP_LENGTH) + m_dvapIndex = 0U; +} + +void CDVAPController::dumpPackets() +{ + unsigned int n = m_dvapIndex; + unsigned int i = 0U; + while (n != m_dvapIndex) { + if (m_dvapLength[n] > 0U) { + wxString text; + text.Printf(wxT("Packet: %u"), i); + + CUtils::dump(text, m_dvapData[n], m_dvapLength[n]); + } + + i++; + n++; + if (n >= DVAP_DUMP_LENGTH) + n = 0U; + } + + m_dvapIndex = 0U; +} + +#endif diff --git a/Common/DVAPController.h b/Common/DVAPController.h new file mode 100644 index 0000000..0e5881b --- /dev/null +++ b/Common/DVAPController.h @@ -0,0 +1,118 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DVAPController_H +#define DVAPController_H + +#define DVAP_DUMP + +#include "SerialDataController.h" +#include "RingBuffer.h" +#include "HeaderData.h" +#include "Modem.h" +#include "Utils.h" + +#include + +enum RESP_TYPE { + RT_TIMEOUT, + RT_ERROR, + RT_UNKNOWN, + RT_NAME, + RT_SERIAL, + RT_FIRMWARE, + RT_START, + RT_STOP, + RT_MODULATION, + RT_MODE, + RT_SQUELCH, + RT_POWER, + RT_FREQUENCY, + RT_FREQLIMITS, + RT_STATE, + RT_PTT, + RT_ACK, + RT_HEADER, + RT_HEADER_ACK, + RT_GMSK_DATA, + RT_FM_DATA +}; + +class CDVAPController : public CModem { +public: + CDVAPController(const wxString& port, unsigned int frequency, int power, int squelch); + virtual ~CDVAPController(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + + virtual bool getSquelch() const; + virtual int getSignal() const; + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + +private: + CSerialDataController m_serial; + wxUint32 m_frequency; + wxInt16 m_power; + wxInt8 m_squelch; + bool m_squelchOpen; + int m_signal; + unsigned char* m_buffer; + wxUint16 m_streamId; + wxUint8 m_framePos; + wxUint8 m_seq; + CRingBuffer m_txData; +#if defined(DVAP_DUMP) + unsigned char** m_dvapData; + unsigned int* m_dvapLength; + unsigned int m_dvapIndex; +#endif + + bool getName(); + bool getFirmware(); + bool getSerial(); + + bool setModulation(); + bool setMode(); + + bool setSquelch(); + bool setPower(); + bool setFrequency(); + + bool startDVAP(); + bool stopDVAP(); + + void writePoll(); + + void resync(); + + RESP_TYPE getResponse(unsigned char* buffer, unsigned int& length); + +#if defined(DVAP_DUMP) + void storePacket(const unsigned char* data, unsigned int length); + void dumpPackets(); +#endif +}; + +#endif diff --git a/Common/DVMegaController.cpp b/Common/DVMegaController.cpp new file mode 100644 index 0000000..f5f1e02 --- /dev/null +++ b/Common/DVMegaController.cpp @@ -0,0 +1,1149 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CCITTChecksumReverse.h" +#include "DVMegaController.h" +#include "CCITTChecksum.h" +#include "DStarDefines.h" +#include "Timer.h" + +#if defined(__WINDOWS__) +#include +#else +#include +#endif + +const unsigned char DVRPTR_HEADER_LENGTH = 5U; + +const unsigned char DVRPTR_FRAME_START = 0xD0U; + +const unsigned char DVRPTR_GET_STATUS = 0x10U; +const unsigned char DVRPTR_GET_VERSION = 0x11U; +const unsigned char DVRPTR_GET_SERIAL = 0x12U; +const unsigned char DVRPTR_GET_CONFIG = 0x13U; +const unsigned char DVRPTR_SET_CONFIG = 0x14U; +const unsigned char DVRPTR_RXPREAMBLE = 0x15U; +const unsigned char DVRPTR_START = 0x16U; +const unsigned char DVRPTR_HEADER = 0x17U; +const unsigned char DVRPTR_RXSYNC = 0x18U; +const unsigned char DVRPTR_DATA = 0x19U; +const unsigned char DVRPTR_EOT = 0x1AU; +const unsigned char DVRPTR_RXLOST = 0x1BU; +const unsigned char DVRPTR_MSG_RSVD1 = 0x1CU; +const unsigned char DVRPTR_MSG_RSVD2 = 0x1DU; +const unsigned char DVRPTR_MSG_RSVD3 = 0x1EU; +const unsigned char DVRPTR_SET_TESTMDE = 0x1FU; + +const unsigned char DVRPTR_ACK = 0x06U; +const unsigned char DVRPTR_NAK = 0x15U; + +const unsigned int MAX_RESPONSES = 30U; + +const unsigned int BUFFER_LENGTH = 200U; + +CDVMegaController::CDVMegaController(const wxString& port, const wxString& path, bool rxInvert, bool txInvert, unsigned int txDelay) : +CModem(), +m_port(port), +m_path(path), +m_rxInvert(rxInvert), +m_txInvert(txInvert), +m_txDelay(txDelay), +m_rxFrequency(0U), +m_txFrequency(0U), +m_power(0U), +m_serial(port, SERIAL_115200, true), +m_buffer(NULL), +m_txData(1000U), +m_txCounter(0U), +m_pktCounter(0U), +m_rx(false), +m_txSpace(0U), +m_txEnabled(false), +m_checksum(false) +{ + wxASSERT(!port.IsEmpty()); + + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CDVMegaController::CDVMegaController(const wxString& port, const wxString& path, unsigned int txDelay, unsigned int rxFrequency, unsigned int txFrequency, unsigned int power) : +CModem(), +m_port(port), +m_path(path), +m_rxInvert(false), +m_txInvert(false), +m_txDelay(txDelay), +m_rxFrequency(rxFrequency), +m_txFrequency(txFrequency), +m_power(power), +m_serial(port, SERIAL_115200, true), +m_buffer(NULL), +m_txData(1000U), +m_txCounter(0U), +m_pktCounter(0U), +m_rx(false), +m_txSpace(0U), +m_txEnabled(false), +m_checksum(false) +{ + wxASSERT(!port.IsEmpty()); + wxASSERT((rxFrequency >= 144000000U && rxFrequency <= 148000000U) || + (rxFrequency >= 420000000U && rxFrequency <= 450000000U)); + wxASSERT((txFrequency >= 144000000U && txFrequency <= 148000000U) || + (txFrequency >= 420000000U && txFrequency <= 450000000U)); + + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CDVMegaController::~CDVMegaController() +{ + delete[] m_buffer; +} + +bool CDVMegaController::start() +{ + findPort(); + + bool ret = openModem(); + if (!ret) + return false; + + findPath(); + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CDVMegaController::Entry() +{ + wxLogMessage(wxT("Starting DVMEGA Controller thread")); + + // Clock every 5ms-ish + CTimer pollTimer(200U, 0U, 100U); + pollTimer.start(); + + unsigned char writeType = DSMTT_NONE; + unsigned char writeLength = 0U; + unsigned char* writeBuffer = new unsigned char[BUFFER_LENGTH]; + + unsigned int space = 0U; + + while (!m_stopped) { + // Poll the modem status every 100ms + if (pollTimer.hasExpired()) { + bool ret = readStatus(); + if (!ret) { + ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DVMEGA Controller thread")); + return NULL; + } + } + + pollTimer.start(); + } + + unsigned int length; + RESP_TYPE_MEGA type = getResponse(m_buffer, length); + + switch (type) { + case RTM_TIMEOUT: + break; + + case RTM_ERROR: { + bool ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DVMEGA Controller thread")); + return NULL; + } + } + break; + + case RTM_RXPREAMBLE: + // wxLogMessage(wxT("RT_PREAMBLE")); + break; + + case RTM_START: + // wxLogMessage(wxT("RT_START")); + break; + + case RTM_HEADER: + // CUtils::dump(wxT("RT_HEADER"), m_buffer, length); + if (length == 7U) { + if (m_buffer[4U] == DVRPTR_NAK) + wxLogWarning(wxT("Received a header NAK from the DVMEGA")); + } else { + bool correct = (m_buffer[5U] & 0x80U) == 0x00U; + if (correct) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES); + + m_rx = true; + } + } + break; + + case RTM_RXSYNC: + // wxLogMessage(wxT("RT_RXSYNC")); + break; + + case RTM_DATA: + // CUtils::dump(wxT("RT_DATA"), m_buffer, length); + if (length == 7U) { + if (m_buffer[4U] == DVRPTR_NAK) + wxLogWarning(wxT("Received a data NAK from the DVMEGA")); + } else { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 8U, DV_FRAME_LENGTH_BYTES); + + m_rx = true; + } + break; + + case RTM_EOT: { + // wxLogMessage(wxT("RT_EOT")); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + break; + + case RTM_RXLOST: { + // wxLogMessage(wxT("RT_LOST")); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_LOST; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + break; + + case RTM_GET_STATUS: { + m_txEnabled = (m_buffer[4U] & 0x02U) == 0x02U; + m_checksum = (m_buffer[4U] & 0x08U) == 0x08U; + m_tx = (m_buffer[5U] & 0x02U) == 0x02U; + m_txSpace = m_buffer[8U]; + space = m_txSpace - m_buffer[9U]; + // CUtils::dump(wxT("GET_STATUS"), m_buffer, length); + // wxLogMessage(wxT("PTT=%d tx=%u space=%u cksum=%d, tx enabled=%d"), int(m_tx), m_txSpace, space, int(m_checksum), int(m_txEnabled)); + } + break; + + // These should not be received in this loop, but don't complain if we do + case RTM_GET_VERSION: + case RTM_GET_SERIAL: + case RTM_GET_CONFIG: + break; + + default: + wxLogMessage(wxT("Unknown message, type: %02X"), m_buffer[3U]); + CUtils::dump(wxT("Buffer dump"), m_buffer, length); + break; + } + + if (space > 0U) { + if (writeType == DSMTT_NONE && m_txData.hasData()) { + wxMutexLocker locker(m_mutex); + + m_txData.getData(&writeType, 1U); + m_txData.getData(&writeLength, 1U); + m_txData.getData(writeBuffer, writeLength); + } + + // Only send the start when the TX is off + if (!m_tx && writeType == DSMTT_START) { + // CUtils::dump(wxT("Write Header"), writeBuffer, writeLength); + + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing the header to the DVMEGA")); + + writeType = DSMTT_NONE; + space--; + } + + if (space > 4U && writeType == DSMTT_HEADER) { + // CUtils::dump(wxT("Write Header"), writeBuffer, writeLength); + + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing the header to the DVMEGA")); + + writeType = DSMTT_NONE; + space -= 4U; + } + + if (writeType == DSMTT_DATA || writeType == DSMTT_EOT) { + // CUtils::dump(wxT("Write Data"), writeBuffer, writeLength); + + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing data to the DVMEGA")); + + writeType = DSMTT_NONE; + space--; + } + } + + Sleep(5UL); + + pollTimer.clock(); + } + + wxLogMessage(wxT("Stopping DVMEGA Controller thread")); + + setEnabled(false); + + delete[] writeBuffer; + + m_serial.close(); + + return NULL; +} + +bool CDVMegaController::writeHeader(const CHeaderData& header) +{ + if (!m_txEnabled) + return false; + + bool ret = m_txData.hasSpace(64U); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + m_txCounter++; + if (m_txCounter == 0U) + m_txCounter = 1U; + + unsigned char buffer1[10U]; + + buffer1[0U] = DVRPTR_FRAME_START; + + buffer1[1U] = 0x03U; + buffer1[2U] = 0x00U; + + buffer1[3U] = DVRPTR_START; + + buffer1[4U] = m_txCounter; + buffer1[5U] = 0x00U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer1 + 0U, 6U); + cksum.result(buffer1 + 6U); + } else { + buffer1[6U] = 0x00U; + buffer1[7U] = 0x0BU; + } + + unsigned char buffer2[60U]; + + buffer2[0U] = DVRPTR_FRAME_START; + + buffer2[1U] = 0x2FU; + buffer2[2U] = 0x00U; + + buffer2[3U] = DVRPTR_HEADER; + + buffer2[4U] = m_txCounter; + buffer2[5U] = 0x00U; + + buffer2[6U] = 0x00U; + buffer2[7U] = 0x00U; + + ::memset(buffer2 + 8U, ' ', RADIO_HEADER_LENGTH_BYTES); + + buffer2[8U] = header.getFlag1(); + buffer2[9U] = header.getFlag2(); + buffer2[10U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer2[i + 11U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer2[i + 19U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer2[i + 27U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer2[i + 35U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer2[i + 43U] = my2.GetChar(i); + + CCCITTChecksumReverse cksum1; + cksum1.update(buffer2 + 8U, RADIO_HEADER_LENGTH_BYTES - 2U); + cksum1.result(buffer2 + 47U); + + buffer2[49U] = 0x00U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer2 + 0U, 50U); + cksum.result(buffer2 + 50U); + } else { + buffer2[50U] = 0x00U; + buffer2[51U] = 0x0BU; + } + + m_pktCounter = 0U; + + wxMutexLocker locker(m_mutex); + + unsigned char type1 = DSMTT_START; + m_txData.addData(&type1, 1U); + + unsigned char len1 = 8U; + m_txData.addData(&len1, 1U); + + m_txData.addData(buffer1, 8U); + + unsigned char type2 = DSMTT_HEADER; + m_txData.addData(&type2, 1U); + + unsigned char len2 = 52U; + m_txData.addData(&len2, 1U); + + m_txData.addData(buffer2, 52U); + + return true; +} + +bool CDVMegaController::writeData(const unsigned char* data, unsigned int, bool end) +{ + if (!m_txEnabled) + return false; + + bool ret = m_txData.hasSpace(26U); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + unsigned char buffer[30U]; + + if (end) { + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x03U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_EOT; + + buffer[4U] = m_txCounter; + buffer[5U] = 0xFFU; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 6U); + cksum.result(buffer + 6U); + } else { + buffer[6U] = 0x00U; + buffer[7U] = 0x0BU; + } + + wxMutexLocker locker(m_mutex); + + unsigned char type = DSMTT_EOT; + m_txData.addData(&type, 1U); + + unsigned char len = 8U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 8U); + + return true; + } + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x13U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_DATA; + + buffer[4U] = m_txCounter; + buffer[5U] = m_pktCounter; + + m_pktCounter++; + if (m_pktCounter >= m_txSpace) + m_pktCounter = 0U; + + buffer[6U] = 0x00U; + buffer[7U] = 0x00U; + + ::memcpy(buffer + 8U, data, DV_FRAME_LENGTH_BYTES); + + buffer[20U] = 0x00U; + buffer[21U] = 0x00U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 22U); + cksum.result(buffer + 22U); + } else { + buffer[22U] = 0x00U; + buffer[23U] = 0x0BU; + } + + wxMutexLocker locker(m_mutex); + + unsigned char type = DSMTT_DATA; + m_txData.addData(&type, 1U); + + unsigned char len = 24U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 24U); + + return true; +} + +unsigned int CDVMegaController::getSpace() +{ + return m_txData.freeSpace() / 26U; +} + +bool CDVMegaController::isTXReady() +{ + if (m_tx) + return false; + + return m_txData.isEmpty(); +} + +bool CDVMegaController::readVersion() +{ + for (unsigned int i = 0U; i < 6U; i++) { + unsigned char buffer[10U]; + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x01U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_GET_VERSION; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 4U); + cksum.result(buffer + 4U); + } else { + buffer[4U] = 0x00U; + buffer[5U] = 0x0BU; + } + + // CUtils::dump(wxT("Written"), buffer, 6U); + + int ret = m_serial.write(buffer, 6U); + if (ret != 6) + return false; + + for (unsigned int count = 0U; count < MAX_RESPONSES; count++) { + ::wxMilliSleep(10UL); + + unsigned int length; + RESP_TYPE_MEGA resp = getResponse(m_buffer, length); + if (resp == RTM_GET_VERSION) { + wxString firmware; + if ((m_buffer[4U] & 0x0FU) > 0x00U) + firmware.Printf(wxT("%u.%u%u%c"), (m_buffer[5U] & 0xF0U) >> 4, m_buffer[5U] & 0x0FU, (m_buffer[4U] & 0xF0U) >> 4, (m_buffer[4U] & 0x0FU) + wxT('a') - 1U); + else + firmware.Printf(wxT("%u.%u%u"), (m_buffer[5U] & 0xF0U) >> 4, m_buffer[5U] & 0x0FU, (m_buffer[4U] & 0xF0U) >> 4); + + wxString hardware((char*)(m_buffer + 6U), wxConvLocal, length - DVRPTR_HEADER_LENGTH - 3U); + + wxLogInfo(wxT("DVMEGA Firmware version: %s, hardware: %s"), firmware.c_str(), hardware.c_str()); + + return true; + } + } + + ::wxSleep(1); + } + + wxLogError(wxT("Unable to read the firmware version after six attempts")); + + return false; +} + +bool CDVMegaController::readStatus() +{ + unsigned char buffer[10U]; + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x01U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_GET_STATUS; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 4U); + cksum.result(buffer + 4U); + } else { + buffer[4U] = 0x00U; + buffer[5U] = 0x0BU; + } + + return m_serial.write(buffer, 6U) == 6; +} + +bool CDVMegaController::setConfig() +{ + unsigned char buffer[20U]; + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x07U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_SET_CONFIG; + + buffer[4U] = 0xC0U; // Physical layer + + buffer[5U] = 0x04U; // Block length + + buffer[6U] = 0x00U; + if (m_rxInvert) + buffer[6U] |= 0x01U; + if (m_txInvert) + buffer[6U] |= 0x02U; + + wxUint16* txDelay = (wxUint16*)(buffer + 8U); + *txDelay = wxUINT16_SWAP_ON_BE(m_txDelay); + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 10U); + cksum.result(buffer + 10U); + } else { + buffer[10U] = 0x00U; + buffer[11U] = 0x0BU; + } + + // CUtils::dump(wxT("Written"), buffer, 12U); + + int ret = m_serial.write(buffer, 12U); + if (ret != 12) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_MEGA resp; + do { + + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RTM_SET_CONFIG) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVMEGA is not responding to the SET_CONFIG command")); + return false; + } + } + } while (resp != RTM_SET_CONFIG); + + // CUtils::dump(wxT("Response"), m_buffer, length); + + unsigned char type = m_buffer[4U]; + if (type != DVRPTR_ACK) { + wxLogError(wxT("Received a NAK to the SET_CONFIG command from the modem")); + return false; + } + + return true; +} + +bool CDVMegaController::setFrequencyAndPower() +{ + unsigned char buffer[25U]; + + ::memset(buffer, 0x00U, 21U); + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x10U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_SET_CONFIG; + + buffer[4U] = 0xC1U; // RF layer + + buffer[5U] = 0x0CU; // Block length + + wxUint32 rxFreq = wxUINT32_SWAP_ON_BE(wxUint32(m_rxFrequency)); + wxUint32 txFreq = wxUINT32_SWAP_ON_BE(wxUint32(m_txFrequency)); + + ::memcpy(buffer + 7U, &rxFreq, sizeof(wxUint32)); + ::memcpy(buffer + 11U, &txFreq, sizeof(wxUint32)); + + buffer[16U] = (m_power * 64U) / 100U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 19U); + cksum.result(buffer + 19U); + } else { + buffer[19U] = 0x00U; + buffer[20U] = 0x0BU; + } + + // CUtils::dump(wxT("Written"), buffer, 21U); + + int ret = m_serial.write(buffer, 21U); + if (ret != 21) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_MEGA resp; + do { + + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RTM_SET_CONFIG) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVMEGA is not responding to the SET_CONFIG command")); + return false; + } + } + } while (resp != RTM_SET_CONFIG); + + // CUtils::dump(wxT("Response"), m_buffer, length); + + unsigned char type = m_buffer[4U]; + if (type != DVRPTR_ACK) { + wxLogError(wxT("Received a NAK to the SET_CONFIG command from the modem")); + return false; + } + + return true; +} + +bool CDVMegaController::setEnabled(bool enable) +{ + unsigned char buffer[10U]; + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x02U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_GET_STATUS; + + // Enable RX, TX, and Watchdog + if (enable) + buffer[4U] = 0x01U | 0x02U | 0x04U; + else + buffer[4U] = 0x00U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 5U); + cksum.result(buffer + 5U); + } else { + buffer[5U] = 0x00U; + buffer[6U] = 0x0BU; + } + + // CUtils::dump(wxT("Written"), buffer, 7U); + + int ret = m_serial.write(buffer, 7U); + if (ret != 7) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_MEGA resp; + do { + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RTM_GET_STATUS) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DVMEGA is not responding to the SET_STATUS command")); + return false; + } + } + } while (resp != RTM_GET_STATUS); + + // CUtils::dump(wxT("Response"), m_buffer, length); + + unsigned char type = m_buffer[4U]; + if (type != DVRPTR_ACK) { + wxLogError(wxT("Received a NAK to the SET_STATUS command from the modem")); + return false; + } + + return true; +} + +RESP_TYPE_MEGA CDVMegaController::getResponse(unsigned char *buffer, unsigned int& length) +{ + // Get the start of the frame or nothing at all + int ret = m_serial.read(buffer, 1U); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DVMEGA")); + return RTM_ERROR; + } + + if (ret == 0) + return RTM_TIMEOUT; + + if (buffer[0U] != DVRPTR_FRAME_START) + return RTM_TIMEOUT; + + unsigned int offset = 1U; + + while (offset < DVRPTR_HEADER_LENGTH) { + ret = m_serial.read(buffer + offset, DVRPTR_HEADER_LENGTH - offset); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DVMEGA")); + return RTM_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + Sleep(5UL); + } + + length = buffer[1U] + buffer[2U] * 256U; + + if (length >= 100U) { + wxLogError(wxT("Invalid data received from the DVMEGA")); + return RTM_ERROR; + } + + // Remove the response bit + unsigned int type = buffer[3U] & 0x7FU; + + offset = 0U; + + while (offset < length) { + ret = m_serial.read(buffer + offset + DVRPTR_HEADER_LENGTH, length - offset); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DVMEGA")); + return RTM_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + Sleep(5UL); + } + + length += DVRPTR_HEADER_LENGTH; + + // CUtils::dump(wxT("Received"), buffer, length); + + switch (type) { + case DVRPTR_GET_STATUS: + return RTM_GET_STATUS; + case DVRPTR_GET_VERSION: + return RTM_GET_VERSION; + case DVRPTR_GET_SERIAL: + return RTM_GET_SERIAL; + case DVRPTR_GET_CONFIG: + return RTM_GET_CONFIG; + case DVRPTR_SET_CONFIG: + return RTM_SET_CONFIG; + case DVRPTR_RXPREAMBLE: + return RTM_RXPREAMBLE; + case DVRPTR_START: + return RTM_START; + case DVRPTR_HEADER: + return RTM_HEADER; + case DVRPTR_RXSYNC: + return RTM_RXSYNC; + case DVRPTR_DATA: + return RTM_DATA; + case DVRPTR_EOT: + return RTM_EOT; + case DVRPTR_RXLOST: + return RTM_RXLOST; + case DVRPTR_SET_TESTMDE: + return RTM_SET_TESTMDE; + default: + return RTM_UNKNOWN; + } +} + +wxString CDVMegaController::getPath() const +{ + return m_path; +} + +bool CDVMegaController::findPort() +{ + if (m_path.IsEmpty()) + return false; + +#if defined(__WINDOWS__) +#else + wxDir dir; + bool ret1 = dir.Open(wxT("/sys/class/tty")); + if (!ret1) { + wxLogError(wxT("Cannot open directory /sys/class/tty")); + return false; + } + + wxString fileName; + ret1 = dir.GetFirst(&fileName, wxT("ttyACM*")); + while (ret1) { + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), fileName.c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + ::strcat(cpath, "/device"); + ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret2); + } else { + // Get all but the last section + wxString fullPath = wxString(symlink, wxConvLocal, ret2); + path = fullPath.BeforeLast(wxT('/')); + } + + if (path.IsSameAs(m_path)) { + m_port.Printf(wxT("/dev/%s"), fileName.c_str()); + + wxLogMessage(wxT("Found modem port of %s based on the path"), m_port.c_str()); + + return true; + } + + ret1 = dir.GetNext(&fileName); + } +#endif + + return false; +} + +bool CDVMegaController::findPath() +{ +#if defined(__WINDOWS__) +#ifdef notdef + GUID guids[5U]; + + DWORD count; + BOOL res = ::SetupDiClassGuidsFromName(L"Multifunction", guids, 5U, &count); + if (!res) { + wxLogError(wxT("Error from SetupDiClassGuidsFromName: err=%u"), ::GetLastError()); + return false; + } + + for (DWORD i = 0U; i < count; i++) { + HDEVINFO devInfo = ::SetupDiGetClassDevs(&guids[i], NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (devInfo == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from SetupDiGetClassDevs: err=%u"), ::GetLastError()); + return false; + } + + SP_DEVICE_INTERFACE_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (DWORD index = 0U; ::SetupDiEnumDeviceInterfaces(devInfo, NULL, &guids[i], index, &devInfoData); index++) { + // Find the required length of the device structure + DWORD length; + ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, NULL, 0U, &length, NULL); + + PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = PSP_DEVICE_INTERFACE_DETAIL_DATA(::malloc(length)); + detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // Get the detailed data into the newly allocated device structure + DWORD required; + res = ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, detailData, length, &required, NULL); + if (!res) { + wxLogError(wxT("Error from SetupDiGetDeviceInterfaceDetail: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::free(detailData); + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + } + + return false; +#endif +#else + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), m_port.Mid(5U).c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + ::strcat(cpath, "/device"); + ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret); + } else { + wxString fullPath = wxString(symlink, wxConvLocal, ret); + path = fullPath.BeforeLast(wxT('/')); + } + + if (m_path.IsEmpty()) + wxLogMessage(wxT("Found modem path of %s"), path.c_str()); + + m_path = path; +#endif + + return true; +} + +bool CDVMegaController::findModem() +{ + m_serial.close(); + + // Tell the repeater that the signal has gone away + if (m_rx) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + + unsigned int count = 0U; + + // Purge the transmit buffer every 500ms to avoid overflow, but only try and reopen the modem every 2s + while (!m_stopped) { + count++; + if (count >= 4U) { + wxLogMessage(wxT("Trying to reopen the modem")); + + bool ret = findPort(); + if (ret) { + ret = openModem(); + if (ret) + return true; + } + + count = 0U; + } + + Sleep(500UL); + } + + return false; +} + +bool CDVMegaController::openModem() +{ + bool ret = m_serial.open(); + if (!ret) + return false; + + ret = readVersion(); + if (!ret) { + m_serial.close(); + return false; + } + + ret = setConfig(); + if (!ret) { + m_serial.close(); + return false; + } + + if (m_rxFrequency != 0U && m_txFrequency != 0U) { + ret = setFrequencyAndPower(); + if (!ret) { + m_serial.close(); + return false; + } + } + + ret = setEnabled(true); + if (!ret) { + m_serial.close(); + return false; + } + + return true; +} + diff --git a/Common/DVMegaController.h b/Common/DVMegaController.h new file mode 100644 index 0000000..9ae29ee --- /dev/null +++ b/Common/DVMegaController.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DVMegaController_H +#define DVMegaController_H + +#include "SerialDataController.h" +#include "RingBuffer.h" +#include "Modem.h" +#include "Utils.h" + +#include + +enum RESP_TYPE_MEGA { + RTM_TIMEOUT, + RTM_ERROR, + RTM_UNKNOWN, + RTM_GET_STATUS, + RTM_GET_VERSION, + RTM_GET_SERIAL, + RTM_GET_CONFIG, + RTM_SET_CONFIG, + RTM_RXPREAMBLE, + RTM_START, + RTM_HEADER, + RTM_RXSYNC, + RTM_DATA, + RTM_EOT, + RTM_RXLOST, + RTM_SET_TESTMDE +}; + +class CDVMegaController : public CModem { +public: + CDVMegaController(const wxString& port, const wxString& path, bool rxInvert, bool txInvert, unsigned int txDelay); + CDVMegaController(const wxString& port, const wxString& path, unsigned int txDelay, unsigned int rxFrequency, unsigned int txFrequency, unsigned int power); + virtual ~CDVMegaController(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + + virtual wxString getPath() const; + +private: + wxString m_port; + wxString m_path; + bool m_rxInvert; + bool m_txInvert; + unsigned int m_txDelay; + unsigned int m_rxFrequency; + unsigned int m_txFrequency; + unsigned int m_power; + CSerialDataController m_serial; + unsigned char* m_buffer; + CRingBuffer m_txData; + unsigned char m_txCounter; + unsigned char m_pktCounter; + bool m_rx; + unsigned int m_txSpace; + bool m_txEnabled; + bool m_checksum; + + bool readVersion(); + bool readStatus(); + bool setConfig(); + bool setFrequencyAndPower(); + bool setEnabled(bool enable); + + RESP_TYPE_MEGA getResponse(unsigned char* buffer, unsigned int& length); + + bool findPort(); + bool findPath(); + + bool findModem(); + bool openModem(); +}; + +#endif + diff --git a/Common/DVRPTRV1Controller.cpp b/Common/DVRPTRV1Controller.cpp new file mode 100644 index 0000000..f0cbff1 --- /dev/null +++ b/Common/DVRPTRV1Controller.cpp @@ -0,0 +1,1042 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CCITTChecksumReverse.h" +#include "DVRPTRV1Controller.h" +#include "CCITTChecksum.h" +#include "DStarDefines.h" +#include "Timer.h" + +#if defined(__WINDOWS__) +#include +#else +#include +#endif + +const unsigned char DVRPTR_HEADER_LENGTH = 5U; + +const unsigned char DVRPTR_FRAME_START = 0xD0U; + +const unsigned char DVRPTR_GET_STATUS = 0x10U; +const unsigned char DVRPTR_GET_VERSION = 0x11U; +const unsigned char DVRPTR_GET_SERIAL = 0x12U; +const unsigned char DVRPTR_GET_CONFIG = 0x13U; +const unsigned char DVRPTR_SET_CONFIG = 0x14U; +const unsigned char DVRPTR_RXPREAMBLE = 0x15U; +const unsigned char DVRPTR_START = 0x16U; +const unsigned char DVRPTR_HEADER = 0x17U; +const unsigned char DVRPTR_RXSYNC = 0x18U; +const unsigned char DVRPTR_DATA = 0x19U; +const unsigned char DVRPTR_EOT = 0x1AU; +const unsigned char DVRPTR_RXLOST = 0x1BU; +const unsigned char DVRPTR_MSG_RSVD1 = 0x1CU; +const unsigned char DVRPTR_MSG_RSVD2 = 0x1DU; +const unsigned char DVRPTR_MSG_RSVD3 = 0x1EU; +const unsigned char DVRPTR_SET_TESTMDE = 0x1FU; + +const unsigned char DVRPTR_ACK = 0x06U; +const unsigned char DVRPTR_NAK = 0x15U; + +const unsigned int MAX_RESPONSES = 30U; + +const unsigned int BUFFER_LENGTH = 200U; + +CDVRPTRV1Controller::CDVRPTRV1Controller(const wxString& port, const wxString& path, bool rxInvert, bool txInvert, bool channel, unsigned int modLevel, unsigned int txDelay) : +CModem(), +m_port(port), +m_path(path), +m_rxInvert(rxInvert), +m_txInvert(txInvert), +m_channel(channel), +m_modLevel(modLevel), +m_txDelay(txDelay), +m_serial(port, SERIAL_115200), +m_buffer(NULL), +m_txData(1000U), +m_txCounter(0U), +m_pktCounter(0U), +m_rx(false), +m_txSpace(0U), +m_txEnabled(false), +m_checksum(false) +{ + wxASSERT(!port.IsEmpty()); + + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CDVRPTRV1Controller::~CDVRPTRV1Controller() +{ + delete[] m_buffer; +} + +bool CDVRPTRV1Controller::start() +{ + findPort(); + + bool ret = openModem(); + if (!ret) + return false; + + findPath(); + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CDVRPTRV1Controller::Entry() +{ + wxLogMessage(wxT("Starting DV-RPTR1 Modem Controller thread")); + + // Clock every 5ms-ish + CTimer pollTimer(200U, 0U, 100U); + pollTimer.start(); + + unsigned char writeType = DSMTT_NONE; + unsigned char writeLength = 0U; + unsigned char* writeBuffer = new unsigned char[BUFFER_LENGTH]; + + unsigned int space = 0U; + + while (!m_stopped) { + // Poll the modem status every 100ms + if (pollTimer.hasExpired()) { + bool ret = readStatus(); + if (!ret) { + ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DV-RPTR1 Modem Controller thread")); + return NULL; + } + } + + pollTimer.start(); + } + + unsigned int length; + RESP_TYPE_V1 type = getResponse(m_buffer, length); + + switch (type) { + case RT1_TIMEOUT: + break; + + case RT1_ERROR: { + bool ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DV-RPTR1 Modem Controller thread")); + return NULL; + } + } + break; + + case RT1_RXPREAMBLE: + // wxLogMessage(wxT("RT_PREAMBLE")); + break; + + case RT1_START: + // wxLogMessage(wxT("RT_START")); + break; + + case RT1_HEADER: + // CUtils::dump(wxT("RT_HEADER"), m_buffer, length); + if (length == 7U) { + if (m_buffer[4U] == DVRPTR_NAK) + wxLogWarning(wxT("Received a header NAK from the modem")); + } else { + bool correct = (m_buffer[5U] & 0x80U) == 0x00U; + if (correct) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES); + + m_rx = true; + } + } + break; + + case RT1_RXSYNC: + // wxLogMessage(wxT("RT_RXSYNC")); + break; + + case RT1_DATA: + // CUtils::dump(wxT("RT_DATA"), m_buffer, length); + if (length == 7U) { + if (m_buffer[4U] == DVRPTR_NAK) + wxLogWarning(wxT("Received a data NAK from the modem")); + } else { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 8U, DV_FRAME_LENGTH_BYTES); + + m_rx = true; + } + break; + + case RT1_EOT: { + // wxLogMessage(wxT("RT_EOT")); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + break; + + case RT1_RXLOST: { + // wxLogMessage(wxT("RT_LOST")); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_LOST; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + break; + + case RT1_GET_STATUS: { + m_txEnabled = (m_buffer[4U] & 0x02U) == 0x02U; + m_checksum = (m_buffer[4U] & 0x08U) == 0x08U; + m_tx = (m_buffer[5U] & 0x02U) == 0x02U; + m_txSpace = m_buffer[8U]; + space = m_txSpace - m_buffer[9U]; + // CUtils::dump(wxT("GET_STATUS"), m_buffer, length); + // wxLogMessage(wxT("PTT=%d tx=%u space=%u cksum=%d, tx enabled=%d"), int(m_tx), m_txSpace, space, int(m_checksum), int(m_txEnabled)); + } + break; + + // These should not be received in this loop, but don't complain if we do + case RT1_GET_VERSION: + case RT1_GET_SERIAL: + case RT1_GET_CONFIG: + break; + + default: + wxLogMessage(wxT("Unknown message, type: %02X"), m_buffer[3U]); + CUtils::dump(wxT("Buffer dump"), m_buffer, length); + break; + } + + if (space > 0U) { + if (writeType == DSMTT_NONE && m_txData.hasData()) { + wxMutexLocker locker(m_mutex); + + m_txData.getData(&writeType, 1U); + m_txData.getData(&writeLength, 1U); + m_txData.getData(writeBuffer, writeLength); + } + + // Only send the start when the TX is off + if (!m_tx && writeType == DSMTT_START) { + // CUtils::dump(wxT("Write Header"), writeBuffer, writeLength); + + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing the header to the DV-RPTR modem")); + + writeType = DSMTT_NONE; + space--; + } + + if (space > 4U && writeType == DSMTT_HEADER) { + // CUtils::dump(wxT("Write Header"), writeBuffer, writeLength); + + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing the header to the DV-RPTR modem")); + + writeType = DSMTT_NONE; + space -= 4U; + } + + if (writeType == DSMTT_DATA || writeType == DSMTT_EOT) { + // CUtils::dump(wxT("Write Data"), writeBuffer, writeLength); + + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing data to the DV-RPTR modem")); + + writeType = DSMTT_NONE; + space--; + } + } + + Sleep(5UL); + + pollTimer.clock(); + } + + wxLogMessage(wxT("Stopping DV-RPTR1 Modem Controller thread")); + + setEnabled(false); + + delete[] writeBuffer; + + m_serial.close(); + + return NULL; +} + +bool CDVRPTRV1Controller::writeHeader(const CHeaderData& header) +{ + if (!m_txEnabled) + return false; + + bool ret = m_txData.hasSpace(64U); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + m_txCounter++; + if (m_txCounter == 0U) + m_txCounter = 1U; + + unsigned char buffer1[10U]; + + buffer1[0U] = DVRPTR_FRAME_START; + + buffer1[1U] = 0x03U; + buffer1[2U] = 0x00U; + + buffer1[3U] = DVRPTR_START; + + buffer1[4U] = m_txCounter; + buffer1[5U] = 0x00U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer1 + 0U, 6U); + cksum.result(buffer1 + 6U); + } else { + buffer1[6U] = 0x00U; + buffer1[7U] = 0x0BU; + } + + unsigned char buffer2[60U]; + + buffer2[0U] = DVRPTR_FRAME_START; + + buffer2[1U] = 0x2FU; + buffer2[2U] = 0x00U; + + buffer2[3U] = DVRPTR_HEADER; + + buffer2[4U] = m_txCounter; + buffer2[5U] = 0x00U; + + buffer2[6U] = 0x00U; + buffer2[7U] = 0x00U; + + ::memset(buffer2 + 8U, ' ', RADIO_HEADER_LENGTH_BYTES); + + buffer2[8U] = header.getFlag1(); + buffer2[9U] = header.getFlag2(); + buffer2[10U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer2[i + 11U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer2[i + 19U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer2[i + 27U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer2[i + 35U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer2[i + 43U] = my2.GetChar(i); + + CCCITTChecksumReverse cksum1; + cksum1.update(buffer2 + 8U, RADIO_HEADER_LENGTH_BYTES - 2U); + cksum1.result(buffer2 + 47U); + + buffer2[49U] = 0x00U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer2 + 0U, 50U); + cksum.result(buffer2 + 50U); + } else { + buffer2[50U] = 0x00U; + buffer2[51U] = 0x0BU; + } + + m_pktCounter = 0U; + + wxMutexLocker locker(m_mutex); + + unsigned char type1 = DSMTT_START; + m_txData.addData(&type1, 1U); + + unsigned char len1 = 8U; + m_txData.addData(&len1, 1U); + + m_txData.addData(buffer1, 8U); + + unsigned char type2 = DSMTT_HEADER; + m_txData.addData(&type2, 1U); + + unsigned char len2 = 52U; + m_txData.addData(&len2, 1U); + + m_txData.addData(buffer2, 52U); + + return true; +} + +bool CDVRPTRV1Controller::writeData(const unsigned char* data, unsigned int, bool end) +{ + if (!m_txEnabled) + return false; + + bool ret = m_txData.hasSpace(26U); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + unsigned char buffer[30U]; + + if (end) { + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x03U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_EOT; + + buffer[4U] = m_txCounter; + buffer[5U] = 0xFFU; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 6U); + cksum.result(buffer + 6U); + } else { + buffer[6U] = 0x00U; + buffer[7U] = 0x0BU; + } + + wxMutexLocker locker(m_mutex); + + unsigned char type = DSMTT_EOT; + m_txData.addData(&type, 1U); + + unsigned char len = 8U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 8U); + + return true; + } + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x13U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_DATA; + + buffer[4U] = m_txCounter; + buffer[5U] = m_pktCounter; + + m_pktCounter++; + if (m_pktCounter >= m_txSpace) + m_pktCounter = 0U; + + buffer[6U] = 0x00U; + buffer[7U] = 0x00U; + + ::memcpy(buffer + 8U, data, DV_FRAME_LENGTH_BYTES); + + buffer[20U] = 0x00U; + buffer[21U] = 0x00U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 22U); + cksum.result(buffer + 22U); + } else { + buffer[22U] = 0x00U; + buffer[23U] = 0x0BU; + } + + wxMutexLocker locker(m_mutex); + + unsigned char type = DSMTT_DATA; + m_txData.addData(&type, 1U); + + unsigned char len = 24U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 24U); + + return true; +} + +unsigned int CDVRPTRV1Controller::getSpace() +{ + return m_txData.freeSpace() / 26U; +} + +bool CDVRPTRV1Controller::isTXReady() +{ + if (m_tx) + return false; + + return m_txData.isEmpty(); +} + +bool CDVRPTRV1Controller::readVersion() +{ + unsigned char buffer[10U]; + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x01U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_GET_VERSION; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 4U); + cksum.result(buffer + 4U); + } else { + buffer[4U] = 0x00U; + buffer[5U] = 0x0BU; + } + + // CUtils::dump(wxT("Written"), buffer, 6U); + + int ret = m_serial.write(buffer, 6U); + if (ret != 6) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_V1 resp; + do { + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT1_GET_VERSION) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DV-RPTR modem is not responding to the version command")); + return false; + } + } + } while (resp != RT1_GET_VERSION); + + wxString firmware; + if ((m_buffer[4U] & 0x0FU) > 0x00U) + firmware.Printf(wxT("%u.%u%u%c"), (m_buffer[5U] & 0xF0U) >> 4, m_buffer[5U] & 0x0FU, (m_buffer[4U] & 0xF0U) >> 4, (m_buffer[4U] & 0x0FU) + wxT('a') - 1U); + else + firmware.Printf(wxT("%u.%u%u"), (m_buffer[5U] & 0xF0U) >> 4, m_buffer[5U] & 0x0FU, (m_buffer[4U] & 0xF0U) >> 4); + + wxString hardware((char*)(m_buffer + 6U), wxConvLocal, length - DVRPTR_HEADER_LENGTH - 3U); + + wxLogInfo(wxT("DV-RPTR Modem Firmware version: %s, hardware: %s"), firmware.c_str(), hardware.c_str()); + + return true; +} + +bool CDVRPTRV1Controller::readStatus() +{ + unsigned char buffer[10U]; + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x01U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_GET_STATUS; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 4U); + cksum.result(buffer + 4U); + } else { + buffer[4U] = 0x00U; + buffer[5U] = 0x0BU; + } + + return m_serial.write(buffer, 6U) == 6; +} + +bool CDVRPTRV1Controller::setConfig() +{ + unsigned char buffer[20U]; + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x07U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_SET_CONFIG; + + buffer[4U] = 0xC0U; // Physical layer + + buffer[5U] = 0x04U; // Block length + + buffer[6U] = 0x00U; + if (m_rxInvert) + buffer[6U] |= 0x01U; + if (m_txInvert) + buffer[6U] |= 0x02U; + if (m_channel) + buffer[6U] |= 0x04U; + + buffer[7U] = (m_modLevel * 256U) / 100U; + + wxUint16* txDelay = (wxUint16*)(buffer + 8U); + *txDelay = wxUINT16_SWAP_ON_BE(m_txDelay); + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 10U); + cksum.result(buffer + 10U); + } else { + buffer[10U] = 0x00U; + buffer[11U] = 0x0BU; + } + + // CUtils::dump(wxT("Written"), buffer, 12U); + + int ret = m_serial.write(buffer, 12U); + if (ret != 12) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_V1 resp; + do { + + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT1_SET_CONFIG) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DV-RPTR modem is not responding to the SET_CONFIG command")); + return false; + } + } + } while (resp != RT1_SET_CONFIG); + + // CUtils::dump(wxT("Response"), m_buffer, length); + + unsigned char type = m_buffer[4U]; + if (type != DVRPTR_ACK) { + wxLogError(wxT("Received a NAK to the SET_CONFIG command from the modem")); + return false; + } + + return true; +} + +bool CDVRPTRV1Controller::setEnabled(bool enable) +{ + unsigned char buffer[10U]; + + buffer[0U] = DVRPTR_FRAME_START; + + buffer[1U] = 0x02U; + buffer[2U] = 0x00U; + + buffer[3U] = DVRPTR_GET_STATUS; + + // Enable RX, TX, and Watchdog + if (enable) + buffer[4U] = 0x01U | 0x02U | 0x04U; + else + buffer[4U] = 0x00U; + + if (m_checksum) { + CCCITTChecksum cksum; + cksum.update(buffer + 0U, 5U); + cksum.result(buffer + 5U); + } else { + buffer[5U] = 0x00U; + buffer[6U] = 0x0BU; + } + + // CUtils::dump(wxT("Written"), buffer, 7U); + + int ret = m_serial.write(buffer, 7U); + if (ret != 7) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_V1 resp; + do { + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT1_GET_STATUS) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DV-RPTR modem is not responding to the SET_STATUS command")); + return false; + } + } + } while (resp != RT1_GET_STATUS); + + // CUtils::dump(wxT("Response"), m_buffer, length); + + unsigned char type = m_buffer[4U]; + if (type != DVRPTR_ACK) { + wxLogError(wxT("Received a NAK to the SET_STATUS command from the modem")); + return false; + } + + return true; +} + +RESP_TYPE_V1 CDVRPTRV1Controller::getResponse(unsigned char *buffer, unsigned int& length) +{ + // Get the start of the frame or nothing at all + int ret = m_serial.read(buffer, 1U); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT1_ERROR; + } + + if (ret == 0) + return RT1_TIMEOUT; + + if (buffer[0U] != DVRPTR_FRAME_START) + return RT1_TIMEOUT; + + unsigned int offset = 1U; + + while (offset < DVRPTR_HEADER_LENGTH) { + ret = m_serial.read(buffer + offset, DVRPTR_HEADER_LENGTH - offset); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT1_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + Sleep(5UL); + } + + length = buffer[1U] + buffer[2U] * 256U; + + // Remove the response bit + unsigned int type = buffer[3U] & 0x7FU; + + offset = 0U; + + while (offset < length) { + ret = m_serial.read(buffer + offset + DVRPTR_HEADER_LENGTH, length - offset); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT1_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + Sleep(5UL); + } + + length += DVRPTR_HEADER_LENGTH; + + // CUtils::dump(wxT("Received"), buffer, length); + + switch (type) { + case DVRPTR_GET_STATUS: + return RT1_GET_STATUS; + case DVRPTR_GET_VERSION: + return RT1_GET_VERSION; + case DVRPTR_GET_SERIAL: + return RT1_GET_SERIAL; + case DVRPTR_GET_CONFIG: + return RT1_GET_CONFIG; + case DVRPTR_SET_CONFIG: + return RT1_SET_CONFIG; + case DVRPTR_RXPREAMBLE: + return RT1_RXPREAMBLE; + case DVRPTR_START: + return RT1_START; + case DVRPTR_HEADER: + return RT1_HEADER; + case DVRPTR_RXSYNC: + return RT1_RXSYNC; + case DVRPTR_DATA: + return RT1_DATA; + case DVRPTR_EOT: + return RT1_EOT; + case DVRPTR_RXLOST: + return RT1_RXLOST; + case DVRPTR_SET_TESTMDE: + return RT1_SET_TESTMDE; + default: + return RT1_UNKNOWN; + } +} + +wxString CDVRPTRV1Controller::getPath() const +{ + return m_path; +} + +bool CDVRPTRV1Controller::findPort() +{ + if (m_path.IsEmpty()) + return false; + +#if defined(__WINDOWS__) +#else + wxDir dir; + bool ret1 = dir.Open(wxT("/sys/class/tty")); + if (!ret1) { + wxLogError(wxT("Cannot open directory /sys/class/tty")); + return false; + } + + wxString fileName; + ret1 = dir.GetFirst(&fileName, wxT("ttyACM*")); + while (ret1) { + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), fileName.c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + ::strcat(cpath, "/device"); + ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret2); + } else { + // Get all but the last section + wxString fullPath = wxString(symlink, wxConvLocal, ret2); + path = fullPath.BeforeLast(wxT('/')); + } + + if (path.IsSameAs(m_path)) { + m_port.Printf(wxT("/dev/%s"), fileName.c_str()); + + wxLogMessage(wxT("Found modem port of %s based on the path"), m_port.c_str()); + + return true; + } + + ret1 = dir.GetNext(&fileName); + } +#endif + + return false; +} + +bool CDVRPTRV1Controller::findPath() +{ +#if defined(__WINDOWS__) +#ifdef notdef + GUID guids[5U]; + + DWORD count; + BOOL res = ::SetupDiClassGuidsFromName(L"Multifunction", guids, 5U, &count); + if (!res) { + wxLogError(wxT("Error from SetupDiClassGuidsFromName: err=%u"), ::GetLastError()); + return false; + } + + for (DWORD i = 0U; i < count; i++) { + HDEVINFO devInfo = ::SetupDiGetClassDevs(&guids[i], NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (devInfo == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from SetupDiGetClassDevs: err=%u"), ::GetLastError()); + return false; + } + + SP_DEVICE_INTERFACE_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (DWORD index = 0U; ::SetupDiEnumDeviceInterfaces(devInfo, NULL, &guids[i], index, &devInfoData); index++) { + // Find the required length of the device structure + DWORD length; + ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, NULL, 0U, &length, NULL); + + PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = PSP_DEVICE_INTERFACE_DETAIL_DATA(::malloc(length)); + detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // Get the detailed data into the newly allocated device structure + DWORD required; + res = ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, detailData, length, &required, NULL); + if (!res) { + wxLogError(wxT("Error from SetupDiGetDeviceInterfaceDetail: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::free(detailData); + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + } + + return false; +#endif +#else + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), m_port.Mid(5U).c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + ::strcat(cpath, "/device"); + ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret); + } else { + wxString fullPath = wxString(symlink, wxConvLocal, ret); + path = fullPath.BeforeLast(wxT('/')); + } + + if (m_path.IsEmpty()) + wxLogMessage(wxT("Found modem path of %s"), path.c_str()); + + m_path = path; +#endif + + return true; +} + +bool CDVRPTRV1Controller::findModem() +{ + m_serial.close(); + + // Tell the repeater that the signal has gone away + if (m_rx) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + + unsigned int count = 0U; + + // Purge the transmit buffer every 500ms to avoid overflow, but only try and reopen the modem every 2s + while (!m_stopped) { + count++; + if (count >= 4U) { + wxLogMessage(wxT("Trying to reopen the modem")); + + bool ret = findPort(); + if (ret) { + ret = openModem(); + if (ret) + return true; + } + + count = 0U; + } + + Sleep(500UL); + } + + return false; +} + +bool CDVRPTRV1Controller::openModem() +{ + bool ret = m_serial.open(); + if (!ret) + return false; + + ret = readVersion(); + if (!ret) { + m_serial.close(); + return false; + } + + ret = setConfig(); + if (!ret) { + m_serial.close(); + return false; + } + + ret = setEnabled(true); + if (!ret) { + m_serial.close(); + return false; + } + + return true; +} + diff --git a/Common/DVRPTRV1Controller.h b/Common/DVRPTRV1Controller.h new file mode 100644 index 0000000..ef93af5 --- /dev/null +++ b/Common/DVRPTRV1Controller.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DVRPTRV1Controller_H +#define DVRPTRV1Controller_H + +#include "SerialDataController.h" +#include "RingBuffer.h" +#include "Modem.h" +#include "Utils.h" + +#include + +enum RESP_TYPE_V1 { + RT1_TIMEOUT, + RT1_ERROR, + RT1_UNKNOWN, + RT1_GET_STATUS, + RT1_GET_VERSION, + RT1_GET_SERIAL, + RT1_GET_CONFIG, + RT1_SET_CONFIG, + RT1_RXPREAMBLE, + RT1_START, + RT1_HEADER, + RT1_RXSYNC, + RT1_DATA, + RT1_EOT, + RT1_RXLOST, + RT1_SET_TESTMDE +}; + +class CDVRPTRV1Controller : public CModem { +public: + CDVRPTRV1Controller(const wxString& port, const wxString& path, bool rxInvert, bool txInvert, bool channel, unsigned int modLevel, unsigned int txDelay); + virtual ~CDVRPTRV1Controller(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + + virtual wxString getPath() const; + +private: + wxString m_port; + wxString m_path; + bool m_rxInvert; + bool m_txInvert; + bool m_channel; + unsigned int m_modLevel; + unsigned int m_txDelay; + CSerialDataController m_serial; + unsigned char* m_buffer; + CRingBuffer m_txData; + unsigned char m_txCounter; + unsigned char m_pktCounter; + bool m_rx; + unsigned int m_txSpace; + bool m_txEnabled; + bool m_checksum; + + bool readVersion(); + bool readStatus(); + bool setConfig(); + bool setEnabled(bool enable); + + RESP_TYPE_V1 getResponse(unsigned char* buffer, unsigned int& length); + + bool findPort(); + bool findPath(); + + bool findModem(); + bool openModem(); +}; + +#endif + diff --git a/Common/DVRPTRV2Controller.cpp b/Common/DVRPTRV2Controller.cpp new file mode 100644 index 0000000..3a75117 --- /dev/null +++ b/Common/DVRPTRV2Controller.cpp @@ -0,0 +1,841 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DVRPTRV2Controller.h" +#include "DStarDefines.h" +#include "Timer.h" + +#if defined(__WINDOWS__) +#include +#else +#include +#endif + +const unsigned int MAX_RESPONSES = 30U; + +const unsigned int BUFFER_LENGTH = 200U; + +CDVRPTRV2Controller::CDVRPTRV2Controller(const wxString& port, const wxString& path, bool txInvert, unsigned int modLevel, bool duplex, const wxString& callsign, unsigned int txDelay) : +CModem(), +m_connection(CT_USB), +m_usbPort(port), +m_usbPath(path), +m_address(), +m_port(0U), +m_txInvert(txInvert), +m_modLevel(modLevel), +m_duplex(duplex), +m_callsign(callsign), +m_txDelay(txDelay), +m_usb(NULL), +m_network(NULL), +m_buffer(NULL), +m_txData(1000U), +m_rx(false) +{ + wxASSERT(!port.IsEmpty()); + + m_usb = new CSerialDataController(port, SERIAL_115200); + + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CDVRPTRV2Controller::CDVRPTRV2Controller(const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, bool duplex, const wxString& callsign, unsigned int txDelay) : +CModem(), +m_connection(CT_NETWORK), +m_usbPort(), +m_usbPath(), +m_address(address), +m_port(port), +m_txInvert(txInvert), +m_modLevel(modLevel), +m_duplex(duplex), +m_callsign(callsign), +m_txDelay(txDelay), +m_usb(NULL), +m_network(NULL), +m_buffer(NULL), +m_txData(1000U), +m_rx(false) +{ + wxASSERT(!address.IsEmpty()); + wxASSERT(port > 0U); + + m_network = new CTCPReaderWriter(address, port); + + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CDVRPTRV2Controller::~CDVRPTRV2Controller() +{ + delete m_usb; + delete m_network; + + delete[] m_buffer; +} + +bool CDVRPTRV2Controller::start() +{ + findPort(); + + bool ret = openModem(); + if (!ret) + return false; + + findPath(); + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CDVRPTRV2Controller::Entry() +{ + wxLogMessage(wxT("Starting DV-RPTR2 Modem Controller thread")); + + // Clock every 5ms-ish + CTimer pollTimer(200U, 0U, 250U); + pollTimer.start(); + + unsigned int space = 0U; + + while (!m_stopped) { + // Poll the modem status every 250ms + if (pollTimer.hasExpired()) { + bool ret = readSpace(); + if (!ret) { + ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DV-RPTR2 Modem Controller thread")); + return NULL; + } + } + + pollTimer.start(); + } + + unsigned int length; + RESP_TYPE_V2 type = getResponse(m_buffer, length); + + switch (type) { + case RT2_TIMEOUT: + break; + + case RT2_ERROR: { + bool ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DV-RPTR2 Modem Controller thread")); + return NULL; + } + } + break; + + case RT2_HEADER: { + // CUtils::dump(wxT("RT2_HEADER"), m_buffer, length); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 9U, RADIO_HEADER_LENGTH_BYTES - 2U); + + // Dummy checksum + data[0U] = 0xFFU; + data[1U] = 0xFFU; + m_rxData.addData(data, 2U); + + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 51U, DV_FRAME_LENGTH_BYTES); + + m_rx = true; + } + break; + + case RT2_DATA: { + // CUtils::dump(wxT("RT2_DATA"), m_buffer, length); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 5U, DV_FRAME_LENGTH_BYTES); + + m_rx = true; + + // End of transmission? + bool end = (m_buffer[19U] & 0x40U) == 0x40U; + if (end) { + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + } + break; + + case RT2_SPACE: + space = m_buffer[9U]; + // CUtils::dump(wxT("RT2_SPACE"), m_buffer, length); + break; + + // These should not be received in this loop, but don't complain if we do + case RT2_QUERY: + case RT2_CONFIG: + break; + + default: + wxLogMessage(wxT("Unknown DV-RPTR2 message, type")); + CUtils::dump(wxT("Buffer dump"), m_buffer, length); + break; + } + + if (space >= 4U) { + if (m_txData.hasData()) { + unsigned char len = 0U; + unsigned char data[200U]; + + { + wxMutexLocker locker(m_mutex); + + m_txData.getData(&len, 1U); + m_txData.getData(data, len); + } + + // CUtils::dump(wxT("Write"), data, len); + + bool ret = writeModem(data, len); + if (!ret) { + ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DV-RPTR2 Modem Controller thread")); + return NULL; + } + } else { + if (len > 100U) + space -= 4U; + else + space--; + } + } + } + + Sleep(5UL); + + pollTimer.clock(); + } + + wxLogMessage(wxT("Stopping DV-RPTR2 Modem Controller thread")); + + closeModem(); + + return NULL; +} + +bool CDVRPTRV2Controller::writeHeader(const CHeaderData& header) +{ + bool ret = m_txData.hasSpace(106U); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + unsigned char buffer[105U]; + + ::memset(buffer, 0x00U, 105U); + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'X'; + buffer[5U] = '0'; + buffer[6U] = '0'; + buffer[7U] = '0'; + buffer[8U] = '2'; + + ::memset(buffer + 9U, ' ', RADIO_HEADER_LENGTH_BYTES); + + buffer[9U] = header.getFlag1(); + buffer[10U] = header.getFlag2(); + buffer[11U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 12U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 20U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 28U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 36U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer[i + 44U] = my2.GetChar(i); + + wxMutexLocker locker(m_mutex); + + unsigned char len = 105U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 105U); + + m_tx = true; + + return true; +} + +bool CDVRPTRV2Controller::writeData(const unsigned char* data, unsigned int, bool end) +{ + bool ret = m_txData.hasSpace(18U); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + unsigned char buffer[17U]; + + ::memset(buffer, 0x00U, 17U); + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'Z'; + + ::memcpy(buffer + 5U, data, DV_FRAME_LENGTH_BYTES); + + if (end) { + buffer[14U] = 0x55U; + buffer[15U] = 0x55U; + buffer[16U] = 0x55U; + + m_tx = false; + } + + wxMutexLocker locker(m_mutex); + + unsigned char len = 17U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 17U); + + return true; +} + +unsigned int CDVRPTRV2Controller::getSpace() +{ + return m_txData.freeSpace() / 18U; +} + +bool CDVRPTRV2Controller::isTXReady() +{ + return m_txData.isEmpty(); +} + +bool CDVRPTRV2Controller::readSerial() +{ + unsigned char buffer[105U]; + + ::memset(buffer, 0x00U, 105U); + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'X'; + buffer[5U] = '9'; + buffer[6U] = '0'; + buffer[7U] = '0'; + buffer[8U] = '0'; + + // CUtils::dump(wxT("Written"), buffer, 105U); + + bool ret = writeModem(buffer, 105U); + if (!ret) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_V2 resp; + do { + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT2_QUERY) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DV-RPTR modem is not responding to the query command")); + return false; + } + } + } while (resp != RT2_QUERY); + + wxLogInfo(wxT("DV-RPTR Modem Hardware serial: 0x%02X%02X%02x%02X"), m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U]); + + return true; +} + +bool CDVRPTRV2Controller::readSpace() +{ + unsigned char buffer[10U]; + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'Y'; + buffer[5U] = '9'; + buffer[6U] = '0'; + buffer[7U] = '1'; + buffer[8U] = '1'; + buffer[9U] = 0x00U; + + // CUtils::dump(wxT("Written"), buffer, 10U); + + return writeModem(buffer, 10U); +} + +bool CDVRPTRV2Controller::setConfig() +{ + unsigned char buffer[105U]; + + ::memset(buffer, 0x00U, 105U); + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'X'; + buffer[5U] = '9'; + buffer[6U] = '0'; + buffer[7U] = '0'; + buffer[8U] = '1'; + + ::memset(buffer + 9U, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH && i < m_callsign.Len(); i++) + buffer[9U + i] = m_callsign.GetChar(i); + + buffer[65U] = m_duplex ? 0x03U : 0x00U; + + buffer[66U] = m_txInvert ? 0x01U : 0x00U; + + buffer[73U] = (m_modLevel * 256U) / 100U; + + // Internal and external speaker on + buffer[86U] = 0x03U; + + buffer[87U] = 0x01U; + + if (m_txDelay < 100U) + m_txDelay = 100U; + if (m_txDelay > 850U) + m_txDelay = 850U; + + buffer[89U] = m_txDelay / 10U; + + // CUtils::dump(wxT("Written"), buffer, 105U); + + bool ret = writeModem(buffer, 105U); + if (!ret) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_V2 resp; + do { + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT2_CONFIG) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DV-RPTR modem is not responding to the configure command")); + return false; + } + } + } while (resp != RT2_CONFIG); + + // CUtils::dump(wxT("Response"), m_buffer, length); + + wxString firmware((char*)(m_buffer + 9U), wxConvLocal); + + wxLogInfo(wxT("DV-RPTR Modem Firmware version: %s"), firmware.c_str()); + + return true; +} + +RESP_TYPE_V2 CDVRPTRV2Controller::getResponse(unsigned char *buffer, unsigned int& length) +{ + // Get the start of the frame or nothing at all + int ret = readModem(buffer + 0U, 5U); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT2_ERROR; + } + + if (ret == 0) + return RT2_TIMEOUT; + + while (::memcmp(buffer + 0U, "HEAD", 4U) != 0) { + buffer[0U] = buffer[1U]; + buffer[1U] = buffer[2U]; + buffer[2U] = buffer[3U]; + buffer[3U] = buffer[4U]; + + ret = readModem(buffer + 4U, 1U); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT2_ERROR; + } + + if (ret == 0) + return RT2_TIMEOUT; + } + + switch (buffer[4U]) { + case 'X': + length = 105U; + break; + case 'Y': + length = 10U; + break; + case 'Z': + length = 20U; + break; + default: + wxLogError(wxT("DV-RPTR frame type is incorrect - 0x%02X"), buffer[4U]); + return RT2_UNKNOWN; + } + + unsigned int offset = 5U; + + while (offset < length) { + ret = readModem(buffer + offset, length - offset); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT2_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + Sleep(5UL); + } + + // CUtils::dump(wxT("Received"), buffer, length); + + if (::memcmp(buffer + 0U, "HEADZ", 5U) == 0) { + return RT2_DATA; + } else if (::memcmp(buffer + 5U, "0001", 4U) == 0) { + if (buffer[104U] == 0x01U) + return RT2_HEADER; + } else if (::memcmp(buffer + 5U, "9900", 4U) == 0) { + return RT2_QUERY; + } else if (::memcmp(buffer + 5U, "9001", 4U) == 0) { + return RT2_CONFIG; + } else if (::memcmp(buffer + 5U, "9011", 4U) == 0) { + return RT2_SPACE; + } else if (::memcmp(buffer + 5U, "9021", 4U) == 0) { + return RT2_TIMEOUT; + } + + return RT2_UNKNOWN; +} + +wxString CDVRPTRV2Controller::getPath() const +{ + return m_usbPath; +} + +bool CDVRPTRV2Controller::findPort() +{ + if (m_connection != CT_USB) + return true; + + if (m_usbPath.IsEmpty()) + return false; + +#if defined(__WINDOWS__) +#else + wxDir dir; + bool ret1 = dir.Open(wxT("/sys/class/tty")); + if (!ret1) { + wxLogError(wxT("Cannot open directory /sys/class/tty")); + return false; + } + + wxString fileName; + ret1 = dir.GetFirst(&fileName, wxT("ttyACM*")); + while (ret1) { + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), fileName.c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + ::strcat(cpath, "/device"); + ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret2); + } else { + // Get all but the last section + wxString fullPath = wxString(symlink, wxConvLocal, ret2); + path = fullPath.BeforeLast(wxT('/')); + } + + if (path.IsSameAs(m_usbPath)) { + m_usbPort.Printf(wxT("/dev/%s"), fileName.c_str()); + + wxLogMessage(wxT("Found modem port of %s based on the path"), m_usbPort.c_str()); + + return true; + } + + ret1 = dir.GetNext(&fileName); + } +#endif + + return false; +} + +bool CDVRPTRV2Controller::findPath() +{ + if (m_connection != CT_USB) + return true; + +#if defined(__WINDOWS__) +#ifdef notdef + GUID guids[5U]; + + DWORD count; + BOOL res = ::SetupDiClassGuidsFromName(L"Multifunction", guids, 5U, &count); + if (!res) { + wxLogError(wxT("Error from SetupDiClassGuidsFromName: err=%u"), ::GetLastError()); + return false; + } + + for (DWORD i = 0U; i < count; i++) { + HDEVINFO devInfo = ::SetupDiGetClassDevs(&guids[i], NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (devInfo == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from SetupDiGetClassDevs: err=%u"), ::GetLastError()); + return false; + } + + SP_DEVICE_INTERFACE_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (DWORD index = 0U; ::SetupDiEnumDeviceInterfaces(devInfo, NULL, &guids[i], index, &devInfoData); index++) { + // Find the required length of the device structure + DWORD length; + ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, NULL, 0U, &length, NULL); + + PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = PSP_DEVICE_INTERFACE_DETAIL_DATA(::malloc(length)); + detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // Get the detailed data into the newly allocated device structure + DWORD required; + res = ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, detailData, length, &required, NULL); + if (!res) { + wxLogError(wxT("Error from SetupDiGetDeviceInterfaceDetail: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::free(detailData); + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + } + + return false; +#endif +#else + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), m_usbPort.Mid(5U).c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + ::strcat(cpath, "/device"); + ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret); + } else { + wxString fullPath = wxString(symlink, wxConvLocal, ret); + path = fullPath.BeforeLast(wxT('/')); + } + + if (m_usbPath.IsEmpty()) + wxLogMessage(wxT("Found modem path of %s"), path.c_str()); + + m_usbPath = path; +#endif + + return true; +} + +bool CDVRPTRV2Controller::findModem() +{ + closeModem(); + + // Tell the repeater that the signal has gone away + if (m_rx) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + + unsigned int count = 0U; + + // Purge the transmit buffer every 500ms to avoid overflow, but only try and reopen the modem every 2s + while (!m_stopped) { + count++; + if (count >= 4U) { + wxLogMessage(wxT("Trying to reopen the modem")); + + bool ret = findPort(); + if (ret) { + ret = openModem(); + if (ret) + return true; + } + + count = 0U; + } + + Sleep(500UL); + } + + return false; +} + +bool CDVRPTRV2Controller::openModem() +{ + bool ret = false; + + switch (m_connection) { + case CT_USB: + ret = m_usb->open(); + break; + case CT_NETWORK: + ret = m_network->open(); + break; + default: + wxLogError(wxT("Invalid connection type: %d"), int(m_connection)); + break; + } + + if (!ret) + return false; + + ret = readSerial(); + if (!ret) { + closeModem(); + return false; + } + + ret = setConfig(); + if (!ret) { + closeModem(); + return false; + } + + return true; +} + +int CDVRPTRV2Controller::readModem(unsigned char* buffer, unsigned int length) +{ + switch (m_connection) { + case CT_USB: + return m_usb->read(buffer, length); + case CT_NETWORK: + return m_network->read(buffer, length, 0U); + default: + return -1; + } +} + +bool CDVRPTRV2Controller::writeModem(const unsigned char* buffer, unsigned int length) +{ + switch (m_connection) { + case CT_USB: + return m_usb->write(buffer, length) == int(length); + case CT_NETWORK: + return m_network->write(buffer, length); + default: + return false; + } +} + +void CDVRPTRV2Controller::closeModem() +{ + switch (m_connection) { + case CT_USB: + return m_usb->close(); + case CT_NETWORK: + return m_network->close(); + default: + return; + } +} diff --git a/Common/DVRPTRV2Controller.h b/Common/DVRPTRV2Controller.h new file mode 100644 index 0000000..8c35f7d --- /dev/null +++ b/Common/DVRPTRV2Controller.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DVRPTRV2Controller_H +#define DVRPTRV2Controller_H + +#include "SerialDataController.h" +#include "TCPReaderWriter.h" +#include "DStarDefines.h" +#include "RingBuffer.h" +#include "Modem.h" +#include "Utils.h" + +#include + +enum RESP_TYPE_V2 { + RT2_TIMEOUT, + RT2_ERROR, + RT2_UNKNOWN, + RT2_SPACE, + RT2_QUERY, + RT2_CONFIG, + RT2_HEADER, + RT2_DATA +}; + +class CDVRPTRV2Controller : public CModem { +public: + CDVRPTRV2Controller(const wxString& port, const wxString& path, bool txInvert, unsigned int modLevel, bool duplex, const wxString& callsign, unsigned int txDelay); + CDVRPTRV2Controller(const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, bool duplex, const wxString& callsign, unsigned int txDelay); + virtual ~CDVRPTRV2Controller(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + + virtual wxString getPath() const; + +private: + CONNECTION_TYPE m_connection; + wxString m_usbPort; + wxString m_usbPath; + wxString m_address; + unsigned int m_port; + bool m_txInvert; + unsigned int m_modLevel; + bool m_duplex; + wxString m_callsign; + unsigned int m_txDelay; + CSerialDataController* m_usb; + CTCPReaderWriter* m_network; + unsigned char* m_buffer; + CRingBuffer m_txData; + bool m_rx; + + bool readSerial(); + bool setConfig(); + bool readSpace(); + + RESP_TYPE_V2 getResponse(unsigned char* buffer, unsigned int& length); + + bool findPort(); + bool findPath(); + + bool findModem(); + bool openModem(); + + int readModem(unsigned char* buffer, unsigned int length); + bool writeModem(const unsigned char* buffer, unsigned int length); + void closeModem(); +}; + +#endif diff --git a/Common/DVRPTRV3Controller.cpp b/Common/DVRPTRV3Controller.cpp new file mode 100644 index 0000000..79fbf09 --- /dev/null +++ b/Common/DVRPTRV3Controller.cpp @@ -0,0 +1,841 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DVRPTRV3Controller.h" +#include "DStarDefines.h" +#include "Timer.h" + +#if defined(__WINDOWS__) +#include +#else +#include +#endif + +const unsigned int MAX_RESPONSES = 30U; + +const unsigned int BUFFER_LENGTH = 200U; + +CDVRPTRV3Controller::CDVRPTRV3Controller(const wxString& port, const wxString& path, bool txInvert, unsigned int modLevel, bool duplex, const wxString& callsign, unsigned int txDelay) : +CModem(), +m_connection(CT_USB), +m_usbPort(port), +m_usbPath(path), +m_address(), +m_port(0U), +m_txInvert(txInvert), +m_modLevel(modLevel), +m_duplex(duplex), +m_callsign(callsign), +m_txDelay(txDelay), +m_usb(NULL), +m_network(NULL), +m_buffer(NULL), +m_txData(1000U), +m_rx(false) +{ + wxASSERT(!port.IsEmpty()); + + m_usb = new CSerialDataController(port, SERIAL_115200); + + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CDVRPTRV3Controller::CDVRPTRV3Controller(const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, bool duplex, const wxString& callsign, unsigned int txDelay) : +CModem(), +m_connection(CT_NETWORK), +m_usbPort(), +m_usbPath(), +m_address(address), +m_port(port), +m_txInvert(txInvert), +m_modLevel(modLevel), +m_duplex(duplex), +m_callsign(callsign), +m_txDelay(txDelay), +m_usb(NULL), +m_network(NULL), +m_buffer(NULL), +m_txData(1000U), +m_rx(false) +{ + wxASSERT(!address.IsEmpty()); + wxASSERT(port > 0U); + + m_network = new CTCPReaderWriter(address, port); + + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CDVRPTRV3Controller::~CDVRPTRV3Controller() +{ + delete m_usb; + delete m_network; + + delete[] m_buffer; +} + +bool CDVRPTRV3Controller::start() +{ + findPort(); + + bool ret = openModem(); + if (!ret) + return false; + + findPath(); + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CDVRPTRV3Controller::Entry() +{ + wxLogMessage(wxT("Starting DV-RPTR3 Modem Controller thread")); + + // Clock every 5ms-ish + CTimer pollTimer(200U, 0U, 250U); + pollTimer.start(); + + unsigned int space = 0U; + + while (!m_stopped) { + // Poll the modem status every 250ms + if (pollTimer.hasExpired()) { + bool ret = readSpace(); + if (!ret) { + ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DV-RPTR3 Modem Controller thread")); + return NULL; + } + } + + pollTimer.start(); + } + + unsigned int length; + RESP_TYPE_V3 type = getResponse(m_buffer, length); + + switch (type) { + case RT3_TIMEOUT: + break; + + case RT3_ERROR: { + bool ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DV-RPTR3 Modem Controller thread")); + return NULL; + } + } + break; + + case RT3_HEADER: { + // CUtils::dump(wxT("RT3_HEADER"), m_buffer, length); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 9U, RADIO_HEADER_LENGTH_BYTES - 2U); + + // Dummy checksum + data[0U] = 0xFFU; + data[1U] = 0xFFU; + m_rxData.addData(data, 2U); + + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 51U, DV_FRAME_LENGTH_BYTES); + + m_rx = true; + } + break; + + case RT3_DATA: { + // CUtils::dump(wxT("RT3_DATA"), m_buffer, length); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 5U, DV_FRAME_LENGTH_BYTES); + + m_rx = true; + + // End of transmission? + bool end = (m_buffer[19U] & 0x40U) == 0x40U; + if (end) { + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + } + break; + + case RT3_SPACE: + space = m_buffer[9U]; + // CUtils::dump(wxT("RT3_SPACE"), m_buffer, length); + break; + + // These should not be received in this loop, but don't complain if we do + case RT3_QUERY: + case RT3_CONFIG: + break; + + default: + wxLogMessage(wxT("Unknown DV-RPTR3 message, type")); + CUtils::dump(wxT("Buffer dump"), m_buffer, length); + break; + } + + if (space >= 4U) { + if (m_txData.hasData()) { + unsigned char len = 0U; + unsigned char data[200U]; + + { + wxMutexLocker locker(m_mutex); + + m_txData.getData(&len, 1U); + m_txData.getData(data, len); + } + + // CUtils::dump(wxT("Write"), data, len); + + bool ret = writeModem(data, len); + if (!ret) { + ret = findModem(); + if (!ret) { + wxLogMessage(wxT("Stopping DV-RPTR3 Modem Controller thread")); + return NULL; + } + } else { + if (len > 100U) + space -= 4U; + else + space--; + } + } + } + + Sleep(5UL); + + pollTimer.clock(); + } + + wxLogMessage(wxT("Stopping DV-RPTR3 Modem Controller thread")); + + closeModem(); + + return NULL; +} + +bool CDVRPTRV3Controller::writeHeader(const CHeaderData& header) +{ + bool ret = m_txData.hasSpace(106U); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + unsigned char buffer[105U]; + + ::memset(buffer, 0x00U, 105U); + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'X'; + buffer[5U] = '0'; + buffer[6U] = '0'; + buffer[7U] = '0'; + buffer[8U] = '2'; + + ::memset(buffer + 9U, ' ', RADIO_HEADER_LENGTH_BYTES); + + buffer[9U] = header.getFlag1(); + buffer[10U] = header.getFlag2(); + buffer[11U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 12U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 20U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 28U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 36U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer[i + 44U] = my2.GetChar(i); + + wxMutexLocker locker(m_mutex); + + unsigned char len = 105U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 105U); + + m_tx = true; + + return true; +} + +bool CDVRPTRV3Controller::writeData(const unsigned char* data, unsigned int, bool end) +{ + bool ret = m_txData.hasSpace(18U); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + unsigned char buffer[17U]; + + ::memset(buffer, 0x00U, 17U); + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'Z'; + + ::memcpy(buffer + 5U, data, DV_FRAME_LENGTH_BYTES); + + if (end) { + buffer[14U] = 0x55U; + buffer[15U] = 0x55U; + buffer[16U] = 0x55U; + + m_tx = false; + } + + wxMutexLocker locker(m_mutex); + + unsigned char len = 17U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 17U); + + return true; +} + +unsigned int CDVRPTRV3Controller::getSpace() +{ + return m_txData.freeSpace() / 18U; +} + +bool CDVRPTRV3Controller::isTXReady() +{ + return m_txData.isEmpty(); +} + +bool CDVRPTRV3Controller::readSerial() +{ + unsigned char buffer[105U]; + + ::memset(buffer, 0x00U, 105U); + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'X'; + buffer[5U] = '9'; + buffer[6U] = '0'; + buffer[7U] = '0'; + buffer[8U] = '0'; + + // CUtils::dump(wxT("Written"), buffer, 105U); + + bool ret = writeModem(buffer, 105U); + if (!ret) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_V3 resp; + do { + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT3_QUERY) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DV-RPTR modem is not responding to the query command")); + return false; + } + } + } while (resp != RT3_QUERY); + + wxLogInfo(wxT("DV-RPTR Modem Hardware serial: 0x%02X%02X%02x%02X"), m_buffer[9U], m_buffer[10U], m_buffer[11U], m_buffer[12U]); + + return true; +} + +bool CDVRPTRV3Controller::readSpace() +{ + unsigned char buffer[10U]; + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'Y'; + buffer[5U] = '9'; + buffer[6U] = '0'; + buffer[7U] = '1'; + buffer[8U] = '1'; + buffer[9U] = 0x00U; + + // CUtils::dump(wxT("Written"), buffer, 10U); + + return writeModem(buffer, 10U); +} + +bool CDVRPTRV3Controller::setConfig() +{ + unsigned char buffer[105U]; + + ::memset(buffer, 0x00U, 105U); + + buffer[0U] = 'H'; + buffer[1U] = 'E'; + buffer[2U] = 'A'; + buffer[3U] = 'D'; + buffer[4U] = 'X'; + buffer[5U] = '9'; + buffer[6U] = '0'; + buffer[7U] = '0'; + buffer[8U] = '1'; + + ::memset(buffer + 9U, ' ', LONG_CALLSIGN_LENGTH); + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH && i < m_callsign.Len(); i++) + buffer[9U + i] = m_callsign.GetChar(i); + + buffer[65U] = m_duplex ? 0x03U : 0x00U; + + buffer[66U] = m_txInvert ? 0x01U : 0x00U; + + buffer[73U] = (m_modLevel * 256U) / 100U; + + // Internal and external speaker on + buffer[86U] = 0x03U; + + buffer[87U] = 0x01U; + + if (m_txDelay < 100U) + m_txDelay = 100U; + if (m_txDelay > 850U) + m_txDelay = 850U; + + buffer[89U] = m_txDelay / 10U; + + // CUtils::dump(wxT("Written"), buffer, 105U); + + bool ret = writeModem(buffer, 105U); + if (!ret) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_V3 resp; + do { + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RT3_CONFIG) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The DV-RPTR modem is not responding to the configure command")); + return false; + } + } + } while (resp != RT3_CONFIG); + + // CUtils::dump(wxT("Response"), m_buffer, length); + + wxString firmware((char*)(m_buffer + 9U), wxConvLocal); + + wxLogInfo(wxT("DV-RPTR Modem Firmware version: %s"), firmware.c_str()); + + return true; +} + +RESP_TYPE_V3 CDVRPTRV3Controller::getResponse(unsigned char *buffer, unsigned int& length) +{ + // Get the start of the frame or nothing at all + int ret = readModem(buffer + 0U, 5U); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT3_ERROR; + } + + if (ret == 0) + return RT3_TIMEOUT; + + while (::memcmp(buffer + 0U, "HEAD", 4U) != 0) { + buffer[0U] = buffer[1U]; + buffer[1U] = buffer[2U]; + buffer[2U] = buffer[3U]; + buffer[3U] = buffer[4U]; + + ret = readModem(buffer + 4U, 1U); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT3_ERROR; + } + + if (ret == 0) + return RT3_TIMEOUT; + } + + switch (buffer[4U]) { + case 'X': + length = 105U; + break; + case 'Y': + length = 10U; + break; + case 'Z': + length = 20U; + break; + default: + wxLogError(wxT("DV-RPTR frame type is incorrect - 0x%02X"), buffer[4U]); + return RT3_UNKNOWN; + } + + unsigned int offset = 5U; + + while (offset < length) { + ret = readModem(buffer + offset, length - offset); + if (ret < 0) { + wxLogError(wxT("Error when reading from the DV-RPTR")); + return RT3_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + Sleep(5UL); + } + + // CUtils::dump(wxT("Received"), buffer, length); + + if (::memcmp(buffer + 0U, "HEADZ", 5U) == 0) { + return RT3_DATA; + } else if (::memcmp(buffer + 5U, "0001", 4U) == 0) { + if (buffer[104U] == 0x01U) + return RT3_HEADER; + } else if (::memcmp(buffer + 5U, "9900", 4U) == 0) { + return RT3_QUERY; + } else if (::memcmp(buffer + 5U, "9001", 4U) == 0) { + return RT3_CONFIG; + } else if (::memcmp(buffer + 5U, "9011", 4U) == 0) { + return RT3_SPACE; + } else if (::memcmp(buffer + 5U, "9021", 4U) == 0) { + return RT3_TIMEOUT; + } + + return RT3_UNKNOWN; +} + +wxString CDVRPTRV3Controller::getPath() const +{ + return m_usbPath; +} + +bool CDVRPTRV3Controller::findPort() +{ + if (m_connection != CT_USB) + return true; + + if (m_usbPath.IsEmpty()) + return false; + +#if defined(__WINDOWS__) +#else + wxDir dir; + bool ret1 = dir.Open(wxT("/sys/class/tty")); + if (!ret1) { + wxLogError(wxT("Cannot open directory /sys/class/tty")); + return false; + } + + wxString fileName; + ret1 = dir.GetFirst(&fileName, wxT("ttyACM*")); + while (ret1) { + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), fileName.c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + ::strcat(cpath, "/device"); + ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret2); + } else { + // Get all but the last section + wxString fullPath = wxString(symlink, wxConvLocal, ret2); + path = fullPath.BeforeLast(wxT('/')); + } + + if (path.IsSameAs(m_usbPath)) { + m_usbPort.Printf(wxT("/dev/%s"), fileName.c_str()); + + wxLogMessage(wxT("Found modem port of %s based on the path"), m_usbPort.c_str()); + + return true; + } + + ret1 = dir.GetNext(&fileName); + } +#endif + + return false; +} + +bool CDVRPTRV3Controller::findPath() +{ + if (m_connection != CT_USB) + return true; + +#if defined(__WINDOWS__) +#ifdef notdef + GUID guids[5U]; + + DWORD count; + BOOL res = ::SetupDiClassGuidsFromName(L"Multifunction", guids, 5U, &count); + if (!res) { + wxLogError(wxT("Error from SetupDiClassGuidsFromName: err=%u"), ::GetLastError()); + return false; + } + + for (DWORD i = 0U; i < count; i++) { + HDEVINFO devInfo = ::SetupDiGetClassDevs(&guids[i], NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (devInfo == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from SetupDiGetClassDevs: err=%u"), ::GetLastError()); + return false; + } + + SP_DEVICE_INTERFACE_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (DWORD index = 0U; ::SetupDiEnumDeviceInterfaces(devInfo, NULL, &guids[i], index, &devInfoData); index++) { + // Find the required length of the device structure + DWORD length; + ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, NULL, 0U, &length, NULL); + + PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = PSP_DEVICE_INTERFACE_DETAIL_DATA(::malloc(length)); + detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // Get the detailed data into the newly allocated device structure + DWORD required; + res = ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, detailData, length, &required, NULL); + if (!res) { + wxLogError(wxT("Error from SetupDiGetDeviceInterfaceDetail: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::free(detailData); + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + } + + return false; +#endif +#else + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), m_usbPort.Mid(5U).c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + ::strcat(cpath, "/device"); + ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret); + } else { + wxString fullPath = wxString(symlink, wxConvLocal, ret); + path = fullPath.BeforeLast(wxT('/')); + } + + if (m_usbPath.IsEmpty()) + wxLogMessage(wxT("Found modem path of %s"), path.c_str()); + + m_usbPath = path; +#endif + + return true; +} + +bool CDVRPTRV3Controller::findModem() +{ + closeModem(); + + // Tell the repeater that the signal has gone away + if (m_rx) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + + unsigned int count = 0U; + + // Purge the transmit buffer every 500ms to avoid overflow, but only try and reopen the modem every 2s + while (!m_stopped) { + count++; + if (count >= 4U) { + wxLogMessage(wxT("Trying to reopen the modem")); + + bool ret = findPort(); + if (ret) { + ret = openModem(); + if (ret) + return true; + } + + count = 0U; + } + + Sleep(500UL); + } + + return false; +} + +bool CDVRPTRV3Controller::openModem() +{ + bool ret = false; + + switch (m_connection) { + case CT_USB: + ret = m_usb->open(); + break; + case CT_NETWORK: + ret = m_network->open(); + break; + default: + wxLogError(wxT("Invalid connection type: %d"), int(m_connection)); + break; + } + + if (!ret) + return false; + + ret = readSerial(); + if (!ret) { + closeModem(); + return false; + } + + ret = setConfig(); + if (!ret) { + closeModem(); + return false; + } + + return true; +} + +int CDVRPTRV3Controller::readModem(unsigned char* buffer, unsigned int length) +{ + switch (m_connection) { + case CT_USB: + return m_usb->read(buffer, length); + case CT_NETWORK: + return m_network->read(buffer, length, 0U); + default: + return -1; + } +} + +bool CDVRPTRV3Controller::writeModem(const unsigned char* buffer, unsigned int length) +{ + switch (m_connection) { + case CT_USB: + return m_usb->write(buffer, length) == int(length); + case CT_NETWORK: + return m_network->write(buffer, length); + default: + return false; + } +} + +void CDVRPTRV3Controller::closeModem() +{ + switch (m_connection) { + case CT_USB: + return m_usb->close(); + case CT_NETWORK: + return m_network->close(); + default: + return; + } +} diff --git a/Common/DVRPTRV3Controller.h b/Common/DVRPTRV3Controller.h new file mode 100644 index 0000000..4829c61 --- /dev/null +++ b/Common/DVRPTRV3Controller.h @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DVRPTRV3Controller_H +#define DVRPTRV3Controller_H + +#include "SerialDataController.h" +#include "TCPReaderWriter.h" +#include "DStarDefines.h" +#include "RingBuffer.h" +#include "Modem.h" +#include "Utils.h" + +#include + +enum RESP_TYPE_V3 { + RT3_TIMEOUT, + RT3_ERROR, + RT3_UNKNOWN, + RT3_SPACE, + RT3_QUERY, + RT3_CONFIG, + RT3_HEADER, + RT3_DATA +}; + +class CDVRPTRV3Controller : public CModem { +public: + CDVRPTRV3Controller(const wxString& port, const wxString& path, bool txInvert, unsigned int modLevel, bool duplex, const wxString& callsign, unsigned int txDelay); + CDVRPTRV3Controller(const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, bool duplex, const wxString& callsign, unsigned int txDelay); + virtual ~CDVRPTRV3Controller(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + + virtual wxString getPath() const; + +private: + CONNECTION_TYPE m_connection; + wxString m_usbPort; + wxString m_usbPath; + wxString m_address; + unsigned int m_port; + bool m_txInvert; + unsigned int m_modLevel; + bool m_duplex; + wxString m_callsign; + unsigned int m_txDelay; + CSerialDataController* m_usb; + CTCPReaderWriter* m_network; + unsigned char* m_buffer; + CRingBuffer m_txData; + bool m_rx; + + bool readSerial(); + bool setConfig(); + bool readSpace(); + + RESP_TYPE_V3 getResponse(unsigned char* buffer, unsigned int& length); + + bool findPort(); + bool findPath(); + + bool findModem(); + bool openModem(); + + int readModem(unsigned char* buffer, unsigned int length); + bool writeModem(const unsigned char* buffer, unsigned int length); + void closeModem(); +}; + +#endif diff --git a/Common/DVTOOLFileReader.cpp b/Common/DVTOOLFileReader.cpp new file mode 100644 index 0000000..0a2550b --- /dev/null +++ b/Common/DVTOOLFileReader.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2009,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DVTOOLFileReader.h" +#include "DStarDefines.h" +#include "Utils.h" + +#include + +static const char DVTOOL_SIGNATURE[] = "DVTOOL"; +static const unsigned int DVTOOL_SIGNATURE_LENGTH = 6U; + +static const char DSVT_SIGNATURE[] = "DSVT"; +static const unsigned int DSVT_SIGNATURE_LENGTH = 4U; + +static const unsigned int FIXED_DATA_LENGTH = 9U; + +static const unsigned char HEADER_FLAG = 0x10; +static const unsigned char DATA_FLAG = 0x20; + +static const unsigned char HEADER_MASK = 0x80; +static const unsigned char TRAILER_MASK = 0x40; + +const unsigned int BUFFER_LENGTH = 255U; + + +CDVTOOLFileReader::CDVTOOLFileReader() : +m_fileName(), +m_file(), +m_records(0U), +m_type(DVTFR_NONE), +m_buffer(NULL), +m_length(0U), +m_end(false) +{ + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CDVTOOLFileReader::~CDVTOOLFileReader() +{ + delete[] m_buffer; +} + +wxString CDVTOOLFileReader::getFileName() const +{ + return m_fileName; +} + +unsigned int CDVTOOLFileReader::getRecords() const +{ + return m_records; +} + +bool CDVTOOLFileReader::open(const wxString& fileName) +{ + m_fileName = fileName; + + bool res = m_file.Open(fileName, wxT("rb")); + if (!res) + return false; + + unsigned char buffer[DVTOOL_SIGNATURE_LENGTH]; + size_t n = m_file.Read(buffer, DVTOOL_SIGNATURE_LENGTH); + if (n != DVTOOL_SIGNATURE_LENGTH) { + m_file.Close(); + return false; + } + + if (::memcmp(buffer, DVTOOL_SIGNATURE, DVTOOL_SIGNATURE_LENGTH) != 0) { + m_file.Close(); + return false; + } + + wxUint32 uint32; + n = m_file.Read(&uint32, sizeof(wxUint32)); + if (n != sizeof(wxUint32)) { + m_file.Close(); + return false; + } + + m_records = wxUINT32_SWAP_ON_LE(uint32); + m_end = false; + + return true; +} + +DVTFR_TYPE CDVTOOLFileReader::read() +{ + wxUint16 uint16; + size_t n = m_file.Read(&uint16, sizeof(wxUint16)); + if (n != sizeof(wxUint16)) + return DVTFR_NONE; + + m_length = wxUINT16_SWAP_ON_BE(uint16) - 15U; + + unsigned char bytes[FIXED_DATA_LENGTH]; + n = m_file.Read(bytes, DSVT_SIGNATURE_LENGTH); + if (n != DSVT_SIGNATURE_LENGTH) + return DVTFR_NONE; + + if (::memcmp(bytes, DSVT_SIGNATURE, DSVT_SIGNATURE_LENGTH) != 0) + return DVTFR_NONE; + + char flag; + n = m_file.Read(&flag, 1U); + if (n != 1U) + return DVTFR_NONE; + + m_type = (flag == HEADER_FLAG) ? DVTFR_HEADER : DVTFR_DATA; + + n = m_file.Read(bytes, FIXED_DATA_LENGTH); + if (n != FIXED_DATA_LENGTH) + return DVTFR_NONE; + + n = m_file.Read(&flag, 1U); + if (n != 1U) + return DVTFR_NONE; + + if (m_type == DVTFR_DATA) { + if ((flag & TRAILER_MASK) == TRAILER_MASK) + m_end = true; + } + + n = m_file.Read(m_buffer, m_length); + if (n != m_length) + return DVTFR_NONE; + + return m_type; +} + +CHeaderData* CDVTOOLFileReader::readHeader() +{ + if (m_type != DVTFR_HEADER) + return NULL; + + if (m_buffer[39U] == 0xFFU && m_buffer[40U] == 0xFFU) + return new CHeaderData(m_buffer, RADIO_HEADER_LENGTH_BYTES, false); + + // Header checksum testing is enabled + CHeaderData* header = new CHeaderData(m_buffer, RADIO_HEADER_LENGTH_BYTES, true); + + if (!header->isValid()) { + CUtils::dump(wxT("Header checksum failure"), m_buffer, RADIO_HEADER_LENGTH_BYTES); + delete header; + return NULL; + } + + return header; +} + +unsigned int CDVTOOLFileReader::readData(unsigned char* buffer, unsigned int length, bool& end) +{ + wxASSERT(buffer != NULL); + wxASSERT(length > 0U); + + if (m_type != DVTFR_DATA) + return 0U; + + if (length > m_length) + length = m_length; + + end = m_end; + + ::memcpy(buffer, m_buffer, length); + + return length; +} + +void CDVTOOLFileReader::close() +{ + m_file.Close(); +} diff --git a/Common/DVTOOLFileReader.h b/Common/DVTOOLFileReader.h new file mode 100644 index 0000000..13ba0f1 --- /dev/null +++ b/Common/DVTOOLFileReader.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2009 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DVTOOLFileReader_H +#define DVTOOLFileReader_H + +#include "HeaderData.h" + +enum DVTFR_TYPE { + DVTFR_NONE, + DVTFR_HEADER, + DVTFR_DATA +}; + +#include +#include + +class CDVTOOLFileReader { +public: + CDVTOOLFileReader(); + ~CDVTOOLFileReader(); + + wxString getFileName() const; + unsigned int getRecords() const; + + bool open(const wxString& fileName); + DVTFR_TYPE read(); + CHeaderData* readHeader(); + unsigned int readData(unsigned char* buffer, unsigned int length, bool& end); + void close(); + +private: + wxString m_fileName; + wxFFile m_file; + wxUint32 m_records; + DVTFR_TYPE m_type; + unsigned char* m_buffer; + unsigned int m_length; + bool m_end; +}; + +#endif diff --git a/Common/DVTOOLFileWriter.cpp b/Common/DVTOOLFileWriter.cpp new file mode 100644 index 0000000..f642e35 --- /dev/null +++ b/Common/DVTOOLFileWriter.cpp @@ -0,0 +1,346 @@ +/* + * Copyright (C) 2009,2011,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CCITTChecksumReverse.h" +#include "DVTOOLFileWriter.h" +#include "DStarDefines.h" + +#include +#include + +static const char DVTOOL_SIGNATURE[] = "DVTOOL"; +static unsigned int DVTOOL_SIGNATURE_LENGTH = 6U; + +static const char DSVT_SIGNATURE[] = "DSVT"; +static unsigned int DSVT_SIGNATURE_LENGTH = 4U; + +static const unsigned char HEADER_FLAG = 0x10; +static const unsigned char DATA_FLAG = 0x20; + +static const unsigned char FIXED_DATA[] = {0x00, 0x81, 0x00, 0x20, 0x00, 0x01, 0x02, 0xC0, 0xDE}; +static unsigned int FIXED_DATA_LENGTH = 9U; + +static const unsigned char TRAILER_DATA[] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; +static unsigned int TRAILER_DATA_LENGTH = 12U; + +static const unsigned char HEADER_MASK = 0x80; +static const unsigned char TRAILER_MASK = 0x40; + +wxString CDVTOOLFileWriter::m_dirName = wxEmptyString; + +CDVTOOLFileWriter::CDVTOOLFileWriter() : +m_fileName(), +m_file(), +m_count(0U), +m_sequence(0U), +m_offset(0) +{ +} + +CDVTOOLFileWriter::~CDVTOOLFileWriter() +{ +} + +void CDVTOOLFileWriter::setDirectory(const wxString& dirName) +{ + m_dirName = dirName; +} + +wxString CDVTOOLFileWriter::getFileName() const +{ + return m_fileName; +} + +bool CDVTOOLFileWriter::open(const wxString& filename, const CHeaderData& header) +{ + if (m_file.IsOpened()) + close(); + + wxString name = filename; +#if !defined(__WINDOWS__) + name.Replace(wxT(" "), wxT("_")); +#endif + + wxFileName fileName(m_dirName, name, wxT("dvtool")); + m_fileName = fileName.GetFullPath(); + + bool res = m_file.Open(m_fileName, wxT("wb")); + if (!res) + return false; + + size_t n = m_file.Write(DVTOOL_SIGNATURE, DVTOOL_SIGNATURE_LENGTH); + if (n != DVTOOL_SIGNATURE_LENGTH) { + m_file.Close(); + return false; + } + + m_offset = m_file.Tell(); + + wxUint32 dummy = 0U; + n = m_file.Write(&dummy, sizeof(wxUint32)); + if (n != sizeof(wxUint32)) { + m_file.Close(); + return false; + } + + m_sequence = 0U; + m_count = 0U; + + res = writeHeader(header); + if (!res) { + m_file.Close(); + return false; + } + + return true; +} + +bool CDVTOOLFileWriter::open(const CHeaderData& header) +{ + if (m_file.IsOpened()) + close(); + + wxDateTime time; + time.SetToCurrent(); + + wxString name = time.Format(wxT("%Y%m%d-%H%M%S-")); + + name.Append(header.getRptCall1()); + name.Append(header.getRptCall2()); + name.Append(header.getYourCall()); + name.Append(header.getMyCall1()); + name.Append(header.getMyCall2()); + +#if !defined(__WINDOWS__) + name.Replace(wxT(" "), wxT("_")); +#endif + name.Replace(wxT("/"), wxT("-")); + + wxFileName fileName(m_dirName, name, wxT("dvtool")); + m_fileName = fileName.GetFullPath(); + + bool res = m_file.Open(m_fileName, wxT("wb")); + if (!res) + return false; + + size_t n = m_file.Write(DVTOOL_SIGNATURE, DVTOOL_SIGNATURE_LENGTH); + if (n != DVTOOL_SIGNATURE_LENGTH) { + m_file.Close(); + return false; + } + + m_offset = m_file.Tell(); + + wxUint32 dummy = 0U; + n = m_file.Write(&dummy, sizeof(wxUint32)); + if (n != sizeof(wxUint32)) { + m_file.Close(); + return false; + } + + m_sequence = 0U; + m_count = 0U; + + res = writeHeader(header); + if (!res) { + m_file.Close(); + return false; + } + + return true; +} + +bool CDVTOOLFileWriter::write(const unsigned char* buffer, unsigned int length) +{ + wxASSERT(buffer != 0); + wxASSERT(length > 0U); + + wxUint16 len = wxUINT16_SWAP_ON_BE(length + 15U); + size_t n = m_file.Write(&len, sizeof(wxUint16)); + if (n != sizeof(wxUint16)) { + m_file.Close(); + return false; + } + + n = m_file.Write(DSVT_SIGNATURE, DSVT_SIGNATURE_LENGTH); + if (n != DSVT_SIGNATURE_LENGTH) { + m_file.Close(); + return false; + } + + char byte = DATA_FLAG; + n = m_file.Write(&byte, 1U); + if (n != 1U) { + m_file.Close(); + return false; + } + + n = m_file.Write(FIXED_DATA, FIXED_DATA_LENGTH); + if (n != FIXED_DATA_LENGTH) { + m_file.Close(); + return false; + } + + byte = m_sequence; + n = m_file.Write(&byte, 1U); + if (n != 1U) { + m_file.Close(); + return false; + } + + n = m_file.Write(buffer, length); + if (n != length) { + m_file.Close(); + return false; + } + + m_count++; + m_sequence++; + if (m_sequence >= 0x15U) + m_sequence = 0U; + + return true; +} + +void CDVTOOLFileWriter::close() +{ + writeTrailer(); + + m_file.Seek(m_offset); + + wxUint32 count = wxUINT32_SWAP_ON_LE(m_count); + m_file.Write(&count, sizeof(wxUint32)); + + m_file.Close(); +} + +bool CDVTOOLFileWriter::writeHeader(const CHeaderData& header) +{ + unsigned char buffer[RADIO_HEADER_LENGTH_BYTES]; + + buffer[0] = header.getFlag1(); + buffer[1] = header.getFlag2(); + buffer[2] = header.getFlag3(); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[3 + i] = header.getRptCall1().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[11 + i] = header.getRptCall2().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[19 + i] = header.getYourCall().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[27 + i] = header.getMyCall1().GetChar(i); + + for (unsigned int i = 0U; i < SHORT_CALLSIGN_LENGTH; i++) + buffer[35 + i] = header.getMyCall2().GetChar(i); + + // Get the checksum for the header + CCCITTChecksumReverse csum; + csum.update(buffer, RADIO_HEADER_LENGTH_BYTES - 2U); + csum.result(buffer + 39U); + + wxUint16 len = wxUINT16_SWAP_ON_BE(RADIO_HEADER_LENGTH_BYTES + 15U); + size_t n = m_file.Write(&len, sizeof(wxUint16)); + if (n != sizeof(wxUint16)) { + m_file.Close(); + return false; + } + + n = m_file.Write(DSVT_SIGNATURE, DSVT_SIGNATURE_LENGTH); + if (n != DSVT_SIGNATURE_LENGTH) { + m_file.Close(); + return false; + } + + char byte = HEADER_FLAG; + n = m_file.Write(&byte, 1U); + if (n != 1U) { + m_file.Close(); + return false; + } + + n = m_file.Write(FIXED_DATA, FIXED_DATA_LENGTH); + if (n != FIXED_DATA_LENGTH) { + m_file.Close(); + return false; + } + + byte = HEADER_MASK; + n = m_file.Write(&byte, 1U); + if (n != 1U) { + m_file.Close(); + return false; + } + + n = m_file.Write(buffer, RADIO_HEADER_LENGTH_BYTES); + if (n != RADIO_HEADER_LENGTH_BYTES) { + m_file.Close(); + return false; + } + + m_count++; + + return true; +} + +bool CDVTOOLFileWriter::writeTrailer() +{ + wxUint16 len = wxUINT16_SWAP_ON_BE(27U); + size_t n = m_file.Write(&len, sizeof(wxUint16)); + if (n != sizeof(wxUint16)) { + m_file.Close(); + return false; + } + + n = m_file.Write(DSVT_SIGNATURE, DSVT_SIGNATURE_LENGTH); + if (n != DSVT_SIGNATURE_LENGTH) { + m_file.Close(); + return false; + } + + char byte = DATA_FLAG; + n = m_file.Write(&byte, 1U); + if (n != 1U) { + m_file.Close(); + return false; + } + + n = m_file.Write(FIXED_DATA, FIXED_DATA_LENGTH); + if (n != FIXED_DATA_LENGTH) { + m_file.Close(); + return false; + } + + byte = TRAILER_MASK | m_sequence; + n = m_file.Write(&byte, 1U); + if (n != 1U) { + m_file.Close(); + return false; + } + + n = m_file.Write(TRAILER_DATA, TRAILER_DATA_LENGTH); + if (n != TRAILER_DATA_LENGTH) { + m_file.Close(); + return false; + } + + return true; +} diff --git a/Common/DVTOOLFileWriter.h b/Common/DVTOOLFileWriter.h new file mode 100644 index 0000000..d771614 --- /dev/null +++ b/Common/DVTOOLFileWriter.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DVTOOLFileWriter_H +#define DVTOOLFileWriter_H + +#include "HeaderData.h" + +#include +#include + +class CDVTOOLFileWriter { +public: + CDVTOOLFileWriter(); + ~CDVTOOLFileWriter(); + + static void setDirectory(const wxString& dirName); + + wxString getFileName() const; + + bool open(const CHeaderData& header); + bool open(const wxString& filename, const CHeaderData& header); + bool write(const unsigned char* buffer, unsigned int length); + void close(); + +private: + static wxString m_dirName; + + wxString m_fileName; + wxFFile m_file; + wxUint32 m_count; + unsigned int m_sequence; + wxFileOffset m_offset; + + bool writeHeader(const CHeaderData& header); + bool writeTrailer(); +}; + +#endif diff --git a/Common/DummyController.cpp b/Common/DummyController.cpp new file mode 100644 index 0000000..3f14c0b --- /dev/null +++ b/Common/DummyController.cpp @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2009,2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "DummyController.h" + +#include + +CDummyController::CDummyController() +{ +} + +CDummyController::~CDummyController() +{ +} + +bool CDummyController::open() +{ + return true; +} + +void CDummyController::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + inp1 = false; + inp2 = false; + inp3 = false; + inp4 = false; + inp5 = false; +} + +void CDummyController::setDigitalOutputs(bool, bool, bool, bool, bool, bool, bool, bool) +{ +} + +void CDummyController::close() +{ +} diff --git a/Common/DummyController.h b/Common/DummyController.h new file mode 100644 index 0000000..f54604e --- /dev/null +++ b/Common/DummyController.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2009 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef DummyController_H +#define DummyController_H + +#include "HardwareController.h" + +class CDummyController : public IHardwareController { +public: + CDummyController(); + virtual ~CDummyController(); + + virtual bool open(); + + virtual void getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5); + + virtual void setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8); + + virtual void close(); + +private: +}; + +#endif diff --git a/Common/ExternalController.cpp b/Common/ExternalController.cpp new file mode 100644 index 0000000..fdb12be --- /dev/null +++ b/Common/ExternalController.cpp @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2009,2010,2014 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "ExternalController.h" + +#include "DStarDefines.h" + +CExternalController::CExternalController(IHardwareController* controller, bool pttInvert) : +wxThread(wxTHREAD_JOINABLE), +m_controller(controller), +m_pttInvert(pttInvert), +m_disable(false), +m_heartbeat(false), +m_active(false), +m_radioTX(false), +m_out1(false), +m_out2(false), +m_out3(false), +m_out4(false), +m_kill(false) +{ + // wxASSERT(controller != NULL); + + if (m_pttInvert) + m_radioTX = true; +} + +CExternalController::~CExternalController() +{ + delete m_controller; +} + +bool CExternalController::open() +{ + bool res = m_controller->open(); + if (!res) + return false; + + Create(); + Run(); + + return true; +} + +void* CExternalController::Entry() +{ + wxASSERT(m_controller != NULL); + + bool dummy1, dummy2, dummy3, dummy4; + + while (!m_kill) { + m_controller->setDigitalOutputs(m_radioTX, false, m_heartbeat, m_active, m_out1, m_out2, m_out3, m_out4); + m_controller->getDigitalInputs(dummy1, dummy2, dummy3, dummy4, m_disable); + + Sleep(DSTAR_FRAME_TIME_MS / 2U); + } + + if (m_pttInvert) + m_controller->setDigitalOutputs(true, false, false, false, false, false, false, false); + else + m_controller->setDigitalOutputs(false, false, false, false, false, false, false, false); + m_controller->getDigitalInputs(dummy1, dummy2, dummy3, dummy4, m_disable); + + Sleep(DSTAR_FRAME_TIME_MS * 3U); + + if (m_pttInvert) + m_controller->setDigitalOutputs(true, false, false, false, false, false, false, false); + else + m_controller->setDigitalOutputs(false, false, false, false, false, false, false, false); + m_controller->getDigitalInputs(dummy1, dummy2, dummy3, dummy4, m_disable); + + m_controller->close(); + + return NULL; +} + +bool CExternalController::getDisable() const +{ + return m_disable; +} + +void CExternalController::setRadioTransmit(bool value) +{ + if (m_pttInvert) + value = !value; + + m_radioTX = value; +} + +void CExternalController::setHeartbeat() +{ + m_heartbeat = !m_heartbeat; +} + +void CExternalController::setActive(bool value) +{ + m_active = value; +} + +void CExternalController::setOutput1(bool value) +{ + m_out1 = value; +} + +void CExternalController::setOutput2(bool value) +{ + m_out2 = value; +} + +void CExternalController::setOutput3(bool value) +{ + m_out3 = value; +} + +void CExternalController::setOutput4(bool value) +{ + m_out4 = value; +} + +void CExternalController::close() +{ + m_kill = true; + + Wait(); +} diff --git a/Common/ExternalController.h b/Common/ExternalController.h new file mode 100644 index 0000000..6a804f5 --- /dev/null +++ b/Common/ExternalController.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2009,2010,2013,2014 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef ExternalController_H +#define ExternalController_H + +#include "HardwareController.h" + +#include + +class CExternalController : public wxThread { +public: + CExternalController(IHardwareController* controller, bool pttInvert); + virtual ~CExternalController(); + + virtual bool open(); + + virtual bool getDisable() const; + + virtual void setRadioTransmit(bool value); + virtual void setHeartbeat(); + virtual void setActive(bool value); + virtual void setOutput1(bool value); + virtual void setOutput2(bool value); + virtual void setOutput3(bool value); + virtual void setOutput4(bool value); + + virtual void close(); + + virtual void* Entry(); + +private: + IHardwareController* m_controller; + bool m_pttInvert; + bool m_disable; + bool m_heartbeat; + bool m_active; + bool m_radioTX; + bool m_out1; + bool m_out2; + bool m_out3; + bool m_out4; + bool m_kill; +}; + +#endif diff --git a/Common/FIRFilter.cpp b/Common/FIRFilter.cpp new file mode 100644 index 0000000..c397b49 --- /dev/null +++ b/Common/FIRFilter.cpp @@ -0,0 +1,109 @@ +/* + * Copyright (C) 2001, 2002, 2003 by Tomi Manninen, OH2BNS + * Copyright (C) 2009 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "FIRFilter.h" + +CFIRFilter::CFIRFilter(const wxFloat32* taps, unsigned int length) : +m_taps(NULL), +m_length(length), +m_buffer(NULL), +m_bufLen(20U * m_length), +m_pointer(length) +{ + wxASSERT(taps != NULL); + wxASSERT(length > 0U); + + m_taps = new wxFloat32[m_length]; + m_buffer = new wxFloat32[m_bufLen]; + + ::memcpy(m_taps, taps, m_length * sizeof(wxFloat32)); + ::memset(m_buffer, 0x00, m_bufLen * sizeof(wxFloat32)); +} + +CFIRFilter::CFIRFilter() : +m_taps(NULL), +m_length(0U), +m_buffer(NULL), +m_bufLen(0U), +m_pointer(0U) +{ +} + +CFIRFilter::~CFIRFilter() +{ + delete[] m_taps; + delete[] m_buffer; +} + +void CFIRFilter::setTaps(const wxFloat32* taps, unsigned int length) +{ + wxASSERT(taps != NULL); + wxASSERT(length > 0U); + + delete[] m_taps; + delete[] m_buffer; + + m_length = length; + m_pointer = length; + m_bufLen = 20U * m_length; + + m_taps = new wxFloat32[m_length]; + m_buffer = new wxFloat32[m_bufLen]; + + ::memcpy(m_taps, taps, m_length * sizeof(wxFloat32)); + ::memset(m_buffer, 0x00, m_bufLen * sizeof(wxFloat32)); +} + +wxFloat32 CFIRFilter::process(wxFloat32 val) +{ + wxFloat32* ptr = m_buffer + m_pointer++; + + *ptr = val; + + wxFloat32* a = ptr - m_length; + wxFloat32* b = m_taps; + + wxFloat32 out = 0.0F; + for (unsigned int i = 0U; i < m_length; i++) + out += (*a++) * (*b++); + + if (m_pointer == m_bufLen) { + ::memcpy(m_buffer, m_buffer + m_bufLen - m_length, m_length * sizeof(wxFloat32)); + m_pointer = m_length; + } + + return out; +} + +void CFIRFilter::process(wxFloat32* inOut, unsigned int length) +{ + wxASSERT(inOut != NULL); + + for (unsigned int i = 0U; i < length; i++) + inOut[i] = process(inOut[i]); +} + +void CFIRFilter::process(const wxFloat32* in, wxFloat32* out, unsigned int length) +{ + wxASSERT(in != NULL); + wxASSERT(out != NULL); + + for (unsigned int i = 0U; i < length; i++) + out[i] = process(in[i]); +} + +void CFIRFilter::reset() +{ + ::memset(m_buffer, 0x00, m_bufLen * sizeof(wxFloat32));; +} diff --git a/Common/FIRFilter.h b/Common/FIRFilter.h new file mode 100644 index 0000000..b164212 --- /dev/null +++ b/Common/FIRFilter.h @@ -0,0 +1,41 @@ +/* + * Copyright (C) 2009,2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef FIRFilter_H +#define FIRFilter_H + +#include + +class CFIRFilter { +public: + CFIRFilter(const wxFloat32* taps, unsigned int length); + CFIRFilter(); + ~CFIRFilter(); + + void setTaps(const wxFloat32* taps, unsigned int length); + + wxFloat32 process(wxFloat32 val); + void process(wxFloat32* inOut, unsigned int length); + void process(const wxFloat32* in, wxFloat32* out, unsigned int length); + + void reset(); + +private: + wxFloat32* m_taps; + unsigned int m_length; + wxFloat32* m_buffer; + unsigned int m_bufLen; + unsigned int m_pointer; +}; + +#endif diff --git a/Common/GMSKController.cpp b/Common/GMSKController.cpp new file mode 100644 index 0000000..00d8162 --- /dev/null +++ b/Common/GMSKController.cpp @@ -0,0 +1,374 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "GMSKController.h" +#if defined(__WINDOWS__) +#include "GMSKModemWinUSB.h" +#endif +#include "GMSKModemLibUsb.h" +#include "Timer.h" + +const unsigned char DVRPTR_HEADER_LENGTH = 5U; + +const unsigned int BUFFER_LENGTH = 200U; + +const unsigned int CYCLE_TIME = 15U; + +CGMSKController::CGMSKController(USB_INTERFACE iface, unsigned int address, bool duplex) : +CModem(), +m_modem(NULL), +m_duplex(duplex), +m_buffer(NULL), +m_txData(1000U) +{ + wxASSERT(address > 0U); + + m_buffer = new unsigned char[BUFFER_LENGTH]; + +#if defined(__WINDOWS__) + switch (iface) { + case UI_LIBUSB: + m_modem = new CGMSKModemLibUsb(address); + break; + case UI_WINUSB: + m_modem = new CGMSKModemWinUSB(address); + break; + default: + wxLogError(wxT("Unknown GMSM modem driver type - %d"), int(iface)); + break; + } +#else + m_modem = new CGMSKModemLibUsb(address); +#endif +} + +CGMSKController::~CGMSKController() +{ + delete[] m_buffer; +} + +bool CGMSKController::start() +{ + if (m_modem == NULL) + return false; + + bool ret = m_modem->open(); + if (!ret) { + delete m_modem; + return false; + } + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CGMSKController::Entry() +{ + wxLogMessage(wxT("Starting GMSK Modem Controller thread")); + + CTimer hdrTimer(1000U, 0U, 100U); + hdrTimer.start(); + + CTimer dataTimer(1000U, 0U, 100U); + + bool rx = false; + + DSMT_TYPE writeType = DSMTT_HEADER; + unsigned char writeLength = 0U; + unsigned char* writeBuffer = new unsigned char[BUFFER_LENGTH]; + + unsigned char readLength = 0U; + unsigned char* readBuffer = new unsigned char[DV_FRAME_LENGTH_BYTES]; + + wxStopWatch stopWatch; + + while (!m_stopped) { + stopWatch.Start(); + + // Only receive when not transmitting or when in duplex mode + if (!m_tx || m_duplex) { + if (rx) { + unsigned char buffer[GMSK_MODEM_DATA_LENGTH]; + bool end; + int ret = m_modem->readData(buffer, GMSK_MODEM_DATA_LENGTH, end); + if (ret >= 0) { + // CUtils::dump(wxT("Read Data"), buffer, ret); + + if (end) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + hdrTimer.start(); + readLength = 0U; + rx = false; + } else { + for (int i = 0; i < ret; i++) { + readBuffer[readLength] = buffer[i]; + + readLength++; + if (readLength >= DV_FRAME_LENGTH_BYTES) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(readBuffer, DV_FRAME_LENGTH_BYTES); + readLength = 0U; + } + } + } + } else { + ret = reopenModem(); + if (!ret) + break; + } + } else { + // Check for a header every 100ms or so + if (hdrTimer.isRunning() && hdrTimer.hasExpired()) { + unsigned char buffer[90U]; + bool ret = m_modem->readHeader(buffer, 90U); + if (ret) { + // CUtils::dump(wxT("Read Header"), buffer, RADIO_HEADER_LENGTH_BYTES); + + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES - 2U; + m_rxData.addData(data, 2U); + + m_rxData.addData(buffer, RADIO_HEADER_LENGTH_BYTES - 2U); + + hdrTimer.stop(); + readLength = 0U; + rx = true; + } else { + hdrTimer.start(); + } + } + } + } + + // Only transmit when not receiving or when in duplex mode + if (!rx || m_duplex) { + if (writeLength == 0U && m_txData.hasData()) { + wxMutexLocker locker(m_mutex); + + unsigned char type = DSMTT_NONE; + m_txData.getData(&type, 1U); + writeType = DSMT_TYPE(type); + + m_txData.getData(&writeLength, 1U); + m_txData.getData(writeBuffer, writeLength); + } + + if (writeLength > 0U) { + if (writeType == DSMTT_HEADER) { + TRISTATE tx = m_modem->getPTT(); + + // Check that the modem isn't still transmitting before sending the new header + if (tx == STATE_FALSE) { + // CUtils::dump(wxT("Write Header"), writeBuffer, writeLength); + m_modem->writeHeader(writeBuffer, writeLength); + m_modem->setPTT(true); + dataTimer.start(); + writeLength = 0U; + m_tx = true; + } else if (tx == STATE_UNKNOWN) { + bool ret = reopenModem(); + if (!ret) + break; + } + } else { + // Don't start sending data until the header has been gone for 100ms or so + if (!dataTimer.isRunning() || dataTimer.hasExpired()) { + dataTimer.stop(); + + TRISTATE ret = m_modem->hasSpace(); + + // Check that there is space in the modem buffer + if (ret == STATE_TRUE) { + // CUtils::dump(wxT("Write Data"), writeBuffer, writeLength); + int ret = m_modem->writeData(writeBuffer, writeLength); + if (ret > 0) { + writeLength -= ret; + + if (writeType == DSMTT_EOT) { + m_modem->setPTT(false); + m_tx = false; + } + } else if (ret < 0) { + ret = reopenModem(); + if (!ret) + break; + } + } else if (ret == STATE_UNKNOWN) { + bool ret = reopenModem(); + if (!ret) + break; + } + } + } + } + } + + unsigned long ms = stopWatch.Time(); + + // Don't sleep when reading from the modem + if (!rx) { + if (ms < CYCLE_TIME) + Sleep(CYCLE_TIME - ms); + + ms = stopWatch.Time(); + } + + // Catch up with the clock + dataTimer.clock(ms); + hdrTimer.clock(ms); + } + + wxLogMessage(wxT("Stopping GMSK Modem Controller thread")); + + if (m_modem != NULL) { + m_modem->close(); + delete m_modem; + } + + delete[] writeBuffer; + delete[] readBuffer; + + return NULL; +} + +bool CGMSKController::writeHeader(const CHeaderData& header) +{ + bool ret = m_txData.hasSpace(RADIO_HEADER_LENGTH_BYTES); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + unsigned char buffer[50U]; + + ::memset(buffer, ' ', RADIO_HEADER_LENGTH_BYTES - 2U); + + buffer[0U] = header.getFlag1(); + buffer[1U] = header.getFlag2(); + buffer[2U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 3U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 11U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 19U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 27U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer[i + 35U] = my2.GetChar(i); + + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES - 2U; + m_txData.addData(data, 2U); + + m_txData.addData(buffer, RADIO_HEADER_LENGTH_BYTES - 2U); + + return true; +} + +bool CGMSKController::writeData(const unsigned char* data, unsigned int, bool end) +{ + bool ret = m_txData.hasSpace(DV_FRAME_LENGTH_BYTES + 2U); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + wxMutexLocker locker(m_mutex); + + unsigned char buffer[2U]; + buffer[0U] = end ? DSMTT_EOT : DSMTT_DATA; + buffer[1U] = DV_FRAME_LENGTH_BYTES; + m_txData.addData(buffer, 2U); + + m_txData.addData(data, DV_FRAME_LENGTH_BYTES); + + return true; +} + +unsigned int CGMSKController::getSpace() +{ + return m_txData.freeSpace() / (DV_FRAME_LENGTH_BYTES + 2U); +} + +bool CGMSKController::isTXReady() +{ + if (m_tx) + return false; + + return m_txData.isEmpty(); +} + +bool CGMSKController::reopenModem() +{ + wxLogMessage(wxT("Connection to the GMSK modem has been lost")); + + m_modem->close(); + + while (!m_stopped) { + bool ret = m_modem->open(); + if (ret) + return true; + + // Reset the drivers state + m_mutex.Lock(); + m_txData.clear(); + m_mutex.Unlock(); + + m_tx = false; + + Sleep(1000UL); + } + + delete m_modem; + m_modem = NULL; + + return false; + +} diff --git a/Common/GMSKController.h b/Common/GMSKController.h new file mode 100644 index 0000000..6eab923 --- /dev/null +++ b/Common/GMSKController.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef GMSKController_H +#define GMSKController_H + +#include "DStarDefines.h" +#include "RingBuffer.h" +#include "GMSKModem.h" +#include "Modem.h" +#include "Utils.h" + +#include + +class CGMSKController : public CModem { +public: + CGMSKController(USB_INTERFACE iface, unsigned int address, bool duplex); + virtual ~CGMSKController(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + +private: + IGMSKModem* m_modem; + bool m_duplex; + unsigned char* m_buffer; + CRingBuffer m_txData; + + bool reopenModem(); +}; + +#endif diff --git a/Common/GMSKModem.cpp b/Common/GMSKModem.cpp new file mode 100644 index 0000000..b78ee8a --- /dev/null +++ b/Common/GMSKModem.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "GMSKModem.h" + +IGMSKModem::~IGMSKModem() +{ +} diff --git a/Common/GMSKModem.h b/Common/GMSKModem.h new file mode 100644 index 0000000..7c1a8c3 --- /dev/null +++ b/Common/GMSKModem.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef GMSKModem_H +#define GMSKModem_H + +#include "Utils.h" + +#include + +class IGMSKModem { +public: + virtual ~IGMSKModem() = 0; + + virtual bool open() = 0; + + virtual bool readHeader(unsigned char* header, unsigned int length) = 0; + virtual int readData(unsigned char* data, unsigned int length, bool& end) = 0; + + virtual TRISTATE getPTT() = 0; + virtual void setPTT(bool on) = 0; + + virtual TRISTATE hasSpace() = 0; + + virtual void writeHeader(unsigned char* data, unsigned int length) = 0; + virtual int writeData(unsigned char* data, unsigned int length) = 0; + + virtual void close() = 0; + +private: +}; + +#endif + diff --git a/Common/GMSKModemLibUsb.cpp b/Common/GMSKModemLibUsb.cpp new file mode 100644 index 0000000..d827090 --- /dev/null +++ b/Common/GMSKModemLibUsb.cpp @@ -0,0 +1,443 @@ +/* + * Copyright (C) 2010-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "GMSKModemLibUsb.h" +#include "DStarDefines.h" + +const unsigned int VENDOR_ID = 0x04D8U; + +const int USB_TIMEOUT = 1000; + +const int SET_AD_INIT = 0x00; +const int SET_PTT = 0x05; +const int PUT_DATA = 0x10; +const int GET_DATA = 0x11; +const int GET_HEADER = 0x21; +const int GET_AD_STATUS = 0x30; +const int SET_MyCALL = 0x40; +const int SET_MyCALL2 = 0x41; +const int SET_YourCALL = 0x42; +const int SET_RPT1CALL = 0x43; +const int SET_RPT2CALL = 0x44; +const int SET_FLAGS = 0x45; +const int GET_REMAINSPACE = 0x50; +const int GET_VERSION = 0xFF; + +const char COS_OnOff = 0x02; +const char CRC_ERROR = 0x04; +const char LAST_FRAME = 0x08; +const char PTT_OnOff = 0x20; + +const int PTT_ON = 1; +const int PTT_OFF = 0; + +#if defined(WIN32) +const wxString LIBNAME = wxT("libusb0"); + +wxDynamicLibrary* CGMSKModemLibUsb::m_library = NULL; +bool CGMSKModemLibUsb::m_loaded = false; + +void (*CGMSKModemLibUsb::m_usbInit)() = NULL; +int (*CGMSKModemLibUsb::m_usbFindBusses)() = NULL; +int (*CGMSKModemLibUsb::m_usbFindDevices)() = NULL; +usb_bus* (*CGMSKModemLibUsb::m_usbGetBusses)() = NULL; +usb_dev_handle* (*CGMSKModemLibUsb::m_usbOpen)(struct usb_device*) = NULL; +int (*CGMSKModemLibUsb::m_usbSetConfiguration)(usb_dev_handle*, int) = NULL; +int (*CGMSKModemLibUsb::m_usbControlMsg)(usb_dev_handle*, int, int, int, int, unsigned char*, int, int) = NULL; +char* (*CGMSKModemLibUsb::m_usbStrerror)() = NULL; +int (*CGMSKModemLibUsb::m_usbClose)(usb_dev_handle*) = NULL; +#endif + +static void libUsbLogError(int ret, const char *message) { +#if defined(WIN32) + wxString errorText(CGMSKModemLibUsb::m_usbStrerror(), wxConvLocal); +#else + wxString errorText(libusb_error_name(ret), wxConvLocal); +#endif + wxLogMessage(wxT("%s, ret: %d, err=%s"), message, ret, errorText.c_str()); +} + +CGMSKModemLibUsb::CGMSKModemLibUsb(unsigned int address) : +m_address(address), +m_dev(NULL), +m_brokenSpace(false) +#if !defined(WIN32) +,m_context(NULL) +#endif +{ + +#if defined(WIN32) + if (m_library == NULL) + m_library = new wxDynamicLibrary(LIBNAME); + + if (!m_library->IsLoaded()) { + wxLogError(wxT("Unable to load shared library %s"), LIBNAME.c_str()); + return; + } + + void* ptr1 = m_library->GetSymbol(wxT("usb_init")); + void* ptr2 = m_library->GetSymbol(wxT("usb_find_busses")); + void* ptr3 = m_library->GetSymbol(wxT("usb_find_devices")); + void* ptr4 = m_library->GetSymbol(wxT("usb_get_busses")); + void* ptr5 = m_library->GetSymbol(wxT("usb_open")); + void* ptr6 = m_library->GetSymbol(wxT("usb_set_configuration")); + void* ptr7 = m_library->GetSymbol(wxT("usb_control_msg")); + void* ptr8 = m_library->GetSymbol(wxT("usb_strerror")); + void* ptr9 = m_library->GetSymbol(wxT("usb_close")); + + if (ptr1 == NULL || ptr2 == NULL || ptr3 == NULL || + ptr4 == NULL || ptr5 == NULL || ptr6 == NULL || + ptr7 == NULL || ptr8 == NULL || ptr9 == NULL) { + wxLogError(wxT("Unable to get symbols from %s"), LIBNAME.c_str()); + return; + } + + m_usbInit = (void (*)())ptr1; + m_usbFindBusses = (int (*)())ptr2; + m_usbFindDevices = (int (*)())ptr3; + m_usbGetBusses = (usb_bus* (*)())ptr4; + m_usbOpen = (usb_dev_handle* (*)(struct usb_device*))ptr5; + m_usbSetConfiguration = (int (*)(usb_dev_handle*, int))ptr6; + m_usbControlMsg = (int (*)(usb_dev_handle*, int, int, int, int, unsigned char*, int, int))ptr7; + m_usbStrerror = (char* (*)())ptr8; + m_usbClose = (int (*)(usb_dev_handle*))ptr9; + + wxLogMessage(wxT("Successfully loaded library %s"), LIBNAME.c_str()); + + m_loaded = true; +#else + ::libusb_init(&m_context); +#endif +} + + +CGMSKModemLibUsb::~CGMSKModemLibUsb() +{ +#if !defined(WIN32) + wxASSERT(m_context != NULL); + ::libusb_exit(m_context); +#endif +} + +bool CGMSKModemLibUsb::open() +{ +#if !defined(WIN32) + wxASSERT(m_context != NULL); +#endif + wxASSERT(m_dev == NULL); + + bool ret1 = openModem(); + if (!ret1) { + wxLogError(wxT("Cannot find the GMSK Modem with address: 0x%04X"), m_address); + return false; + } + + wxLogInfo(wxT("Found the GMSK Modem with address: 0x%04X"), m_address); + + wxString version; + + int ret2; + do { + unsigned char buffer[GMSK_MODEM_DATA_LENGTH]; + ret2 = io(0xC0, GET_VERSION, 0, 0, buffer, GMSK_MODEM_DATA_LENGTH, USB_TIMEOUT); + if (ret2 > 0) { + wxString text((char *) buffer, wxConvLocal, ret2); + version.Append(text); + } else if (ret2 < 0) { + ::libUsbLogError(ret2, "GET_VERSION"); + close(); + return false; + } + } while (ret2 == int(GMSK_MODEM_DATA_LENGTH)); + + wxLogInfo(wxT("Firmware version: %s"), version.c_str()); + + // Trap firmware version 0.1.00 of DUTCH*Star and complain loudly + if (version.Find(wxT("DUTCH*Star")) != wxNOT_FOUND && version.Find(wxT("0.1.00")) != wxNOT_FOUND) { + wxLogWarning(wxT("This modem firmware is not supported by the repeater")); + wxLogWarning(wxT("Please upgrade to a newer version")); + close(); + return false; + } + + // DUTCH*Star firmware has a broken concept of free space + if (version.Find(wxT("DUTCH*Star")) != wxNOT_FOUND) + m_brokenSpace = true; + + return true; +} + +bool CGMSKModemLibUsb::readHeader(unsigned char* header, unsigned int length) +{ + wxASSERT(header != NULL); + wxASSERT(length > (RADIO_HEADER_LENGTH_BYTES * 2U)); + + unsigned int offset = 0U; + + while (offset < RADIO_HEADER_LENGTH_BYTES) { + int ret = io(0xC0, GET_HEADER, 0, 0, (header + offset), 8, USB_TIMEOUT); + if (ret < 0) { + ::libUsbLogError(ret, "GET_HEADER"); + + if (ret == -19) // -ENODEV + return false; + + ::wxMilliSleep(10UL); + } else if (ret == 0) { + if (offset == 0U) + return false; + + ::wxMilliSleep(10UL); + + unsigned char status; + int ret = io(0xC0, GET_AD_STATUS, 0, 0, &status, 1, USB_TIMEOUT); + if (ret < 0) { + ::libUsbLogError(ret, "GET_COS"); + + if (ret == -19) // -ENODEV + return false; + + ::wxMilliSleep(10UL); + } else if (ret > 0) { + if ((status & COS_OnOff) == COS_OnOff) + offset = 0U; + } + } else { + offset += ret; + } + } + + unsigned char status; + int ret = io(0xC0, GET_AD_STATUS, 0, 0, &status, 1, USB_TIMEOUT); + if (ret < 0) { + ::libUsbLogError(ret, "GET_CRC"); + return false; + } + + if ((status & CRC_ERROR) == CRC_ERROR) { + wxLogMessage(wxT("Header - CRC Error")); + return false; + } + + return true; +} + +int CGMSKModemLibUsb::readData(unsigned char* data, unsigned int length, bool& end) +{ + wxASSERT(data != NULL); + wxASSERT(length > 0U); + + end = false; + + int ret = io(0xC0, GET_DATA, 0, 0, data, GMSK_MODEM_DATA_LENGTH, USB_TIMEOUT); + if (ret < 0) { + if (ret == -19) { // -ENODEV + ::libUsbLogError(ret, "GET_DATA"); + return ret; + } + + return 0; + } else if (ret == 0) { + unsigned char status; + int ret = io(0xC0, GET_AD_STATUS, 0, 0, &status, 1, USB_TIMEOUT); + if (ret < 0) { + ::libUsbLogError(ret, "LAST_FRAME"); + + if (ret == -19) // -ENODEV + return ret; + + return 0; + } + + if ((status & LAST_FRAME) == LAST_FRAME) + end = true; + } + + return ret; +} + +void CGMSKModemLibUsb::writeHeader(unsigned char* header, unsigned int length) +{ + wxASSERT(header != NULL); + wxASSERT(length >= (RADIO_HEADER_LENGTH_BYTES - 2U)); + + io(0x40, SET_MyCALL2, 0, 0, (header + 35U), SHORT_CALLSIGN_LENGTH, USB_TIMEOUT); + io(0x40, SET_MyCALL, 0, 0, (header + 27U), LONG_CALLSIGN_LENGTH, USB_TIMEOUT); + io(0x40, SET_YourCALL, 0, 0, (header + 19U), LONG_CALLSIGN_LENGTH, USB_TIMEOUT); + io(0x40, SET_RPT1CALL, 0, 0, (header + 11U), LONG_CALLSIGN_LENGTH, USB_TIMEOUT); + io(0x40, SET_RPT2CALL, 0, 0, (header + 3U), LONG_CALLSIGN_LENGTH, USB_TIMEOUT); + io(0x40, SET_FLAGS, 0, 0, (header + 0U), 3U, USB_TIMEOUT); +} + +TRISTATE CGMSKModemLibUsb::hasSpace() +{ + unsigned char space; + int rc = io(0xC0, GET_REMAINSPACE, 0, 0, &space, 1, USB_TIMEOUT); + if (rc != 1) { + ::libUsbLogError(rc, "GET_REMAINSPACE"); + return STATE_UNKNOWN; + } + + if (space >= DV_FRAME_LENGTH_BYTES) + return STATE_TRUE; + else + return STATE_FALSE; +} + +TRISTATE CGMSKModemLibUsb::getPTT() +{ + unsigned char status; + int rc = io(0xC0, GET_AD_STATUS, 0, 0, &status, 1, USB_TIMEOUT); + if (rc < 1) { + ::libUsbLogError(rc, "GET_PTT"); + return STATE_UNKNOWN; + } + + if ((status & PTT_OnOff) == PTT_OnOff) + return STATE_TRUE; + else + return STATE_FALSE; +} + +void CGMSKModemLibUsb::setPTT(bool on) +{ + unsigned char c; + io(0x40, SET_PTT, on ? PTT_ON : PTT_OFF, 0, &c, 0, USB_TIMEOUT); +} + +int CGMSKModemLibUsb::writeData(unsigned char* data, unsigned int length) +{ + wxASSERT(data != NULL); + wxASSERT(length > 0U && length <= DV_FRAME_LENGTH_BYTES); + + if (length > GMSK_MODEM_DATA_LENGTH) { + int ret = io(0x40, PUT_DATA, 0, 0, data, GMSK_MODEM_DATA_LENGTH, USB_TIMEOUT); + if (ret < 0) { + if (ret == -19) { // -ENODEV + ::libUsbLogError(ret, "PUT_DATA 1"); + return ret; + } + + return 0; + } + + // Give libUSB some recovery time + ::wxMilliSleep(3UL); + + ret = io(0x40, PUT_DATA, 0, 0, (data + GMSK_MODEM_DATA_LENGTH), length - GMSK_MODEM_DATA_LENGTH, USB_TIMEOUT); + if (ret < 0) { + if (ret == -19) { // -ENODEV + ::libUsbLogError(ret, "PUT_DATA 2"); + return ret; + } + + return int(GMSK_MODEM_DATA_LENGTH); + } + + return length; + } else { + int ret = io(0x40, PUT_DATA, 0, 0, data, length, USB_TIMEOUT); + if (ret < 0) { + if (ret == -19) { // -ENODEV + ::libUsbLogError(ret, "PUT_DATA"); + return ret; + } + + return 0; + } + + return length; + } +} + +void CGMSKModemLibUsb::close() +{ + wxASSERT(m_dev != NULL); + +#if defined(WIN32) + m_usbClose(m_dev); +#else + libusb_close(m_dev); +#endif + m_dev = NULL; +} + +bool CGMSKModemLibUsb::openModem() +{ +#if defined(WIN32) + if (!m_loaded) + return false; + + m_usbInit(); + m_usbFindBusses(); + m_usbFindDevices(); + + for (struct usb_bus* bus = m_usbGetBusses(); bus != NULL; bus = bus->next) { + for (struct usb_device* dev = bus->devices; dev != NULL; dev = dev->next) { + if (dev->descriptor.idVendor == VENDOR_ID && dev->descriptor.idProduct == m_address) { + m_dev = m_usbOpen(dev); + break; + } + } + } + + if (m_dev == NULL) + return false; + + m_usbSetConfiguration(m_dev, 1); +#else + m_dev = ::libusb_open_device_with_vid_pid(m_context, VENDOR_ID, m_address); + if (m_dev == NULL) + return false; + + ::libusb_set_configuration(m_dev, 1); +#endif + + unsigned char c; + io(0x40, SET_AD_INIT, 0, 0, &c, 0, USB_TIMEOUT); + + setPTT(false); + + return true; +} + +int CGMSKModemLibUsb::io(uint8_t requestType, uint8_t request, uint16_t value, + uint16_t index, unsigned char* data, uint16_t length, + unsigned int timeout) +{ + wxASSERT(m_dev != NULL); + wxASSERT(data != NULL); + + int ret = 0; + for (unsigned int i = 0U; i < 4U; i++) { +#if defined(WIN32) + ret = m_usbControlMsg(m_dev, requestType, request, value, index, data, length, timeout); +#else + ret = ::libusb_control_transfer(m_dev, requestType, request, value, index, data, length, timeout); +#endif + if (ret >= 0) + return ret; + + if (ret == -19) // ENODEV + return ret; + + ::wxMilliSleep(5UL); + } + + return ret; +} \ No newline at end of file diff --git a/Common/GMSKModemLibUsb.h b/Common/GMSKModemLibUsb.h new file mode 100644 index 0000000..ccc01e5 --- /dev/null +++ b/Common/GMSKModemLibUsb.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2010-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef GMSKModemLibUsb_H +#define GMSKModemLibUsb_H + +#include "GMSKModem.h" +#include "Utils.h" + +#include +#if defined(WIN32) +#include +#if _MSC_VER == 1900 +#undef __USB_H__ +#include +#else +#include "lusb0_usb.h" +#endif +#else +#include +#endif + +class CGMSKModemLibUsb : public IGMSKModem { +public: + CGMSKModemLibUsb(unsigned int address); + virtual ~CGMSKModemLibUsb(); + + virtual bool open(); + + virtual bool readHeader(unsigned char* header, unsigned int length); + virtual int readData(unsigned char* data, unsigned int length, bool& end); + + virtual TRISTATE getPTT(); + virtual void setPTT(bool on); + + virtual TRISTATE hasSpace(); + + virtual void writeHeader(unsigned char* data, unsigned int length); + virtual int writeData(unsigned char* data, unsigned int length); + + virtual void close(); +#if defined(WIN32) + static char* (*m_usbStrerror)(); +#endif + +private: + unsigned int m_address; +#if defined(WIN32) + struct usb_dev_handle* m_dev; + + static wxDynamicLibrary* m_library; + static bool m_loaded; + + static void (*m_usbInit)(); + static int (*m_usbFindBusses)(); + static int (*m_usbFindDevices)(); + static struct usb_bus* (*m_usbGetBusses)(); + static usb_dev_handle* (*m_usbOpen)(struct usb_device*); + static int (*m_usbSetConfiguration)(usb_dev_handle*, int); + static int (*m_usbControlMsg)(usb_dev_handle*, int, int, int, int, unsigned char*, int, int); + static int (*m_usbClose)(usb_dev_handle*); + +#else + libusb_context* m_context; + libusb_device_handle* m_dev; + +#endif + int io(uint8_t requestType, uint8_t request, uint16_t value, uint16_t index, unsigned char* data, uint16_t length, unsigned int timeout); + + bool m_brokenSpace; + + bool openModem(); +}; + +#endif diff --git a/Common/GMSKModemWinUSB.cpp b/Common/GMSKModemWinUSB.cpp new file mode 100644 index 0000000..40bc13e --- /dev/null +++ b/Common/GMSKModemWinUSB.cpp @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2010-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "GMSKModemWinUSB.h" +#include "DStarDefines.h" + +#include + +const unsigned char SET_AD_INIT = 0x00U; +const unsigned char SET_PTT = 0x05U; +const unsigned char PUT_DATA = 0x10U; +const unsigned char GET_DATA = 0x11U; +const unsigned char GET_HEADER = 0x21U; +const unsigned char GET_AD_STATUS = 0x30U; +const unsigned char SET_MyCALL = 0x40U; +const unsigned char SET_MyCALL2 = 0x41U; +const unsigned char SET_YourCALL = 0x42U; +const unsigned char SET_RPT1CALL = 0x43U; +const unsigned char SET_RPT2CALL = 0x44U; +const unsigned char SET_FLAGS = 0x45U; +const unsigned char GET_REMAINSPACE = 0x50U; +const unsigned char GET_VERSION = 0xFFU; + +const unsigned char COS_OnOff = 0x02U; +const unsigned char CRC_ERROR = 0x04U; +const unsigned char LAST_FRAME = 0x08U; +const unsigned char PTT_OnOff = 0x20U; + +const unsigned char PTT_ON = 1U; +const unsigned char PTT_OFF = 0U; + +CGMSKModemWinUSB::CGMSKModemWinUSB(unsigned int address) : +m_address(address), +m_file(INVALID_HANDLE_VALUE), +m_handle(INVALID_HANDLE_VALUE), +m_brokenSpace(false) +{ +} + +CGMSKModemWinUSB::~CGMSKModemWinUSB() +{ +} + +bool CGMSKModemWinUSB::open() +{ + wxASSERT(m_handle == INVALID_HANDLE_VALUE); + + bool res = openModem(); + if (!res) { + wxLogError(wxT("Cannot find the GMSK Modem with address: 0x%04X"), m_address); + return false; + } + + wxLogInfo(wxT("Found the GMSK Modem with address: 0x%04X"), m_address); + + wxString version; + + int ret; + do { + unsigned char buffer[GMSK_MODEM_DATA_LENGTH]; + ret = io(GET_VERSION, 0xC0U, 0U, buffer, GMSK_MODEM_DATA_LENGTH); + if (ret > 0) { + wxString text((char*)buffer, wxConvLocal, ret); + version.Append(text); + } else if (ret < 0) { + wxLogError(wxT("GET_VERSION returned %d"), -ret); + close(); + return false; + } + } while (ret == int(GMSK_MODEM_DATA_LENGTH)); + + wxLogInfo(wxT("Firmware version: %s"), version.c_str()); + + // Trap firmware version 0.1.00 of DUTCH*Star and complain loudly + if (version.Find(wxT("DUTCH*Star")) != wxNOT_FOUND && version.Find(wxT("0.1.00")) != wxNOT_FOUND) { + wxLogWarning(wxT("This modem firmware is not supported by the repeater")); + wxLogWarning(wxT("Please upgrade to a newer version")); + close(); + return false; + } + + // DUTCH*Star firmware has a broken concept of free space + if (version.Find(wxT("DUTCH*Star")) != wxNOT_FOUND) + m_brokenSpace = true; + + return true; +} + +bool CGMSKModemWinUSB::readHeader(unsigned char* header, unsigned int length) +{ + wxASSERT(header != NULL); + wxASSERT(length > (RADIO_HEADER_LENGTH_BYTES * 2U)); + + unsigned int offset = 0U; + + while (offset < RADIO_HEADER_LENGTH_BYTES) { + int ret = io(GET_HEADER, 0xC0U, 0U, header + offset, GMSK_MODEM_DATA_LENGTH); + if (ret < 0) { + wxLogError(wxT("GET_HEADER returned %d"), -ret); + return false; + } else if (ret == 0) { + if (offset == 0U) + return false; + + ::wxMilliSleep(10UL); + + unsigned char status; + ret = io(GET_AD_STATUS, 0xC0U, 0U, &status, 1U); + if (ret < 0) { + wxLogError(wxT("GET_COS returned %d"), -ret); + return false; + } else if (ret > 0) { + if ((status & COS_OnOff) == COS_OnOff) + offset = 0U; + } + } else { + offset += ret; + } + } + + unsigned char status; + int ret = io(GET_AD_STATUS, 0xC0U, 0U, &status, 1U); + if (ret < 0) { + wxLogError(wxT("GET_CRC returned %d"), -ret); + return false; + } + + if ((status & CRC_ERROR) == CRC_ERROR) { + wxLogMessage(wxT("Invalid CRC on header")); + return false; + } + + return true; +} + +int CGMSKModemWinUSB::readData(unsigned char* data, unsigned int length, bool& end) +{ + wxASSERT(data != NULL); + wxASSERT(length > 0U); + + end = false; + + int ret = io(GET_DATA, 0xC0U, 0U, data, GMSK_MODEM_DATA_LENGTH); + if (ret < 0) { + wxLogError(wxT("GET_DATA returned %d"), -ret); + return ret; + } else if (ret == 0) { + unsigned char status; + int ret = io(GET_AD_STATUS, 0xC0U, 0U, &status, 1U); + if (ret < 0) { + wxLogError(wxT("LAST_FRAME returned %d"), -ret); + return ret; + } + + if ((status & LAST_FRAME) == LAST_FRAME) + end = true; + } + + return ret; +} + +void CGMSKModemWinUSB::writeHeader(unsigned char* header, unsigned int length) +{ + wxASSERT(header != NULL); + wxASSERT(length >= (RADIO_HEADER_LENGTH_BYTES - 2U)); + + io(SET_MyCALL2, 0x40U, 0U, header + 35U, SHORT_CALLSIGN_LENGTH); + io(SET_MyCALL, 0x40U, 0U, header + 27U, LONG_CALLSIGN_LENGTH); + io(SET_YourCALL, 0x40U, 0U, header + 19U, LONG_CALLSIGN_LENGTH); + io(SET_RPT1CALL, 0x40U, 0U, header + 11U, LONG_CALLSIGN_LENGTH); + io(SET_RPT2CALL, 0x40U, 0U, header + 3U, LONG_CALLSIGN_LENGTH); + io(SET_FLAGS, 0x40U, 0U, header + 0U, 3U); +} + +TRISTATE CGMSKModemWinUSB::getPTT() +{ + unsigned char status; + int ret = io(GET_AD_STATUS, 0xC0U, 0U, &status, 1U); + if (ret != 1) { + wxLogError(wxT("GET_PTT returned %d"), -ret); + return STATE_UNKNOWN; + } + + if ((status & PTT_OnOff) == PTT_OnOff) + return STATE_TRUE; + else + return STATE_FALSE; +} + +void CGMSKModemWinUSB::setPTT(bool on) +{ + unsigned char c; + io(SET_PTT, 0x40U, on ? PTT_ON : PTT_OFF, &c, 0U); +} + +TRISTATE CGMSKModemWinUSB::hasSpace() +{ + unsigned char space; + int ret = io(GET_REMAINSPACE, 0xC0U, 0U, &space, 1U); + if (ret != 1) { + wxLogError(wxT("GET_REMAINSPACE returned %d"), -ret); + return STATE_UNKNOWN; + } + + if (space >= DV_FRAME_LENGTH_BYTES) + return STATE_TRUE; + else + return STATE_FALSE; +} + +int CGMSKModemWinUSB::writeData(unsigned char* data, unsigned int length) +{ + wxASSERT(data != NULL); + wxASSERT(length > 0U && length <= DV_FRAME_LENGTH_BYTES); + + if (length > GMSK_MODEM_DATA_LENGTH) { + int ret = io(PUT_DATA, 0x40U, 0U, data, GMSK_MODEM_DATA_LENGTH); + if (ret < 0) { + if (ret == -22) { + wxLogError(wxT("PUT_DATA 1, returned %d"), -ret); + return ret; + } + + return 0; + } + + ret = io(PUT_DATA, 0x40U, 0U, data + GMSK_MODEM_DATA_LENGTH, length - GMSK_MODEM_DATA_LENGTH); + if (ret < 0) { + if (ret == -22) { + wxLogError(wxT("PUT_DATA 2, returned %d"), -ret); + return ret; + } + + return int(GMSK_MODEM_DATA_LENGTH); + } + + return length; + } else { + int ret = io(PUT_DATA, 0x40U, 0U, data, length); + if (ret < 0) { + if (ret == -22) { + wxLogError(wxT("PUT_DATA returned %d"), -ret); + return ret; + } + + return 0; + } + + return length; + } +} + +void CGMSKModemWinUSB::close() +{ + wxASSERT(m_file != INVALID_HANDLE_VALUE); + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + ::CloseHandle(m_file); + ::WinUsb_Free(m_handle); + + m_file = INVALID_HANDLE_VALUE; + m_handle = INVALID_HANDLE_VALUE; +} + +bool CGMSKModemWinUSB::openModem() +{ + WCHAR id1[15U], id2[15U]; + ::swprintf(id1, L"pid_%04x", m_address); + ::swprintf(id2, L"pid_%04X", m_address); + + wxString wxId1(id1, wxConvLocal); + wxString wxId2(id2, wxConvLocal); + + CLSID clsId; + LPOLESTR str = OLESTR("{136C76EF-3F4E-4030-A7E3-E1003EF0A715}"); + HRESULT result = ::CLSIDFromString(str, &clsId); + if (result != NOERROR) { + wxLogError(wxT("Error from CLSIDFromString: err=%lu"), ::GetLastError()); + return false; + } + + HDEVINFO devInfo = ::SetupDiGetClassDevs(&clsId, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); + if (devInfo == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from SetupDiGetClassDevs: err=%lu"), ::GetLastError()); + return false; + } + + SP_DEVICE_INTERFACE_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (unsigned int index = 0U; ::SetupDiEnumDeviceInterfaces(devInfo, NULL, &clsId, index, &devInfoData); index++) { + DWORD length; + ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, NULL, 0U, &length, NULL); + + PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = PSP_DEVICE_INTERFACE_DETAIL_DATA(::malloc(length)); + detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + DWORD required; + BOOL ret1 = ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, detailData, length, &required, NULL); + if (!ret1) { + wxLogError(wxT("Error from SetupDiGetDeviceInterfaceDetail: err=%lu"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + // Check the name to see if it's the correct vendor id and address + if (::wcsstr(detailData->DevicePath, id1) == NULL && ::wcsstr(detailData->DevicePath, id2) == NULL) { + ::free(detailData); + continue; + } + + m_file = ::CreateFile(detailData->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (m_file == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from CreateFile: err=%lu"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ret1 = ::WinUsb_Initialize(m_file, &m_handle); + if (!ret1) { + wxLogError(wxT("Error from WinUsb_Initialize: err=%lu"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::CloseHandle(m_file); + ::free(detailData); + return false; + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + + unsigned char c; + io(SET_AD_INIT, 0x40U, 0U, &c, 0U); + + setPTT(false); + + return true; + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + + return false; +} + +int CGMSKModemWinUSB::io(unsigned char type, unsigned char n1, unsigned char n2, unsigned char* buffer, unsigned int length) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + WINUSB_SETUP_PACKET packet; + packet.RequestType = n1; + packet.Request = type; + packet.Value = n2; + packet.Index = 0U; + packet.Length = length; + + ULONG transferred; + BOOL ret = ::WinUsb_ControlTransfer(m_handle, packet, buffer, length, &transferred, 0); + if (!ret) { + long error = ::GetLastError(); + return -error; + } + + return transferred; +} + diff --git a/Common/GMSKModemWinUSB.h b/Common/GMSKModemWinUSB.h new file mode 100644 index 0000000..789061d --- /dev/null +++ b/Common/GMSKModemWinUSB.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2010-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef GMSKModemWinUSB_H +#define GMSKModemWinUSB_H + +#include "GMSKModem.h" +#include "Utils.h" + +#include + +#include "winusb.h" + +class CGMSKModemWinUSB : public IGMSKModem { +public: + CGMSKModemWinUSB(unsigned int address); + virtual ~CGMSKModemWinUSB(); + + virtual bool open(); + + virtual bool readHeader(unsigned char* header, unsigned int length); + virtual int readData(unsigned char* data, unsigned int length, bool& end); + + virtual TRISTATE getPTT(); + virtual void setPTT(bool on); + + virtual TRISTATE hasSpace(); + + virtual void writeHeader(unsigned char* data, unsigned int length); + virtual int writeData(unsigned char* data, unsigned int length); + + virtual void close(); + +private: + unsigned int m_address; + HANDLE m_file; + WINUSB_INTERFACE_HANDLE m_handle; + bool m_brokenSpace; + + bool openModem(); + int io(unsigned char type, unsigned char n1, unsigned char n2, unsigned char* buffer, unsigned int length); +}; + +#endif + diff --git a/Common/GPIOController.cpp b/Common/GPIOController.cpp new file mode 100644 index 0000000..d98ccfc --- /dev/null +++ b/Common/GPIOController.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2012,2013,2015 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "GPIOController.h" + +CGPIOController::CGPIOController(unsigned int config) : +m_config(config), +m_outp1(false), +m_outp2(false), +m_outp3(false), +m_outp4(false), +m_outp5(false), +m_outp6(false), +m_outp7(false), +m_outp8(false) +{ +} + +CGPIOController::~CGPIOController() +{ +} + +#include + +bool CGPIOController::open() +{ + bool ret = ::wiringPiSetup() != -1; + if (!ret) { + wxLogError(wxT("Unable to initialise wiringPi")); + return false; + } + + ::pinMode(8, INPUT); + ::pinMode(9, INPUT); + ::pinMode(7, INPUT); + ::pinMode(0, INPUT); + ::pinMode(2, INPUT); + + // Set pull ups on the input pins + ::pullUpDnControl(8, PUD_UP); + ::pullUpDnControl(9, PUD_UP); + ::pullUpDnControl(7, PUD_UP); + ::pullUpDnControl(0, PUD_UP); + ::pullUpDnControl(2, PUD_UP); + + ::pinMode(12, OUTPUT); + ::pinMode(13, OUTPUT); + ::pinMode(14, OUTPUT); + ::pinMode(11, OUTPUT); + ::pinMode(10, OUTPUT); + ::pinMode(6, OUTPUT); + ::pinMode(5, OUTPUT); + ::pinMode(4, OUTPUT); + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + return true; +} + +void CGPIOController::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + inp1 = ::digitalRead(8) == LOW; + + inp2 = ::digitalRead(9) == LOW; + + inp3 = ::digitalRead(7) == LOW; + + inp4 = ::digitalRead(0) == LOW; + + inp5 = ::digitalRead(2) == LOW; +} + +void CGPIOController::setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8) +{ + if (m_config == 1U) { + if (outp1 != m_outp1) { + ::digitalWrite(12, outp1 ? HIGH : LOW); + m_outp1 = outp1; + } + + if (outp2 != m_outp2) { + ::digitalWrite(13, outp2 ? HIGH : LOW); + m_outp2 = outp2; + } + + if (outp3 != m_outp3) { + ::digitalWrite(14, outp3 ? HIGH : LOW); + m_outp3 = outp3; + } + + if (outp4 != m_outp4) { + ::digitalWrite(11, outp4 ? HIGH : LOW); + m_outp4 = outp4; + } + + if (outp5 != m_outp5) { + ::digitalWrite(10, outp5 ? HIGH : LOW); + m_outp5 = outp5; + } + + if (outp6 != m_outp6) { + ::digitalWrite(6, outp6 ? HIGH : LOW); + m_outp6 = outp6; + } + + if (outp7 != m_outp7) { + ::digitalWrite(5, outp7 ? HIGH : LOW); + m_outp7 = outp7; + } + + if (outp8 != m_outp8) { + ::digitalWrite(4, outp8 ? HIGH : LOW); + m_outp8 = outp8; + } + } else { + if (outp1 != m_outp1) { + ::digitalWrite(4, outp1 ? HIGH : LOW); + m_outp1 = outp1; + } + + if (outp2 != m_outp2) { + ::digitalWrite(5, outp2 ? HIGH : LOW); + m_outp2 = outp2; + } + + if (outp3 != m_outp3) { + ::digitalWrite(6, outp3 ? HIGH : LOW); + m_outp3 = outp3; + } + + if (outp4 != m_outp4) { + ::digitalWrite(10, outp4 ? HIGH : LOW); + m_outp4 = outp4; + } + + if (outp5 != m_outp5) { + ::digitalWrite(11, outp5 ? HIGH : LOW); + m_outp5 = outp5; + } + + if (outp6 != m_outp6) { + ::digitalWrite(14, outp6 ? HIGH : LOW); + m_outp6 = outp6; + } + + if (outp7 != m_outp7) { + ::digitalWrite(13, outp7 ? HIGH : LOW); + m_outp7 = outp7; + } + + if (outp8 != m_outp8) { + ::digitalWrite(12, outp8 ? HIGH : LOW); + m_outp8 = outp8; + } + } +} + +void CGPIOController::close() +{ +} diff --git a/Common/GPIOController.h b/Common/GPIOController.h new file mode 100644 index 0000000..aa170f0 --- /dev/null +++ b/Common/GPIOController.h @@ -0,0 +1,46 @@ +/* + * Copyright (C) 2012,2015 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef GPIOController_H +#define GPIOController_H + +#include "HardwareController.h" + +#include + +class CGPIOController : public IHardwareController { +public: + CGPIOController(unsigned int config); + virtual ~CGPIOController(); + + virtual bool open(); + + virtual void getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5); + + virtual void setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8); + + virtual void close(); + +private: + unsigned int m_config; + bool m_outp1; + bool m_outp2; + bool m_outp3; + bool m_outp4; + bool m_outp5; + bool m_outp6; + bool m_outp7; + bool m_outp8; +}; + +#endif diff --git a/Common/GatewayProtocolHandler.cpp b/Common/GatewayProtocolHandler.cpp new file mode 100644 index 0000000..d0b11d9 --- /dev/null +++ b/Common/GatewayProtocolHandler.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (C) 2009-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "GatewayProtocolHandler.h" +#include "CCITTChecksumReverse.h" +#include "DStarDefines.h" +#include "Utils.h" + +// #define DUMP_TX + +const unsigned int BUFFER_LENGTH = 255U; + +CGatewayProtocolHandler::CGatewayProtocolHandler(const wxString& localAddress, unsigned int localPort) : +m_socket(localAddress, localPort), +m_type(NETWORK_NONE), +m_buffer(NULL), +m_length(0U) +{ + m_buffer = new unsigned char[BUFFER_LENGTH]; + + wxDateTime now = wxDateTime::UNow(); + ::srand(now.GetMillisecond()); +} + +CGatewayProtocolHandler::~CGatewayProtocolHandler() +{ + delete[] m_buffer; +} + +bool CGatewayProtocolHandler::open() +{ + return m_socket.open(); +} + +bool CGatewayProtocolHandler::writeHeader(const unsigned char* header, wxUint16 id, const in_addr& address, unsigned int port) +{ + unsigned char buffer[50U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x20U; + + buffer[5] = id / 256U; // Unique session id + buffer[6] = id % 256U; + + buffer[7] = 0U; + + ::memcpy(buffer + 8U, header + 0U, RADIO_HEADER_LENGTH_BYTES - 2U); + + // Get the checksum for the header + CCCITTChecksumReverse csum; + csum.update(buffer + 8U, RADIO_HEADER_LENGTH_BYTES - 2U); + csum.result(buffer + 8U + RADIO_HEADER_LENGTH_BYTES - 2U); + +#if defined(DUMP_TX) + CUtils::dump(wxT("Sending Header"), buffer, 49U); +#endif + + for (unsigned int i = 0U; i < 4U; i++) { + bool ret = m_socket.write(buffer, 49U, address, port); + if (!ret) + return false; + } + + return true; +} + +bool CGatewayProtocolHandler::writeData(const unsigned char* data, unsigned int length, wxUint16 id, wxUint8 seqNo, const in_addr& address, unsigned int port) +{ + wxASSERT(data != NULL); + wxASSERT(length == DV_FRAME_LENGTH_BYTES || length == DV_FRAME_MAX_LENGTH_BYTES); + + unsigned char buffer[30U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x21U; + + buffer[5] = id / 256U; // Unique session id + buffer[6] = id % 256U; + + buffer[7] = seqNo; + + buffer[8] = 0U; + + ::memcpy(buffer + 9U, data, length); + +#if defined(DUMP_TX) + CUtils::dump(wxT("Sending Data"), buffer, length + 9U); +#endif + + return m_socket.write(buffer, length + 9U, address, port); +} + +NETWORK_TYPE CGatewayProtocolHandler::read(wxUint16& id, in_addr& address, unsigned int& port) +{ + bool res = true; + + // Loop until we have no more data from the socket or we have data for the higher layers + while (res) + res = readPackets(id, address, port); + + return m_type; +} + +bool CGatewayProtocolHandler::readPackets(wxUint16& id, in_addr& address, unsigned int& port) +{ + m_type = NETWORK_NONE; + + // No more data? + int length = m_socket.read(m_buffer, BUFFER_LENGTH, address, port); + if (length <= 0) + return false; + + m_length = length; + + // Invalid packet type? + if (m_buffer[0] == 'D' && m_buffer[1] == 'S' && m_buffer[2] == 'R' && m_buffer[3] == 'P') { + // Header data + if (m_buffer[4] == 0x20U) { + id = m_buffer[5U] * 256U + m_buffer[6U]; + m_type = NETWORK_HEADER; + return false; + } + + // User data + else if (m_buffer[4] == 0x21U) { + id = m_buffer[5U] * 256U + m_buffer[6U]; + m_type = NETWORK_DATA; + return false; + } + + // Register data + else if (m_buffer[4] == 0x0BU) { + m_type = NETWORK_REGISTER; + return false; + } + } + + CUtils::dump(wxT("Unknown packet from the Repeater"), m_buffer, m_length); + + return true; +} + +unsigned int CGatewayProtocolHandler::readHeader(unsigned char* buffer, unsigned int length) +{ + if (m_type != NETWORK_HEADER) + return 0U; + + // If the checksum is 0xFFFF then we accept the header without testing the checksum + if (m_buffer[47U] == 0xFFU && m_buffer[48U] == 0xFFU) { + ::memcpy(buffer, m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES); + return RADIO_HEADER_LENGTH_BYTES; + } + + // Get the checksum for the header + CCCITTChecksumReverse csum; + csum.update(m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES - 2U); + + bool check = csum.check(m_buffer + 8U + RADIO_HEADER_LENGTH_BYTES - 2U); + if (!check) { + CUtils::dump(wxT("Header checksum failure from the Repeater"), m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES); + return 0U; + } + + ::memcpy(buffer, m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES); + + return RADIO_HEADER_LENGTH_BYTES; +} + +unsigned int CGatewayProtocolHandler::readData(unsigned char* buffer, unsigned int length, wxUint8& seqNo, unsigned int& errors) +{ + if (m_type != NETWORK_DATA) + return 0U; + + unsigned int dataLen = m_length - 9U; + + // Is our buffer too small? + if (dataLen > length) + dataLen = length; + + seqNo = m_buffer[7U]; + + errors = m_buffer[8U]; + + ::memcpy(buffer, m_buffer + 9U, dataLen); + + // Simple sanity checks of the incoming sync bits + if (seqNo == 0U) { + // Regenerate sync bytes + buffer[9U] = DATA_SYNC_BYTES[0U]; + buffer[10U] = DATA_SYNC_BYTES[1U]; + buffer[11U] = DATA_SYNC_BYTES[2U]; + } else if (::memcmp(buffer + 9U, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES) == 0) { + // Sync bytes appearing where they shouldn't! + buffer[9U] = 0x70U; + buffer[10U] = 0x4FU; + buffer[11U] = 0x93U; + } + + return dataLen; +} + +unsigned int CGatewayProtocolHandler::readRegister(wxString& name) +{ + if (m_type != NETWORK_REGISTER) + return 0U; + + name = wxString((char*)(m_buffer + 5U), wxConvLocal); + + return m_length - 6U; +} + +void CGatewayProtocolHandler::close() +{ + m_socket.close(); +} diff --git a/Common/GatewayProtocolHandler.h b/Common/GatewayProtocolHandler.h new file mode 100644 index 0000000..107080e --- /dev/null +++ b/Common/GatewayProtocolHandler.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef GatewayProtocolHander_H +#define GatewayProtocolHander_H + +#include "UDPReaderWriter.h" +#include "DStarDefines.h" + +#include +#include + +class CGatewayProtocolHandler { +public: + CGatewayProtocolHandler(const wxString& localAddress, unsigned int localPort); + ~CGatewayProtocolHandler(); + + bool open(); + + bool writeHeader(const unsigned char* header, wxUint16 id, const in_addr& address, unsigned int port); + bool writeData(const unsigned char* data, unsigned int length, wxUint16 id, wxUint8 seqNo, const in_addr& address, unsigned int port); + + NETWORK_TYPE read(wxUint16& id, in_addr& address, unsigned int& port); + unsigned int readHeader(unsigned char* data, unsigned int length); + unsigned int readData(unsigned char* data, unsigned int length, wxUint8& seqNo, unsigned int& errors); + unsigned int readRegister(wxString& name); + + void close(); + +private: + CUDPReaderWriter m_socket; + NETWORK_TYPE m_type; + unsigned char* m_buffer; + unsigned int m_length; + + bool readPackets(wxUint16& id, in_addr& address, unsigned int& port); +}; + +#endif diff --git a/Common/Golay.cpp b/Common/Golay.cpp new file mode 100644 index 0000000..3ab026b --- /dev/null +++ b/Common/Golay.cpp @@ -0,0 +1,1092 @@ +/* + * Copyright (C) 2010 by Jonathan Naylor G4KLX + * Copyright (C) 2002 by Robert H. Morelos-Zaragoza. All rights reserved. + */ + +#include "Golay.h" + +static const unsigned int ENCODING_TABLE_23127[] = { + 0x000000U, 0x0018EAU, 0x00293EU, 0x0031D4U, 0x004A96U, 0x00527CU, 0x0063A8U, 0x007B42U, 0x008DC6U, 0x00952CU, + 0x00A4F8U, 0x00BC12U, 0x00C750U, 0x00DFBAU, 0x00EE6EU, 0x00F684U, 0x010366U, 0x011B8CU, 0x012A58U, 0x0132B2U, + 0x0149F0U, 0x01511AU, 0x0160CEU, 0x017824U, 0x018EA0U, 0x01964AU, 0x01A79EU, 0x01BF74U, 0x01C436U, 0x01DCDCU, + 0x01ED08U, 0x01F5E2U, 0x0206CCU, 0x021E26U, 0x022FF2U, 0x023718U, 0x024C5AU, 0x0254B0U, 0x026564U, 0x027D8EU, + 0x028B0AU, 0x0293E0U, 0x02A234U, 0x02BADEU, 0x02C19CU, 0x02D976U, 0x02E8A2U, 0x02F048U, 0x0305AAU, 0x031D40U, + 0x032C94U, 0x03347EU, 0x034F3CU, 0x0357D6U, 0x036602U, 0x037EE8U, 0x03886CU, 0x039086U, 0x03A152U, 0x03B9B8U, + 0x03C2FAU, 0x03DA10U, 0x03EBC4U, 0x03F32EU, 0x040D98U, 0x041572U, 0x0424A6U, 0x043C4CU, 0x04470EU, 0x045FE4U, + 0x046E30U, 0x0476DAU, 0x04805EU, 0x0498B4U, 0x04A960U, 0x04B18AU, 0x04CAC8U, 0x04D222U, 0x04E3F6U, 0x04FB1CU, + 0x050EFEU, 0x051614U, 0x0527C0U, 0x053F2AU, 0x054468U, 0x055C82U, 0x056D56U, 0x0575BCU, 0x058338U, 0x059BD2U, + 0x05AA06U, 0x05B2ECU, 0x05C9AEU, 0x05D144U, 0x05E090U, 0x05F87AU, 0x060B54U, 0x0613BEU, 0x06226AU, 0x063A80U, + 0x0641C2U, 0x065928U, 0x0668FCU, 0x067016U, 0x068692U, 0x069E78U, 0x06AFACU, 0x06B746U, 0x06CC04U, 0x06D4EEU, + 0x06E53AU, 0x06FDD0U, 0x070832U, 0x0710D8U, 0x07210CU, 0x0739E6U, 0x0742A4U, 0x075A4EU, 0x076B9AU, 0x077370U, + 0x0785F4U, 0x079D1EU, 0x07ACCAU, 0x07B420U, 0x07CF62U, 0x07D788U, 0x07E65CU, 0x07FEB6U, 0x0803DAU, 0x081B30U, + 0x082AE4U, 0x08320EU, 0x08494CU, 0x0851A6U, 0x086072U, 0x087898U, 0x088E1CU, 0x0896F6U, 0x08A722U, 0x08BFC8U, + 0x08C48AU, 0x08DC60U, 0x08EDB4U, 0x08F55EU, 0x0900BCU, 0x091856U, 0x092982U, 0x093168U, 0x094A2AU, 0x0952C0U, + 0x096314U, 0x097BFEU, 0x098D7AU, 0x099590U, 0x09A444U, 0x09BCAEU, 0x09C7ECU, 0x09DF06U, 0x09EED2U, 0x09F638U, + 0x0A0516U, 0x0A1DFCU, 0x0A2C28U, 0x0A34C2U, 0x0A4F80U, 0x0A576AU, 0x0A66BEU, 0x0A7E54U, 0x0A88D0U, 0x0A903AU, + 0x0AA1EEU, 0x0AB904U, 0x0AC246U, 0x0ADAACU, 0x0AEB78U, 0x0AF392U, 0x0B0670U, 0x0B1E9AU, 0x0B2F4EU, 0x0B37A4U, + 0x0B4CE6U, 0x0B540CU, 0x0B65D8U, 0x0B7D32U, 0x0B8BB6U, 0x0B935CU, 0x0BA288U, 0x0BBA62U, 0x0BC120U, 0x0BD9CAU, + 0x0BE81EU, 0x0BF0F4U, 0x0C0E42U, 0x0C16A8U, 0x0C277CU, 0x0C3F96U, 0x0C44D4U, 0x0C5C3EU, 0x0C6DEAU, 0x0C7500U, + 0x0C8384U, 0x0C9B6EU, 0x0CAABAU, 0x0CB250U, 0x0CC912U, 0x0CD1F8U, 0x0CE02CU, 0x0CF8C6U, 0x0D0D24U, 0x0D15CEU, + 0x0D241AU, 0x0D3CF0U, 0x0D47B2U, 0x0D5F58U, 0x0D6E8CU, 0x0D7666U, 0x0D80E2U, 0x0D9808U, 0x0DA9DCU, 0x0DB136U, + 0x0DCA74U, 0x0DD29EU, 0x0DE34AU, 0x0DFBA0U, 0x0E088EU, 0x0E1064U, 0x0E21B0U, 0x0E395AU, 0x0E4218U, 0x0E5AF2U, + 0x0E6B26U, 0x0E73CCU, 0x0E8548U, 0x0E9DA2U, 0x0EAC76U, 0x0EB49CU, 0x0ECFDEU, 0x0ED734U, 0x0EE6E0U, 0x0EFE0AU, + 0x0F0BE8U, 0x0F1302U, 0x0F22D6U, 0x0F3A3CU, 0x0F417EU, 0x0F5994U, 0x0F6840U, 0x0F70AAU, 0x0F862EU, 0x0F9EC4U, + 0x0FAF10U, 0x0FB7FAU, 0x0FCCB8U, 0x0FD452U, 0x0FE586U, 0x0FFD6CU, 0x1007B4U, 0x101F5EU, 0x102E8AU, 0x103660U, + 0x104D22U, 0x1055C8U, 0x10641CU, 0x107CF6U, 0x108A72U, 0x109298U, 0x10A34CU, 0x10BBA6U, 0x10C0E4U, 0x10D80EU, + 0x10E9DAU, 0x10F130U, 0x1104D2U, 0x111C38U, 0x112DECU, 0x113506U, 0x114E44U, 0x1156AEU, 0x11677AU, 0x117F90U, + 0x118914U, 0x1191FEU, 0x11A02AU, 0x11B8C0U, 0x11C382U, 0x11DB68U, 0x11EABCU, 0x11F256U, 0x120178U, 0x121992U, + 0x122846U, 0x1230ACU, 0x124BEEU, 0x125304U, 0x1262D0U, 0x127A3AU, 0x128CBEU, 0x129454U, 0x12A580U, 0x12BD6AU, + 0x12C628U, 0x12DEC2U, 0x12EF16U, 0x12F7FCU, 0x13021EU, 0x131AF4U, 0x132B20U, 0x1333CAU, 0x134888U, 0x135062U, + 0x1361B6U, 0x13795CU, 0x138FD8U, 0x139732U, 0x13A6E6U, 0x13BE0CU, 0x13C54EU, 0x13DDA4U, 0x13EC70U, 0x13F49AU, + 0x140A2CU, 0x1412C6U, 0x142312U, 0x143BF8U, 0x1440BAU, 0x145850U, 0x146984U, 0x14716EU, 0x1487EAU, 0x149F00U, + 0x14AED4U, 0x14B63EU, 0x14CD7CU, 0x14D596U, 0x14E442U, 0x14FCA8U, 0x15094AU, 0x1511A0U, 0x152074U, 0x15389EU, + 0x1543DCU, 0x155B36U, 0x156AE2U, 0x157208U, 0x15848CU, 0x159C66U, 0x15ADB2U, 0x15B558U, 0x15CE1AU, 0x15D6F0U, + 0x15E724U, 0x15FFCEU, 0x160CE0U, 0x16140AU, 0x1625DEU, 0x163D34U, 0x164676U, 0x165E9CU, 0x166F48U, 0x1677A2U, + 0x168126U, 0x1699CCU, 0x16A818U, 0x16B0F2U, 0x16CBB0U, 0x16D35AU, 0x16E28EU, 0x16FA64U, 0x170F86U, 0x17176CU, + 0x1726B8U, 0x173E52U, 0x174510U, 0x175DFAU, 0x176C2EU, 0x1774C4U, 0x178240U, 0x179AAAU, 0x17AB7EU, 0x17B394U, + 0x17C8D6U, 0x17D03CU, 0x17E1E8U, 0x17F902U, 0x18046EU, 0x181C84U, 0x182D50U, 0x1835BAU, 0x184EF8U, 0x185612U, + 0x1867C6U, 0x187F2CU, 0x1889A8U, 0x189142U, 0x18A096U, 0x18B87CU, 0x18C33EU, 0x18DBD4U, 0x18EA00U, 0x18F2EAU, + 0x190708U, 0x191FE2U, 0x192E36U, 0x1936DCU, 0x194D9EU, 0x195574U, 0x1964A0U, 0x197C4AU, 0x198ACEU, 0x199224U, + 0x19A3F0U, 0x19BB1AU, 0x19C058U, 0x19D8B2U, 0x19E966U, 0x19F18CU, 0x1A02A2U, 0x1A1A48U, 0x1A2B9CU, 0x1A3376U, + 0x1A4834U, 0x1A50DEU, 0x1A610AU, 0x1A79E0U, 0x1A8F64U, 0x1A978EU, 0x1AA65AU, 0x1ABEB0U, 0x1AC5F2U, 0x1ADD18U, + 0x1AECCCU, 0x1AF426U, 0x1B01C4U, 0x1B192EU, 0x1B28FAU, 0x1B3010U, 0x1B4B52U, 0x1B53B8U, 0x1B626CU, 0x1B7A86U, + 0x1B8C02U, 0x1B94E8U, 0x1BA53CU, 0x1BBDD6U, 0x1BC694U, 0x1BDE7EU, 0x1BEFAAU, 0x1BF740U, 0x1C09F6U, 0x1C111CU, + 0x1C20C8U, 0x1C3822U, 0x1C4360U, 0x1C5B8AU, 0x1C6A5EU, 0x1C72B4U, 0x1C8430U, 0x1C9CDAU, 0x1CAD0EU, 0x1CB5E4U, + 0x1CCEA6U, 0x1CD64CU, 0x1CE798U, 0x1CFF72U, 0x1D0A90U, 0x1D127AU, 0x1D23AEU, 0x1D3B44U, 0x1D4006U, 0x1D58ECU, + 0x1D6938U, 0x1D71D2U, 0x1D8756U, 0x1D9FBCU, 0x1DAE68U, 0x1DB682U, 0x1DCDC0U, 0x1DD52AU, 0x1DE4FEU, 0x1DFC14U, + 0x1E0F3AU, 0x1E17D0U, 0x1E2604U, 0x1E3EEEU, 0x1E45ACU, 0x1E5D46U, 0x1E6C92U, 0x1E7478U, 0x1E82FCU, 0x1E9A16U, + 0x1EABC2U, 0x1EB328U, 0x1EC86AU, 0x1ED080U, 0x1EE154U, 0x1EF9BEU, 0x1F0C5CU, 0x1F14B6U, 0x1F2562U, 0x1F3D88U, + 0x1F46CAU, 0x1F5E20U, 0x1F6FF4U, 0x1F771EU, 0x1F819AU, 0x1F9970U, 0x1FA8A4U, 0x1FB04EU, 0x1FCB0CU, 0x1FD3E6U, + 0x1FE232U, 0x1FFAD8U, 0x200F68U, 0x201782U, 0x202656U, 0x203EBCU, 0x2045FEU, 0x205D14U, 0x206CC0U, 0x20742AU, + 0x2082AEU, 0x209A44U, 0x20AB90U, 0x20B37AU, 0x20C838U, 0x20D0D2U, 0x20E106U, 0x20F9ECU, 0x210C0EU, 0x2114E4U, + 0x212530U, 0x213DDAU, 0x214698U, 0x215E72U, 0x216FA6U, 0x21774CU, 0x2181C8U, 0x219922U, 0x21A8F6U, 0x21B01CU, + 0x21CB5EU, 0x21D3B4U, 0x21E260U, 0x21FA8AU, 0x2209A4U, 0x22114EU, 0x22209AU, 0x223870U, 0x224332U, 0x225BD8U, + 0x226A0CU, 0x2272E6U, 0x228462U, 0x229C88U, 0x22AD5CU, 0x22B5B6U, 0x22CEF4U, 0x22D61EU, 0x22E7CAU, 0x22FF20U, + 0x230AC2U, 0x231228U, 0x2323FCU, 0x233B16U, 0x234054U, 0x2358BEU, 0x23696AU, 0x237180U, 0x238704U, 0x239FEEU, + 0x23AE3AU, 0x23B6D0U, 0x23CD92U, 0x23D578U, 0x23E4ACU, 0x23FC46U, 0x2402F0U, 0x241A1AU, 0x242BCEU, 0x243324U, + 0x244866U, 0x24508CU, 0x246158U, 0x2479B2U, 0x248F36U, 0x2497DCU, 0x24A608U, 0x24BEE2U, 0x24C5A0U, 0x24DD4AU, + 0x24EC9EU, 0x24F474U, 0x250196U, 0x25197CU, 0x2528A8U, 0x253042U, 0x254B00U, 0x2553EAU, 0x25623EU, 0x257AD4U, + 0x258C50U, 0x2594BAU, 0x25A56EU, 0x25BD84U, 0x25C6C6U, 0x25DE2CU, 0x25EFF8U, 0x25F712U, 0x26043CU, 0x261CD6U, + 0x262D02U, 0x2635E8U, 0x264EAAU, 0x265640U, 0x266794U, 0x267F7EU, 0x2689FAU, 0x269110U, 0x26A0C4U, 0x26B82EU, + 0x26C36CU, 0x26DB86U, 0x26EA52U, 0x26F2B8U, 0x27075AU, 0x271FB0U, 0x272E64U, 0x27368EU, 0x274DCCU, 0x275526U, + 0x2764F2U, 0x277C18U, 0x278A9CU, 0x279276U, 0x27A3A2U, 0x27BB48U, 0x27C00AU, 0x27D8E0U, 0x27E934U, 0x27F1DEU, + 0x280CB2U, 0x281458U, 0x28258CU, 0x283D66U, 0x284624U, 0x285ECEU, 0x286F1AU, 0x2877F0U, 0x288174U, 0x28999EU, + 0x28A84AU, 0x28B0A0U, 0x28CBE2U, 0x28D308U, 0x28E2DCU, 0x28FA36U, 0x290FD4U, 0x29173EU, 0x2926EAU, 0x293E00U, + 0x294542U, 0x295DA8U, 0x296C7CU, 0x297496U, 0x298212U, 0x299AF8U, 0x29AB2CU, 0x29B3C6U, 0x29C884U, 0x29D06EU, + 0x29E1BAU, 0x29F950U, 0x2A0A7EU, 0x2A1294U, 0x2A2340U, 0x2A3BAAU, 0x2A40E8U, 0x2A5802U, 0x2A69D6U, 0x2A713CU, + 0x2A87B8U, 0x2A9F52U, 0x2AAE86U, 0x2AB66CU, 0x2ACD2EU, 0x2AD5C4U, 0x2AE410U, 0x2AFCFAU, 0x2B0918U, 0x2B11F2U, + 0x2B2026U, 0x2B38CCU, 0x2B438EU, 0x2B5B64U, 0x2B6AB0U, 0x2B725AU, 0x2B84DEU, 0x2B9C34U, 0x2BADE0U, 0x2BB50AU, + 0x2BCE48U, 0x2BD6A2U, 0x2BE776U, 0x2BFF9CU, 0x2C012AU, 0x2C19C0U, 0x2C2814U, 0x2C30FEU, 0x2C4BBCU, 0x2C5356U, + 0x2C6282U, 0x2C7A68U, 0x2C8CECU, 0x2C9406U, 0x2CA5D2U, 0x2CBD38U, 0x2CC67AU, 0x2CDE90U, 0x2CEF44U, 0x2CF7AEU, + 0x2D024CU, 0x2D1AA6U, 0x2D2B72U, 0x2D3398U, 0x2D48DAU, 0x2D5030U, 0x2D61E4U, 0x2D790EU, 0x2D8F8AU, 0x2D9760U, + 0x2DA6B4U, 0x2DBE5EU, 0x2DC51CU, 0x2DDDF6U, 0x2DEC22U, 0x2DF4C8U, 0x2E07E6U, 0x2E1F0CU, 0x2E2ED8U, 0x2E3632U, + 0x2E4D70U, 0x2E559AU, 0x2E644EU, 0x2E7CA4U, 0x2E8A20U, 0x2E92CAU, 0x2EA31EU, 0x2EBBF4U, 0x2EC0B6U, 0x2ED85CU, + 0x2EE988U, 0x2EF162U, 0x2F0480U, 0x2F1C6AU, 0x2F2DBEU, 0x2F3554U, 0x2F4E16U, 0x2F56FCU, 0x2F6728U, 0x2F7FC2U, + 0x2F8946U, 0x2F91ACU, 0x2FA078U, 0x2FB892U, 0x2FC3D0U, 0x2FDB3AU, 0x2FEAEEU, 0x2FF204U, 0x3008DCU, 0x301036U, + 0x3021E2U, 0x303908U, 0x30424AU, 0x305AA0U, 0x306B74U, 0x30739EU, 0x30851AU, 0x309DF0U, 0x30AC24U, 0x30B4CEU, + 0x30CF8CU, 0x30D766U, 0x30E6B2U, 0x30FE58U, 0x310BBAU, 0x311350U, 0x312284U, 0x313A6EU, 0x31412CU, 0x3159C6U, + 0x316812U, 0x3170F8U, 0x31867CU, 0x319E96U, 0x31AF42U, 0x31B7A8U, 0x31CCEAU, 0x31D400U, 0x31E5D4U, 0x31FD3EU, + 0x320E10U, 0x3216FAU, 0x32272EU, 0x323FC4U, 0x324486U, 0x325C6CU, 0x326DB8U, 0x327552U, 0x3283D6U, 0x329B3CU, + 0x32AAE8U, 0x32B202U, 0x32C940U, 0x32D1AAU, 0x32E07EU, 0x32F894U, 0x330D76U, 0x33159CU, 0x332448U, 0x333CA2U, + 0x3347E0U, 0x335F0AU, 0x336EDEU, 0x337634U, 0x3380B0U, 0x33985AU, 0x33A98EU, 0x33B164U, 0x33CA26U, 0x33D2CCU, + 0x33E318U, 0x33FBF2U, 0x340544U, 0x341DAEU, 0x342C7AU, 0x343490U, 0x344FD2U, 0x345738U, 0x3466ECU, 0x347E06U, + 0x348882U, 0x349068U, 0x34A1BCU, 0x34B956U, 0x34C214U, 0x34DAFEU, 0x34EB2AU, 0x34F3C0U, 0x350622U, 0x351EC8U, + 0x352F1CU, 0x3537F6U, 0x354CB4U, 0x35545EU, 0x35658AU, 0x357D60U, 0x358BE4U, 0x35930EU, 0x35A2DAU, 0x35BA30U, + 0x35C172U, 0x35D998U, 0x35E84CU, 0x35F0A6U, 0x360388U, 0x361B62U, 0x362AB6U, 0x36325CU, 0x36491EU, 0x3651F4U, + 0x366020U, 0x3678CAU, 0x368E4EU, 0x3696A4U, 0x36A770U, 0x36BF9AU, 0x36C4D8U, 0x36DC32U, 0x36EDE6U, 0x36F50CU, + 0x3700EEU, 0x371804U, 0x3729D0U, 0x37313AU, 0x374A78U, 0x375292U, 0x376346U, 0x377BACU, 0x378D28U, 0x3795C2U, + 0x37A416U, 0x37BCFCU, 0x37C7BEU, 0x37DF54U, 0x37EE80U, 0x37F66AU, 0x380B06U, 0x3813ECU, 0x382238U, 0x383AD2U, + 0x384190U, 0x38597AU, 0x3868AEU, 0x387044U, 0x3886C0U, 0x389E2AU, 0x38AFFEU, 0x38B714U, 0x38CC56U, 0x38D4BCU, + 0x38E568U, 0x38FD82U, 0x390860U, 0x39108AU, 0x39215EU, 0x3939B4U, 0x3942F6U, 0x395A1CU, 0x396BC8U, 0x397322U, + 0x3985A6U, 0x399D4CU, 0x39AC98U, 0x39B472U, 0x39CF30U, 0x39D7DAU, 0x39E60EU, 0x39FEE4U, 0x3A0DCAU, 0x3A1520U, + 0x3A24F4U, 0x3A3C1EU, 0x3A475CU, 0x3A5FB6U, 0x3A6E62U, 0x3A7688U, 0x3A800CU, 0x3A98E6U, 0x3AA932U, 0x3AB1D8U, + 0x3ACA9AU, 0x3AD270U, 0x3AE3A4U, 0x3AFB4EU, 0x3B0EACU, 0x3B1646U, 0x3B2792U, 0x3B3F78U, 0x3B443AU, 0x3B5CD0U, + 0x3B6D04U, 0x3B75EEU, 0x3B836AU, 0x3B9B80U, 0x3BAA54U, 0x3BB2BEU, 0x3BC9FCU, 0x3BD116U, 0x3BE0C2U, 0x3BF828U, + 0x3C069EU, 0x3C1E74U, 0x3C2FA0U, 0x3C374AU, 0x3C4C08U, 0x3C54E2U, 0x3C6536U, 0x3C7DDCU, 0x3C8B58U, 0x3C93B2U, + 0x3CA266U, 0x3CBA8CU, 0x3CC1CEU, 0x3CD924U, 0x3CE8F0U, 0x3CF01AU, 0x3D05F8U, 0x3D1D12U, 0x3D2CC6U, 0x3D342CU, + 0x3D4F6EU, 0x3D5784U, 0x3D6650U, 0x3D7EBAU, 0x3D883EU, 0x3D90D4U, 0x3DA100U, 0x3DB9EAU, 0x3DC2A8U, 0x3DDA42U, + 0x3DEB96U, 0x3DF37CU, 0x3E0052U, 0x3E18B8U, 0x3E296CU, 0x3E3186U, 0x3E4AC4U, 0x3E522EU, 0x3E63FAU, 0x3E7B10U, + 0x3E8D94U, 0x3E957EU, 0x3EA4AAU, 0x3EBC40U, 0x3EC702U, 0x3EDFE8U, 0x3EEE3CU, 0x3EF6D6U, 0x3F0334U, 0x3F1BDEU, + 0x3F2A0AU, 0x3F32E0U, 0x3F49A2U, 0x3F5148U, 0x3F609CU, 0x3F7876U, 0x3F8EF2U, 0x3F9618U, 0x3FA7CCU, 0x3FBF26U, + 0x3FC464U, 0x3FDC8EU, 0x3FED5AU, 0x3FF5B0U, 0x40063AU, 0x401ED0U, 0x402F04U, 0x4037EEU, 0x404CACU, 0x405446U, + 0x406592U, 0x407D78U, 0x408BFCU, 0x409316U, 0x40A2C2U, 0x40BA28U, 0x40C16AU, 0x40D980U, 0x40E854U, 0x40F0BEU, + 0x41055CU, 0x411DB6U, 0x412C62U, 0x413488U, 0x414FCAU, 0x415720U, 0x4166F4U, 0x417E1EU, 0x41889AU, 0x419070U, + 0x41A1A4U, 0x41B94EU, 0x41C20CU, 0x41DAE6U, 0x41EB32U, 0x41F3D8U, 0x4200F6U, 0x42181CU, 0x4229C8U, 0x423122U, + 0x424A60U, 0x42528AU, 0x42635EU, 0x427BB4U, 0x428D30U, 0x4295DAU, 0x42A40EU, 0x42BCE4U, 0x42C7A6U, 0x42DF4CU, + 0x42EE98U, 0x42F672U, 0x430390U, 0x431B7AU, 0x432AAEU, 0x433244U, 0x434906U, 0x4351ECU, 0x436038U, 0x4378D2U, + 0x438E56U, 0x4396BCU, 0x43A768U, 0x43BF82U, 0x43C4C0U, 0x43DC2AU, 0x43EDFEU, 0x43F514U, 0x440BA2U, 0x441348U, + 0x44229CU, 0x443A76U, 0x444134U, 0x4459DEU, 0x44680AU, 0x4470E0U, 0x448664U, 0x449E8EU, 0x44AF5AU, 0x44B7B0U, + 0x44CCF2U, 0x44D418U, 0x44E5CCU, 0x44FD26U, 0x4508C4U, 0x45102EU, 0x4521FAU, 0x453910U, 0x454252U, 0x455AB8U, + 0x456B6CU, 0x457386U, 0x458502U, 0x459DE8U, 0x45AC3CU, 0x45B4D6U, 0x45CF94U, 0x45D77EU, 0x45E6AAU, 0x45FE40U, + 0x460D6EU, 0x461584U, 0x462450U, 0x463CBAU, 0x4647F8U, 0x465F12U, 0x466EC6U, 0x46762CU, 0x4680A8U, 0x469842U, + 0x46A996U, 0x46B17CU, 0x46CA3EU, 0x46D2D4U, 0x46E300U, 0x46FBEAU, 0x470E08U, 0x4716E2U, 0x472736U, 0x473FDCU, + 0x47449EU, 0x475C74U, 0x476DA0U, 0x47754AU, 0x4783CEU, 0x479B24U, 0x47AAF0U, 0x47B21AU, 0x47C958U, 0x47D1B2U, + 0x47E066U, 0x47F88CU, 0x4805E0U, 0x481D0AU, 0x482CDEU, 0x483434U, 0x484F76U, 0x48579CU, 0x486648U, 0x487EA2U, + 0x488826U, 0x4890CCU, 0x48A118U, 0x48B9F2U, 0x48C2B0U, 0x48DA5AU, 0x48EB8EU, 0x48F364U, 0x490686U, 0x491E6CU, + 0x492FB8U, 0x493752U, 0x494C10U, 0x4954FAU, 0x49652EU, 0x497DC4U, 0x498B40U, 0x4993AAU, 0x49A27EU, 0x49BA94U, + 0x49C1D6U, 0x49D93CU, 0x49E8E8U, 0x49F002U, 0x4A032CU, 0x4A1BC6U, 0x4A2A12U, 0x4A32F8U, 0x4A49BAU, 0x4A5150U, + 0x4A6084U, 0x4A786EU, 0x4A8EEAU, 0x4A9600U, 0x4AA7D4U, 0x4ABF3EU, 0x4AC47CU, 0x4ADC96U, 0x4AED42U, 0x4AF5A8U, + 0x4B004AU, 0x4B18A0U, 0x4B2974U, 0x4B319EU, 0x4B4ADCU, 0x4B5236U, 0x4B63E2U, 0x4B7B08U, 0x4B8D8CU, 0x4B9566U, + 0x4BA4B2U, 0x4BBC58U, 0x4BC71AU, 0x4BDFF0U, 0x4BEE24U, 0x4BF6CEU, 0x4C0878U, 0x4C1092U, 0x4C2146U, 0x4C39ACU, + 0x4C42EEU, 0x4C5A04U, 0x4C6BD0U, 0x4C733AU, 0x4C85BEU, 0x4C9D54U, 0x4CAC80U, 0x4CB46AU, 0x4CCF28U, 0x4CD7C2U, + 0x4CE616U, 0x4CFEFCU, 0x4D0B1EU, 0x4D13F4U, 0x4D2220U, 0x4D3ACAU, 0x4D4188U, 0x4D5962U, 0x4D68B6U, 0x4D705CU, + 0x4D86D8U, 0x4D9E32U, 0x4DAFE6U, 0x4DB70CU, 0x4DCC4EU, 0x4DD4A4U, 0x4DE570U, 0x4DFD9AU, 0x4E0EB4U, 0x4E165EU, + 0x4E278AU, 0x4E3F60U, 0x4E4422U, 0x4E5CC8U, 0x4E6D1CU, 0x4E75F6U, 0x4E8372U, 0x4E9B98U, 0x4EAA4CU, 0x4EB2A6U, + 0x4EC9E4U, 0x4ED10EU, 0x4EE0DAU, 0x4EF830U, 0x4F0DD2U, 0x4F1538U, 0x4F24ECU, 0x4F3C06U, 0x4F4744U, 0x4F5FAEU, + 0x4F6E7AU, 0x4F7690U, 0x4F8014U, 0x4F98FEU, 0x4FA92AU, 0x4FB1C0U, 0x4FCA82U, 0x4FD268U, 0x4FE3BCU, 0x4FFB56U, + 0x50018EU, 0x501964U, 0x5028B0U, 0x50305AU, 0x504B18U, 0x5053F2U, 0x506226U, 0x507ACCU, 0x508C48U, 0x5094A2U, + 0x50A576U, 0x50BD9CU, 0x50C6DEU, 0x50DE34U, 0x50EFE0U, 0x50F70AU, 0x5102E8U, 0x511A02U, 0x512BD6U, 0x51333CU, + 0x51487EU, 0x515094U, 0x516140U, 0x5179AAU, 0x518F2EU, 0x5197C4U, 0x51A610U, 0x51BEFAU, 0x51C5B8U, 0x51DD52U, + 0x51EC86U, 0x51F46CU, 0x520742U, 0x521FA8U, 0x522E7CU, 0x523696U, 0x524DD4U, 0x52553EU, 0x5264EAU, 0x527C00U, + 0x528A84U, 0x52926EU, 0x52A3BAU, 0x52BB50U, 0x52C012U, 0x52D8F8U, 0x52E92CU, 0x52F1C6U, 0x530424U, 0x531CCEU, + 0x532D1AU, 0x5335F0U, 0x534EB2U, 0x535658U, 0x53678CU, 0x537F66U, 0x5389E2U, 0x539108U, 0x53A0DCU, 0x53B836U, + 0x53C374U, 0x53DB9EU, 0x53EA4AU, 0x53F2A0U, 0x540C16U, 0x5414FCU, 0x542528U, 0x543DC2U, 0x544680U, 0x545E6AU, + 0x546FBEU, 0x547754U, 0x5481D0U, 0x54993AU, 0x54A8EEU, 0x54B004U, 0x54CB46U, 0x54D3ACU, 0x54E278U, 0x54FA92U, + 0x550F70U, 0x55179AU, 0x55264EU, 0x553EA4U, 0x5545E6U, 0x555D0CU, 0x556CD8U, 0x557432U, 0x5582B6U, 0x559A5CU, + 0x55AB88U, 0x55B362U, 0x55C820U, 0x55D0CAU, 0x55E11EU, 0x55F9F4U, 0x560ADAU, 0x561230U, 0x5623E4U, 0x563B0EU, + 0x56404CU, 0x5658A6U, 0x566972U, 0x567198U, 0x56871CU, 0x569FF6U, 0x56AE22U, 0x56B6C8U, 0x56CD8AU, 0x56D560U, + 0x56E4B4U, 0x56FC5EU, 0x5709BCU, 0x571156U, 0x572082U, 0x573868U, 0x57432AU, 0x575BC0U, 0x576A14U, 0x5772FEU, + 0x57847AU, 0x579C90U, 0x57AD44U, 0x57B5AEU, 0x57CEECU, 0x57D606U, 0x57E7D2U, 0x57FF38U, 0x580254U, 0x581ABEU, + 0x582B6AU, 0x583380U, 0x5848C2U, 0x585028U, 0x5861FCU, 0x587916U, 0x588F92U, 0x589778U, 0x58A6ACU, 0x58BE46U, + 0x58C504U, 0x58DDEEU, 0x58EC3AU, 0x58F4D0U, 0x590132U, 0x5919D8U, 0x59280CU, 0x5930E6U, 0x594BA4U, 0x59534EU, + 0x59629AU, 0x597A70U, 0x598CF4U, 0x59941EU, 0x59A5CAU, 0x59BD20U, 0x59C662U, 0x59DE88U, 0x59EF5CU, 0x59F7B6U, + 0x5A0498U, 0x5A1C72U, 0x5A2DA6U, 0x5A354CU, 0x5A4E0EU, 0x5A56E4U, 0x5A6730U, 0x5A7FDAU, 0x5A895EU, 0x5A91B4U, + 0x5AA060U, 0x5AB88AU, 0x5AC3C8U, 0x5ADB22U, 0x5AEAF6U, 0x5AF21CU, 0x5B07FEU, 0x5B1F14U, 0x5B2EC0U, 0x5B362AU, + 0x5B4D68U, 0x5B5582U, 0x5B6456U, 0x5B7CBCU, 0x5B8A38U, 0x5B92D2U, 0x5BA306U, 0x5BBBECU, 0x5BC0AEU, 0x5BD844U, + 0x5BE990U, 0x5BF17AU, 0x5C0FCCU, 0x5C1726U, 0x5C26F2U, 0x5C3E18U, 0x5C455AU, 0x5C5DB0U, 0x5C6C64U, 0x5C748EU, + 0x5C820AU, 0x5C9AE0U, 0x5CAB34U, 0x5CB3DEU, 0x5CC89CU, 0x5CD076U, 0x5CE1A2U, 0x5CF948U, 0x5D0CAAU, 0x5D1440U, + 0x5D2594U, 0x5D3D7EU, 0x5D463CU, 0x5D5ED6U, 0x5D6F02U, 0x5D77E8U, 0x5D816CU, 0x5D9986U, 0x5DA852U, 0x5DB0B8U, + 0x5DCBFAU, 0x5DD310U, 0x5DE2C4U, 0x5DFA2EU, 0x5E0900U, 0x5E11EAU, 0x5E203EU, 0x5E38D4U, 0x5E4396U, 0x5E5B7CU, + 0x5E6AA8U, 0x5E7242U, 0x5E84C6U, 0x5E9C2CU, 0x5EADF8U, 0x5EB512U, 0x5ECE50U, 0x5ED6BAU, 0x5EE76EU, 0x5EFF84U, + 0x5F0A66U, 0x5F128CU, 0x5F2358U, 0x5F3BB2U, 0x5F40F0U, 0x5F581AU, 0x5F69CEU, 0x5F7124U, 0x5F87A0U, 0x5F9F4AU, + 0x5FAE9EU, 0x5FB674U, 0x5FCD36U, 0x5FD5DCU, 0x5FE408U, 0x5FFCE2U, 0x600952U, 0x6011B8U, 0x60206CU, 0x603886U, + 0x6043C4U, 0x605B2EU, 0x606AFAU, 0x607210U, 0x608494U, 0x609C7EU, 0x60ADAAU, 0x60B540U, 0x60CE02U, 0x60D6E8U, + 0x60E73CU, 0x60FFD6U, 0x610A34U, 0x6112DEU, 0x61230AU, 0x613BE0U, 0x6140A2U, 0x615848U, 0x61699CU, 0x617176U, + 0x6187F2U, 0x619F18U, 0x61AECCU, 0x61B626U, 0x61CD64U, 0x61D58EU, 0x61E45AU, 0x61FCB0U, 0x620F9EU, 0x621774U, + 0x6226A0U, 0x623E4AU, 0x624508U, 0x625DE2U, 0x626C36U, 0x6274DCU, 0x628258U, 0x629AB2U, 0x62AB66U, 0x62B38CU, + 0x62C8CEU, 0x62D024U, 0x62E1F0U, 0x62F91AU, 0x630CF8U, 0x631412U, 0x6325C6U, 0x633D2CU, 0x63466EU, 0x635E84U, + 0x636F50U, 0x6377BAU, 0x63813EU, 0x6399D4U, 0x63A800U, 0x63B0EAU, 0x63CBA8U, 0x63D342U, 0x63E296U, 0x63FA7CU, + 0x6404CAU, 0x641C20U, 0x642DF4U, 0x64351EU, 0x644E5CU, 0x6456B6U, 0x646762U, 0x647F88U, 0x64890CU, 0x6491E6U, + 0x64A032U, 0x64B8D8U, 0x64C39AU, 0x64DB70U, 0x64EAA4U, 0x64F24EU, 0x6507ACU, 0x651F46U, 0x652E92U, 0x653678U, + 0x654D3AU, 0x6555D0U, 0x656404U, 0x657CEEU, 0x658A6AU, 0x659280U, 0x65A354U, 0x65BBBEU, 0x65C0FCU, 0x65D816U, + 0x65E9C2U, 0x65F128U, 0x660206U, 0x661AECU, 0x662B38U, 0x6633D2U, 0x664890U, 0x66507AU, 0x6661AEU, 0x667944U, + 0x668FC0U, 0x66972AU, 0x66A6FEU, 0x66BE14U, 0x66C556U, 0x66DDBCU, 0x66EC68U, 0x66F482U, 0x670160U, 0x67198AU, + 0x67285EU, 0x6730B4U, 0x674BF6U, 0x67531CU, 0x6762C8U, 0x677A22U, 0x678CA6U, 0x67944CU, 0x67A598U, 0x67BD72U, + 0x67C630U, 0x67DEDAU, 0x67EF0EU, 0x67F7E4U, 0x680A88U, 0x681262U, 0x6823B6U, 0x683B5CU, 0x68401EU, 0x6858F4U, + 0x686920U, 0x6871CAU, 0x68874EU, 0x689FA4U, 0x68AE70U, 0x68B69AU, 0x68CDD8U, 0x68D532U, 0x68E4E6U, 0x68FC0CU, + 0x6909EEU, 0x691104U, 0x6920D0U, 0x69383AU, 0x694378U, 0x695B92U, 0x696A46U, 0x6972ACU, 0x698428U, 0x699CC2U, + 0x69AD16U, 0x69B5FCU, 0x69CEBEU, 0x69D654U, 0x69E780U, 0x69FF6AU, 0x6A0C44U, 0x6A14AEU, 0x6A257AU, 0x6A3D90U, + 0x6A46D2U, 0x6A5E38U, 0x6A6FECU, 0x6A7706U, 0x6A8182U, 0x6A9968U, 0x6AA8BCU, 0x6AB056U, 0x6ACB14U, 0x6AD3FEU, + 0x6AE22AU, 0x6AFAC0U, 0x6B0F22U, 0x6B17C8U, 0x6B261CU, 0x6B3EF6U, 0x6B45B4U, 0x6B5D5EU, 0x6B6C8AU, 0x6B7460U, + 0x6B82E4U, 0x6B9A0EU, 0x6BABDAU, 0x6BB330U, 0x6BC872U, 0x6BD098U, 0x6BE14CU, 0x6BF9A6U, 0x6C0710U, 0x6C1FFAU, + 0x6C2E2EU, 0x6C36C4U, 0x6C4D86U, 0x6C556CU, 0x6C64B8U, 0x6C7C52U, 0x6C8AD6U, 0x6C923CU, 0x6CA3E8U, 0x6CBB02U, + 0x6CC040U, 0x6CD8AAU, 0x6CE97EU, 0x6CF194U, 0x6D0476U, 0x6D1C9CU, 0x6D2D48U, 0x6D35A2U, 0x6D4EE0U, 0x6D560AU, + 0x6D67DEU, 0x6D7F34U, 0x6D89B0U, 0x6D915AU, 0x6DA08EU, 0x6DB864U, 0x6DC326U, 0x6DDBCCU, 0x6DEA18U, 0x6DF2F2U, + 0x6E01DCU, 0x6E1936U, 0x6E28E2U, 0x6E3008U, 0x6E4B4AU, 0x6E53A0U, 0x6E6274U, 0x6E7A9EU, 0x6E8C1AU, 0x6E94F0U, + 0x6EA524U, 0x6EBDCEU, 0x6EC68CU, 0x6EDE66U, 0x6EEFB2U, 0x6EF758U, 0x6F02BAU, 0x6F1A50U, 0x6F2B84U, 0x6F336EU, + 0x6F482CU, 0x6F50C6U, 0x6F6112U, 0x6F79F8U, 0x6F8F7CU, 0x6F9796U, 0x6FA642U, 0x6FBEA8U, 0x6FC5EAU, 0x6FDD00U, + 0x6FECD4U, 0x6FF43EU, 0x700EE6U, 0x70160CU, 0x7027D8U, 0x703F32U, 0x704470U, 0x705C9AU, 0x706D4EU, 0x7075A4U, + 0x708320U, 0x709BCAU, 0x70AA1EU, 0x70B2F4U, 0x70C9B6U, 0x70D15CU, 0x70E088U, 0x70F862U, 0x710D80U, 0x71156AU, + 0x7124BEU, 0x713C54U, 0x714716U, 0x715FFCU, 0x716E28U, 0x7176C2U, 0x718046U, 0x7198ACU, 0x71A978U, 0x71B192U, + 0x71CAD0U, 0x71D23AU, 0x71E3EEU, 0x71FB04U, 0x72082AU, 0x7210C0U, 0x722114U, 0x7239FEU, 0x7242BCU, 0x725A56U, + 0x726B82U, 0x727368U, 0x7285ECU, 0x729D06U, 0x72ACD2U, 0x72B438U, 0x72CF7AU, 0x72D790U, 0x72E644U, 0x72FEAEU, + 0x730B4CU, 0x7313A6U, 0x732272U, 0x733A98U, 0x7341DAU, 0x735930U, 0x7368E4U, 0x73700EU, 0x73868AU, 0x739E60U, + 0x73AFB4U, 0x73B75EU, 0x73CC1CU, 0x73D4F6U, 0x73E522U, 0x73FDC8U, 0x74037EU, 0x741B94U, 0x742A40U, 0x7432AAU, + 0x7449E8U, 0x745102U, 0x7460D6U, 0x74783CU, 0x748EB8U, 0x749652U, 0x74A786U, 0x74BF6CU, 0x74C42EU, 0x74DCC4U, + 0x74ED10U, 0x74F5FAU, 0x750018U, 0x7518F2U, 0x752926U, 0x7531CCU, 0x754A8EU, 0x755264U, 0x7563B0U, 0x757B5AU, + 0x758DDEU, 0x759534U, 0x75A4E0U, 0x75BC0AU, 0x75C748U, 0x75DFA2U, 0x75EE76U, 0x75F69CU, 0x7605B2U, 0x761D58U, + 0x762C8CU, 0x763466U, 0x764F24U, 0x7657CEU, 0x76661AU, 0x767EF0U, 0x768874U, 0x76909EU, 0x76A14AU, 0x76B9A0U, + 0x76C2E2U, 0x76DA08U, 0x76EBDCU, 0x76F336U, 0x7706D4U, 0x771E3EU, 0x772FEAU, 0x773700U, 0x774C42U, 0x7754A8U, + 0x77657CU, 0x777D96U, 0x778B12U, 0x7793F8U, 0x77A22CU, 0x77BAC6U, 0x77C184U, 0x77D96EU, 0x77E8BAU, 0x77F050U, + 0x780D3CU, 0x7815D6U, 0x782402U, 0x783CE8U, 0x7847AAU, 0x785F40U, 0x786E94U, 0x78767EU, 0x7880FAU, 0x789810U, + 0x78A9C4U, 0x78B12EU, 0x78CA6CU, 0x78D286U, 0x78E352U, 0x78FBB8U, 0x790E5AU, 0x7916B0U, 0x792764U, 0x793F8EU, + 0x7944CCU, 0x795C26U, 0x796DF2U, 0x797518U, 0x79839CU, 0x799B76U, 0x79AAA2U, 0x79B248U, 0x79C90AU, 0x79D1E0U, + 0x79E034U, 0x79F8DEU, 0x7A0BF0U, 0x7A131AU, 0x7A22CEU, 0x7A3A24U, 0x7A4166U, 0x7A598CU, 0x7A6858U, 0x7A70B2U, + 0x7A8636U, 0x7A9EDCU, 0x7AAF08U, 0x7AB7E2U, 0x7ACCA0U, 0x7AD44AU, 0x7AE59EU, 0x7AFD74U, 0x7B0896U, 0x7B107CU, + 0x7B21A8U, 0x7B3942U, 0x7B4200U, 0x7B5AEAU, 0x7B6B3EU, 0x7B73D4U, 0x7B8550U, 0x7B9DBAU, 0x7BAC6EU, 0x7BB484U, + 0x7BCFC6U, 0x7BD72CU, 0x7BE6F8U, 0x7BFE12U, 0x7C00A4U, 0x7C184EU, 0x7C299AU, 0x7C3170U, 0x7C4A32U, 0x7C52D8U, + 0x7C630CU, 0x7C7BE6U, 0x7C8D62U, 0x7C9588U, 0x7CA45CU, 0x7CBCB6U, 0x7CC7F4U, 0x7CDF1EU, 0x7CEECAU, 0x7CF620U, + 0x7D03C2U, 0x7D1B28U, 0x7D2AFCU, 0x7D3216U, 0x7D4954U, 0x7D51BEU, 0x7D606AU, 0x7D7880U, 0x7D8E04U, 0x7D96EEU, + 0x7DA73AU, 0x7DBFD0U, 0x7DC492U, 0x7DDC78U, 0x7DEDACU, 0x7DF546U, 0x7E0668U, 0x7E1E82U, 0x7E2F56U, 0x7E37BCU, + 0x7E4CFEU, 0x7E5414U, 0x7E65C0U, 0x7E7D2AU, 0x7E8BAEU, 0x7E9344U, 0x7EA290U, 0x7EBA7AU, 0x7EC138U, 0x7ED9D2U, + 0x7EE806U, 0x7EF0ECU, 0x7F050EU, 0x7F1DE4U, 0x7F2C30U, 0x7F34DAU, 0x7F4F98U, 0x7F5772U, 0x7F66A6U, 0x7F7E4CU, + 0x7F88C8U, 0x7F9022U, 0x7FA1F6U, 0x7FB91CU, 0x7FC25EU, 0x7FDAB4U, 0x7FEB60U, 0x7FF38AU, 0x800C74U, 0x80149EU, + 0x80254AU, 0x803DA0U, 0x8046E2U, 0x805E08U, 0x806FDCU, 0x807736U, 0x8081B2U, 0x809958U, 0x80A88CU, 0x80B066U, + 0x80CB24U, 0x80D3CEU, 0x80E21AU, 0x80FAF0U, 0x810F12U, 0x8117F8U, 0x81262CU, 0x813EC6U, 0x814584U, 0x815D6EU, + 0x816CBAU, 0x817450U, 0x8182D4U, 0x819A3EU, 0x81ABEAU, 0x81B300U, 0x81C842U, 0x81D0A8U, 0x81E17CU, 0x81F996U, + 0x820AB8U, 0x821252U, 0x822386U, 0x823B6CU, 0x82402EU, 0x8258C4U, 0x826910U, 0x8271FAU, 0x82877EU, 0x829F94U, + 0x82AE40U, 0x82B6AAU, 0x82CDE8U, 0x82D502U, 0x82E4D6U, 0x82FC3CU, 0x8309DEU, 0x831134U, 0x8320E0U, 0x83380AU, + 0x834348U, 0x835BA2U, 0x836A76U, 0x83729CU, 0x838418U, 0x839CF2U, 0x83AD26U, 0x83B5CCU, 0x83CE8EU, 0x83D664U, + 0x83E7B0U, 0x83FF5AU, 0x8401ECU, 0x841906U, 0x8428D2U, 0x843038U, 0x844B7AU, 0x845390U, 0x846244U, 0x847AAEU, + 0x848C2AU, 0x8494C0U, 0x84A514U, 0x84BDFEU, 0x84C6BCU, 0x84DE56U, 0x84EF82U, 0x84F768U, 0x85028AU, 0x851A60U, + 0x852BB4U, 0x85335EU, 0x85481CU, 0x8550F6U, 0x856122U, 0x8579C8U, 0x858F4CU, 0x8597A6U, 0x85A672U, 0x85BE98U, + 0x85C5DAU, 0x85DD30U, 0x85ECE4U, 0x85F40EU, 0x860720U, 0x861FCAU, 0x862E1EU, 0x8636F4U, 0x864DB6U, 0x86555CU, + 0x866488U, 0x867C62U, 0x868AE6U, 0x86920CU, 0x86A3D8U, 0x86BB32U, 0x86C070U, 0x86D89AU, 0x86E94EU, 0x86F1A4U, + 0x870446U, 0x871CACU, 0x872D78U, 0x873592U, 0x874ED0U, 0x87563AU, 0x8767EEU, 0x877F04U, 0x878980U, 0x87916AU, + 0x87A0BEU, 0x87B854U, 0x87C316U, 0x87DBFCU, 0x87EA28U, 0x87F2C2U, 0x880FAEU, 0x881744U, 0x882690U, 0x883E7AU, + 0x884538U, 0x885DD2U, 0x886C06U, 0x8874ECU, 0x888268U, 0x889A82U, 0x88AB56U, 0x88B3BCU, 0x88C8FEU, 0x88D014U, + 0x88E1C0U, 0x88F92AU, 0x890CC8U, 0x891422U, 0x8925F6U, 0x893D1CU, 0x89465EU, 0x895EB4U, 0x896F60U, 0x89778AU, + 0x89810EU, 0x8999E4U, 0x89A830U, 0x89B0DAU, 0x89CB98U, 0x89D372U, 0x89E2A6U, 0x89FA4CU, 0x8A0962U, 0x8A1188U, + 0x8A205CU, 0x8A38B6U, 0x8A43F4U, 0x8A5B1EU, 0x8A6ACAU, 0x8A7220U, 0x8A84A4U, 0x8A9C4EU, 0x8AAD9AU, 0x8AB570U, + 0x8ACE32U, 0x8AD6D8U, 0x8AE70CU, 0x8AFFE6U, 0x8B0A04U, 0x8B12EEU, 0x8B233AU, 0x8B3BD0U, 0x8B4092U, 0x8B5878U, + 0x8B69ACU, 0x8B7146U, 0x8B87C2U, 0x8B9F28U, 0x8BAEFCU, 0x8BB616U, 0x8BCD54U, 0x8BD5BEU, 0x8BE46AU, 0x8BFC80U, + 0x8C0236U, 0x8C1ADCU, 0x8C2B08U, 0x8C33E2U, 0x8C48A0U, 0x8C504AU, 0x8C619EU, 0x8C7974U, 0x8C8FF0U, 0x8C971AU, + 0x8CA6CEU, 0x8CBE24U, 0x8CC566U, 0x8CDD8CU, 0x8CEC58U, 0x8CF4B2U, 0x8D0150U, 0x8D19BAU, 0x8D286EU, 0x8D3084U, + 0x8D4BC6U, 0x8D532CU, 0x8D62F8U, 0x8D7A12U, 0x8D8C96U, 0x8D947CU, 0x8DA5A8U, 0x8DBD42U, 0x8DC600U, 0x8DDEEAU, + 0x8DEF3EU, 0x8DF7D4U, 0x8E04FAU, 0x8E1C10U, 0x8E2DC4U, 0x8E352EU, 0x8E4E6CU, 0x8E5686U, 0x8E6752U, 0x8E7FB8U, + 0x8E893CU, 0x8E91D6U, 0x8EA002U, 0x8EB8E8U, 0x8EC3AAU, 0x8EDB40U, 0x8EEA94U, 0x8EF27EU, 0x8F079CU, 0x8F1F76U, + 0x8F2EA2U, 0x8F3648U, 0x8F4D0AU, 0x8F55E0U, 0x8F6434U, 0x8F7CDEU, 0x8F8A5AU, 0x8F92B0U, 0x8FA364U, 0x8FBB8EU, + 0x8FC0CCU, 0x8FD826U, 0x8FE9F2U, 0x8FF118U, 0x900BC0U, 0x90132AU, 0x9022FEU, 0x903A14U, 0x904156U, 0x9059BCU, + 0x906868U, 0x907082U, 0x908606U, 0x909EECU, 0x90AF38U, 0x90B7D2U, 0x90CC90U, 0x90D47AU, 0x90E5AEU, 0x90FD44U, + 0x9108A6U, 0x91104CU, 0x912198U, 0x913972U, 0x914230U, 0x915ADAU, 0x916B0EU, 0x9173E4U, 0x918560U, 0x919D8AU, + 0x91AC5EU, 0x91B4B4U, 0x91CFF6U, 0x91D71CU, 0x91E6C8U, 0x91FE22U, 0x920D0CU, 0x9215E6U, 0x922432U, 0x923CD8U, + 0x92479AU, 0x925F70U, 0x926EA4U, 0x92764EU, 0x9280CAU, 0x929820U, 0x92A9F4U, 0x92B11EU, 0x92CA5CU, 0x92D2B6U, + 0x92E362U, 0x92FB88U, 0x930E6AU, 0x931680U, 0x932754U, 0x933FBEU, 0x9344FCU, 0x935C16U, 0x936DC2U, 0x937528U, + 0x9383ACU, 0x939B46U, 0x93AA92U, 0x93B278U, 0x93C93AU, 0x93D1D0U, 0x93E004U, 0x93F8EEU, 0x940658U, 0x941EB2U, + 0x942F66U, 0x94378CU, 0x944CCEU, 0x945424U, 0x9465F0U, 0x947D1AU, 0x948B9EU, 0x949374U, 0x94A2A0U, 0x94BA4AU, + 0x94C108U, 0x94D9E2U, 0x94E836U, 0x94F0DCU, 0x95053EU, 0x951DD4U, 0x952C00U, 0x9534EAU, 0x954FA8U, 0x955742U, + 0x956696U, 0x957E7CU, 0x9588F8U, 0x959012U, 0x95A1C6U, 0x95B92CU, 0x95C26EU, 0x95DA84U, 0x95EB50U, 0x95F3BAU, + 0x960094U, 0x96187EU, 0x9629AAU, 0x963140U, 0x964A02U, 0x9652E8U, 0x96633CU, 0x967BD6U, 0x968D52U, 0x9695B8U, + 0x96A46CU, 0x96BC86U, 0x96C7C4U, 0x96DF2EU, 0x96EEFAU, 0x96F610U, 0x9703F2U, 0x971B18U, 0x972ACCU, 0x973226U, + 0x974964U, 0x97518EU, 0x97605AU, 0x9778B0U, 0x978E34U, 0x9796DEU, 0x97A70AU, 0x97BFE0U, 0x97C4A2U, 0x97DC48U, + 0x97ED9CU, 0x97F576U, 0x98081AU, 0x9810F0U, 0x982124U, 0x9839CEU, 0x98428CU, 0x985A66U, 0x986BB2U, 0x987358U, + 0x9885DCU, 0x989D36U, 0x98ACE2U, 0x98B408U, 0x98CF4AU, 0x98D7A0U, 0x98E674U, 0x98FE9EU, 0x990B7CU, 0x991396U, + 0x992242U, 0x993AA8U, 0x9941EAU, 0x995900U, 0x9968D4U, 0x99703EU, 0x9986BAU, 0x999E50U, 0x99AF84U, 0x99B76EU, + 0x99CC2CU, 0x99D4C6U, 0x99E512U, 0x99FDF8U, 0x9A0ED6U, 0x9A163CU, 0x9A27E8U, 0x9A3F02U, 0x9A4440U, 0x9A5CAAU, + 0x9A6D7EU, 0x9A7594U, 0x9A8310U, 0x9A9BFAU, 0x9AAA2EU, 0x9AB2C4U, 0x9AC986U, 0x9AD16CU, 0x9AE0B8U, 0x9AF852U, + 0x9B0DB0U, 0x9B155AU, 0x9B248EU, 0x9B3C64U, 0x9B4726U, 0x9B5FCCU, 0x9B6E18U, 0x9B76F2U, 0x9B8076U, 0x9B989CU, + 0x9BA948U, 0x9BB1A2U, 0x9BCAE0U, 0x9BD20AU, 0x9BE3DEU, 0x9BFB34U, 0x9C0582U, 0x9C1D68U, 0x9C2CBCU, 0x9C3456U, + 0x9C4F14U, 0x9C57FEU, 0x9C662AU, 0x9C7EC0U, 0x9C8844U, 0x9C90AEU, 0x9CA17AU, 0x9CB990U, 0x9CC2D2U, 0x9CDA38U, + 0x9CEBECU, 0x9CF306U, 0x9D06E4U, 0x9D1E0EU, 0x9D2FDAU, 0x9D3730U, 0x9D4C72U, 0x9D5498U, 0x9D654CU, 0x9D7DA6U, + 0x9D8B22U, 0x9D93C8U, 0x9DA21CU, 0x9DBAF6U, 0x9DC1B4U, 0x9DD95EU, 0x9DE88AU, 0x9DF060U, 0x9E034EU, 0x9E1BA4U, + 0x9E2A70U, 0x9E329AU, 0x9E49D8U, 0x9E5132U, 0x9E60E6U, 0x9E780CU, 0x9E8E88U, 0x9E9662U, 0x9EA7B6U, 0x9EBF5CU, + 0x9EC41EU, 0x9EDCF4U, 0x9EED20U, 0x9EF5CAU, 0x9F0028U, 0x9F18C2U, 0x9F2916U, 0x9F31FCU, 0x9F4ABEU, 0x9F5254U, + 0x9F6380U, 0x9F7B6AU, 0x9F8DEEU, 0x9F9504U, 0x9FA4D0U, 0x9FBC3AU, 0x9FC778U, 0x9FDF92U, 0x9FEE46U, 0x9FF6ACU, + 0xA0031CU, 0xA01BF6U, 0xA02A22U, 0xA032C8U, 0xA0498AU, 0xA05160U, 0xA060B4U, 0xA0785EU, 0xA08EDAU, 0xA09630U, + 0xA0A7E4U, 0xA0BF0EU, 0xA0C44CU, 0xA0DCA6U, 0xA0ED72U, 0xA0F598U, 0xA1007AU, 0xA11890U, 0xA12944U, 0xA131AEU, + 0xA14AECU, 0xA15206U, 0xA163D2U, 0xA17B38U, 0xA18DBCU, 0xA19556U, 0xA1A482U, 0xA1BC68U, 0xA1C72AU, 0xA1DFC0U, + 0xA1EE14U, 0xA1F6FEU, 0xA205D0U, 0xA21D3AU, 0xA22CEEU, 0xA23404U, 0xA24F46U, 0xA257ACU, 0xA26678U, 0xA27E92U, + 0xA28816U, 0xA290FCU, 0xA2A128U, 0xA2B9C2U, 0xA2C280U, 0xA2DA6AU, 0xA2EBBEU, 0xA2F354U, 0xA306B6U, 0xA31E5CU, + 0xA32F88U, 0xA33762U, 0xA34C20U, 0xA354CAU, 0xA3651EU, 0xA37DF4U, 0xA38B70U, 0xA3939AU, 0xA3A24EU, 0xA3BAA4U, + 0xA3C1E6U, 0xA3D90CU, 0xA3E8D8U, 0xA3F032U, 0xA40E84U, 0xA4166EU, 0xA427BAU, 0xA43F50U, 0xA44412U, 0xA45CF8U, + 0xA46D2CU, 0xA475C6U, 0xA48342U, 0xA49BA8U, 0xA4AA7CU, 0xA4B296U, 0xA4C9D4U, 0xA4D13EU, 0xA4E0EAU, 0xA4F800U, + 0xA50DE2U, 0xA51508U, 0xA524DCU, 0xA53C36U, 0xA54774U, 0xA55F9EU, 0xA56E4AU, 0xA576A0U, 0xA58024U, 0xA598CEU, + 0xA5A91AU, 0xA5B1F0U, 0xA5CAB2U, 0xA5D258U, 0xA5E38CU, 0xA5FB66U, 0xA60848U, 0xA610A2U, 0xA62176U, 0xA6399CU, + 0xA642DEU, 0xA65A34U, 0xA66BE0U, 0xA6730AU, 0xA6858EU, 0xA69D64U, 0xA6ACB0U, 0xA6B45AU, 0xA6CF18U, 0xA6D7F2U, + 0xA6E626U, 0xA6FECCU, 0xA70B2EU, 0xA713C4U, 0xA72210U, 0xA73AFAU, 0xA741B8U, 0xA75952U, 0xA76886U, 0xA7706CU, + 0xA786E8U, 0xA79E02U, 0xA7AFD6U, 0xA7B73CU, 0xA7CC7EU, 0xA7D494U, 0xA7E540U, 0xA7FDAAU, 0xA800C6U, 0xA8182CU, + 0xA829F8U, 0xA83112U, 0xA84A50U, 0xA852BAU, 0xA8636EU, 0xA87B84U, 0xA88D00U, 0xA895EAU, 0xA8A43EU, 0xA8BCD4U, + 0xA8C796U, 0xA8DF7CU, 0xA8EEA8U, 0xA8F642U, 0xA903A0U, 0xA91B4AU, 0xA92A9EU, 0xA93274U, 0xA94936U, 0xA951DCU, + 0xA96008U, 0xA978E2U, 0xA98E66U, 0xA9968CU, 0xA9A758U, 0xA9BFB2U, 0xA9C4F0U, 0xA9DC1AU, 0xA9EDCEU, 0xA9F524U, + 0xAA060AU, 0xAA1EE0U, 0xAA2F34U, 0xAA37DEU, 0xAA4C9CU, 0xAA5476U, 0xAA65A2U, 0xAA7D48U, 0xAA8BCCU, 0xAA9326U, + 0xAAA2F2U, 0xAABA18U, 0xAAC15AU, 0xAAD9B0U, 0xAAE864U, 0xAAF08EU, 0xAB056CU, 0xAB1D86U, 0xAB2C52U, 0xAB34B8U, + 0xAB4FFAU, 0xAB5710U, 0xAB66C4U, 0xAB7E2EU, 0xAB88AAU, 0xAB9040U, 0xABA194U, 0xABB97EU, 0xABC23CU, 0xABDAD6U, + 0xABEB02U, 0xABF3E8U, 0xAC0D5EU, 0xAC15B4U, 0xAC2460U, 0xAC3C8AU, 0xAC47C8U, 0xAC5F22U, 0xAC6EF6U, 0xAC761CU, + 0xAC8098U, 0xAC9872U, 0xACA9A6U, 0xACB14CU, 0xACCA0EU, 0xACD2E4U, 0xACE330U, 0xACFBDAU, 0xAD0E38U, 0xAD16D2U, + 0xAD2706U, 0xAD3FECU, 0xAD44AEU, 0xAD5C44U, 0xAD6D90U, 0xAD757AU, 0xAD83FEU, 0xAD9B14U, 0xADAAC0U, 0xADB22AU, + 0xADC968U, 0xADD182U, 0xADE056U, 0xADF8BCU, 0xAE0B92U, 0xAE1378U, 0xAE22ACU, 0xAE3A46U, 0xAE4104U, 0xAE59EEU, + 0xAE683AU, 0xAE70D0U, 0xAE8654U, 0xAE9EBEU, 0xAEAF6AU, 0xAEB780U, 0xAECCC2U, 0xAED428U, 0xAEE5FCU, 0xAEFD16U, + 0xAF08F4U, 0xAF101EU, 0xAF21CAU, 0xAF3920U, 0xAF4262U, 0xAF5A88U, 0xAF6B5CU, 0xAF73B6U, 0xAF8532U, 0xAF9DD8U, + 0xAFAC0CU, 0xAFB4E6U, 0xAFCFA4U, 0xAFD74EU, 0xAFE69AU, 0xAFFE70U, 0xB004A8U, 0xB01C42U, 0xB02D96U, 0xB0357CU, + 0xB04E3EU, 0xB056D4U, 0xB06700U, 0xB07FEAU, 0xB0896EU, 0xB09184U, 0xB0A050U, 0xB0B8BAU, 0xB0C3F8U, 0xB0DB12U, + 0xB0EAC6U, 0xB0F22CU, 0xB107CEU, 0xB11F24U, 0xB12EF0U, 0xB1361AU, 0xB14D58U, 0xB155B2U, 0xB16466U, 0xB17C8CU, + 0xB18A08U, 0xB192E2U, 0xB1A336U, 0xB1BBDCU, 0xB1C09EU, 0xB1D874U, 0xB1E9A0U, 0xB1F14AU, 0xB20264U, 0xB21A8EU, + 0xB22B5AU, 0xB233B0U, 0xB248F2U, 0xB25018U, 0xB261CCU, 0xB27926U, 0xB28FA2U, 0xB29748U, 0xB2A69CU, 0xB2BE76U, + 0xB2C534U, 0xB2DDDEU, 0xB2EC0AU, 0xB2F4E0U, 0xB30102U, 0xB319E8U, 0xB3283CU, 0xB330D6U, 0xB34B94U, 0xB3537EU, + 0xB362AAU, 0xB37A40U, 0xB38CC4U, 0xB3942EU, 0xB3A5FAU, 0xB3BD10U, 0xB3C652U, 0xB3DEB8U, 0xB3EF6CU, 0xB3F786U, + 0xB40930U, 0xB411DAU, 0xB4200EU, 0xB438E4U, 0xB443A6U, 0xB45B4CU, 0xB46A98U, 0xB47272U, 0xB484F6U, 0xB49C1CU, + 0xB4ADC8U, 0xB4B522U, 0xB4CE60U, 0xB4D68AU, 0xB4E75EU, 0xB4FFB4U, 0xB50A56U, 0xB512BCU, 0xB52368U, 0xB53B82U, + 0xB540C0U, 0xB5582AU, 0xB569FEU, 0xB57114U, 0xB58790U, 0xB59F7AU, 0xB5AEAEU, 0xB5B644U, 0xB5CD06U, 0xB5D5ECU, + 0xB5E438U, 0xB5FCD2U, 0xB60FFCU, 0xB61716U, 0xB626C2U, 0xB63E28U, 0xB6456AU, 0xB65D80U, 0xB66C54U, 0xB674BEU, + 0xB6823AU, 0xB69AD0U, 0xB6AB04U, 0xB6B3EEU, 0xB6C8ACU, 0xB6D046U, 0xB6E192U, 0xB6F978U, 0xB70C9AU, 0xB71470U, + 0xB725A4U, 0xB73D4EU, 0xB7460CU, 0xB75EE6U, 0xB76F32U, 0xB777D8U, 0xB7815CU, 0xB799B6U, 0xB7A862U, 0xB7B088U, + 0xB7CBCAU, 0xB7D320U, 0xB7E2F4U, 0xB7FA1EU, 0xB80772U, 0xB81F98U, 0xB82E4CU, 0xB836A6U, 0xB84DE4U, 0xB8550EU, + 0xB864DAU, 0xB87C30U, 0xB88AB4U, 0xB8925EU, 0xB8A38AU, 0xB8BB60U, 0xB8C022U, 0xB8D8C8U, 0xB8E91CU, 0xB8F1F6U, + 0xB90414U, 0xB91CFEU, 0xB92D2AU, 0xB935C0U, 0xB94E82U, 0xB95668U, 0xB967BCU, 0xB97F56U, 0xB989D2U, 0xB99138U, + 0xB9A0ECU, 0xB9B806U, 0xB9C344U, 0xB9DBAEU, 0xB9EA7AU, 0xB9F290U, 0xBA01BEU, 0xBA1954U, 0xBA2880U, 0xBA306AU, + 0xBA4B28U, 0xBA53C2U, 0xBA6216U, 0xBA7AFCU, 0xBA8C78U, 0xBA9492U, 0xBAA546U, 0xBABDACU, 0xBAC6EEU, 0xBADE04U, + 0xBAEFD0U, 0xBAF73AU, 0xBB02D8U, 0xBB1A32U, 0xBB2BE6U, 0xBB330CU, 0xBB484EU, 0xBB50A4U, 0xBB6170U, 0xBB799AU, + 0xBB8F1EU, 0xBB97F4U, 0xBBA620U, 0xBBBECAU, 0xBBC588U, 0xBBDD62U, 0xBBECB6U, 0xBBF45CU, 0xBC0AEAU, 0xBC1200U, + 0xBC23D4U, 0xBC3B3EU, 0xBC407CU, 0xBC5896U, 0xBC6942U, 0xBC71A8U, 0xBC872CU, 0xBC9FC6U, 0xBCAE12U, 0xBCB6F8U, + 0xBCCDBAU, 0xBCD550U, 0xBCE484U, 0xBCFC6EU, 0xBD098CU, 0xBD1166U, 0xBD20B2U, 0xBD3858U, 0xBD431AU, 0xBD5BF0U, + 0xBD6A24U, 0xBD72CEU, 0xBD844AU, 0xBD9CA0U, 0xBDAD74U, 0xBDB59EU, 0xBDCEDCU, 0xBDD636U, 0xBDE7E2U, 0xBDFF08U, + 0xBE0C26U, 0xBE14CCU, 0xBE2518U, 0xBE3DF2U, 0xBE46B0U, 0xBE5E5AU, 0xBE6F8EU, 0xBE7764U, 0xBE81E0U, 0xBE990AU, + 0xBEA8DEU, 0xBEB034U, 0xBECB76U, 0xBED39CU, 0xBEE248U, 0xBEFAA2U, 0xBF0F40U, 0xBF17AAU, 0xBF267EU, 0xBF3E94U, + 0xBF45D6U, 0xBF5D3CU, 0xBF6CE8U, 0xBF7402U, 0xBF8286U, 0xBF9A6CU, 0xBFABB8U, 0xBFB352U, 0xBFC810U, 0xBFD0FAU, + 0xBFE12EU, 0xBFF9C4U, 0xC00A4EU, 0xC012A4U, 0xC02370U, 0xC03B9AU, 0xC040D8U, 0xC05832U, 0xC069E6U, 0xC0710CU, + 0xC08788U, 0xC09F62U, 0xC0AEB6U, 0xC0B65CU, 0xC0CD1EU, 0xC0D5F4U, 0xC0E420U, 0xC0FCCAU, 0xC10928U, 0xC111C2U, + 0xC12016U, 0xC138FCU, 0xC143BEU, 0xC15B54U, 0xC16A80U, 0xC1726AU, 0xC184EEU, 0xC19C04U, 0xC1ADD0U, 0xC1B53AU, + 0xC1CE78U, 0xC1D692U, 0xC1E746U, 0xC1FFACU, 0xC20C82U, 0xC21468U, 0xC225BCU, 0xC23D56U, 0xC24614U, 0xC25EFEU, + 0xC26F2AU, 0xC277C0U, 0xC28144U, 0xC299AEU, 0xC2A87AU, 0xC2B090U, 0xC2CBD2U, 0xC2D338U, 0xC2E2ECU, 0xC2FA06U, + 0xC30FE4U, 0xC3170EU, 0xC326DAU, 0xC33E30U, 0xC34572U, 0xC35D98U, 0xC36C4CU, 0xC374A6U, 0xC38222U, 0xC39AC8U, + 0xC3AB1CU, 0xC3B3F6U, 0xC3C8B4U, 0xC3D05EU, 0xC3E18AU, 0xC3F960U, 0xC407D6U, 0xC41F3CU, 0xC42EE8U, 0xC43602U, + 0xC44D40U, 0xC455AAU, 0xC4647EU, 0xC47C94U, 0xC48A10U, 0xC492FAU, 0xC4A32EU, 0xC4BBC4U, 0xC4C086U, 0xC4D86CU, + 0xC4E9B8U, 0xC4F152U, 0xC504B0U, 0xC51C5AU, 0xC52D8EU, 0xC53564U, 0xC54E26U, 0xC556CCU, 0xC56718U, 0xC57FF2U, + 0xC58976U, 0xC5919CU, 0xC5A048U, 0xC5B8A2U, 0xC5C3E0U, 0xC5DB0AU, 0xC5EADEU, 0xC5F234U, 0xC6011AU, 0xC619F0U, + 0xC62824U, 0xC630CEU, 0xC64B8CU, 0xC65366U, 0xC662B2U, 0xC67A58U, 0xC68CDCU, 0xC69436U, 0xC6A5E2U, 0xC6BD08U, + 0xC6C64AU, 0xC6DEA0U, 0xC6EF74U, 0xC6F79EU, 0xC7027CU, 0xC71A96U, 0xC72B42U, 0xC733A8U, 0xC748EAU, 0xC75000U, + 0xC761D4U, 0xC7793EU, 0xC78FBAU, 0xC79750U, 0xC7A684U, 0xC7BE6EU, 0xC7C52CU, 0xC7DDC6U, 0xC7EC12U, 0xC7F4F8U, + 0xC80994U, 0xC8117EU, 0xC820AAU, 0xC83840U, 0xC84302U, 0xC85BE8U, 0xC86A3CU, 0xC872D6U, 0xC88452U, 0xC89CB8U, + 0xC8AD6CU, 0xC8B586U, 0xC8CEC4U, 0xC8D62EU, 0xC8E7FAU, 0xC8FF10U, 0xC90AF2U, 0xC91218U, 0xC923CCU, 0xC93B26U, + 0xC94064U, 0xC9588EU, 0xC9695AU, 0xC971B0U, 0xC98734U, 0xC99FDEU, 0xC9AE0AU, 0xC9B6E0U, 0xC9CDA2U, 0xC9D548U, + 0xC9E49CU, 0xC9FC76U, 0xCA0F58U, 0xCA17B2U, 0xCA2666U, 0xCA3E8CU, 0xCA45CEU, 0xCA5D24U, 0xCA6CF0U, 0xCA741AU, + 0xCA829EU, 0xCA9A74U, 0xCAABA0U, 0xCAB34AU, 0xCAC808U, 0xCAD0E2U, 0xCAE136U, 0xCAF9DCU, 0xCB0C3EU, 0xCB14D4U, + 0xCB2500U, 0xCB3DEAU, 0xCB46A8U, 0xCB5E42U, 0xCB6F96U, 0xCB777CU, 0xCB81F8U, 0xCB9912U, 0xCBA8C6U, 0xCBB02CU, + 0xCBCB6EU, 0xCBD384U, 0xCBE250U, 0xCBFABAU, 0xCC040CU, 0xCC1CE6U, 0xCC2D32U, 0xCC35D8U, 0xCC4E9AU, 0xCC5670U, + 0xCC67A4U, 0xCC7F4EU, 0xCC89CAU, 0xCC9120U, 0xCCA0F4U, 0xCCB81EU, 0xCCC35CU, 0xCCDBB6U, 0xCCEA62U, 0xCCF288U, + 0xCD076AU, 0xCD1F80U, 0xCD2E54U, 0xCD36BEU, 0xCD4DFCU, 0xCD5516U, 0xCD64C2U, 0xCD7C28U, 0xCD8AACU, 0xCD9246U, + 0xCDA392U, 0xCDBB78U, 0xCDC03AU, 0xCDD8D0U, 0xCDE904U, 0xCDF1EEU, 0xCE02C0U, 0xCE1A2AU, 0xCE2BFEU, 0xCE3314U, + 0xCE4856U, 0xCE50BCU, 0xCE6168U, 0xCE7982U, 0xCE8F06U, 0xCE97ECU, 0xCEA638U, 0xCEBED2U, 0xCEC590U, 0xCEDD7AU, + 0xCEECAEU, 0xCEF444U, 0xCF01A6U, 0xCF194CU, 0xCF2898U, 0xCF3072U, 0xCF4B30U, 0xCF53DAU, 0xCF620EU, 0xCF7AE4U, + 0xCF8C60U, 0xCF948AU, 0xCFA55EU, 0xCFBDB4U, 0xCFC6F6U, 0xCFDE1CU, 0xCFEFC8U, 0xCFF722U, 0xD00DFAU, 0xD01510U, + 0xD024C4U, 0xD03C2EU, 0xD0476CU, 0xD05F86U, 0xD06E52U, 0xD076B8U, 0xD0803CU, 0xD098D6U, 0xD0A902U, 0xD0B1E8U, + 0xD0CAAAU, 0xD0D240U, 0xD0E394U, 0xD0FB7EU, 0xD10E9CU, 0xD11676U, 0xD127A2U, 0xD13F48U, 0xD1440AU, 0xD15CE0U, + 0xD16D34U, 0xD175DEU, 0xD1835AU, 0xD19BB0U, 0xD1AA64U, 0xD1B28EU, 0xD1C9CCU, 0xD1D126U, 0xD1E0F2U, 0xD1F818U, + 0xD20B36U, 0xD213DCU, 0xD22208U, 0xD23AE2U, 0xD241A0U, 0xD2594AU, 0xD2689EU, 0xD27074U, 0xD286F0U, 0xD29E1AU, + 0xD2AFCEU, 0xD2B724U, 0xD2CC66U, 0xD2D48CU, 0xD2E558U, 0xD2FDB2U, 0xD30850U, 0xD310BAU, 0xD3216EU, 0xD33984U, + 0xD342C6U, 0xD35A2CU, 0xD36BF8U, 0xD37312U, 0xD38596U, 0xD39D7CU, 0xD3ACA8U, 0xD3B442U, 0xD3CF00U, 0xD3D7EAU, + 0xD3E63EU, 0xD3FED4U, 0xD40062U, 0xD41888U, 0xD4295CU, 0xD431B6U, 0xD44AF4U, 0xD4521EU, 0xD463CAU, 0xD47B20U, + 0xD48DA4U, 0xD4954EU, 0xD4A49AU, 0xD4BC70U, 0xD4C732U, 0xD4DFD8U, 0xD4EE0CU, 0xD4F6E6U, 0xD50304U, 0xD51BEEU, + 0xD52A3AU, 0xD532D0U, 0xD54992U, 0xD55178U, 0xD560ACU, 0xD57846U, 0xD58EC2U, 0xD59628U, 0xD5A7FCU, 0xD5BF16U, + 0xD5C454U, 0xD5DCBEU, 0xD5ED6AU, 0xD5F580U, 0xD606AEU, 0xD61E44U, 0xD62F90U, 0xD6377AU, 0xD64C38U, 0xD654D2U, + 0xD66506U, 0xD67DECU, 0xD68B68U, 0xD69382U, 0xD6A256U, 0xD6BABCU, 0xD6C1FEU, 0xD6D914U, 0xD6E8C0U, 0xD6F02AU, + 0xD705C8U, 0xD71D22U, 0xD72CF6U, 0xD7341CU, 0xD74F5EU, 0xD757B4U, 0xD76660U, 0xD77E8AU, 0xD7880EU, 0xD790E4U, + 0xD7A130U, 0xD7B9DAU, 0xD7C298U, 0xD7DA72U, 0xD7EBA6U, 0xD7F34CU, 0xD80E20U, 0xD816CAU, 0xD8271EU, 0xD83FF4U, + 0xD844B6U, 0xD85C5CU, 0xD86D88U, 0xD87562U, 0xD883E6U, 0xD89B0CU, 0xD8AAD8U, 0xD8B232U, 0xD8C970U, 0xD8D19AU, + 0xD8E04EU, 0xD8F8A4U, 0xD90D46U, 0xD915ACU, 0xD92478U, 0xD93C92U, 0xD947D0U, 0xD95F3AU, 0xD96EEEU, 0xD97604U, + 0xD98080U, 0xD9986AU, 0xD9A9BEU, 0xD9B154U, 0xD9CA16U, 0xD9D2FCU, 0xD9E328U, 0xD9FBC2U, 0xDA08ECU, 0xDA1006U, + 0xDA21D2U, 0xDA3938U, 0xDA427AU, 0xDA5A90U, 0xDA6B44U, 0xDA73AEU, 0xDA852AU, 0xDA9DC0U, 0xDAAC14U, 0xDAB4FEU, + 0xDACFBCU, 0xDAD756U, 0xDAE682U, 0xDAFE68U, 0xDB0B8AU, 0xDB1360U, 0xDB22B4U, 0xDB3A5EU, 0xDB411CU, 0xDB59F6U, + 0xDB6822U, 0xDB70C8U, 0xDB864CU, 0xDB9EA6U, 0xDBAF72U, 0xDBB798U, 0xDBCCDAU, 0xDBD430U, 0xDBE5E4U, 0xDBFD0EU, + 0xDC03B8U, 0xDC1B52U, 0xDC2A86U, 0xDC326CU, 0xDC492EU, 0xDC51C4U, 0xDC6010U, 0xDC78FAU, 0xDC8E7EU, 0xDC9694U, + 0xDCA740U, 0xDCBFAAU, 0xDCC4E8U, 0xDCDC02U, 0xDCEDD6U, 0xDCF53CU, 0xDD00DEU, 0xDD1834U, 0xDD29E0U, 0xDD310AU, + 0xDD4A48U, 0xDD52A2U, 0xDD6376U, 0xDD7B9CU, 0xDD8D18U, 0xDD95F2U, 0xDDA426U, 0xDDBCCCU, 0xDDC78EU, 0xDDDF64U, + 0xDDEEB0U, 0xDDF65AU, 0xDE0574U, 0xDE1D9EU, 0xDE2C4AU, 0xDE34A0U, 0xDE4FE2U, 0xDE5708U, 0xDE66DCU, 0xDE7E36U, + 0xDE88B2U, 0xDE9058U, 0xDEA18CU, 0xDEB966U, 0xDEC224U, 0xDEDACEU, 0xDEEB1AU, 0xDEF3F0U, 0xDF0612U, 0xDF1EF8U, + 0xDF2F2CU, 0xDF37C6U, 0xDF4C84U, 0xDF546EU, 0xDF65BAU, 0xDF7D50U, 0xDF8BD4U, 0xDF933EU, 0xDFA2EAU, 0xDFBA00U, + 0xDFC142U, 0xDFD9A8U, 0xDFE87CU, 0xDFF096U, 0xE00526U, 0xE01DCCU, 0xE02C18U, 0xE034F2U, 0xE04FB0U, 0xE0575AU, + 0xE0668EU, 0xE07E64U, 0xE088E0U, 0xE0900AU, 0xE0A1DEU, 0xE0B934U, 0xE0C276U, 0xE0DA9CU, 0xE0EB48U, 0xE0F3A2U, + 0xE10640U, 0xE11EAAU, 0xE12F7EU, 0xE13794U, 0xE14CD6U, 0xE1543CU, 0xE165E8U, 0xE17D02U, 0xE18B86U, 0xE1936CU, + 0xE1A2B8U, 0xE1BA52U, 0xE1C110U, 0xE1D9FAU, 0xE1E82EU, 0xE1F0C4U, 0xE203EAU, 0xE21B00U, 0xE22AD4U, 0xE2323EU, + 0xE2497CU, 0xE25196U, 0xE26042U, 0xE278A8U, 0xE28E2CU, 0xE296C6U, 0xE2A712U, 0xE2BFF8U, 0xE2C4BAU, 0xE2DC50U, + 0xE2ED84U, 0xE2F56EU, 0xE3008CU, 0xE31866U, 0xE329B2U, 0xE33158U, 0xE34A1AU, 0xE352F0U, 0xE36324U, 0xE37BCEU, + 0xE38D4AU, 0xE395A0U, 0xE3A474U, 0xE3BC9EU, 0xE3C7DCU, 0xE3DF36U, 0xE3EEE2U, 0xE3F608U, 0xE408BEU, 0xE41054U, + 0xE42180U, 0xE4396AU, 0xE44228U, 0xE45AC2U, 0xE46B16U, 0xE473FCU, 0xE48578U, 0xE49D92U, 0xE4AC46U, 0xE4B4ACU, + 0xE4CFEEU, 0xE4D704U, 0xE4E6D0U, 0xE4FE3AU, 0xE50BD8U, 0xE51332U, 0xE522E6U, 0xE53A0CU, 0xE5414EU, 0xE559A4U, + 0xE56870U, 0xE5709AU, 0xE5861EU, 0xE59EF4U, 0xE5AF20U, 0xE5B7CAU, 0xE5CC88U, 0xE5D462U, 0xE5E5B6U, 0xE5FD5CU, + 0xE60E72U, 0xE61698U, 0xE6274CU, 0xE63FA6U, 0xE644E4U, 0xE65C0EU, 0xE66DDAU, 0xE67530U, 0xE683B4U, 0xE69B5EU, + 0xE6AA8AU, 0xE6B260U, 0xE6C922U, 0xE6D1C8U, 0xE6E01CU, 0xE6F8F6U, 0xE70D14U, 0xE715FEU, 0xE7242AU, 0xE73CC0U, + 0xE74782U, 0xE75F68U, 0xE76EBCU, 0xE77656U, 0xE780D2U, 0xE79838U, 0xE7A9ECU, 0xE7B106U, 0xE7CA44U, 0xE7D2AEU, + 0xE7E37AU, 0xE7FB90U, 0xE806FCU, 0xE81E16U, 0xE82FC2U, 0xE83728U, 0xE84C6AU, 0xE85480U, 0xE86554U, 0xE87DBEU, + 0xE88B3AU, 0xE893D0U, 0xE8A204U, 0xE8BAEEU, 0xE8C1ACU, 0xE8D946U, 0xE8E892U, 0xE8F078U, 0xE9059AU, 0xE91D70U, + 0xE92CA4U, 0xE9344EU, 0xE94F0CU, 0xE957E6U, 0xE96632U, 0xE97ED8U, 0xE9885CU, 0xE990B6U, 0xE9A162U, 0xE9B988U, + 0xE9C2CAU, 0xE9DA20U, 0xE9EBF4U, 0xE9F31EU, 0xEA0030U, 0xEA18DAU, 0xEA290EU, 0xEA31E4U, 0xEA4AA6U, 0xEA524CU, + 0xEA6398U, 0xEA7B72U, 0xEA8DF6U, 0xEA951CU, 0xEAA4C8U, 0xEABC22U, 0xEAC760U, 0xEADF8AU, 0xEAEE5EU, 0xEAF6B4U, + 0xEB0356U, 0xEB1BBCU, 0xEB2A68U, 0xEB3282U, 0xEB49C0U, 0xEB512AU, 0xEB60FEU, 0xEB7814U, 0xEB8E90U, 0xEB967AU, + 0xEBA7AEU, 0xEBBF44U, 0xEBC406U, 0xEBDCECU, 0xEBED38U, 0xEBF5D2U, 0xEC0B64U, 0xEC138EU, 0xEC225AU, 0xEC3AB0U, + 0xEC41F2U, 0xEC5918U, 0xEC68CCU, 0xEC7026U, 0xEC86A2U, 0xEC9E48U, 0xECAF9CU, 0xECB776U, 0xECCC34U, 0xECD4DEU, + 0xECE50AU, 0xECFDE0U, 0xED0802U, 0xED10E8U, 0xED213CU, 0xED39D6U, 0xED4294U, 0xED5A7EU, 0xED6BAAU, 0xED7340U, + 0xED85C4U, 0xED9D2EU, 0xEDACFAU, 0xEDB410U, 0xEDCF52U, 0xEDD7B8U, 0xEDE66CU, 0xEDFE86U, 0xEE0DA8U, 0xEE1542U, + 0xEE2496U, 0xEE3C7CU, 0xEE473EU, 0xEE5FD4U, 0xEE6E00U, 0xEE76EAU, 0xEE806EU, 0xEE9884U, 0xEEA950U, 0xEEB1BAU, + 0xEECAF8U, 0xEED212U, 0xEEE3C6U, 0xEEFB2CU, 0xEF0ECEU, 0xEF1624U, 0xEF27F0U, 0xEF3F1AU, 0xEF4458U, 0xEF5CB2U, + 0xEF6D66U, 0xEF758CU, 0xEF8308U, 0xEF9BE2U, 0xEFAA36U, 0xEFB2DCU, 0xEFC99EU, 0xEFD174U, 0xEFE0A0U, 0xEFF84AU, + 0xF00292U, 0xF01A78U, 0xF02BACU, 0xF03346U, 0xF04804U, 0xF050EEU, 0xF0613AU, 0xF079D0U, 0xF08F54U, 0xF097BEU, + 0xF0A66AU, 0xF0BE80U, 0xF0C5C2U, 0xF0DD28U, 0xF0ECFCU, 0xF0F416U, 0xF101F4U, 0xF1191EU, 0xF128CAU, 0xF13020U, + 0xF14B62U, 0xF15388U, 0xF1625CU, 0xF17AB6U, 0xF18C32U, 0xF194D8U, 0xF1A50CU, 0xF1BDE6U, 0xF1C6A4U, 0xF1DE4EU, + 0xF1EF9AU, 0xF1F770U, 0xF2045EU, 0xF21CB4U, 0xF22D60U, 0xF2358AU, 0xF24EC8U, 0xF25622U, 0xF267F6U, 0xF27F1CU, + 0xF28998U, 0xF29172U, 0xF2A0A6U, 0xF2B84CU, 0xF2C30EU, 0xF2DBE4U, 0xF2EA30U, 0xF2F2DAU, 0xF30738U, 0xF31FD2U, + 0xF32E06U, 0xF336ECU, 0xF34DAEU, 0xF35544U, 0xF36490U, 0xF37C7AU, 0xF38AFEU, 0xF39214U, 0xF3A3C0U, 0xF3BB2AU, + 0xF3C068U, 0xF3D882U, 0xF3E956U, 0xF3F1BCU, 0xF40F0AU, 0xF417E0U, 0xF42634U, 0xF43EDEU, 0xF4459CU, 0xF45D76U, + 0xF46CA2U, 0xF47448U, 0xF482CCU, 0xF49A26U, 0xF4ABF2U, 0xF4B318U, 0xF4C85AU, 0xF4D0B0U, 0xF4E164U, 0xF4F98EU, + 0xF50C6CU, 0xF51486U, 0xF52552U, 0xF53DB8U, 0xF546FAU, 0xF55E10U, 0xF56FC4U, 0xF5772EU, 0xF581AAU, 0xF59940U, + 0xF5A894U, 0xF5B07EU, 0xF5CB3CU, 0xF5D3D6U, 0xF5E202U, 0xF5FAE8U, 0xF609C6U, 0xF6112CU, 0xF620F8U, 0xF63812U, + 0xF64350U, 0xF65BBAU, 0xF66A6EU, 0xF67284U, 0xF68400U, 0xF69CEAU, 0xF6AD3EU, 0xF6B5D4U, 0xF6CE96U, 0xF6D67CU, + 0xF6E7A8U, 0xF6FF42U, 0xF70AA0U, 0xF7124AU, 0xF7239EU, 0xF73B74U, 0xF74036U, 0xF758DCU, 0xF76908U, 0xF771E2U, + 0xF78766U, 0xF79F8CU, 0xF7AE58U, 0xF7B6B2U, 0xF7CDF0U, 0xF7D51AU, 0xF7E4CEU, 0xF7FC24U, 0xF80148U, 0xF819A2U, + 0xF82876U, 0xF8309CU, 0xF84BDEU, 0xF85334U, 0xF862E0U, 0xF87A0AU, 0xF88C8EU, 0xF89464U, 0xF8A5B0U, 0xF8BD5AU, + 0xF8C618U, 0xF8DEF2U, 0xF8EF26U, 0xF8F7CCU, 0xF9022EU, 0xF91AC4U, 0xF92B10U, 0xF933FAU, 0xF948B8U, 0xF95052U, + 0xF96186U, 0xF9796CU, 0xF98FE8U, 0xF99702U, 0xF9A6D6U, 0xF9BE3CU, 0xF9C57EU, 0xF9DD94U, 0xF9EC40U, 0xF9F4AAU, + 0xFA0784U, 0xFA1F6EU, 0xFA2EBAU, 0xFA3650U, 0xFA4D12U, 0xFA55F8U, 0xFA642CU, 0xFA7CC6U, 0xFA8A42U, 0xFA92A8U, + 0xFAA37CU, 0xFABB96U, 0xFAC0D4U, 0xFAD83EU, 0xFAE9EAU, 0xFAF100U, 0xFB04E2U, 0xFB1C08U, 0xFB2DDCU, 0xFB3536U, + 0xFB4E74U, 0xFB569EU, 0xFB674AU, 0xFB7FA0U, 0xFB8924U, 0xFB91CEU, 0xFBA01AU, 0xFBB8F0U, 0xFBC3B2U, 0xFBDB58U, + 0xFBEA8CU, 0xFBF266U, 0xFC0CD0U, 0xFC143AU, 0xFC25EEU, 0xFC3D04U, 0xFC4646U, 0xFC5EACU, 0xFC6F78U, 0xFC7792U, + 0xFC8116U, 0xFC99FCU, 0xFCA828U, 0xFCB0C2U, 0xFCCB80U, 0xFCD36AU, 0xFCE2BEU, 0xFCFA54U, 0xFD0FB6U, 0xFD175CU, + 0xFD2688U, 0xFD3E62U, 0xFD4520U, 0xFD5DCAU, 0xFD6C1EU, 0xFD74F4U, 0xFD8270U, 0xFD9A9AU, 0xFDAB4EU, 0xFDB3A4U, + 0xFDC8E6U, 0xFDD00CU, 0xFDE1D8U, 0xFDF932U, 0xFE0A1CU, 0xFE12F6U, 0xFE2322U, 0xFE3BC8U, 0xFE408AU, 0xFE5860U, + 0xFE69B4U, 0xFE715EU, 0xFE87DAU, 0xFE9F30U, 0xFEAEE4U, 0xFEB60EU, 0xFECD4CU, 0xFED5A6U, 0xFEE472U, 0xFEFC98U, + 0xFF097AU, 0xFF1190U, 0xFF2044U, 0xFF38AEU, 0xFF43ECU, 0xFF5B06U, 0xFF6AD2U, 0xFF7238U, 0xFF84BCU, 0xFF9C56U, + 0xFFAD82U, 0xFFB568U, 0xFFCE2AU, 0xFFD6C0U, 0xFFE714U, 0xFFFFFEU}; + +static const unsigned int ENCODING_TABLE_24128[] = { + 0x000000U, 0x0018EBU, 0x00293EU, 0x0031D5U, 0x004A97U, 0x00527CU, 0x0063A9U, 0x007B42U, 0x008DC6U, 0x00952DU, + 0x00A4F8U, 0x00BC13U, 0x00C751U, 0x00DFBAU, 0x00EE6FU, 0x00F684U, 0x010367U, 0x011B8CU, 0x012A59U, 0x0132B2U, + 0x0149F0U, 0x01511BU, 0x0160CEU, 0x017825U, 0x018EA1U, 0x01964AU, 0x01A79FU, 0x01BF74U, 0x01C436U, 0x01DCDDU, + 0x01ED08U, 0x01F5E3U, 0x0206CDU, 0x021E26U, 0x022FF3U, 0x023718U, 0x024C5AU, 0x0254B1U, 0x026564U, 0x027D8FU, + 0x028B0BU, 0x0293E0U, 0x02A235U, 0x02BADEU, 0x02C19CU, 0x02D977U, 0x02E8A2U, 0x02F049U, 0x0305AAU, 0x031D41U, + 0x032C94U, 0x03347FU, 0x034F3DU, 0x0357D6U, 0x036603U, 0x037EE8U, 0x03886CU, 0x039087U, 0x03A152U, 0x03B9B9U, + 0x03C2FBU, 0x03DA10U, 0x03EBC5U, 0x03F32EU, 0x040D99U, 0x041572U, 0x0424A7U, 0x043C4CU, 0x04470EU, 0x045FE5U, + 0x046E30U, 0x0476DBU, 0x04805FU, 0x0498B4U, 0x04A961U, 0x04B18AU, 0x04CAC8U, 0x04D223U, 0x04E3F6U, 0x04FB1DU, + 0x050EFEU, 0x051615U, 0x0527C0U, 0x053F2BU, 0x054469U, 0x055C82U, 0x056D57U, 0x0575BCU, 0x058338U, 0x059BD3U, + 0x05AA06U, 0x05B2EDU, 0x05C9AFU, 0x05D144U, 0x05E091U, 0x05F87AU, 0x060B54U, 0x0613BFU, 0x06226AU, 0x063A81U, + 0x0641C3U, 0x065928U, 0x0668FDU, 0x067016U, 0x068692U, 0x069E79U, 0x06AFACU, 0x06B747U, 0x06CC05U, 0x06D4EEU, + 0x06E53BU, 0x06FDD0U, 0x070833U, 0x0710D8U, 0x07210DU, 0x0739E6U, 0x0742A4U, 0x075A4FU, 0x076B9AU, 0x077371U, + 0x0785F5U, 0x079D1EU, 0x07ACCBU, 0x07B420U, 0x07CF62U, 0x07D789U, 0x07E65CU, 0x07FEB7U, 0x0803DAU, 0x081B31U, + 0x082AE4U, 0x08320FU, 0x08494DU, 0x0851A6U, 0x086073U, 0x087898U, 0x088E1CU, 0x0896F7U, 0x08A722U, 0x08BFC9U, + 0x08C48BU, 0x08DC60U, 0x08EDB5U, 0x08F55EU, 0x0900BDU, 0x091856U, 0x092983U, 0x093168U, 0x094A2AU, 0x0952C1U, + 0x096314U, 0x097BFFU, 0x098D7BU, 0x099590U, 0x09A445U, 0x09BCAEU, 0x09C7ECU, 0x09DF07U, 0x09EED2U, 0x09F639U, + 0x0A0517U, 0x0A1DFCU, 0x0A2C29U, 0x0A34C2U, 0x0A4F80U, 0x0A576BU, 0x0A66BEU, 0x0A7E55U, 0x0A88D1U, 0x0A903AU, + 0x0AA1EFU, 0x0AB904U, 0x0AC246U, 0x0ADAADU, 0x0AEB78U, 0x0AF393U, 0x0B0670U, 0x0B1E9BU, 0x0B2F4EU, 0x0B37A5U, + 0x0B4CE7U, 0x0B540CU, 0x0B65D9U, 0x0B7D32U, 0x0B8BB6U, 0x0B935DU, 0x0BA288U, 0x0BBA63U, 0x0BC121U, 0x0BD9CAU, + 0x0BE81FU, 0x0BF0F4U, 0x0C0E43U, 0x0C16A8U, 0x0C277DU, 0x0C3F96U, 0x0C44D4U, 0x0C5C3FU, 0x0C6DEAU, 0x0C7501U, + 0x0C8385U, 0x0C9B6EU, 0x0CAABBU, 0x0CB250U, 0x0CC912U, 0x0CD1F9U, 0x0CE02CU, 0x0CF8C7U, 0x0D0D24U, 0x0D15CFU, + 0x0D241AU, 0x0D3CF1U, 0x0D47B3U, 0x0D5F58U, 0x0D6E8DU, 0x0D7666U, 0x0D80E2U, 0x0D9809U, 0x0DA9DCU, 0x0DB137U, + 0x0DCA75U, 0x0DD29EU, 0x0DE34BU, 0x0DFBA0U, 0x0E088EU, 0x0E1065U, 0x0E21B0U, 0x0E395BU, 0x0E4219U, 0x0E5AF2U, + 0x0E6B27U, 0x0E73CCU, 0x0E8548U, 0x0E9DA3U, 0x0EAC76U, 0x0EB49DU, 0x0ECFDFU, 0x0ED734U, 0x0EE6E1U, 0x0EFE0AU, + 0x0F0BE9U, 0x0F1302U, 0x0F22D7U, 0x0F3A3CU, 0x0F417EU, 0x0F5995U, 0x0F6840U, 0x0F70ABU, 0x0F862FU, 0x0F9EC4U, + 0x0FAF11U, 0x0FB7FAU, 0x0FCCB8U, 0x0FD453U, 0x0FE586U, 0x0FFD6DU, 0x1007B4U, 0x101F5FU, 0x102E8AU, 0x103661U, + 0x104D23U, 0x1055C8U, 0x10641DU, 0x107CF6U, 0x108A72U, 0x109299U, 0x10A34CU, 0x10BBA7U, 0x10C0E5U, 0x10D80EU, + 0x10E9DBU, 0x10F130U, 0x1104D3U, 0x111C38U, 0x112DEDU, 0x113506U, 0x114E44U, 0x1156AFU, 0x11677AU, 0x117F91U, + 0x118915U, 0x1191FEU, 0x11A02BU, 0x11B8C0U, 0x11C382U, 0x11DB69U, 0x11EABCU, 0x11F257U, 0x120179U, 0x121992U, + 0x122847U, 0x1230ACU, 0x124BEEU, 0x125305U, 0x1262D0U, 0x127A3BU, 0x128CBFU, 0x129454U, 0x12A581U, 0x12BD6AU, + 0x12C628U, 0x12DEC3U, 0x12EF16U, 0x12F7FDU, 0x13021EU, 0x131AF5U, 0x132B20U, 0x1333CBU, 0x134889U, 0x135062U, + 0x1361B7U, 0x13795CU, 0x138FD8U, 0x139733U, 0x13A6E6U, 0x13BE0DU, 0x13C54FU, 0x13DDA4U, 0x13EC71U, 0x13F49AU, + 0x140A2DU, 0x1412C6U, 0x142313U, 0x143BF8U, 0x1440BAU, 0x145851U, 0x146984U, 0x14716FU, 0x1487EBU, 0x149F00U, + 0x14AED5U, 0x14B63EU, 0x14CD7CU, 0x14D597U, 0x14E442U, 0x14FCA9U, 0x15094AU, 0x1511A1U, 0x152074U, 0x15389FU, + 0x1543DDU, 0x155B36U, 0x156AE3U, 0x157208U, 0x15848CU, 0x159C67U, 0x15ADB2U, 0x15B559U, 0x15CE1BU, 0x15D6F0U, + 0x15E725U, 0x15FFCEU, 0x160CE0U, 0x16140BU, 0x1625DEU, 0x163D35U, 0x164677U, 0x165E9CU, 0x166F49U, 0x1677A2U, + 0x168126U, 0x1699CDU, 0x16A818U, 0x16B0F3U, 0x16CBB1U, 0x16D35AU, 0x16E28FU, 0x16FA64U, 0x170F87U, 0x17176CU, + 0x1726B9U, 0x173E52U, 0x174510U, 0x175DFBU, 0x176C2EU, 0x1774C5U, 0x178241U, 0x179AAAU, 0x17AB7FU, 0x17B394U, + 0x17C8D6U, 0x17D03DU, 0x17E1E8U, 0x17F903U, 0x18046EU, 0x181C85U, 0x182D50U, 0x1835BBU, 0x184EF9U, 0x185612U, + 0x1867C7U, 0x187F2CU, 0x1889A8U, 0x189143U, 0x18A096U, 0x18B87DU, 0x18C33FU, 0x18DBD4U, 0x18EA01U, 0x18F2EAU, + 0x190709U, 0x191FE2U, 0x192E37U, 0x1936DCU, 0x194D9EU, 0x195575U, 0x1964A0U, 0x197C4BU, 0x198ACFU, 0x199224U, + 0x19A3F1U, 0x19BB1AU, 0x19C058U, 0x19D8B3U, 0x19E966U, 0x19F18DU, 0x1A02A3U, 0x1A1A48U, 0x1A2B9DU, 0x1A3376U, + 0x1A4834U, 0x1A50DFU, 0x1A610AU, 0x1A79E1U, 0x1A8F65U, 0x1A978EU, 0x1AA65BU, 0x1ABEB0U, 0x1AC5F2U, 0x1ADD19U, + 0x1AECCCU, 0x1AF427U, 0x1B01C4U, 0x1B192FU, 0x1B28FAU, 0x1B3011U, 0x1B4B53U, 0x1B53B8U, 0x1B626DU, 0x1B7A86U, + 0x1B8C02U, 0x1B94E9U, 0x1BA53CU, 0x1BBDD7U, 0x1BC695U, 0x1BDE7EU, 0x1BEFABU, 0x1BF740U, 0x1C09F7U, 0x1C111CU, + 0x1C20C9U, 0x1C3822U, 0x1C4360U, 0x1C5B8BU, 0x1C6A5EU, 0x1C72B5U, 0x1C8431U, 0x1C9CDAU, 0x1CAD0FU, 0x1CB5E4U, + 0x1CCEA6U, 0x1CD64DU, 0x1CE798U, 0x1CFF73U, 0x1D0A90U, 0x1D127BU, 0x1D23AEU, 0x1D3B45U, 0x1D4007U, 0x1D58ECU, + 0x1D6939U, 0x1D71D2U, 0x1D8756U, 0x1D9FBDU, 0x1DAE68U, 0x1DB683U, 0x1DCDC1U, 0x1DD52AU, 0x1DE4FFU, 0x1DFC14U, + 0x1E0F3AU, 0x1E17D1U, 0x1E2604U, 0x1E3EEFU, 0x1E45ADU, 0x1E5D46U, 0x1E6C93U, 0x1E7478U, 0x1E82FCU, 0x1E9A17U, + 0x1EABC2U, 0x1EB329U, 0x1EC86BU, 0x1ED080U, 0x1EE155U, 0x1EF9BEU, 0x1F0C5DU, 0x1F14B6U, 0x1F2563U, 0x1F3D88U, + 0x1F46CAU, 0x1F5E21U, 0x1F6FF4U, 0x1F771FU, 0x1F819BU, 0x1F9970U, 0x1FA8A5U, 0x1FB04EU, 0x1FCB0CU, 0x1FD3E7U, + 0x1FE232U, 0x1FFAD9U, 0x200F68U, 0x201783U, 0x202656U, 0x203EBDU, 0x2045FFU, 0x205D14U, 0x206CC1U, 0x20742AU, + 0x2082AEU, 0x209A45U, 0x20AB90U, 0x20B37BU, 0x20C839U, 0x20D0D2U, 0x20E107U, 0x20F9ECU, 0x210C0FU, 0x2114E4U, + 0x212531U, 0x213DDAU, 0x214698U, 0x215E73U, 0x216FA6U, 0x21774DU, 0x2181C9U, 0x219922U, 0x21A8F7U, 0x21B01CU, + 0x21CB5EU, 0x21D3B5U, 0x21E260U, 0x21FA8BU, 0x2209A5U, 0x22114EU, 0x22209BU, 0x223870U, 0x224332U, 0x225BD9U, + 0x226A0CU, 0x2272E7U, 0x228463U, 0x229C88U, 0x22AD5DU, 0x22B5B6U, 0x22CEF4U, 0x22D61FU, 0x22E7CAU, 0x22FF21U, + 0x230AC2U, 0x231229U, 0x2323FCU, 0x233B17U, 0x234055U, 0x2358BEU, 0x23696BU, 0x237180U, 0x238704U, 0x239FEFU, + 0x23AE3AU, 0x23B6D1U, 0x23CD93U, 0x23D578U, 0x23E4ADU, 0x23FC46U, 0x2402F1U, 0x241A1AU, 0x242BCFU, 0x243324U, + 0x244866U, 0x24508DU, 0x246158U, 0x2479B3U, 0x248F37U, 0x2497DCU, 0x24A609U, 0x24BEE2U, 0x24C5A0U, 0x24DD4BU, + 0x24EC9EU, 0x24F475U, 0x250196U, 0x25197DU, 0x2528A8U, 0x253043U, 0x254B01U, 0x2553EAU, 0x25623FU, 0x257AD4U, + 0x258C50U, 0x2594BBU, 0x25A56EU, 0x25BD85U, 0x25C6C7U, 0x25DE2CU, 0x25EFF9U, 0x25F712U, 0x26043CU, 0x261CD7U, + 0x262D02U, 0x2635E9U, 0x264EABU, 0x265640U, 0x266795U, 0x267F7EU, 0x2689FAU, 0x269111U, 0x26A0C4U, 0x26B82FU, + 0x26C36DU, 0x26DB86U, 0x26EA53U, 0x26F2B8U, 0x27075BU, 0x271FB0U, 0x272E65U, 0x27368EU, 0x274DCCU, 0x275527U, + 0x2764F2U, 0x277C19U, 0x278A9DU, 0x279276U, 0x27A3A3U, 0x27BB48U, 0x27C00AU, 0x27D8E1U, 0x27E934U, 0x27F1DFU, + 0x280CB2U, 0x281459U, 0x28258CU, 0x283D67U, 0x284625U, 0x285ECEU, 0x286F1BU, 0x2877F0U, 0x288174U, 0x28999FU, + 0x28A84AU, 0x28B0A1U, 0x28CBE3U, 0x28D308U, 0x28E2DDU, 0x28FA36U, 0x290FD5U, 0x29173EU, 0x2926EBU, 0x293E00U, + 0x294542U, 0x295DA9U, 0x296C7CU, 0x297497U, 0x298213U, 0x299AF8U, 0x29AB2DU, 0x29B3C6U, 0x29C884U, 0x29D06FU, + 0x29E1BAU, 0x29F951U, 0x2A0A7FU, 0x2A1294U, 0x2A2341U, 0x2A3BAAU, 0x2A40E8U, 0x2A5803U, 0x2A69D6U, 0x2A713DU, + 0x2A87B9U, 0x2A9F52U, 0x2AAE87U, 0x2AB66CU, 0x2ACD2EU, 0x2AD5C5U, 0x2AE410U, 0x2AFCFBU, 0x2B0918U, 0x2B11F3U, + 0x2B2026U, 0x2B38CDU, 0x2B438FU, 0x2B5B64U, 0x2B6AB1U, 0x2B725AU, 0x2B84DEU, 0x2B9C35U, 0x2BADE0U, 0x2BB50BU, + 0x2BCE49U, 0x2BD6A2U, 0x2BE777U, 0x2BFF9CU, 0x2C012BU, 0x2C19C0U, 0x2C2815U, 0x2C30FEU, 0x2C4BBCU, 0x2C5357U, + 0x2C6282U, 0x2C7A69U, 0x2C8CEDU, 0x2C9406U, 0x2CA5D3U, 0x2CBD38U, 0x2CC67AU, 0x2CDE91U, 0x2CEF44U, 0x2CF7AFU, + 0x2D024CU, 0x2D1AA7U, 0x2D2B72U, 0x2D3399U, 0x2D48DBU, 0x2D5030U, 0x2D61E5U, 0x2D790EU, 0x2D8F8AU, 0x2D9761U, + 0x2DA6B4U, 0x2DBE5FU, 0x2DC51DU, 0x2DDDF6U, 0x2DEC23U, 0x2DF4C8U, 0x2E07E6U, 0x2E1F0DU, 0x2E2ED8U, 0x2E3633U, + 0x2E4D71U, 0x2E559AU, 0x2E644FU, 0x2E7CA4U, 0x2E8A20U, 0x2E92CBU, 0x2EA31EU, 0x2EBBF5U, 0x2EC0B7U, 0x2ED85CU, + 0x2EE989U, 0x2EF162U, 0x2F0481U, 0x2F1C6AU, 0x2F2DBFU, 0x2F3554U, 0x2F4E16U, 0x2F56FDU, 0x2F6728U, 0x2F7FC3U, + 0x2F8947U, 0x2F91ACU, 0x2FA079U, 0x2FB892U, 0x2FC3D0U, 0x2FDB3BU, 0x2FEAEEU, 0x2FF205U, 0x3008DCU, 0x301037U, + 0x3021E2U, 0x303909U, 0x30424BU, 0x305AA0U, 0x306B75U, 0x30739EU, 0x30851AU, 0x309DF1U, 0x30AC24U, 0x30B4CFU, + 0x30CF8DU, 0x30D766U, 0x30E6B3U, 0x30FE58U, 0x310BBBU, 0x311350U, 0x312285U, 0x313A6EU, 0x31412CU, 0x3159C7U, + 0x316812U, 0x3170F9U, 0x31867DU, 0x319E96U, 0x31AF43U, 0x31B7A8U, 0x31CCEAU, 0x31D401U, 0x31E5D4U, 0x31FD3FU, + 0x320E11U, 0x3216FAU, 0x32272FU, 0x323FC4U, 0x324486U, 0x325C6DU, 0x326DB8U, 0x327553U, 0x3283D7U, 0x329B3CU, + 0x32AAE9U, 0x32B202U, 0x32C940U, 0x32D1ABU, 0x32E07EU, 0x32F895U, 0x330D76U, 0x33159DU, 0x332448U, 0x333CA3U, + 0x3347E1U, 0x335F0AU, 0x336EDFU, 0x337634U, 0x3380B0U, 0x33985BU, 0x33A98EU, 0x33B165U, 0x33CA27U, 0x33D2CCU, + 0x33E319U, 0x33FBF2U, 0x340545U, 0x341DAEU, 0x342C7BU, 0x343490U, 0x344FD2U, 0x345739U, 0x3466ECU, 0x347E07U, + 0x348883U, 0x349068U, 0x34A1BDU, 0x34B956U, 0x34C214U, 0x34DAFFU, 0x34EB2AU, 0x34F3C1U, 0x350622U, 0x351EC9U, + 0x352F1CU, 0x3537F7U, 0x354CB5U, 0x35545EU, 0x35658BU, 0x357D60U, 0x358BE4U, 0x35930FU, 0x35A2DAU, 0x35BA31U, + 0x35C173U, 0x35D998U, 0x35E84DU, 0x35F0A6U, 0x360388U, 0x361B63U, 0x362AB6U, 0x36325DU, 0x36491FU, 0x3651F4U, + 0x366021U, 0x3678CAU, 0x368E4EU, 0x3696A5U, 0x36A770U, 0x36BF9BU, 0x36C4D9U, 0x36DC32U, 0x36EDE7U, 0x36F50CU, + 0x3700EFU, 0x371804U, 0x3729D1U, 0x37313AU, 0x374A78U, 0x375293U, 0x376346U, 0x377BADU, 0x378D29U, 0x3795C2U, + 0x37A417U, 0x37BCFCU, 0x37C7BEU, 0x37DF55U, 0x37EE80U, 0x37F66BU, 0x380B06U, 0x3813EDU, 0x382238U, 0x383AD3U, + 0x384191U, 0x38597AU, 0x3868AFU, 0x387044U, 0x3886C0U, 0x389E2BU, 0x38AFFEU, 0x38B715U, 0x38CC57U, 0x38D4BCU, + 0x38E569U, 0x38FD82U, 0x390861U, 0x39108AU, 0x39215FU, 0x3939B4U, 0x3942F6U, 0x395A1DU, 0x396BC8U, 0x397323U, + 0x3985A7U, 0x399D4CU, 0x39AC99U, 0x39B472U, 0x39CF30U, 0x39D7DBU, 0x39E60EU, 0x39FEE5U, 0x3A0DCBU, 0x3A1520U, + 0x3A24F5U, 0x3A3C1EU, 0x3A475CU, 0x3A5FB7U, 0x3A6E62U, 0x3A7689U, 0x3A800DU, 0x3A98E6U, 0x3AA933U, 0x3AB1D8U, + 0x3ACA9AU, 0x3AD271U, 0x3AE3A4U, 0x3AFB4FU, 0x3B0EACU, 0x3B1647U, 0x3B2792U, 0x3B3F79U, 0x3B443BU, 0x3B5CD0U, + 0x3B6D05U, 0x3B75EEU, 0x3B836AU, 0x3B9B81U, 0x3BAA54U, 0x3BB2BFU, 0x3BC9FDU, 0x3BD116U, 0x3BE0C3U, 0x3BF828U, + 0x3C069FU, 0x3C1E74U, 0x3C2FA1U, 0x3C374AU, 0x3C4C08U, 0x3C54E3U, 0x3C6536U, 0x3C7DDDU, 0x3C8B59U, 0x3C93B2U, + 0x3CA267U, 0x3CBA8CU, 0x3CC1CEU, 0x3CD925U, 0x3CE8F0U, 0x3CF01BU, 0x3D05F8U, 0x3D1D13U, 0x3D2CC6U, 0x3D342DU, + 0x3D4F6FU, 0x3D5784U, 0x3D6651U, 0x3D7EBAU, 0x3D883EU, 0x3D90D5U, 0x3DA100U, 0x3DB9EBU, 0x3DC2A9U, 0x3DDA42U, + 0x3DEB97U, 0x3DF37CU, 0x3E0052U, 0x3E18B9U, 0x3E296CU, 0x3E3187U, 0x3E4AC5U, 0x3E522EU, 0x3E63FBU, 0x3E7B10U, + 0x3E8D94U, 0x3E957FU, 0x3EA4AAU, 0x3EBC41U, 0x3EC703U, 0x3EDFE8U, 0x3EEE3DU, 0x3EF6D6U, 0x3F0335U, 0x3F1BDEU, + 0x3F2A0BU, 0x3F32E0U, 0x3F49A2U, 0x3F5149U, 0x3F609CU, 0x3F7877U, 0x3F8EF3U, 0x3F9618U, 0x3FA7CDU, 0x3FBF26U, + 0x3FC464U, 0x3FDC8FU, 0x3FED5AU, 0x3FF5B1U, 0x40063BU, 0x401ED0U, 0x402F05U, 0x4037EEU, 0x404CACU, 0x405447U, + 0x406592U, 0x407D79U, 0x408BFDU, 0x409316U, 0x40A2C3U, 0x40BA28U, 0x40C16AU, 0x40D981U, 0x40E854U, 0x40F0BFU, + 0x41055CU, 0x411DB7U, 0x412C62U, 0x413489U, 0x414FCBU, 0x415720U, 0x4166F5U, 0x417E1EU, 0x41889AU, 0x419071U, + 0x41A1A4U, 0x41B94FU, 0x41C20DU, 0x41DAE6U, 0x41EB33U, 0x41F3D8U, 0x4200F6U, 0x42181DU, 0x4229C8U, 0x423123U, + 0x424A61U, 0x42528AU, 0x42635FU, 0x427BB4U, 0x428D30U, 0x4295DBU, 0x42A40EU, 0x42BCE5U, 0x42C7A7U, 0x42DF4CU, + 0x42EE99U, 0x42F672U, 0x430391U, 0x431B7AU, 0x432AAFU, 0x433244U, 0x434906U, 0x4351EDU, 0x436038U, 0x4378D3U, + 0x438E57U, 0x4396BCU, 0x43A769U, 0x43BF82U, 0x43C4C0U, 0x43DC2BU, 0x43EDFEU, 0x43F515U, 0x440BA2U, 0x441349U, + 0x44229CU, 0x443A77U, 0x444135U, 0x4459DEU, 0x44680BU, 0x4470E0U, 0x448664U, 0x449E8FU, 0x44AF5AU, 0x44B7B1U, + 0x44CCF3U, 0x44D418U, 0x44E5CDU, 0x44FD26U, 0x4508C5U, 0x45102EU, 0x4521FBU, 0x453910U, 0x454252U, 0x455AB9U, + 0x456B6CU, 0x457387U, 0x458503U, 0x459DE8U, 0x45AC3DU, 0x45B4D6U, 0x45CF94U, 0x45D77FU, 0x45E6AAU, 0x45FE41U, + 0x460D6FU, 0x461584U, 0x462451U, 0x463CBAU, 0x4647F8U, 0x465F13U, 0x466EC6U, 0x46762DU, 0x4680A9U, 0x469842U, + 0x46A997U, 0x46B17CU, 0x46CA3EU, 0x46D2D5U, 0x46E300U, 0x46FBEBU, 0x470E08U, 0x4716E3U, 0x472736U, 0x473FDDU, + 0x47449FU, 0x475C74U, 0x476DA1U, 0x47754AU, 0x4783CEU, 0x479B25U, 0x47AAF0U, 0x47B21BU, 0x47C959U, 0x47D1B2U, + 0x47E067U, 0x47F88CU, 0x4805E1U, 0x481D0AU, 0x482CDFU, 0x483434U, 0x484F76U, 0x48579DU, 0x486648U, 0x487EA3U, + 0x488827U, 0x4890CCU, 0x48A119U, 0x48B9F2U, 0x48C2B0U, 0x48DA5BU, 0x48EB8EU, 0x48F365U, 0x490686U, 0x491E6DU, + 0x492FB8U, 0x493753U, 0x494C11U, 0x4954FAU, 0x49652FU, 0x497DC4U, 0x498B40U, 0x4993ABU, 0x49A27EU, 0x49BA95U, + 0x49C1D7U, 0x49D93CU, 0x49E8E9U, 0x49F002U, 0x4A032CU, 0x4A1BC7U, 0x4A2A12U, 0x4A32F9U, 0x4A49BBU, 0x4A5150U, + 0x4A6085U, 0x4A786EU, 0x4A8EEAU, 0x4A9601U, 0x4AA7D4U, 0x4ABF3FU, 0x4AC47DU, 0x4ADC96U, 0x4AED43U, 0x4AF5A8U, + 0x4B004BU, 0x4B18A0U, 0x4B2975U, 0x4B319EU, 0x4B4ADCU, 0x4B5237U, 0x4B63E2U, 0x4B7B09U, 0x4B8D8DU, 0x4B9566U, + 0x4BA4B3U, 0x4BBC58U, 0x4BC71AU, 0x4BDFF1U, 0x4BEE24U, 0x4BF6CFU, 0x4C0878U, 0x4C1093U, 0x4C2146U, 0x4C39ADU, + 0x4C42EFU, 0x4C5A04U, 0x4C6BD1U, 0x4C733AU, 0x4C85BEU, 0x4C9D55U, 0x4CAC80U, 0x4CB46BU, 0x4CCF29U, 0x4CD7C2U, + 0x4CE617U, 0x4CFEFCU, 0x4D0B1FU, 0x4D13F4U, 0x4D2221U, 0x4D3ACAU, 0x4D4188U, 0x4D5963U, 0x4D68B6U, 0x4D705DU, + 0x4D86D9U, 0x4D9E32U, 0x4DAFE7U, 0x4DB70CU, 0x4DCC4EU, 0x4DD4A5U, 0x4DE570U, 0x4DFD9BU, 0x4E0EB5U, 0x4E165EU, + 0x4E278BU, 0x4E3F60U, 0x4E4422U, 0x4E5CC9U, 0x4E6D1CU, 0x4E75F7U, 0x4E8373U, 0x4E9B98U, 0x4EAA4DU, 0x4EB2A6U, + 0x4EC9E4U, 0x4ED10FU, 0x4EE0DAU, 0x4EF831U, 0x4F0DD2U, 0x4F1539U, 0x4F24ECU, 0x4F3C07U, 0x4F4745U, 0x4F5FAEU, + 0x4F6E7BU, 0x4F7690U, 0x4F8014U, 0x4F98FFU, 0x4FA92AU, 0x4FB1C1U, 0x4FCA83U, 0x4FD268U, 0x4FE3BDU, 0x4FFB56U, + 0x50018FU, 0x501964U, 0x5028B1U, 0x50305AU, 0x504B18U, 0x5053F3U, 0x506226U, 0x507ACDU, 0x508C49U, 0x5094A2U, + 0x50A577U, 0x50BD9CU, 0x50C6DEU, 0x50DE35U, 0x50EFE0U, 0x50F70BU, 0x5102E8U, 0x511A03U, 0x512BD6U, 0x51333DU, + 0x51487FU, 0x515094U, 0x516141U, 0x5179AAU, 0x518F2EU, 0x5197C5U, 0x51A610U, 0x51BEFBU, 0x51C5B9U, 0x51DD52U, + 0x51EC87U, 0x51F46CU, 0x520742U, 0x521FA9U, 0x522E7CU, 0x523697U, 0x524DD5U, 0x52553EU, 0x5264EBU, 0x527C00U, + 0x528A84U, 0x52926FU, 0x52A3BAU, 0x52BB51U, 0x52C013U, 0x52D8F8U, 0x52E92DU, 0x52F1C6U, 0x530425U, 0x531CCEU, + 0x532D1BU, 0x5335F0U, 0x534EB2U, 0x535659U, 0x53678CU, 0x537F67U, 0x5389E3U, 0x539108U, 0x53A0DDU, 0x53B836U, + 0x53C374U, 0x53DB9FU, 0x53EA4AU, 0x53F2A1U, 0x540C16U, 0x5414FDU, 0x542528U, 0x543DC3U, 0x544681U, 0x545E6AU, + 0x546FBFU, 0x547754U, 0x5481D0U, 0x54993BU, 0x54A8EEU, 0x54B005U, 0x54CB47U, 0x54D3ACU, 0x54E279U, 0x54FA92U, + 0x550F71U, 0x55179AU, 0x55264FU, 0x553EA4U, 0x5545E6U, 0x555D0DU, 0x556CD8U, 0x557433U, 0x5582B7U, 0x559A5CU, + 0x55AB89U, 0x55B362U, 0x55C820U, 0x55D0CBU, 0x55E11EU, 0x55F9F5U, 0x560ADBU, 0x561230U, 0x5623E5U, 0x563B0EU, + 0x56404CU, 0x5658A7U, 0x566972U, 0x567199U, 0x56871DU, 0x569FF6U, 0x56AE23U, 0x56B6C8U, 0x56CD8AU, 0x56D561U, + 0x56E4B4U, 0x56FC5FU, 0x5709BCU, 0x571157U, 0x572082U, 0x573869U, 0x57432BU, 0x575BC0U, 0x576A15U, 0x5772FEU, + 0x57847AU, 0x579C91U, 0x57AD44U, 0x57B5AFU, 0x57CEEDU, 0x57D606U, 0x57E7D3U, 0x57FF38U, 0x580255U, 0x581ABEU, + 0x582B6BU, 0x583380U, 0x5848C2U, 0x585029U, 0x5861FCU, 0x587917U, 0x588F93U, 0x589778U, 0x58A6ADU, 0x58BE46U, + 0x58C504U, 0x58DDEFU, 0x58EC3AU, 0x58F4D1U, 0x590132U, 0x5919D9U, 0x59280CU, 0x5930E7U, 0x594BA5U, 0x59534EU, + 0x59629BU, 0x597A70U, 0x598CF4U, 0x59941FU, 0x59A5CAU, 0x59BD21U, 0x59C663U, 0x59DE88U, 0x59EF5DU, 0x59F7B6U, + 0x5A0498U, 0x5A1C73U, 0x5A2DA6U, 0x5A354DU, 0x5A4E0FU, 0x5A56E4U, 0x5A6731U, 0x5A7FDAU, 0x5A895EU, 0x5A91B5U, + 0x5AA060U, 0x5AB88BU, 0x5AC3C9U, 0x5ADB22U, 0x5AEAF7U, 0x5AF21CU, 0x5B07FFU, 0x5B1F14U, 0x5B2EC1U, 0x5B362AU, + 0x5B4D68U, 0x5B5583U, 0x5B6456U, 0x5B7CBDU, 0x5B8A39U, 0x5B92D2U, 0x5BA307U, 0x5BBBECU, 0x5BC0AEU, 0x5BD845U, + 0x5BE990U, 0x5BF17BU, 0x5C0FCCU, 0x5C1727U, 0x5C26F2U, 0x5C3E19U, 0x5C455BU, 0x5C5DB0U, 0x5C6C65U, 0x5C748EU, + 0x5C820AU, 0x5C9AE1U, 0x5CAB34U, 0x5CB3DFU, 0x5CC89DU, 0x5CD076U, 0x5CE1A3U, 0x5CF948U, 0x5D0CABU, 0x5D1440U, + 0x5D2595U, 0x5D3D7EU, 0x5D463CU, 0x5D5ED7U, 0x5D6F02U, 0x5D77E9U, 0x5D816DU, 0x5D9986U, 0x5DA853U, 0x5DB0B8U, + 0x5DCBFAU, 0x5DD311U, 0x5DE2C4U, 0x5DFA2FU, 0x5E0901U, 0x5E11EAU, 0x5E203FU, 0x5E38D4U, 0x5E4396U, 0x5E5B7DU, + 0x5E6AA8U, 0x5E7243U, 0x5E84C7U, 0x5E9C2CU, 0x5EADF9U, 0x5EB512U, 0x5ECE50U, 0x5ED6BBU, 0x5EE76EU, 0x5EFF85U, + 0x5F0A66U, 0x5F128DU, 0x5F2358U, 0x5F3BB3U, 0x5F40F1U, 0x5F581AU, 0x5F69CFU, 0x5F7124U, 0x5F87A0U, 0x5F9F4BU, + 0x5FAE9EU, 0x5FB675U, 0x5FCD37U, 0x5FD5DCU, 0x5FE409U, 0x5FFCE2U, 0x600953U, 0x6011B8U, 0x60206DU, 0x603886U, + 0x6043C4U, 0x605B2FU, 0x606AFAU, 0x607211U, 0x608495U, 0x609C7EU, 0x60ADABU, 0x60B540U, 0x60CE02U, 0x60D6E9U, + 0x60E73CU, 0x60FFD7U, 0x610A34U, 0x6112DFU, 0x61230AU, 0x613BE1U, 0x6140A3U, 0x615848U, 0x61699DU, 0x617176U, + 0x6187F2U, 0x619F19U, 0x61AECCU, 0x61B627U, 0x61CD65U, 0x61D58EU, 0x61E45BU, 0x61FCB0U, 0x620F9EU, 0x621775U, + 0x6226A0U, 0x623E4BU, 0x624509U, 0x625DE2U, 0x626C37U, 0x6274DCU, 0x628258U, 0x629AB3U, 0x62AB66U, 0x62B38DU, + 0x62C8CFU, 0x62D024U, 0x62E1F1U, 0x62F91AU, 0x630CF9U, 0x631412U, 0x6325C7U, 0x633D2CU, 0x63466EU, 0x635E85U, + 0x636F50U, 0x6377BBU, 0x63813FU, 0x6399D4U, 0x63A801U, 0x63B0EAU, 0x63CBA8U, 0x63D343U, 0x63E296U, 0x63FA7DU, + 0x6404CAU, 0x641C21U, 0x642DF4U, 0x64351FU, 0x644E5DU, 0x6456B6U, 0x646763U, 0x647F88U, 0x64890CU, 0x6491E7U, + 0x64A032U, 0x64B8D9U, 0x64C39BU, 0x64DB70U, 0x64EAA5U, 0x64F24EU, 0x6507ADU, 0x651F46U, 0x652E93U, 0x653678U, + 0x654D3AU, 0x6555D1U, 0x656404U, 0x657CEFU, 0x658A6BU, 0x659280U, 0x65A355U, 0x65BBBEU, 0x65C0FCU, 0x65D817U, + 0x65E9C2U, 0x65F129U, 0x660207U, 0x661AECU, 0x662B39U, 0x6633D2U, 0x664890U, 0x66507BU, 0x6661AEU, 0x667945U, + 0x668FC1U, 0x66972AU, 0x66A6FFU, 0x66BE14U, 0x66C556U, 0x66DDBDU, 0x66EC68U, 0x66F483U, 0x670160U, 0x67198BU, + 0x67285EU, 0x6730B5U, 0x674BF7U, 0x67531CU, 0x6762C9U, 0x677A22U, 0x678CA6U, 0x67944DU, 0x67A598U, 0x67BD73U, + 0x67C631U, 0x67DEDAU, 0x67EF0FU, 0x67F7E4U, 0x680A89U, 0x681262U, 0x6823B7U, 0x683B5CU, 0x68401EU, 0x6858F5U, + 0x686920U, 0x6871CBU, 0x68874FU, 0x689FA4U, 0x68AE71U, 0x68B69AU, 0x68CDD8U, 0x68D533U, 0x68E4E6U, 0x68FC0DU, + 0x6909EEU, 0x691105U, 0x6920D0U, 0x69383BU, 0x694379U, 0x695B92U, 0x696A47U, 0x6972ACU, 0x698428U, 0x699CC3U, + 0x69AD16U, 0x69B5FDU, 0x69CEBFU, 0x69D654U, 0x69E781U, 0x69FF6AU, 0x6A0C44U, 0x6A14AFU, 0x6A257AU, 0x6A3D91U, + 0x6A46D3U, 0x6A5E38U, 0x6A6FEDU, 0x6A7706U, 0x6A8182U, 0x6A9969U, 0x6AA8BCU, 0x6AB057U, 0x6ACB15U, 0x6AD3FEU, + 0x6AE22BU, 0x6AFAC0U, 0x6B0F23U, 0x6B17C8U, 0x6B261DU, 0x6B3EF6U, 0x6B45B4U, 0x6B5D5FU, 0x6B6C8AU, 0x6B7461U, + 0x6B82E5U, 0x6B9A0EU, 0x6BABDBU, 0x6BB330U, 0x6BC872U, 0x6BD099U, 0x6BE14CU, 0x6BF9A7U, 0x6C0710U, 0x6C1FFBU, + 0x6C2E2EU, 0x6C36C5U, 0x6C4D87U, 0x6C556CU, 0x6C64B9U, 0x6C7C52U, 0x6C8AD6U, 0x6C923DU, 0x6CA3E8U, 0x6CBB03U, + 0x6CC041U, 0x6CD8AAU, 0x6CE97FU, 0x6CF194U, 0x6D0477U, 0x6D1C9CU, 0x6D2D49U, 0x6D35A2U, 0x6D4EE0U, 0x6D560BU, + 0x6D67DEU, 0x6D7F35U, 0x6D89B1U, 0x6D915AU, 0x6DA08FU, 0x6DB864U, 0x6DC326U, 0x6DDBCDU, 0x6DEA18U, 0x6DF2F3U, + 0x6E01DDU, 0x6E1936U, 0x6E28E3U, 0x6E3008U, 0x6E4B4AU, 0x6E53A1U, 0x6E6274U, 0x6E7A9FU, 0x6E8C1BU, 0x6E94F0U, + 0x6EA525U, 0x6EBDCEU, 0x6EC68CU, 0x6EDE67U, 0x6EEFB2U, 0x6EF759U, 0x6F02BAU, 0x6F1A51U, 0x6F2B84U, 0x6F336FU, + 0x6F482DU, 0x6F50C6U, 0x6F6113U, 0x6F79F8U, 0x6F8F7CU, 0x6F9797U, 0x6FA642U, 0x6FBEA9U, 0x6FC5EBU, 0x6FDD00U, + 0x6FECD5U, 0x6FF43EU, 0x700EE7U, 0x70160CU, 0x7027D9U, 0x703F32U, 0x704470U, 0x705C9BU, 0x706D4EU, 0x7075A5U, + 0x708321U, 0x709BCAU, 0x70AA1FU, 0x70B2F4U, 0x70C9B6U, 0x70D15DU, 0x70E088U, 0x70F863U, 0x710D80U, 0x71156BU, + 0x7124BEU, 0x713C55U, 0x714717U, 0x715FFCU, 0x716E29U, 0x7176C2U, 0x718046U, 0x7198ADU, 0x71A978U, 0x71B193U, + 0x71CAD1U, 0x71D23AU, 0x71E3EFU, 0x71FB04U, 0x72082AU, 0x7210C1U, 0x722114U, 0x7239FFU, 0x7242BDU, 0x725A56U, + 0x726B83U, 0x727368U, 0x7285ECU, 0x729D07U, 0x72ACD2U, 0x72B439U, 0x72CF7BU, 0x72D790U, 0x72E645U, 0x72FEAEU, + 0x730B4DU, 0x7313A6U, 0x732273U, 0x733A98U, 0x7341DAU, 0x735931U, 0x7368E4U, 0x73700FU, 0x73868BU, 0x739E60U, + 0x73AFB5U, 0x73B75EU, 0x73CC1CU, 0x73D4F7U, 0x73E522U, 0x73FDC9U, 0x74037EU, 0x741B95U, 0x742A40U, 0x7432ABU, + 0x7449E9U, 0x745102U, 0x7460D7U, 0x74783CU, 0x748EB8U, 0x749653U, 0x74A786U, 0x74BF6DU, 0x74C42FU, 0x74DCC4U, + 0x74ED11U, 0x74F5FAU, 0x750019U, 0x7518F2U, 0x752927U, 0x7531CCU, 0x754A8EU, 0x755265U, 0x7563B0U, 0x757B5BU, + 0x758DDFU, 0x759534U, 0x75A4E1U, 0x75BC0AU, 0x75C748U, 0x75DFA3U, 0x75EE76U, 0x75F69DU, 0x7605B3U, 0x761D58U, + 0x762C8DU, 0x763466U, 0x764F24U, 0x7657CFU, 0x76661AU, 0x767EF1U, 0x768875U, 0x76909EU, 0x76A14BU, 0x76B9A0U, + 0x76C2E2U, 0x76DA09U, 0x76EBDCU, 0x76F337U, 0x7706D4U, 0x771E3FU, 0x772FEAU, 0x773701U, 0x774C43U, 0x7754A8U, + 0x77657DU, 0x777D96U, 0x778B12U, 0x7793F9U, 0x77A22CU, 0x77BAC7U, 0x77C185U, 0x77D96EU, 0x77E8BBU, 0x77F050U, + 0x780D3DU, 0x7815D6U, 0x782403U, 0x783CE8U, 0x7847AAU, 0x785F41U, 0x786E94U, 0x78767FU, 0x7880FBU, 0x789810U, + 0x78A9C5U, 0x78B12EU, 0x78CA6CU, 0x78D287U, 0x78E352U, 0x78FBB9U, 0x790E5AU, 0x7916B1U, 0x792764U, 0x793F8FU, + 0x7944CDU, 0x795C26U, 0x796DF3U, 0x797518U, 0x79839CU, 0x799B77U, 0x79AAA2U, 0x79B249U, 0x79C90BU, 0x79D1E0U, + 0x79E035U, 0x79F8DEU, 0x7A0BF0U, 0x7A131BU, 0x7A22CEU, 0x7A3A25U, 0x7A4167U, 0x7A598CU, 0x7A6859U, 0x7A70B2U, + 0x7A8636U, 0x7A9EDDU, 0x7AAF08U, 0x7AB7E3U, 0x7ACCA1U, 0x7AD44AU, 0x7AE59FU, 0x7AFD74U, 0x7B0897U, 0x7B107CU, + 0x7B21A9U, 0x7B3942U, 0x7B4200U, 0x7B5AEBU, 0x7B6B3EU, 0x7B73D5U, 0x7B8551U, 0x7B9DBAU, 0x7BAC6FU, 0x7BB484U, + 0x7BCFC6U, 0x7BD72DU, 0x7BE6F8U, 0x7BFE13U, 0x7C00A4U, 0x7C184FU, 0x7C299AU, 0x7C3171U, 0x7C4A33U, 0x7C52D8U, + 0x7C630DU, 0x7C7BE6U, 0x7C8D62U, 0x7C9589U, 0x7CA45CU, 0x7CBCB7U, 0x7CC7F5U, 0x7CDF1EU, 0x7CEECBU, 0x7CF620U, + 0x7D03C3U, 0x7D1B28U, 0x7D2AFDU, 0x7D3216U, 0x7D4954U, 0x7D51BFU, 0x7D606AU, 0x7D7881U, 0x7D8E05U, 0x7D96EEU, + 0x7DA73BU, 0x7DBFD0U, 0x7DC492U, 0x7DDC79U, 0x7DEDACU, 0x7DF547U, 0x7E0669U, 0x7E1E82U, 0x7E2F57U, 0x7E37BCU, + 0x7E4CFEU, 0x7E5415U, 0x7E65C0U, 0x7E7D2BU, 0x7E8BAFU, 0x7E9344U, 0x7EA291U, 0x7EBA7AU, 0x7EC138U, 0x7ED9D3U, + 0x7EE806U, 0x7EF0EDU, 0x7F050EU, 0x7F1DE5U, 0x7F2C30U, 0x7F34DBU, 0x7F4F99U, 0x7F5772U, 0x7F66A7U, 0x7F7E4CU, + 0x7F88C8U, 0x7F9023U, 0x7FA1F6U, 0x7FB91DU, 0x7FC25FU, 0x7FDAB4U, 0x7FEB61U, 0x7FF38AU, 0x800C75U, 0x80149EU, + 0x80254BU, 0x803DA0U, 0x8046E2U, 0x805E09U, 0x806FDCU, 0x807737U, 0x8081B3U, 0x809958U, 0x80A88DU, 0x80B066U, + 0x80CB24U, 0x80D3CFU, 0x80E21AU, 0x80FAF1U, 0x810F12U, 0x8117F9U, 0x81262CU, 0x813EC7U, 0x814585U, 0x815D6EU, + 0x816CBBU, 0x817450U, 0x8182D4U, 0x819A3FU, 0x81ABEAU, 0x81B301U, 0x81C843U, 0x81D0A8U, 0x81E17DU, 0x81F996U, + 0x820AB8U, 0x821253U, 0x822386U, 0x823B6DU, 0x82402FU, 0x8258C4U, 0x826911U, 0x8271FAU, 0x82877EU, 0x829F95U, + 0x82AE40U, 0x82B6ABU, 0x82CDE9U, 0x82D502U, 0x82E4D7U, 0x82FC3CU, 0x8309DFU, 0x831134U, 0x8320E1U, 0x83380AU, + 0x834348U, 0x835BA3U, 0x836A76U, 0x83729DU, 0x838419U, 0x839CF2U, 0x83AD27U, 0x83B5CCU, 0x83CE8EU, 0x83D665U, + 0x83E7B0U, 0x83FF5BU, 0x8401ECU, 0x841907U, 0x8428D2U, 0x843039U, 0x844B7BU, 0x845390U, 0x846245U, 0x847AAEU, + 0x848C2AU, 0x8494C1U, 0x84A514U, 0x84BDFFU, 0x84C6BDU, 0x84DE56U, 0x84EF83U, 0x84F768U, 0x85028BU, 0x851A60U, + 0x852BB5U, 0x85335EU, 0x85481CU, 0x8550F7U, 0x856122U, 0x8579C9U, 0x858F4DU, 0x8597A6U, 0x85A673U, 0x85BE98U, + 0x85C5DAU, 0x85DD31U, 0x85ECE4U, 0x85F40FU, 0x860721U, 0x861FCAU, 0x862E1FU, 0x8636F4U, 0x864DB6U, 0x86555DU, + 0x866488U, 0x867C63U, 0x868AE7U, 0x86920CU, 0x86A3D9U, 0x86BB32U, 0x86C070U, 0x86D89BU, 0x86E94EU, 0x86F1A5U, + 0x870446U, 0x871CADU, 0x872D78U, 0x873593U, 0x874ED1U, 0x87563AU, 0x8767EFU, 0x877F04U, 0x878980U, 0x87916BU, + 0x87A0BEU, 0x87B855U, 0x87C317U, 0x87DBFCU, 0x87EA29U, 0x87F2C2U, 0x880FAFU, 0x881744U, 0x882691U, 0x883E7AU, + 0x884538U, 0x885DD3U, 0x886C06U, 0x8874EDU, 0x888269U, 0x889A82U, 0x88AB57U, 0x88B3BCU, 0x88C8FEU, 0x88D015U, + 0x88E1C0U, 0x88F92BU, 0x890CC8U, 0x891423U, 0x8925F6U, 0x893D1DU, 0x89465FU, 0x895EB4U, 0x896F61U, 0x89778AU, + 0x89810EU, 0x8999E5U, 0x89A830U, 0x89B0DBU, 0x89CB99U, 0x89D372U, 0x89E2A7U, 0x89FA4CU, 0x8A0962U, 0x8A1189U, + 0x8A205CU, 0x8A38B7U, 0x8A43F5U, 0x8A5B1EU, 0x8A6ACBU, 0x8A7220U, 0x8A84A4U, 0x8A9C4FU, 0x8AAD9AU, 0x8AB571U, + 0x8ACE33U, 0x8AD6D8U, 0x8AE70DU, 0x8AFFE6U, 0x8B0A05U, 0x8B12EEU, 0x8B233BU, 0x8B3BD0U, 0x8B4092U, 0x8B5879U, + 0x8B69ACU, 0x8B7147U, 0x8B87C3U, 0x8B9F28U, 0x8BAEFDU, 0x8BB616U, 0x8BCD54U, 0x8BD5BFU, 0x8BE46AU, 0x8BFC81U, + 0x8C0236U, 0x8C1ADDU, 0x8C2B08U, 0x8C33E3U, 0x8C48A1U, 0x8C504AU, 0x8C619FU, 0x8C7974U, 0x8C8FF0U, 0x8C971BU, + 0x8CA6CEU, 0x8CBE25U, 0x8CC567U, 0x8CDD8CU, 0x8CEC59U, 0x8CF4B2U, 0x8D0151U, 0x8D19BAU, 0x8D286FU, 0x8D3084U, + 0x8D4BC6U, 0x8D532DU, 0x8D62F8U, 0x8D7A13U, 0x8D8C97U, 0x8D947CU, 0x8DA5A9U, 0x8DBD42U, 0x8DC600U, 0x8DDEEBU, + 0x8DEF3EU, 0x8DF7D5U, 0x8E04FBU, 0x8E1C10U, 0x8E2DC5U, 0x8E352EU, 0x8E4E6CU, 0x8E5687U, 0x8E6752U, 0x8E7FB9U, + 0x8E893DU, 0x8E91D6U, 0x8EA003U, 0x8EB8E8U, 0x8EC3AAU, 0x8EDB41U, 0x8EEA94U, 0x8EF27FU, 0x8F079CU, 0x8F1F77U, + 0x8F2EA2U, 0x8F3649U, 0x8F4D0BU, 0x8F55E0U, 0x8F6435U, 0x8F7CDEU, 0x8F8A5AU, 0x8F92B1U, 0x8FA364U, 0x8FBB8FU, + 0x8FC0CDU, 0x8FD826U, 0x8FE9F3U, 0x8FF118U, 0x900BC1U, 0x90132AU, 0x9022FFU, 0x903A14U, 0x904156U, 0x9059BDU, + 0x906868U, 0x907083U, 0x908607U, 0x909EECU, 0x90AF39U, 0x90B7D2U, 0x90CC90U, 0x90D47BU, 0x90E5AEU, 0x90FD45U, + 0x9108A6U, 0x91104DU, 0x912198U, 0x913973U, 0x914231U, 0x915ADAU, 0x916B0FU, 0x9173E4U, 0x918560U, 0x919D8BU, + 0x91AC5EU, 0x91B4B5U, 0x91CFF7U, 0x91D71CU, 0x91E6C9U, 0x91FE22U, 0x920D0CU, 0x9215E7U, 0x922432U, 0x923CD9U, + 0x92479BU, 0x925F70U, 0x926EA5U, 0x92764EU, 0x9280CAU, 0x929821U, 0x92A9F4U, 0x92B11FU, 0x92CA5DU, 0x92D2B6U, + 0x92E363U, 0x92FB88U, 0x930E6BU, 0x931680U, 0x932755U, 0x933FBEU, 0x9344FCU, 0x935C17U, 0x936DC2U, 0x937529U, + 0x9383ADU, 0x939B46U, 0x93AA93U, 0x93B278U, 0x93C93AU, 0x93D1D1U, 0x93E004U, 0x93F8EFU, 0x940658U, 0x941EB3U, + 0x942F66U, 0x94378DU, 0x944CCFU, 0x945424U, 0x9465F1U, 0x947D1AU, 0x948B9EU, 0x949375U, 0x94A2A0U, 0x94BA4BU, + 0x94C109U, 0x94D9E2U, 0x94E837U, 0x94F0DCU, 0x95053FU, 0x951DD4U, 0x952C01U, 0x9534EAU, 0x954FA8U, 0x955743U, + 0x956696U, 0x957E7DU, 0x9588F9U, 0x959012U, 0x95A1C7U, 0x95B92CU, 0x95C26EU, 0x95DA85U, 0x95EB50U, 0x95F3BBU, + 0x960095U, 0x96187EU, 0x9629ABU, 0x963140U, 0x964A02U, 0x9652E9U, 0x96633CU, 0x967BD7U, 0x968D53U, 0x9695B8U, + 0x96A46DU, 0x96BC86U, 0x96C7C4U, 0x96DF2FU, 0x96EEFAU, 0x96F611U, 0x9703F2U, 0x971B19U, 0x972ACCU, 0x973227U, + 0x974965U, 0x97518EU, 0x97605BU, 0x9778B0U, 0x978E34U, 0x9796DFU, 0x97A70AU, 0x97BFE1U, 0x97C4A3U, 0x97DC48U, + 0x97ED9DU, 0x97F576U, 0x98081BU, 0x9810F0U, 0x982125U, 0x9839CEU, 0x98428CU, 0x985A67U, 0x986BB2U, 0x987359U, + 0x9885DDU, 0x989D36U, 0x98ACE3U, 0x98B408U, 0x98CF4AU, 0x98D7A1U, 0x98E674U, 0x98FE9FU, 0x990B7CU, 0x991397U, + 0x992242U, 0x993AA9U, 0x9941EBU, 0x995900U, 0x9968D5U, 0x99703EU, 0x9986BAU, 0x999E51U, 0x99AF84U, 0x99B76FU, + 0x99CC2DU, 0x99D4C6U, 0x99E513U, 0x99FDF8U, 0x9A0ED6U, 0x9A163DU, 0x9A27E8U, 0x9A3F03U, 0x9A4441U, 0x9A5CAAU, + 0x9A6D7FU, 0x9A7594U, 0x9A8310U, 0x9A9BFBU, 0x9AAA2EU, 0x9AB2C5U, 0x9AC987U, 0x9AD16CU, 0x9AE0B9U, 0x9AF852U, + 0x9B0DB1U, 0x9B155AU, 0x9B248FU, 0x9B3C64U, 0x9B4726U, 0x9B5FCDU, 0x9B6E18U, 0x9B76F3U, 0x9B8077U, 0x9B989CU, + 0x9BA949U, 0x9BB1A2U, 0x9BCAE0U, 0x9BD20BU, 0x9BE3DEU, 0x9BFB35U, 0x9C0582U, 0x9C1D69U, 0x9C2CBCU, 0x9C3457U, + 0x9C4F15U, 0x9C57FEU, 0x9C662BU, 0x9C7EC0U, 0x9C8844U, 0x9C90AFU, 0x9CA17AU, 0x9CB991U, 0x9CC2D3U, 0x9CDA38U, + 0x9CEBEDU, 0x9CF306U, 0x9D06E5U, 0x9D1E0EU, 0x9D2FDBU, 0x9D3730U, 0x9D4C72U, 0x9D5499U, 0x9D654CU, 0x9D7DA7U, + 0x9D8B23U, 0x9D93C8U, 0x9DA21DU, 0x9DBAF6U, 0x9DC1B4U, 0x9DD95FU, 0x9DE88AU, 0x9DF061U, 0x9E034FU, 0x9E1BA4U, + 0x9E2A71U, 0x9E329AU, 0x9E49D8U, 0x9E5133U, 0x9E60E6U, 0x9E780DU, 0x9E8E89U, 0x9E9662U, 0x9EA7B7U, 0x9EBF5CU, + 0x9EC41EU, 0x9EDCF5U, 0x9EED20U, 0x9EF5CBU, 0x9F0028U, 0x9F18C3U, 0x9F2916U, 0x9F31FDU, 0x9F4ABFU, 0x9F5254U, + 0x9F6381U, 0x9F7B6AU, 0x9F8DEEU, 0x9F9505U, 0x9FA4D0U, 0x9FBC3BU, 0x9FC779U, 0x9FDF92U, 0x9FEE47U, 0x9FF6ACU, + 0xA0031DU, 0xA01BF6U, 0xA02A23U, 0xA032C8U, 0xA0498AU, 0xA05161U, 0xA060B4U, 0xA0785FU, 0xA08EDBU, 0xA09630U, + 0xA0A7E5U, 0xA0BF0EU, 0xA0C44CU, 0xA0DCA7U, 0xA0ED72U, 0xA0F599U, 0xA1007AU, 0xA11891U, 0xA12944U, 0xA131AFU, + 0xA14AEDU, 0xA15206U, 0xA163D3U, 0xA17B38U, 0xA18DBCU, 0xA19557U, 0xA1A482U, 0xA1BC69U, 0xA1C72BU, 0xA1DFC0U, + 0xA1EE15U, 0xA1F6FEU, 0xA205D0U, 0xA21D3BU, 0xA22CEEU, 0xA23405U, 0xA24F47U, 0xA257ACU, 0xA26679U, 0xA27E92U, + 0xA28816U, 0xA290FDU, 0xA2A128U, 0xA2B9C3U, 0xA2C281U, 0xA2DA6AU, 0xA2EBBFU, 0xA2F354U, 0xA306B7U, 0xA31E5CU, + 0xA32F89U, 0xA33762U, 0xA34C20U, 0xA354CBU, 0xA3651EU, 0xA37DF5U, 0xA38B71U, 0xA3939AU, 0xA3A24FU, 0xA3BAA4U, + 0xA3C1E6U, 0xA3D90DU, 0xA3E8D8U, 0xA3F033U, 0xA40E84U, 0xA4166FU, 0xA427BAU, 0xA43F51U, 0xA44413U, 0xA45CF8U, + 0xA46D2DU, 0xA475C6U, 0xA48342U, 0xA49BA9U, 0xA4AA7CU, 0xA4B297U, 0xA4C9D5U, 0xA4D13EU, 0xA4E0EBU, 0xA4F800U, + 0xA50DE3U, 0xA51508U, 0xA524DDU, 0xA53C36U, 0xA54774U, 0xA55F9FU, 0xA56E4AU, 0xA576A1U, 0xA58025U, 0xA598CEU, + 0xA5A91BU, 0xA5B1F0U, 0xA5CAB2U, 0xA5D259U, 0xA5E38CU, 0xA5FB67U, 0xA60849U, 0xA610A2U, 0xA62177U, 0xA6399CU, + 0xA642DEU, 0xA65A35U, 0xA66BE0U, 0xA6730BU, 0xA6858FU, 0xA69D64U, 0xA6ACB1U, 0xA6B45AU, 0xA6CF18U, 0xA6D7F3U, + 0xA6E626U, 0xA6FECDU, 0xA70B2EU, 0xA713C5U, 0xA72210U, 0xA73AFBU, 0xA741B9U, 0xA75952U, 0xA76887U, 0xA7706CU, + 0xA786E8U, 0xA79E03U, 0xA7AFD6U, 0xA7B73DU, 0xA7CC7FU, 0xA7D494U, 0xA7E541U, 0xA7FDAAU, 0xA800C7U, 0xA8182CU, + 0xA829F9U, 0xA83112U, 0xA84A50U, 0xA852BBU, 0xA8636EU, 0xA87B85U, 0xA88D01U, 0xA895EAU, 0xA8A43FU, 0xA8BCD4U, + 0xA8C796U, 0xA8DF7DU, 0xA8EEA8U, 0xA8F643U, 0xA903A0U, 0xA91B4BU, 0xA92A9EU, 0xA93275U, 0xA94937U, 0xA951DCU, + 0xA96009U, 0xA978E2U, 0xA98E66U, 0xA9968DU, 0xA9A758U, 0xA9BFB3U, 0xA9C4F1U, 0xA9DC1AU, 0xA9EDCFU, 0xA9F524U, + 0xAA060AU, 0xAA1EE1U, 0xAA2F34U, 0xAA37DFU, 0xAA4C9DU, 0xAA5476U, 0xAA65A3U, 0xAA7D48U, 0xAA8BCCU, 0xAA9327U, + 0xAAA2F2U, 0xAABA19U, 0xAAC15BU, 0xAAD9B0U, 0xAAE865U, 0xAAF08EU, 0xAB056DU, 0xAB1D86U, 0xAB2C53U, 0xAB34B8U, + 0xAB4FFAU, 0xAB5711U, 0xAB66C4U, 0xAB7E2FU, 0xAB88ABU, 0xAB9040U, 0xABA195U, 0xABB97EU, 0xABC23CU, 0xABDAD7U, + 0xABEB02U, 0xABF3E9U, 0xAC0D5EU, 0xAC15B5U, 0xAC2460U, 0xAC3C8BU, 0xAC47C9U, 0xAC5F22U, 0xAC6EF7U, 0xAC761CU, + 0xAC8098U, 0xAC9873U, 0xACA9A6U, 0xACB14DU, 0xACCA0FU, 0xACD2E4U, 0xACE331U, 0xACFBDAU, 0xAD0E39U, 0xAD16D2U, + 0xAD2707U, 0xAD3FECU, 0xAD44AEU, 0xAD5C45U, 0xAD6D90U, 0xAD757BU, 0xAD83FFU, 0xAD9B14U, 0xADAAC1U, 0xADB22AU, + 0xADC968U, 0xADD183U, 0xADE056U, 0xADF8BDU, 0xAE0B93U, 0xAE1378U, 0xAE22ADU, 0xAE3A46U, 0xAE4104U, 0xAE59EFU, + 0xAE683AU, 0xAE70D1U, 0xAE8655U, 0xAE9EBEU, 0xAEAF6BU, 0xAEB780U, 0xAECCC2U, 0xAED429U, 0xAEE5FCU, 0xAEFD17U, + 0xAF08F4U, 0xAF101FU, 0xAF21CAU, 0xAF3921U, 0xAF4263U, 0xAF5A88U, 0xAF6B5DU, 0xAF73B6U, 0xAF8532U, 0xAF9DD9U, + 0xAFAC0CU, 0xAFB4E7U, 0xAFCFA5U, 0xAFD74EU, 0xAFE69BU, 0xAFFE70U, 0xB004A9U, 0xB01C42U, 0xB02D97U, 0xB0357CU, + 0xB04E3EU, 0xB056D5U, 0xB06700U, 0xB07FEBU, 0xB0896FU, 0xB09184U, 0xB0A051U, 0xB0B8BAU, 0xB0C3F8U, 0xB0DB13U, + 0xB0EAC6U, 0xB0F22DU, 0xB107CEU, 0xB11F25U, 0xB12EF0U, 0xB1361BU, 0xB14D59U, 0xB155B2U, 0xB16467U, 0xB17C8CU, + 0xB18A08U, 0xB192E3U, 0xB1A336U, 0xB1BBDDU, 0xB1C09FU, 0xB1D874U, 0xB1E9A1U, 0xB1F14AU, 0xB20264U, 0xB21A8FU, + 0xB22B5AU, 0xB233B1U, 0xB248F3U, 0xB25018U, 0xB261CDU, 0xB27926U, 0xB28FA2U, 0xB29749U, 0xB2A69CU, 0xB2BE77U, + 0xB2C535U, 0xB2DDDEU, 0xB2EC0BU, 0xB2F4E0U, 0xB30103U, 0xB319E8U, 0xB3283DU, 0xB330D6U, 0xB34B94U, 0xB3537FU, + 0xB362AAU, 0xB37A41U, 0xB38CC5U, 0xB3942EU, 0xB3A5FBU, 0xB3BD10U, 0xB3C652U, 0xB3DEB9U, 0xB3EF6CU, 0xB3F787U, + 0xB40930U, 0xB411DBU, 0xB4200EU, 0xB438E5U, 0xB443A7U, 0xB45B4CU, 0xB46A99U, 0xB47272U, 0xB484F6U, 0xB49C1DU, + 0xB4ADC8U, 0xB4B523U, 0xB4CE61U, 0xB4D68AU, 0xB4E75FU, 0xB4FFB4U, 0xB50A57U, 0xB512BCU, 0xB52369U, 0xB53B82U, + 0xB540C0U, 0xB5582BU, 0xB569FEU, 0xB57115U, 0xB58791U, 0xB59F7AU, 0xB5AEAFU, 0xB5B644U, 0xB5CD06U, 0xB5D5EDU, + 0xB5E438U, 0xB5FCD3U, 0xB60FFDU, 0xB61716U, 0xB626C3U, 0xB63E28U, 0xB6456AU, 0xB65D81U, 0xB66C54U, 0xB674BFU, + 0xB6823BU, 0xB69AD0U, 0xB6AB05U, 0xB6B3EEU, 0xB6C8ACU, 0xB6D047U, 0xB6E192U, 0xB6F979U, 0xB70C9AU, 0xB71471U, + 0xB725A4U, 0xB73D4FU, 0xB7460DU, 0xB75EE6U, 0xB76F33U, 0xB777D8U, 0xB7815CU, 0xB799B7U, 0xB7A862U, 0xB7B089U, + 0xB7CBCBU, 0xB7D320U, 0xB7E2F5U, 0xB7FA1EU, 0xB80773U, 0xB81F98U, 0xB82E4DU, 0xB836A6U, 0xB84DE4U, 0xB8550FU, + 0xB864DAU, 0xB87C31U, 0xB88AB5U, 0xB8925EU, 0xB8A38BU, 0xB8BB60U, 0xB8C022U, 0xB8D8C9U, 0xB8E91CU, 0xB8F1F7U, + 0xB90414U, 0xB91CFFU, 0xB92D2AU, 0xB935C1U, 0xB94E83U, 0xB95668U, 0xB967BDU, 0xB97F56U, 0xB989D2U, 0xB99139U, + 0xB9A0ECU, 0xB9B807U, 0xB9C345U, 0xB9DBAEU, 0xB9EA7BU, 0xB9F290U, 0xBA01BEU, 0xBA1955U, 0xBA2880U, 0xBA306BU, + 0xBA4B29U, 0xBA53C2U, 0xBA6217U, 0xBA7AFCU, 0xBA8C78U, 0xBA9493U, 0xBAA546U, 0xBABDADU, 0xBAC6EFU, 0xBADE04U, + 0xBAEFD1U, 0xBAF73AU, 0xBB02D9U, 0xBB1A32U, 0xBB2BE7U, 0xBB330CU, 0xBB484EU, 0xBB50A5U, 0xBB6170U, 0xBB799BU, + 0xBB8F1FU, 0xBB97F4U, 0xBBA621U, 0xBBBECAU, 0xBBC588U, 0xBBDD63U, 0xBBECB6U, 0xBBF45DU, 0xBC0AEAU, 0xBC1201U, + 0xBC23D4U, 0xBC3B3FU, 0xBC407DU, 0xBC5896U, 0xBC6943U, 0xBC71A8U, 0xBC872CU, 0xBC9FC7U, 0xBCAE12U, 0xBCB6F9U, + 0xBCCDBBU, 0xBCD550U, 0xBCE485U, 0xBCFC6EU, 0xBD098DU, 0xBD1166U, 0xBD20B3U, 0xBD3858U, 0xBD431AU, 0xBD5BF1U, + 0xBD6A24U, 0xBD72CFU, 0xBD844BU, 0xBD9CA0U, 0xBDAD75U, 0xBDB59EU, 0xBDCEDCU, 0xBDD637U, 0xBDE7E2U, 0xBDFF09U, + 0xBE0C27U, 0xBE14CCU, 0xBE2519U, 0xBE3DF2U, 0xBE46B0U, 0xBE5E5BU, 0xBE6F8EU, 0xBE7765U, 0xBE81E1U, 0xBE990AU, + 0xBEA8DFU, 0xBEB034U, 0xBECB76U, 0xBED39DU, 0xBEE248U, 0xBEFAA3U, 0xBF0F40U, 0xBF17ABU, 0xBF267EU, 0xBF3E95U, + 0xBF45D7U, 0xBF5D3CU, 0xBF6CE9U, 0xBF7402U, 0xBF8286U, 0xBF9A6DU, 0xBFABB8U, 0xBFB353U, 0xBFC811U, 0xBFD0FAU, + 0xBFE12FU, 0xBFF9C4U, 0xC00A4EU, 0xC012A5U, 0xC02370U, 0xC03B9BU, 0xC040D9U, 0xC05832U, 0xC069E7U, 0xC0710CU, + 0xC08788U, 0xC09F63U, 0xC0AEB6U, 0xC0B65DU, 0xC0CD1FU, 0xC0D5F4U, 0xC0E421U, 0xC0FCCAU, 0xC10929U, 0xC111C2U, + 0xC12017U, 0xC138FCU, 0xC143BEU, 0xC15B55U, 0xC16A80U, 0xC1726BU, 0xC184EFU, 0xC19C04U, 0xC1ADD1U, 0xC1B53AU, + 0xC1CE78U, 0xC1D693U, 0xC1E746U, 0xC1FFADU, 0xC20C83U, 0xC21468U, 0xC225BDU, 0xC23D56U, 0xC24614U, 0xC25EFFU, + 0xC26F2AU, 0xC277C1U, 0xC28145U, 0xC299AEU, 0xC2A87BU, 0xC2B090U, 0xC2CBD2U, 0xC2D339U, 0xC2E2ECU, 0xC2FA07U, + 0xC30FE4U, 0xC3170FU, 0xC326DAU, 0xC33E31U, 0xC34573U, 0xC35D98U, 0xC36C4DU, 0xC374A6U, 0xC38222U, 0xC39AC9U, + 0xC3AB1CU, 0xC3B3F7U, 0xC3C8B5U, 0xC3D05EU, 0xC3E18BU, 0xC3F960U, 0xC407D7U, 0xC41F3CU, 0xC42EE9U, 0xC43602U, + 0xC44D40U, 0xC455ABU, 0xC4647EU, 0xC47C95U, 0xC48A11U, 0xC492FAU, 0xC4A32FU, 0xC4BBC4U, 0xC4C086U, 0xC4D86DU, + 0xC4E9B8U, 0xC4F153U, 0xC504B0U, 0xC51C5BU, 0xC52D8EU, 0xC53565U, 0xC54E27U, 0xC556CCU, 0xC56719U, 0xC57FF2U, + 0xC58976U, 0xC5919DU, 0xC5A048U, 0xC5B8A3U, 0xC5C3E1U, 0xC5DB0AU, 0xC5EADFU, 0xC5F234U, 0xC6011AU, 0xC619F1U, + 0xC62824U, 0xC630CFU, 0xC64B8DU, 0xC65366U, 0xC662B3U, 0xC67A58U, 0xC68CDCU, 0xC69437U, 0xC6A5E2U, 0xC6BD09U, + 0xC6C64BU, 0xC6DEA0U, 0xC6EF75U, 0xC6F79EU, 0xC7027DU, 0xC71A96U, 0xC72B43U, 0xC733A8U, 0xC748EAU, 0xC75001U, + 0xC761D4U, 0xC7793FU, 0xC78FBBU, 0xC79750U, 0xC7A685U, 0xC7BE6EU, 0xC7C52CU, 0xC7DDC7U, 0xC7EC12U, 0xC7F4F9U, + 0xC80994U, 0xC8117FU, 0xC820AAU, 0xC83841U, 0xC84303U, 0xC85BE8U, 0xC86A3DU, 0xC872D6U, 0xC88452U, 0xC89CB9U, + 0xC8AD6CU, 0xC8B587U, 0xC8CEC5U, 0xC8D62EU, 0xC8E7FBU, 0xC8FF10U, 0xC90AF3U, 0xC91218U, 0xC923CDU, 0xC93B26U, + 0xC94064U, 0xC9588FU, 0xC9695AU, 0xC971B1U, 0xC98735U, 0xC99FDEU, 0xC9AE0BU, 0xC9B6E0U, 0xC9CDA2U, 0xC9D549U, + 0xC9E49CU, 0xC9FC77U, 0xCA0F59U, 0xCA17B2U, 0xCA2667U, 0xCA3E8CU, 0xCA45CEU, 0xCA5D25U, 0xCA6CF0U, 0xCA741BU, + 0xCA829FU, 0xCA9A74U, 0xCAABA1U, 0xCAB34AU, 0xCAC808U, 0xCAD0E3U, 0xCAE136U, 0xCAF9DDU, 0xCB0C3EU, 0xCB14D5U, + 0xCB2500U, 0xCB3DEBU, 0xCB46A9U, 0xCB5E42U, 0xCB6F97U, 0xCB777CU, 0xCB81F8U, 0xCB9913U, 0xCBA8C6U, 0xCBB02DU, + 0xCBCB6FU, 0xCBD384U, 0xCBE251U, 0xCBFABAU, 0xCC040DU, 0xCC1CE6U, 0xCC2D33U, 0xCC35D8U, 0xCC4E9AU, 0xCC5671U, + 0xCC67A4U, 0xCC7F4FU, 0xCC89CBU, 0xCC9120U, 0xCCA0F5U, 0xCCB81EU, 0xCCC35CU, 0xCCDBB7U, 0xCCEA62U, 0xCCF289U, + 0xCD076AU, 0xCD1F81U, 0xCD2E54U, 0xCD36BFU, 0xCD4DFDU, 0xCD5516U, 0xCD64C3U, 0xCD7C28U, 0xCD8AACU, 0xCD9247U, + 0xCDA392U, 0xCDBB79U, 0xCDC03BU, 0xCDD8D0U, 0xCDE905U, 0xCDF1EEU, 0xCE02C0U, 0xCE1A2BU, 0xCE2BFEU, 0xCE3315U, + 0xCE4857U, 0xCE50BCU, 0xCE6169U, 0xCE7982U, 0xCE8F06U, 0xCE97EDU, 0xCEA638U, 0xCEBED3U, 0xCEC591U, 0xCEDD7AU, + 0xCEECAFU, 0xCEF444U, 0xCF01A7U, 0xCF194CU, 0xCF2899U, 0xCF3072U, 0xCF4B30U, 0xCF53DBU, 0xCF620EU, 0xCF7AE5U, + 0xCF8C61U, 0xCF948AU, 0xCFA55FU, 0xCFBDB4U, 0xCFC6F6U, 0xCFDE1DU, 0xCFEFC8U, 0xCFF723U, 0xD00DFAU, 0xD01511U, + 0xD024C4U, 0xD03C2FU, 0xD0476DU, 0xD05F86U, 0xD06E53U, 0xD076B8U, 0xD0803CU, 0xD098D7U, 0xD0A902U, 0xD0B1E9U, + 0xD0CAABU, 0xD0D240U, 0xD0E395U, 0xD0FB7EU, 0xD10E9DU, 0xD11676U, 0xD127A3U, 0xD13F48U, 0xD1440AU, 0xD15CE1U, + 0xD16D34U, 0xD175DFU, 0xD1835BU, 0xD19BB0U, 0xD1AA65U, 0xD1B28EU, 0xD1C9CCU, 0xD1D127U, 0xD1E0F2U, 0xD1F819U, + 0xD20B37U, 0xD213DCU, 0xD22209U, 0xD23AE2U, 0xD241A0U, 0xD2594BU, 0xD2689EU, 0xD27075U, 0xD286F1U, 0xD29E1AU, + 0xD2AFCFU, 0xD2B724U, 0xD2CC66U, 0xD2D48DU, 0xD2E558U, 0xD2FDB3U, 0xD30850U, 0xD310BBU, 0xD3216EU, 0xD33985U, + 0xD342C7U, 0xD35A2CU, 0xD36BF9U, 0xD37312U, 0xD38596U, 0xD39D7DU, 0xD3ACA8U, 0xD3B443U, 0xD3CF01U, 0xD3D7EAU, + 0xD3E63FU, 0xD3FED4U, 0xD40063U, 0xD41888U, 0xD4295DU, 0xD431B6U, 0xD44AF4U, 0xD4521FU, 0xD463CAU, 0xD47B21U, + 0xD48DA5U, 0xD4954EU, 0xD4A49BU, 0xD4BC70U, 0xD4C732U, 0xD4DFD9U, 0xD4EE0CU, 0xD4F6E7U, 0xD50304U, 0xD51BEFU, + 0xD52A3AU, 0xD532D1U, 0xD54993U, 0xD55178U, 0xD560ADU, 0xD57846U, 0xD58EC2U, 0xD59629U, 0xD5A7FCU, 0xD5BF17U, + 0xD5C455U, 0xD5DCBEU, 0xD5ED6BU, 0xD5F580U, 0xD606AEU, 0xD61E45U, 0xD62F90U, 0xD6377BU, 0xD64C39U, 0xD654D2U, + 0xD66507U, 0xD67DECU, 0xD68B68U, 0xD69383U, 0xD6A256U, 0xD6BABDU, 0xD6C1FFU, 0xD6D914U, 0xD6E8C1U, 0xD6F02AU, + 0xD705C9U, 0xD71D22U, 0xD72CF7U, 0xD7341CU, 0xD74F5EU, 0xD757B5U, 0xD76660U, 0xD77E8BU, 0xD7880FU, 0xD790E4U, + 0xD7A131U, 0xD7B9DAU, 0xD7C298U, 0xD7DA73U, 0xD7EBA6U, 0xD7F34DU, 0xD80E20U, 0xD816CBU, 0xD8271EU, 0xD83FF5U, + 0xD844B7U, 0xD85C5CU, 0xD86D89U, 0xD87562U, 0xD883E6U, 0xD89B0DU, 0xD8AAD8U, 0xD8B233U, 0xD8C971U, 0xD8D19AU, + 0xD8E04FU, 0xD8F8A4U, 0xD90D47U, 0xD915ACU, 0xD92479U, 0xD93C92U, 0xD947D0U, 0xD95F3BU, 0xD96EEEU, 0xD97605U, + 0xD98081U, 0xD9986AU, 0xD9A9BFU, 0xD9B154U, 0xD9CA16U, 0xD9D2FDU, 0xD9E328U, 0xD9FBC3U, 0xDA08EDU, 0xDA1006U, + 0xDA21D3U, 0xDA3938U, 0xDA427AU, 0xDA5A91U, 0xDA6B44U, 0xDA73AFU, 0xDA852BU, 0xDA9DC0U, 0xDAAC15U, 0xDAB4FEU, + 0xDACFBCU, 0xDAD757U, 0xDAE682U, 0xDAFE69U, 0xDB0B8AU, 0xDB1361U, 0xDB22B4U, 0xDB3A5FU, 0xDB411DU, 0xDB59F6U, + 0xDB6823U, 0xDB70C8U, 0xDB864CU, 0xDB9EA7U, 0xDBAF72U, 0xDBB799U, 0xDBCCDBU, 0xDBD430U, 0xDBE5E5U, 0xDBFD0EU, + 0xDC03B9U, 0xDC1B52U, 0xDC2A87U, 0xDC326CU, 0xDC492EU, 0xDC51C5U, 0xDC6010U, 0xDC78FBU, 0xDC8E7FU, 0xDC9694U, + 0xDCA741U, 0xDCBFAAU, 0xDCC4E8U, 0xDCDC03U, 0xDCEDD6U, 0xDCF53DU, 0xDD00DEU, 0xDD1835U, 0xDD29E0U, 0xDD310BU, + 0xDD4A49U, 0xDD52A2U, 0xDD6377U, 0xDD7B9CU, 0xDD8D18U, 0xDD95F3U, 0xDDA426U, 0xDDBCCDU, 0xDDC78FU, 0xDDDF64U, + 0xDDEEB1U, 0xDDF65AU, 0xDE0574U, 0xDE1D9FU, 0xDE2C4AU, 0xDE34A1U, 0xDE4FE3U, 0xDE5708U, 0xDE66DDU, 0xDE7E36U, + 0xDE88B2U, 0xDE9059U, 0xDEA18CU, 0xDEB967U, 0xDEC225U, 0xDEDACEU, 0xDEEB1BU, 0xDEF3F0U, 0xDF0613U, 0xDF1EF8U, + 0xDF2F2DU, 0xDF37C6U, 0xDF4C84U, 0xDF546FU, 0xDF65BAU, 0xDF7D51U, 0xDF8BD5U, 0xDF933EU, 0xDFA2EBU, 0xDFBA00U, + 0xDFC142U, 0xDFD9A9U, 0xDFE87CU, 0xDFF097U, 0xE00526U, 0xE01DCDU, 0xE02C18U, 0xE034F3U, 0xE04FB1U, 0xE0575AU, + 0xE0668FU, 0xE07E64U, 0xE088E0U, 0xE0900BU, 0xE0A1DEU, 0xE0B935U, 0xE0C277U, 0xE0DA9CU, 0xE0EB49U, 0xE0F3A2U, + 0xE10641U, 0xE11EAAU, 0xE12F7FU, 0xE13794U, 0xE14CD6U, 0xE1543DU, 0xE165E8U, 0xE17D03U, 0xE18B87U, 0xE1936CU, + 0xE1A2B9U, 0xE1BA52U, 0xE1C110U, 0xE1D9FBU, 0xE1E82EU, 0xE1F0C5U, 0xE203EBU, 0xE21B00U, 0xE22AD5U, 0xE2323EU, + 0xE2497CU, 0xE25197U, 0xE26042U, 0xE278A9U, 0xE28E2DU, 0xE296C6U, 0xE2A713U, 0xE2BFF8U, 0xE2C4BAU, 0xE2DC51U, + 0xE2ED84U, 0xE2F56FU, 0xE3008CU, 0xE31867U, 0xE329B2U, 0xE33159U, 0xE34A1BU, 0xE352F0U, 0xE36325U, 0xE37BCEU, + 0xE38D4AU, 0xE395A1U, 0xE3A474U, 0xE3BC9FU, 0xE3C7DDU, 0xE3DF36U, 0xE3EEE3U, 0xE3F608U, 0xE408BFU, 0xE41054U, + 0xE42181U, 0xE4396AU, 0xE44228U, 0xE45AC3U, 0xE46B16U, 0xE473FDU, 0xE48579U, 0xE49D92U, 0xE4AC47U, 0xE4B4ACU, + 0xE4CFEEU, 0xE4D705U, 0xE4E6D0U, 0xE4FE3BU, 0xE50BD8U, 0xE51333U, 0xE522E6U, 0xE53A0DU, 0xE5414FU, 0xE559A4U, + 0xE56871U, 0xE5709AU, 0xE5861EU, 0xE59EF5U, 0xE5AF20U, 0xE5B7CBU, 0xE5CC89U, 0xE5D462U, 0xE5E5B7U, 0xE5FD5CU, + 0xE60E72U, 0xE61699U, 0xE6274CU, 0xE63FA7U, 0xE644E5U, 0xE65C0EU, 0xE66DDBU, 0xE67530U, 0xE683B4U, 0xE69B5FU, + 0xE6AA8AU, 0xE6B261U, 0xE6C923U, 0xE6D1C8U, 0xE6E01DU, 0xE6F8F6U, 0xE70D15U, 0xE715FEU, 0xE7242BU, 0xE73CC0U, + 0xE74782U, 0xE75F69U, 0xE76EBCU, 0xE77657U, 0xE780D3U, 0xE79838U, 0xE7A9EDU, 0xE7B106U, 0xE7CA44U, 0xE7D2AFU, + 0xE7E37AU, 0xE7FB91U, 0xE806FCU, 0xE81E17U, 0xE82FC2U, 0xE83729U, 0xE84C6BU, 0xE85480U, 0xE86555U, 0xE87DBEU, + 0xE88B3AU, 0xE893D1U, 0xE8A204U, 0xE8BAEFU, 0xE8C1ADU, 0xE8D946U, 0xE8E893U, 0xE8F078U, 0xE9059BU, 0xE91D70U, + 0xE92CA5U, 0xE9344EU, 0xE94F0CU, 0xE957E7U, 0xE96632U, 0xE97ED9U, 0xE9885DU, 0xE990B6U, 0xE9A163U, 0xE9B988U, + 0xE9C2CAU, 0xE9DA21U, 0xE9EBF4U, 0xE9F31FU, 0xEA0031U, 0xEA18DAU, 0xEA290FU, 0xEA31E4U, 0xEA4AA6U, 0xEA524DU, + 0xEA6398U, 0xEA7B73U, 0xEA8DF7U, 0xEA951CU, 0xEAA4C9U, 0xEABC22U, 0xEAC760U, 0xEADF8BU, 0xEAEE5EU, 0xEAF6B5U, + 0xEB0356U, 0xEB1BBDU, 0xEB2A68U, 0xEB3283U, 0xEB49C1U, 0xEB512AU, 0xEB60FFU, 0xEB7814U, 0xEB8E90U, 0xEB967BU, + 0xEBA7AEU, 0xEBBF45U, 0xEBC407U, 0xEBDCECU, 0xEBED39U, 0xEBF5D2U, 0xEC0B65U, 0xEC138EU, 0xEC225BU, 0xEC3AB0U, + 0xEC41F2U, 0xEC5919U, 0xEC68CCU, 0xEC7027U, 0xEC86A3U, 0xEC9E48U, 0xECAF9DU, 0xECB776U, 0xECCC34U, 0xECD4DFU, + 0xECE50AU, 0xECFDE1U, 0xED0802U, 0xED10E9U, 0xED213CU, 0xED39D7U, 0xED4295U, 0xED5A7EU, 0xED6BABU, 0xED7340U, + 0xED85C4U, 0xED9D2FU, 0xEDACFAU, 0xEDB411U, 0xEDCF53U, 0xEDD7B8U, 0xEDE66DU, 0xEDFE86U, 0xEE0DA8U, 0xEE1543U, + 0xEE2496U, 0xEE3C7DU, 0xEE473FU, 0xEE5FD4U, 0xEE6E01U, 0xEE76EAU, 0xEE806EU, 0xEE9885U, 0xEEA950U, 0xEEB1BBU, + 0xEECAF9U, 0xEED212U, 0xEEE3C7U, 0xEEFB2CU, 0xEF0ECFU, 0xEF1624U, 0xEF27F1U, 0xEF3F1AU, 0xEF4458U, 0xEF5CB3U, + 0xEF6D66U, 0xEF758DU, 0xEF8309U, 0xEF9BE2U, 0xEFAA37U, 0xEFB2DCU, 0xEFC99EU, 0xEFD175U, 0xEFE0A0U, 0xEFF84BU, + 0xF00292U, 0xF01A79U, 0xF02BACU, 0xF03347U, 0xF04805U, 0xF050EEU, 0xF0613BU, 0xF079D0U, 0xF08F54U, 0xF097BFU, + 0xF0A66AU, 0xF0BE81U, 0xF0C5C3U, 0xF0DD28U, 0xF0ECFDU, 0xF0F416U, 0xF101F5U, 0xF1191EU, 0xF128CBU, 0xF13020U, + 0xF14B62U, 0xF15389U, 0xF1625CU, 0xF17AB7U, 0xF18C33U, 0xF194D8U, 0xF1A50DU, 0xF1BDE6U, 0xF1C6A4U, 0xF1DE4FU, + 0xF1EF9AU, 0xF1F771U, 0xF2045FU, 0xF21CB4U, 0xF22D61U, 0xF2358AU, 0xF24EC8U, 0xF25623U, 0xF267F6U, 0xF27F1DU, + 0xF28999U, 0xF29172U, 0xF2A0A7U, 0xF2B84CU, 0xF2C30EU, 0xF2DBE5U, 0xF2EA30U, 0xF2F2DBU, 0xF30738U, 0xF31FD3U, + 0xF32E06U, 0xF336EDU, 0xF34DAFU, 0xF35544U, 0xF36491U, 0xF37C7AU, 0xF38AFEU, 0xF39215U, 0xF3A3C0U, 0xF3BB2BU, + 0xF3C069U, 0xF3D882U, 0xF3E957U, 0xF3F1BCU, 0xF40F0BU, 0xF417E0U, 0xF42635U, 0xF43EDEU, 0xF4459CU, 0xF45D77U, + 0xF46CA2U, 0xF47449U, 0xF482CDU, 0xF49A26U, 0xF4ABF3U, 0xF4B318U, 0xF4C85AU, 0xF4D0B1U, 0xF4E164U, 0xF4F98FU, + 0xF50C6CU, 0xF51487U, 0xF52552U, 0xF53DB9U, 0xF546FBU, 0xF55E10U, 0xF56FC5U, 0xF5772EU, 0xF581AAU, 0xF59941U, + 0xF5A894U, 0xF5B07FU, 0xF5CB3DU, 0xF5D3D6U, 0xF5E203U, 0xF5FAE8U, 0xF609C6U, 0xF6112DU, 0xF620F8U, 0xF63813U, + 0xF64351U, 0xF65BBAU, 0xF66A6FU, 0xF67284U, 0xF68400U, 0xF69CEBU, 0xF6AD3EU, 0xF6B5D5U, 0xF6CE97U, 0xF6D67CU, + 0xF6E7A9U, 0xF6FF42U, 0xF70AA1U, 0xF7124AU, 0xF7239FU, 0xF73B74U, 0xF74036U, 0xF758DDU, 0xF76908U, 0xF771E3U, + 0xF78767U, 0xF79F8CU, 0xF7AE59U, 0xF7B6B2U, 0xF7CDF0U, 0xF7D51BU, 0xF7E4CEU, 0xF7FC25U, 0xF80148U, 0xF819A3U, + 0xF82876U, 0xF8309DU, 0xF84BDFU, 0xF85334U, 0xF862E1U, 0xF87A0AU, 0xF88C8EU, 0xF89465U, 0xF8A5B0U, 0xF8BD5BU, + 0xF8C619U, 0xF8DEF2U, 0xF8EF27U, 0xF8F7CCU, 0xF9022FU, 0xF91AC4U, 0xF92B11U, 0xF933FAU, 0xF948B8U, 0xF95053U, + 0xF96186U, 0xF9796DU, 0xF98FE9U, 0xF99702U, 0xF9A6D7U, 0xF9BE3CU, 0xF9C57EU, 0xF9DD95U, 0xF9EC40U, 0xF9F4ABU, + 0xFA0785U, 0xFA1F6EU, 0xFA2EBBU, 0xFA3650U, 0xFA4D12U, 0xFA55F9U, 0xFA642CU, 0xFA7CC7U, 0xFA8A43U, 0xFA92A8U, + 0xFAA37DU, 0xFABB96U, 0xFAC0D4U, 0xFAD83FU, 0xFAE9EAU, 0xFAF101U, 0xFB04E2U, 0xFB1C09U, 0xFB2DDCU, 0xFB3537U, + 0xFB4E75U, 0xFB569EU, 0xFB674BU, 0xFB7FA0U, 0xFB8924U, 0xFB91CFU, 0xFBA01AU, 0xFBB8F1U, 0xFBC3B3U, 0xFBDB58U, + 0xFBEA8DU, 0xFBF266U, 0xFC0CD1U, 0xFC143AU, 0xFC25EFU, 0xFC3D04U, 0xFC4646U, 0xFC5EADU, 0xFC6F78U, 0xFC7793U, + 0xFC8117U, 0xFC99FCU, 0xFCA829U, 0xFCB0C2U, 0xFCCB80U, 0xFCD36BU, 0xFCE2BEU, 0xFCFA55U, 0xFD0FB6U, 0xFD175DU, + 0xFD2688U, 0xFD3E63U, 0xFD4521U, 0xFD5DCAU, 0xFD6C1FU, 0xFD74F4U, 0xFD8270U, 0xFD9A9BU, 0xFDAB4EU, 0xFDB3A5U, + 0xFDC8E7U, 0xFDD00CU, 0xFDE1D9U, 0xFDF932U, 0xFE0A1CU, 0xFE12F7U, 0xFE2322U, 0xFE3BC9U, 0xFE408BU, 0xFE5860U, + 0xFE69B5U, 0xFE715EU, 0xFE87DAU, 0xFE9F31U, 0xFEAEE4U, 0xFEB60FU, 0xFECD4DU, 0xFED5A6U, 0xFEE473U, 0xFEFC98U, + 0xFF097BU, 0xFF1190U, 0xFF2045U, 0xFF38AEU, 0xFF43ECU, 0xFF5B07U, 0xFF6AD2U, 0xFF7239U, 0xFF84BDU, 0xFF9C56U, + 0xFFAD83U, 0xFFB568U, 0xFFCE2AU, 0xFFD6C1U, 0xFFE714U, 0xFFFFFFU}; + +static const unsigned int DECODING_TABLE_23127[] = { + 0x000000U, 0x000001U, 0x000002U, 0x000003U, 0x000004U, 0x000005U, 0x000006U, 0x000007U, 0x000008U, 0x000009U, + 0x00000AU, 0x00000BU, 0x00000CU, 0x00000DU, 0x00000EU, 0x024020U, 0x000010U, 0x000011U, 0x000012U, 0x000013U, + 0x000014U, 0x000015U, 0x000016U, 0x412000U, 0x000018U, 0x000019U, 0x00001AU, 0x180800U, 0x00001CU, 0x200300U, + 0x048040U, 0x001480U, 0x000020U, 0x000021U, 0x000022U, 0x000023U, 0x000024U, 0x000025U, 0x000026U, 0x024008U, + 0x000028U, 0x000029U, 0x00002AU, 0x024004U, 0x00002CU, 0x024002U, 0x024001U, 0x024000U, 0x000030U, 0x000031U, + 0x000032U, 0x008180U, 0x000034U, 0x000C40U, 0x301000U, 0x0C0200U, 0x000038U, 0x043000U, 0x400600U, 0x210040U, + 0x090080U, 0x508000U, 0x002900U, 0x024010U, 0x000040U, 0x000041U, 0x000042U, 0x000043U, 0x000044U, 0x000045U, + 0x000046U, 0x280080U, 0x000048U, 0x000049U, 0x00004AU, 0x002500U, 0x00004CU, 0x111000U, 0x048010U, 0x400A00U, + 0x000050U, 0x000051U, 0x000052U, 0x021200U, 0x000054U, 0x000C20U, 0x048008U, 0x104100U, 0x000058U, 0x404080U, + 0x048004U, 0x210020U, 0x048002U, 0x0A2000U, 0x048000U, 0x048001U, 0x000060U, 0x000061U, 0x000062U, 0x540000U, + 0x000064U, 0x000C10U, 0x010300U, 0x00B000U, 0x000068U, 0x088200U, 0x001880U, 0x210010U, 0x602000U, 0x040180U, + 0x180400U, 0x024040U, 0x000070U, 0x000C04U, 0x086000U, 0x210008U, 0x000C01U, 0x000C00U, 0x420080U, 0x000C02U, + 0x120100U, 0x210002U, 0x210001U, 0x210000U, 0x005200U, 0x000C08U, 0x048020U, 0x210004U, 0x000080U, 0x000081U, + 0x000082U, 0x000083U, 0x000084U, 0x000085U, 0x000086U, 0x280040U, 0x000088U, 0x000089U, 0x00008AU, 0x050200U, + 0x00008CU, 0x00A800U, 0x500100U, 0x001410U, 0x000090U, 0x000091U, 0x000092U, 0x008120U, 0x000094U, 0x160000U, + 0x004A00U, 0x001408U, 0x000098U, 0x404040U, 0x222000U, 0x001404U, 0x090020U, 0x001402U, 0x001401U, 0x001400U, + 0x0000A0U, 0x0000A1U, 0x0000A2U, 0x008110U, 0x0000A4U, 0x401200U, 0x042400U, 0x110800U, 0x0000A8U, 0x300400U, + 0x001840U, 0x482000U, 0x090010U, 0x040140U, 0x208200U, 0x024080U, 0x0000B0U, 0x008102U, 0x008101U, 0x008100U, + 0x090008U, 0x206000U, 0x420040U, 0x008104U, 0x090004U, 0x020A00U, 0x144000U, 0x008108U, 0x090000U, 0x090001U, + 0x090002U, 0x001420U, 0x0000C0U, 0x0000C1U, 0x0000C2U, 0x280004U, 0x0000C4U, 0x280002U, 0x280001U, 0x280000U, + 0x0000C8U, 0x404010U, 0x001820U, 0x128000U, 0x020600U, 0x040120U, 0x016000U, 0x280008U, 0x0000D0U, 0x404008U, + 0x110400U, 0x042800U, 0x003100U, 0x018200U, 0x420020U, 0x280010U, 0x404001U, 0x404000U, 0x080300U, 0x404002U, + 0x300800U, 0x404004U, 0x048080U, 0x001440U, 0x0000E0U, 0x032000U, 0x001808U, 0x004600U, 0x10C000U, 0x040108U, + 0x420010U, 0x280020U, 0x001802U, 0x040104U, 0x001800U, 0x001801U, 0x040101U, 0x040100U, 0x001804U, 0x040102U, + 0x240200U, 0x181000U, 0x420004U, 0x008140U, 0x420002U, 0x000C80U, 0x420000U, 0x420001U, 0x00A400U, 0x404020U, + 0x001810U, 0x210080U, 0x090040U, 0x040110U, 0x420008U, 0x102200U, 0x000100U, 0x000101U, 0x000102U, 0x000103U, + 0x000104U, 0x000105U, 0x000106U, 0x041800U, 0x000108U, 0x000109U, 0x00010AU, 0x002440U, 0x00010CU, 0x200210U, + 0x500080U, 0x098000U, 0x000110U, 0x000111U, 0x000112U, 0x0080A0U, 0x000114U, 0x200208U, 0x0A0400U, 0x104040U, + 0x000118U, 0x200204U, 0x015000U, 0x460000U, 0x200201U, 0x200200U, 0x002820U, 0x200202U, 0x000120U, 0x000121U, + 0x000122U, 0x008090U, 0x000124U, 0x182000U, 0x010240U, 0x600400U, 0x000128U, 0x410800U, 0x2C0000U, 0x101200U, + 0x009400U, 0x0400C0U, 0x002810U, 0x024100U, 0x000130U, 0x008082U, 0x008081U, 0x008080U, 0x444000U, 0x031000U, + 0x002808U, 0x008084U, 0x120040U, 0x084400U, 0x002804U, 0x008088U, 0x002802U, 0x200220U, 0x002800U, 0x002801U, + 0x000140U, 0x000141U, 0x000142U, 0x002408U, 0x000144U, 0x428000U, 0x010220U, 0x104010U, 0x000148U, 0x002402U, + 0x002401U, 0x002400U, 0x084800U, 0x0400A0U, 0x221000U, 0x002404U, 0x000150U, 0x0D0000U, 0x600800U, 0x104004U, + 0x003080U, 0x104002U, 0x104001U, 0x104000U, 0x120020U, 0x009800U, 0x080280U, 0x002410U, 0x410400U, 0x200240U, + 0x048100U, 0x104008U, 0x000160U, 0x205000U, 0x010204U, 0x0A0800U, 0x010202U, 0x040088U, 0x010200U, 0x010201U, + 0x120010U, 0x040084U, 0x40C000U, 0x002420U, 0x040081U, 0x040080U, 0x010208U, 0x040082U, 0x120008U, 0x402200U, + 0x041400U, 0x0080C0U, 0x288000U, 0x000D00U, 0x010210U, 0x104020U, 0x120000U, 0x120001U, 0x120002U, 0x210100U, + 0x120004U, 0x040090U, 0x002840U, 0x481000U, 0x000180U, 0x000181U, 0x000182U, 0x008030U, 0x000184U, 0x014400U, + 0x500008U, 0x022200U, 0x000188U, 0x0A1000U, 0x500004U, 0x204800U, 0x500002U, 0x040060U, 0x500000U, 0x500001U, + 0x000190U, 0x008022U, 0x008021U, 0x008020U, 0x003040U, 0x480800U, 0x250000U, 0x008024U, 0x040C00U, 0x112000U, + 0x080240U, 0x008028U, 0x02C000U, 0x200280U, 0x500010U, 0x001500U, 0x0001A0U, 0x008012U, 0x008011U, 0x008010U, + 0x220800U, 0x040048U, 0x085000U, 0x008014U, 0x006200U, 0x040044U, 0x030400U, 0x008018U, 0x040041U, 0x040040U, + 0x500020U, 0x040042U, 0x008003U, 0x008002U, 0x008001U, 0x008000U, 0x100600U, 0x008006U, 0x008005U, 0x008004U, + 0x601000U, 0x00800AU, 0x008009U, 0x008008U, 0x090100U, 0x040050U, 0x002880U, 0x00800CU, 0x0001C0U, 0x100A00U, + 0x064000U, 0x411000U, 0x003010U, 0x040028U, 0x008C00U, 0x280100U, 0x218000U, 0x040024U, 0x080210U, 0x002480U, + 0x040021U, 0x040020U, 0x500040U, 0x040022U, 0x003004U, 0x220400U, 0x080208U, 0x008060U, 0x003000U, 0x003001U, + 0x003002U, 0x104080U, 0x080202U, 0x404100U, 0x080200U, 0x080201U, 0x003008U, 0x040030U, 0x080204U, 0x030800U, + 0x480400U, 0x04000CU, 0x302000U, 0x008050U, 0x040009U, 0x040008U, 0x010280U, 0x04000AU, 0x040005U, 0x040004U, + 0x001900U, 0x040006U, 0x040001U, 0x040000U, 0x040003U, 0x040002U, 0x014800U, 0x008042U, 0x008041U, 0x008040U, + 0x003020U, 0x040018U, 0x420100U, 0x008044U, 0x120080U, 0x040014U, 0x080220U, 0x008048U, 0x040011U, 0x040010U, + 0x204400U, 0x040012U, 0x000200U, 0x000201U, 0x000202U, 0x000203U, 0x000204U, 0x000205U, 0x000206U, 0x108400U, + 0x000208U, 0x000209U, 0x00020AU, 0x050080U, 0x00020CU, 0x200110U, 0x083000U, 0x400840U, 0x000210U, 0x000211U, + 0x000212U, 0x021040U, 0x000214U, 0x200108U, 0x004880U, 0x0C0020U, 0x000218U, 0x200104U, 0x400420U, 0x00E000U, + 0x200101U, 0x200100U, 0x130000U, 0x200102U, 0x000220U, 0x000221U, 0x000222U, 0x202800U, 0x000224U, 0x401080U, + 0x010140U, 0x0C0010U, 0x000228U, 0x088040U, 0x400410U, 0x101100U, 0x140800U, 0x012400U, 0x208080U, 0x024200U, + 0x000230U, 0x114000U, 0x400408U, 0x0C0004U, 0x02A000U, 0x0C0002U, 0x0C0001U, 0x0C0000U, 0x400402U, 0x020880U, + 0x400400U, 0x400401U, 0x005040U, 0x200120U, 0x400404U, 0x0C0008U, 0x000240U, 0x000241U, 0x000242U, 0x021010U, + 0x000244U, 0x046000U, 0x010120U, 0x400808U, 0x000248U, 0x088020U, 0x304000U, 0x400804U, 0x020480U, 0x400802U, + 0x400801U, 0x400800U, 0x000250U, 0x021002U, 0x021001U, 0x021000U, 0x580000U, 0x018080U, 0x202400U, 0x021004U, + 0x012800U, 0x140400U, 0x080180U, 0x021008U, 0x005020U, 0x200140U, 0x048200U, 0x400810U, 0x000260U, 0x088008U, + 0x010104U, 0x004480U, 0x010102U, 0x320000U, 0x010100U, 0x010101U, 0x088001U, 0x088000U, 0x062000U, 0x088002U, + 0x005010U, 0x088004U, 0x010108U, 0x400820U, 0x240080U, 0x402100U, 0x108800U, 0x021020U, 0x005008U, 0x000E00U, + 0x010110U, 0x0C0040U, 0x005004U, 0x088010U, 0x400440U, 0x210200U, 0x005000U, 0x005001U, 0x005002U, 0x102080U, + 0x000280U, 0x000281U, 0x000282U, 0x050008U, 0x000284U, 0x401020U, 0x004810U, 0x022100U, 0x000288U, 0x050002U, + 0x050001U, 0x050000U, 0x020440U, 0x184000U, 0x208020U, 0x050004U, 0x000290U, 0x082400U, 0x004804U, 0x700000U, + 0x004802U, 0x018040U, 0x004800U, 0x004801U, 0x109000U, 0x020820U, 0x080140U, 0x050010U, 0x442000U, 0x200180U, + 0x004808U, 0x001600U, 0x0002A0U, 0x401004U, 0x1A0000U, 0x004440U, 0x401001U, 0x401000U, 0x208008U, 0x401002U, + 0x006100U, 0x020810U, 0x208004U, 0x050020U, 0x208002U, 0x401008U, 0x208000U, 0x208001U, 0x240040U, 0x020808U, + 0x013000U, 0x008300U, 0x100500U, 0x401010U, 0x004820U, 0x0C0080U, 0x020801U, 0x020800U, 0x400480U, 0x020802U, + 0x090200U, 0x020804U, 0x208010U, 0x102040U, 0x0002C0U, 0x100900U, 0x40A000U, 0x004420U, 0x020408U, 0x018010U, + 0x141000U, 0x280200U, 0x020404U, 0x203000U, 0x080110U, 0x050040U, 0x020400U, 0x020401U, 0x020402U, 0x400880U, + 0x240020U, 0x018004U, 0x080108U, 0x021080U, 0x018001U, 0x018000U, 0x004840U, 0x018002U, 0x080102U, 0x404200U, + 0x080100U, 0x080101U, 0x020410U, 0x018008U, 0x080104U, 0x102020U, 0x240010U, 0x004402U, 0x004401U, 0x004400U, + 0x082800U, 0x401040U, 0x010180U, 0x004404U, 0x510000U, 0x088080U, 0x001A00U, 0x004408U, 0x020420U, 0x040300U, + 0x208040U, 0x102010U, 0x240000U, 0x240001U, 0x240002U, 0x004410U, 0x240004U, 0x018020U, 0x420200U, 0x102008U, + 0x240008U, 0x020840U, 0x080120U, 0x102004U, 0x005080U, 0x102002U, 0x102001U, 0x102000U, 0x000300U, 0x000301U, + 0x000302U, 0x484000U, 0x000304U, 0x200018U, 0x010060U, 0x022080U, 0x000308U, 0x200014U, 0x028800U, 0x101020U, + 0x200011U, 0x200010U, 0x044400U, 0x200012U, 0x000310U, 0x20000CU, 0x142000U, 0x010C00U, 0x200009U, 0x200008U, + 0x409000U, 0x20000AU, 0x200005U, 0x200004U, 0x0800C0U, 0x200006U, 0x200001U, 0x200000U, 0x200003U, 0x200002U, + 0x000320U, 0x060400U, 0x010044U, 0x101008U, 0x010042U, 0x00C800U, 0x010040U, 0x010041U, 0x006080U, 0x101002U, + 0x101001U, 0x101000U, 0x4A0000U, 0x200030U, 0x010048U, 0x101004U, 0x081800U, 0x402040U, 0x224000U, 0x008280U, + 0x100480U, 0x200028U, 0x010050U, 0x0C0100U, 0x058000U, 0x200024U, 0x400500U, 0x101010U, 0x200021U, 0x200020U, + 0x002A00U, 0x200022U, 0x000340U, 0x100880U, 0x010024U, 0x248000U, 0x010022U, 0x081400U, 0x010020U, 0x010021U, + 0x441000U, 0x034000U, 0x080090U, 0x002600U, 0x10A000U, 0x200050U, 0x010028U, 0x400900U, 0x00C400U, 0x402020U, + 0x080088U, 0x021100U, 0x060800U, 0x200048U, 0x010030U, 0x104200U, 0x080082U, 0x200044U, 0x080080U, 0x080081U, + 0x200041U, 0x200040U, 0x080084U, 0x200042U, 0x010006U, 0x402010U, 0x010004U, 0x010005U, 0x010002U, 0x010003U, + 0x010000U, 0x010001U, 0x200C00U, 0x088100U, 0x01000CU, 0x101040U, 0x01000AU, 0x040280U, 0x010008U, 0x010009U, + 0x402001U, 0x402000U, 0x010014U, 0x402002U, 0x010012U, 0x402004U, 0x010010U, 0x010011U, 0x120200U, 0x402008U, + 0x0800A0U, 0x044800U, 0x005100U, 0x200060U, 0x010018U, 0x028400U, 0x000380U, 0x100840U, 0x201400U, 0x022004U, + 0x0C8000U, 0x022002U, 0x022001U, 0x022000U, 0x006020U, 0x408400U, 0x080050U, 0x050100U, 0x011800U, 0x200090U, + 0x500200U, 0x022008U, 0x430000U, 0x045000U, 0x080048U, 0x008220U, 0x100420U, 0x200088U, 0x004900U, 0x022010U, + 0x080042U, 0x200084U, 0x080040U, 0x080041U, 0x200081U, 0x200080U, 0x080044U, 0x200082U, 0x006008U, 0x290000U, + 0x440800U, 0x008210U, 0x100410U, 0x401100U, 0x0100C0U, 0x022020U, 0x006000U, 0x006001U, 0x006002U, 0x101080U, + 0x006004U, 0x040240U, 0x208100U, 0x080C00U, 0x100404U, 0x008202U, 0x008201U, 0x008200U, 0x100400U, 0x100401U, + 0x100402U, 0x008204U, 0x006010U, 0x020900U, 0x080060U, 0x008208U, 0x100408U, 0x2000A0U, 0x061000U, 0x414000U, + 0x100801U, 0x100800U, 0x080018U, 0x100802U, 0x604000U, 0x100804U, 0x0100A0U, 0x022040U, 0x080012U, 0x100808U, + 0x080010U, 0x080011U, 0x020500U, 0x040220U, 0x080014U, 0x00D000U, 0x08000AU, 0x100810U, 0x080008U, 0x080009U, + 0x003200U, 0x018100U, 0x08000CU, 0x440400U, 0x080002U, 0x080003U, 0x080000U, 0x080001U, 0x080006U, 0x2000C0U, + 0x080004U, 0x080005U, 0x029000U, 0x100820U, 0x010084U, 0x004500U, 0x010082U, 0x040208U, 0x010080U, 0x010081U, + 0x006040U, 0x040204U, 0x080030U, 0x620000U, 0x040201U, 0x040200U, 0x010088U, 0x040202U, 0x240100U, 0x402080U, + 0x080028U, 0x008240U, 0x100440U, 0x0A4000U, 0x010090U, 0x201800U, 0x080022U, 0x011400U, 0x080020U, 0x080021U, + 0x408800U, 0x040210U, 0x080024U, 0x102100U, 0x000400U, 0x000401U, 0x000402U, 0x000403U, 0x000404U, 0x000405U, + 0x000406U, 0x108200U, 0x000408U, 0x000409U, 0x00040AU, 0x002140U, 0x00040CU, 0x4C0000U, 0x210800U, 0x001090U, + 0x000410U, 0x000411U, 0x000412U, 0x244000U, 0x000414U, 0x000860U, 0x0A0100U, 0x001088U, 0x000418U, 0x038000U, + 0x400220U, 0x001084U, 0x106000U, 0x001082U, 0x001081U, 0x001080U, 0x000420U, 0x000421U, 0x000422U, 0x091000U, + 0x000424U, 0x000850U, 0x042080U, 0x600100U, 0x000428U, 0x300080U, 0x400210U, 0x048800U, 0x009100U, 0x012200U, + 0x180040U, 0x024400U, 0x000430U, 0x000844U, 0x400208U, 0x122000U, 0x000841U, 0x000840U, 0x01C000U, 0x000842U, + 0x400202U, 0x084100U, 0x400200U, 0x400201U, 0x260000U, 0x000848U, 0x400204U, 0x0010A0U, 0x000440U, 0x000441U, + 0x000442U, 0x002108U, 0x000444U, 0x000830U, 0x405000U, 0x070000U, 0x000448U, 0x002102U, 0x002101U, 0x002100U, + 0x020280U, 0x20C000U, 0x180020U, 0x002104U, 0x000450U, 0x000824U, 0x110080U, 0x488000U, 0x000821U, 0x000820U, + 0x202200U, 0x000822U, 0x281000U, 0x140200U, 0x024800U, 0x002110U, 0x410100U, 0x000828U, 0x048400U, 0x0010C0U, + 0x000460U, 0x000814U, 0x228000U, 0x004280U, 0x000811U, 0x000810U, 0x180008U, 0x000812U, 0x054000U, 0x421000U, + 0x180004U, 0x002120U, 0x180002U, 0x000818U, 0x180000U, 0x180001U, 0x000805U, 0x000804U, 0x041100U, 0x000806U, + 0x000801U, 0x000800U, 0x000803U, 0x000802U, 0x00A080U, 0x00080CU, 0x400240U, 0x210400U, 0x000809U, 0x000808U, + 0x180010U, 0x00080AU, 0x000480U, 0x000481U, 0x000482U, 0x420800U, 0x000484U, 0x014100U, 0x042020U, 0x001018U, + 0x000488U, 0x300020U, 0x08C000U, 0x001014U, 0x020240U, 0x001012U, 0x001011U, 0x001010U, 0x000490U, 0x082200U, + 0x110040U, 0x00100CU, 0x608000U, 0x00100AU, 0x001009U, 0x001008U, 0x040900U, 0x001006U, 0x001005U, 0x001004U, + 0x001003U, 0x001002U, 0x001001U, 0x001000U, 0x0004A0U, 0x300008U, 0x042004U, 0x004240U, 0x042002U, 0x0A8000U, + 0x042000U, 0x042001U, 0x300001U, 0x300000U, 0x030100U, 0x300002U, 0x404800U, 0x300004U, 0x042008U, 0x001030U, + 0x025000U, 0x450000U, 0x280800U, 0x008500U, 0x100300U, 0x0008C0U, 0x042010U, 0x001028U, 0x00A040U, 0x300010U, + 0x400280U, 0x001024U, 0x090400U, 0x001022U, 0x001021U, 0x001020U, 0x0004C0U, 0x049000U, 0x110010U, 0x004220U, + 0x020208U, 0x502000U, 0x008900U, 0x280400U, 0x020204U, 0x090800U, 0x640000U, 0x002180U, 0x020200U, 0x020201U, + 0x020202U, 0x001050U, 0x110002U, 0x220100U, 0x110000U, 0x110001U, 0x0C4000U, 0x0008A0U, 0x110004U, 0x001048U, + 0x00A020U, 0x404400U, 0x110008U, 0x001044U, 0x020210U, 0x001042U, 0x001041U, 0x001040U, 0x480100U, 0x004202U, + 0x004201U, 0x004200U, 0x211000U, 0x000890U, 0x042040U, 0x004204U, 0x00A010U, 0x300040U, 0x001C00U, 0x004208U, + 0x020220U, 0x040500U, 0x180080U, 0x418000U, 0x00A008U, 0x000884U, 0x110020U, 0x004210U, 0x000881U, 0x000880U, + 0x420400U, 0x000882U, 0x00A000U, 0x00A001U, 0x00A002U, 0x0E0000U, 0x00A004U, 0x000888U, 0x204100U, 0x001060U, + 0x000500U, 0x000501U, 0x000502U, 0x002048U, 0x000504U, 0x014080U, 0x0A0010U, 0x600020U, 0x000508U, 0x002042U, + 0x002041U, 0x002040U, 0x009020U, 0x120800U, 0x044200U, 0x002044U, 0x000510U, 0x501000U, 0x0A0004U, 0x010A00U, + 0x0A0002U, 0x04A000U, 0x0A0000U, 0x0A0001U, 0x040880U, 0x084020U, 0x308000U, 0x002050U, 0x410040U, 0x200600U, + 0x0A0008U, 0x001180U, 0x000520U, 0x060200U, 0x104800U, 0x600004U, 0x009008U, 0x600002U, 0x600001U, 0x600000U, + 0x009004U, 0x084010U, 0x030080U, 0x002060U, 0x009000U, 0x009001U, 0x009002U, 0x600008U, 0x212000U, 0x084008U, + 0x041040U, 0x008480U, 0x100280U, 0x000940U, 0x0A0020U, 0x600010U, 0x084001U, 0x084000U, 0x400300U, 0x084002U, + 0x009010U, 0x084004U, 0x002C00U, 0x150000U, 0x000540U, 0x00200AU, 0x002009U, 0x002008U, 0x340000U, 0x081200U, + 0x008880U, 0x00200CU, 0x002003U, 0x002002U, 0x002001U, 0x002000U, 0x410010U, 0x002006U, 0x002005U, 0x002004U, + 0x00C200U, 0x220080U, 0x041020U, 0x002018U, 0x410008U, 0x000920U, 0x0A0040U, 0x104400U, 0x410004U, 0x002012U, + 0x002011U, 0x002010U, 0x410000U, 0x410001U, 0x410002U, 0x002014U, 0x480080U, 0x118000U, 0x041010U, 0x002028U, + 0x026000U, 0x000910U, 0x010600U, 0x600040U, 0x200A00U, 0x002022U, 0x002021U, 0x002020U, 0x009040U, 0x040480U, + 0x180100U, 0x002024U, 0x041002U, 0x000904U, 0x041000U, 0x041001U, 0x000901U, 0x000900U, 0x041004U, 0x000902U, + 0x120400U, 0x084040U, 0x041008U, 0x002030U, 0x410020U, 0x000908U, 0x204080U, 0x028200U, 0x000580U, 0x014004U, + 0x201200U, 0x1C0000U, 0x014001U, 0x014000U, 0x008840U, 0x014002U, 0x040810U, 0x408200U, 0x030020U, 0x0020C0U, + 0x282000U, 0x014008U, 0x500400U, 0x001110U, 0x040808U, 0x220040U, 0x406000U, 0x008420U, 0x100220U, 0x014010U, + 0x0A0080U, 0x001108U, 0x040800U, 0x040801U, 0x040802U, 0x001104U, 0x040804U, 0x001102U, 0x001101U, 0x001100U, + 0x480040U, 0x003800U, 0x030008U, 0x008410U, 0x100210U, 0x014020U, 0x042100U, 0x600080U, 0x030002U, 0x300100U, + 0x030000U, 0x030001U, 0x009080U, 0x040440U, 0x030004U, 0x080A00U, 0x100204U, 0x008402U, 0x008401U, 0x008400U, + 0x100200U, 0x100201U, 0x100202U, 0x008404U, 0x040820U, 0x084080U, 0x030010U, 0x008408U, 0x100208U, 0x422000U, + 0x204040U, 0x001120U, 0x480020U, 0x220010U, 0x008804U, 0x002088U, 0x008802U, 0x014040U, 0x008800U, 0x008801U, + 0x105000U, 0x002082U, 0x002081U, 0x002080U, 0x020300U, 0x040420U, 0x008808U, 0x002084U, 0x220001U, 0x220000U, + 0x110100U, 0x220002U, 0x003400U, 0x220004U, 0x008810U, 0x440200U, 0x040840U, 0x220008U, 0x080600U, 0x002090U, + 0x410080U, 0x188000U, 0x204020U, 0x001140U, 0x480000U, 0x480001U, 0x480002U, 0x004300U, 0x480004U, 0x040408U, + 0x008820U, 0x121000U, 0x480008U, 0x040404U, 0x030040U, 0x0020A0U, 0x040401U, 0x040400U, 0x204010U, 0x040402U, + 0x480010U, 0x220020U, 0x041080U, 0x008440U, 0x100240U, 0x000980U, 0x204008U, 0x092000U, 0x00A100U, 0x011200U, + 0x204004U, 0x500800U, 0x204002U, 0x040410U, 0x204000U, 0x204001U, 0x000600U, 0x000601U, 0x000602U, 0x108004U, + 0x000604U, 0x108002U, 0x108001U, 0x108000U, 0x000608U, 0x005800U, 0x400030U, 0x2A0000U, 0x0200C0U, 0x012020U, + 0x044100U, 0x108008U, 0x000610U, 0x082080U, 0x400028U, 0x010900U, 0x051000U, 0x424000U, 0x202040U, 0x108010U, + 0x400022U, 0x140040U, 0x400020U, 0x400021U, 0x088800U, 0x200500U, 0x400024U, 0x001280U, 0x000620U, 0x060100U, + 0x400018U, 0x0040C0U, 0x284000U, 0x012008U, 0x021800U, 0x108020U, 0x400012U, 0x012004U, 0x400010U, 0x400011U, + 0x012001U, 0x012000U, 0x400014U, 0x012002U, 0x40000AU, 0x209000U, 0x400008U, 0x400009U, 0x100180U, 0x000A40U, + 0x40000CU, 0x0C0400U, 0x400002U, 0x400003U, 0x400000U, 0x400001U, 0x400006U, 0x012010U, 0x400004U, 0x400005U, + 0x000640U, 0x610000U, 0x0C0800U, 0x0040A0U, 0x020088U, 0x081100U, 0x202010U, 0x108040U, 0x020084U, 0x140010U, + 0x019000U, 0x002300U, 0x020080U, 0x020081U, 0x020082U, 0x400C00U, 0x00C100U, 0x140008U, 0x202004U, 0x021400U, + 0x202002U, 0x000A20U, 0x202000U, 0x202001U, 0x140001U, 0x140000U, 0x400060U, 0x140002U, 0x020090U, 0x140004U, + 0x202008U, 0x094000U, 0x103000U, 0x004082U, 0x004081U, 0x004080U, 0x448000U, 0x000A10U, 0x010500U, 0x004084U, + 0x200900U, 0x088400U, 0x400050U, 0x004088U, 0x0200A0U, 0x012040U, 0x180200U, 0x241000U, 0x0B0000U, 0x000A04U, + 0x400048U, 0x004090U, 0x000A01U, 0x000A00U, 0x202020U, 0x000A02U, 0x400042U, 0x140020U, 0x400040U, 0x400041U, + 0x005400U, 0x000A08U, 0x400044U, 0x028100U, 0x000680U, 0x082010U, 0x201100U, 0x004060U, 0x020048U, 0x240800U, + 0x490000U, 0x108080U, 0x020044U, 0x408100U, 0x102800U, 0x050400U, 0x020040U, 0x020041U, 0x020042U, 0x001210U, + 0x082001U, 0x082000U, 0x068000U, 0x082002U, 0x100120U, 0x082004U, 0x004C00U, 0x001208U, 0x214000U, 0x082008U, + 0x4000A0U, 0x001204U, 0x020050U, 0x001202U, 0x001201U, 0x001200U, 0x018800U, 0x004042U, 0x004041U, 0x004040U, + 0x100110U, 0x401400U, 0x042200U, 0x004044U, 0x0C1000U, 0x300200U, 0x400090U, 0x004048U, 0x020060U, 0x012080U, + 0x208400U, 0x080900U, 0x100104U, 0x082020U, 0x400088U, 0x004050U, 0x100100U, 0x100101U, 0x100102U, 0x230000U, + 0x400082U, 0x020C00U, 0x400080U, 0x400081U, 0x100108U, 0x04C000U, 0x400084U, 0x001220U, 0x02000CU, 0x004022U, + 0x004021U, 0x004020U, 0x020008U, 0x020009U, 0x02000AU, 0x004024U, 0x020004U, 0x020005U, 0x020006U, 0x004028U, + 0x020000U, 0x020001U, 0x020002U, 0x020003U, 0x401800U, 0x082040U, 0x110200U, 0x004030U, 0x020018U, 0x018400U, + 0x202080U, 0x440100U, 0x020014U, 0x140080U, 0x080500U, 0x208800U, 0x020010U, 0x020011U, 0x020012U, 0x001240U, + 0x004003U, 0x004002U, 0x004001U, 0x004000U, 0x020028U, 0x004006U, 0x004005U, 0x004004U, 0x020024U, 0x00400AU, + 0x004009U, 0x004008U, 0x020020U, 0x020021U, 0x020022U, 0x00400CU, 0x240400U, 0x004012U, 0x004011U, 0x004010U, + 0x100140U, 0x000A80U, 0x089000U, 0x004014U, 0x00A200U, 0x011100U, 0x4000C0U, 0x004018U, 0x020030U, 0x680000U, + 0x050800U, 0x102400U, 0x000700U, 0x060020U, 0x201080U, 0x010810U, 0x402800U, 0x081040U, 0x044008U, 0x108100U, + 0x190000U, 0x408080U, 0x044004U, 0x002240U, 0x044002U, 0x200410U, 0x044000U, 0x044001U, 0x00C040U, 0x010802U, + 0x010801U, 0x010800U, 0x1000A0U, 0x200408U, 0x0A0200U, 0x010804U, 0x023000U, 0x200404U, 0x400120U, 0x010808U, + 0x200401U, 0x200400U, 0x044010U, 0x200402U, 0x060001U, 0x060000U, 0x08A000U, 0x060002U, 0x100090U, 0x060004U, + 0x010440U, 0x600200U, 0x200840U, 0x060008U, 0x400110U, 0x101400U, 0x009200U, 0x012100U, 0x044020U, 0x080880U, + 0x100084U, 0x060010U, 0x400108U, 0x010820U, 0x100080U, 0x100081U, 0x100082U, 0x007000U, 0x400102U, 0x084200U, + 0x400100U, 0x400101U, 0x100088U, 0x200420U, 0x400104U, 0x028040U, 0x00C010U, 0x081004U, 0x520000U, 0x002208U, + 0x081001U, 0x081000U, 0x010420U, 0x081002U, 0x200820U, 0x002202U, 0x002201U, 0x002200U, 0x020180U, 0x081008U, + 0x044040U, 0x002204U, 0x00C000U, 0x00C001U, 0x00C002U, 0x010840U, 0x00C004U, 0x081010U, 0x202100U, 0x440080U, + 0x00C008U, 0x140100U, 0x080480U, 0x002210U, 0x410200U, 0x200440U, 0x101800U, 0x028020U, 0x200808U, 0x060040U, + 0x010404U, 0x004180U, 0x010402U, 0x081020U, 0x010400U, 0x010401U, 0x200800U, 0x200801U, 0x200802U, 0x002220U, + 0x200804U, 0x504000U, 0x010408U, 0x028010U, 0x00C020U, 0x402400U, 0x041200U, 0x380000U, 0x1000C0U, 0x000B00U, + 0x010410U, 0x028008U, 0x200810U, 0x011080U, 0x400140U, 0x028004U, 0x0C2000U, 0x028002U, 0x028001U, 0x028000U, + 0x201002U, 0x408008U, 0x201000U, 0x201001U, 0x100030U, 0x014200U, 0x201004U, 0x022400U, 0x408001U, 0x408000U, + 0x201008U, 0x408002U, 0x020140U, 0x408004U, 0x044080U, 0x080820U, 0x100024U, 0x082100U, 0x201010U, 0x010880U, + 0x100020U, 0x100021U, 0x100022U, 0x440040U, 0x040A00U, 0x408010U, 0x080440U, 0x124000U, 0x100028U, 0x200480U, + 0x01A000U, 0x001300U, 0x100014U, 0x060080U, 0x201020U, 0x004140U, 0x100010U, 0x100011U, 0x100012U, 0x080808U, + 0x006400U, 0x408020U, 0x030200U, 0x080804U, 0x100018U, 0x080802U, 0x080801U, 0x080800U, 0x100004U, 0x100005U, + 0x100006U, 0x008600U, 0x100000U, 0x100001U, 0x100002U, 0x100003U, 0x10000CU, 0x011040U, 0x400180U, 0x242000U, + 0x100008U, 0x100009U, 0x10000AU, 0x080810U, 0x052000U, 0x100C00U, 0x201040U, 0x004120U, 0x020108U, 0x081080U, + 0x008A00U, 0x440010U, 0x020104U, 0x408040U, 0x080410U, 0x002280U, 0x020100U, 0x020101U, 0x020102U, 0x310000U, + 0x00C080U, 0x220200U, 0x080408U, 0x440004U, 0x100060U, 0x440002U, 0x440001U, 0x440000U, 0x080402U, 0x011020U, + 0x080400U, 0x080401U, 0x020110U, 0x006800U, 0x080404U, 0x440008U, 0x480200U, 0x004102U, 0x004101U, 0x004100U, + 0x100050U, 0x20A000U, 0x010480U, 0x004104U, 0x200880U, 0x011010U, 0x148000U, 0x004108U, 0x020120U, 0x040600U, + 0x403000U, 0x080840U, 0x100044U, 0x011008U, 0x022800U, 0x004110U, 0x100040U, 0x100041U, 0x100042U, 0x440020U, + 0x011001U, 0x011000U, 0x080420U, 0x011002U, 0x100048U, 0x011004U, 0x204200U, 0x028080U}; + +#define X22 0x00400000 /* vector representation of X^{22} */ +#define X11 0x00000800 /* vector representation of X^{11} */ +#define MASK12 0xfffff800 /* auxiliary vector for testing */ +#define GENPOL 0x00000c75 /* generator polinomial, g(x) */ + +static unsigned int get_syndrome_23127(unsigned int pattern) +/* + * Compute the syndrome corresponding to the given pattern, i.e., the + * remainder after dividing the pattern (when considering it as the vector + * representation of a polynomial) by the generator polynomial, GENPOL. + * In the program this pattern has several meanings: (1) pattern = infomation + * bits, when constructing the encoding table; (2) pattern = error pattern, + * when constructing the decoding table; and (3) pattern = received vector, to + * obtain its syndrome in decoding. + */ +{ + unsigned int aux = X22; + + if (pattern >= X11) { + while (pattern & MASK12) { + while (!(aux & pattern)) + aux = aux >> 1; + + pattern ^= (aux / X11) * GENPOL; + } + } + + return pattern; +} + +unsigned int CGolay::encode23127(unsigned int data) +{ + return ENCODING_TABLE_23127[data]; +} + +unsigned int CGolay::encode24128(unsigned int data) +{ + return ENCODING_TABLE_24128[data]; +} + +unsigned int CGolay::decode23127(unsigned int code) +{ + unsigned int syndrome = ::get_syndrome_23127(code); + unsigned int error_pattern = DECODING_TABLE_23127[syndrome]; + + code ^= error_pattern; + + return code >> 11; +} + +unsigned int CGolay::decode24128(unsigned int code) +{ + return decode23127(code >> 1); +} diff --git a/Common/Golay.h b/Common/Golay.h new file mode 100644 index 0000000..eb3d65f --- /dev/null +++ b/Common/Golay.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2010 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Golay_H +#define Golay_H + +class CGolay { +public: + static unsigned int encode23127(unsigned int data); + static unsigned int encode24128(unsigned int data); + + static unsigned int decode23127(unsigned int code); + static unsigned int decode24128(unsigned int code); +}; + +#endif diff --git a/Common/HardwareController.cpp b/Common/HardwareController.cpp new file mode 100644 index 0000000..c0ba8c9 --- /dev/null +++ b/Common/HardwareController.cpp @@ -0,0 +1,18 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "HardwareController.h" + +IHardwareController::~IHardwareController() +{ +} diff --git a/Common/HardwareController.h b/Common/HardwareController.h new file mode 100644 index 0000000..0035f84 --- /dev/null +++ b/Common/HardwareController.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009,2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef HardwareController_H +#define HardwareController_H + +class IHardwareController { +public: + virtual ~IHardwareController() = 0; + + virtual bool open() = 0; + + virtual void getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) = 0; + + virtual void setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8) = 0; + + virtual void close() = 0; + +private: +}; + +#endif diff --git a/Common/HeaderData.cpp b/Common/HeaderData.cpp new file mode 100644 index 0000000..7f612af --- /dev/null +++ b/Common/HeaderData.cpp @@ -0,0 +1,350 @@ +/* + * Copyright (C) 2009,2011,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CCITTChecksumReverse.h" +#include "HeaderData.h" +#include "DStarDefines.h" + +CHeaderData::CHeaderData() : +m_time(), +m_myCall1(), +m_myCall2(), +m_yourCall(), +m_rptCall1(), +m_rptCall2(), +m_flag1(0x00), +m_flag2(0x00), +m_flag3(0x00), +m_valid(false) +{ +} + +CHeaderData::CHeaderData(const CHeaderData& header) : +m_time(header.m_time), +m_myCall1(header.m_myCall1), +m_myCall2(header.m_myCall2), +m_yourCall(header.m_yourCall), +m_rptCall1(header.m_rptCall1), +m_rptCall2(header.m_rptCall2), +m_flag1(header.m_flag1), +m_flag2(header.m_flag2), +m_flag3(header.m_flag3), +m_valid(header.m_valid) +{ +} + +CHeaderData::CHeaderData(const unsigned char* data, unsigned int length, bool check) : +m_time(), +m_myCall1(), +m_myCall2(), +m_yourCall(), +m_rptCall1(), +m_rptCall2(), +m_flag1(0x00), +m_flag2(0x00), +m_flag3(0x00), +m_valid(true) +{ + wxASSERT(data != NULL); + wxASSERT(length >= (RADIO_HEADER_LENGTH_BYTES - 2U)); + + const unsigned char* p = data; + m_flag1 = *p++; + m_flag2 = *p++; + m_flag3 = *p++; + + m_rptCall2 = wxString((const char*)p, wxConvLocal, LONG_CALLSIGN_LENGTH); + p += LONG_CALLSIGN_LENGTH; + + m_rptCall1 = wxString((const char*)p, wxConvLocal, LONG_CALLSIGN_LENGTH); + p += LONG_CALLSIGN_LENGTH; + + m_yourCall = wxString((const char*)p, wxConvLocal, LONG_CALLSIGN_LENGTH); + p += LONG_CALLSIGN_LENGTH; + + m_myCall1 = wxString((const char*)p, wxConvLocal, LONG_CALLSIGN_LENGTH); + p += LONG_CALLSIGN_LENGTH; + + m_myCall2 = wxString((const char*)p, wxConvLocal, SHORT_CALLSIGN_LENGTH); + + m_time.SetToCurrent(); + + // We have a checksum, check it if asked + if (length >= RADIO_HEADER_LENGTH_BYTES && check) { + CCCITTChecksumReverse cksum; + + cksum.update(data, RADIO_HEADER_LENGTH_BYTES - 2U); + + m_valid = cksum.check(data + RADIO_HEADER_LENGTH_BYTES - 2U); + } +} + +CHeaderData::CHeaderData(const wxString& myCall1, const wxString& myCall2, const wxString& yourCall, + const wxString& rptCall1, const wxString& rptCall2, unsigned char flag1, + unsigned char flag2, unsigned char flag3) : +m_time(), +m_myCall1(myCall1), +m_myCall2(myCall2), +m_yourCall(yourCall), +m_rptCall1(rptCall1), +m_rptCall2(rptCall2), +m_flag1(flag1), +m_flag2(flag2), +m_flag3(flag3), +m_valid(true) +{ + m_time.SetToCurrent(); + + m_myCall1.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_myCall2.Append(wxT(' '), SHORT_CALLSIGN_LENGTH); + m_yourCall.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_rptCall1.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_rptCall2.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + + m_myCall1.Truncate(LONG_CALLSIGN_LENGTH); + m_myCall2.Truncate(SHORT_CALLSIGN_LENGTH); + m_yourCall.Truncate(LONG_CALLSIGN_LENGTH); + m_rptCall1.Truncate(LONG_CALLSIGN_LENGTH); + m_rptCall2.Truncate(LONG_CALLSIGN_LENGTH); +} + +CHeaderData::~CHeaderData() +{ +} + +wxDateTime CHeaderData::getTime() const +{ + return m_time; +} + +wxString CHeaderData::getMyCall1() const +{ + return m_myCall1; +} + +wxString CHeaderData::getMyCall2() const +{ + return m_myCall2; +} + +wxString CHeaderData::getYourCall() const +{ + return m_yourCall; +} + +wxString CHeaderData::getRptCall1() const +{ + return m_rptCall1; +} + +wxString CHeaderData::getRptCall2() const +{ + return m_rptCall2; +} + +unsigned char CHeaderData::getFlag1() const +{ + return m_flag1; +} + +unsigned char CHeaderData::getFlag2() const +{ + return m_flag2; +} + +unsigned char CHeaderData::getFlag3() const +{ + return m_flag3; +} + +void CHeaderData::setMyCall1(const wxString& callsign) +{ + m_myCall1 = callsign; + m_myCall1.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_myCall1.Truncate(LONG_CALLSIGN_LENGTH); +} + +void CHeaderData::setMyCall2(const wxString& callsign) +{ + m_myCall2 = callsign; + m_myCall2.Append(wxT(' '), SHORT_CALLSIGN_LENGTH); + m_myCall2.Truncate(SHORT_CALLSIGN_LENGTH); +} + +void CHeaderData::setYourCall(const wxString& callsign) +{ + m_yourCall = callsign; + m_yourCall.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_yourCall.Truncate(LONG_CALLSIGN_LENGTH); +} + +void CHeaderData::setRptCall1(const wxString& callsign) +{ + m_rptCall1 = callsign; + m_rptCall1.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_rptCall1.Truncate(LONG_CALLSIGN_LENGTH); +} + +void CHeaderData::setRptCall2(const wxString& callsign) +{ + m_rptCall2 = callsign; + m_rptCall2.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_rptCall2.Truncate(LONG_CALLSIGN_LENGTH); +} + +bool CHeaderData::isAck() const +{ + return (m_flag1 & ACK_FLAG) == ACK_FLAG; +} + +bool CHeaderData::isNoResponse() const +{ + return (m_flag1 & NO_RESPONSE) == NO_RESPONSE; +} + +bool CHeaderData::isRelayUnavailable() const +{ + return (m_flag1 & RELAY_UNAVAILABLE) == RELAY_UNAVAILABLE; +} + +void CHeaderData::setRepeaterMode(bool set) +{ + if (set) + m_flag1 |= REPEATER_MASK; + else + m_flag1 &= ~REPEATER_MASK; +} + +bool CHeaderData::isRepeaterMode() const +{ + return (m_flag1 & REPEATER_MASK) == REPEATER_MASK; +} + +void CHeaderData::setDataPacket(bool set) +{ + if (set) + m_flag1 |= DATA_MASK; + else + m_flag1 &= ~DATA_MASK; +} + +bool CHeaderData::isDataPacket() const +{ + return (m_flag1 & DATA_MASK) == DATA_MASK; +} + +void CHeaderData::setInterrupted(bool set) +{ + if (set) + m_flag1 |= INTERRUPTED_MASK; + else + m_flag1 &= ~INTERRUPTED_MASK; +} + +bool CHeaderData::isInterrupted() const +{ + return (m_flag1 & INTERRUPTED_MASK) == INTERRUPTED_MASK; +} + +void CHeaderData::setControlSignal(bool set) +{ + if (set) + m_flag1 |= CONTROL_SIGNAL_MASK; + else + m_flag1 &= ~CONTROL_SIGNAL_MASK; +} + +bool CHeaderData::isControlSignal() const +{ + return (m_flag1 & CONTROL_SIGNAL_MASK) == CONTROL_SIGNAL_MASK; +} + +void CHeaderData::setUrgent(bool set) +{ + if (set) + m_flag1 |= URGENT_MASK; + else + m_flag1 &= ~URGENT_MASK; +} + +bool CHeaderData::isUrgent() const +{ + return (m_flag1 & URGENT_MASK) == URGENT_MASK; +} + +void CHeaderData::setFlag1(unsigned char flag) +{ + m_flag1 = flag; +} + +void CHeaderData::setFlag2(unsigned char flag) +{ + m_flag2 = flag; +} + +void CHeaderData::setFlag3(unsigned char flag) +{ + m_flag3 = flag; +} + +void CHeaderData::setRepeaterFlags(unsigned char set) +{ + m_flag1 &= ~REPEATER_CONTROL_MASK; + m_flag1 |= set & REPEATER_CONTROL_MASK; +} + +unsigned char CHeaderData::getRepeaterFlags() const +{ + return m_flag1 & REPEATER_CONTROL_MASK; +} + +bool CHeaderData::isValid() const +{ + return m_valid; +} + +void CHeaderData::reset() +{ + m_myCall1 = wxT(" "); + m_myCall2 = wxT(" "); + m_yourCall = wxT("CQCQCQ "); + m_rptCall1 = wxT("DIRECT "); + m_rptCall2 = wxT("DIRECT "); + + m_flag1 = 0x00; + m_flag2 = 0x00; + m_flag3 = 0x00; +} + +CHeaderData& CHeaderData::operator=(const CHeaderData& header) +{ + if (&header != this) { + m_time = header.m_time; + m_myCall1 = header.m_myCall1; + m_myCall2 = header.m_myCall2; + m_yourCall = header.m_yourCall; + m_rptCall1 = header.m_rptCall1; + m_rptCall2 = header.m_rptCall2; + m_flag1 = header.m_flag1; + m_flag2 = header.m_flag2; + m_flag3 = header.m_flag3; + m_valid = header.m_valid; + } + + return *this; +} diff --git a/Common/HeaderData.h b/Common/HeaderData.h new file mode 100644 index 0000000..69207dd --- /dev/null +++ b/Common/HeaderData.h @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2009,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef HeaderData_H +#define HeaderData_H + +#include +#include + +class CHeaderData { +public: + CHeaderData(); + CHeaderData(const CHeaderData& header); + CHeaderData(const unsigned char* data, unsigned int length, bool check); + CHeaderData(const wxString& myCall1, const wxString& myCall2, const wxString& yourCall, + const wxString& rptCall1, const wxString& rptCall2, unsigned char flag1 = 0x00, + unsigned char flag2 = 0x00, unsigned char flag3 = 0x00); + ~CHeaderData(); + + wxDateTime getTime() const; + wxString getMyCall1() const; + wxString getMyCall2() const; + wxString getYourCall() const; + wxString getRptCall1() const; + wxString getRptCall2() const; + + unsigned char getFlag1() const; + unsigned char getFlag2() const; + unsigned char getFlag3() const; + + bool isAck() const; + bool isNoResponse() const; + bool isRelayUnavailable() const; + bool isRepeaterMode() const; + bool isDataPacket() const; + bool isInterrupted() const; + bool isControlSignal() const; + bool isUrgent() const; + unsigned char getRepeaterFlags() const; + + void setFlag1(unsigned char flag); + void setFlag2(unsigned char flag); + void setFlag3(unsigned char flag); + + void setMyCall1(const wxString& callsign); + void setMyCall2(const wxString& callsign); + void setYourCall(const wxString& callsign); + void setRptCall1(const wxString& callsign); + void setRptCall2(const wxString& callsign); + + void setRepeaterMode(bool set); + void setDataPacket(bool set); + void setInterrupted(bool set); + void setControlSignal(bool set); + void setUrgent(bool set); + void setRepeaterFlags(unsigned char set); + + bool isValid() const; + + void reset(); + + CHeaderData& operator=(const CHeaderData& header); + +private: + wxDateTime m_time; + wxString m_myCall1; + wxString m_myCall2; + wxString m_yourCall; + wxString m_rptCall1; + wxString m_rptCall2; + unsigned char m_flag1; + unsigned char m_flag2; + unsigned char m_flag3; + bool m_valid; +}; + +#endif diff --git a/Common/K8055Controller.cpp b/Common/K8055Controller.cpp new file mode 100644 index 0000000..6cbe8eb --- /dev/null +++ b/Common/K8055Controller.cpp @@ -0,0 +1,412 @@ +/* + * Copyright (C) 2009,2010 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "K8055Controller.h" + +const unsigned int VELLEMAN_VENDOR_ID = 0x10CFU; +const unsigned int VELLEMAN_PRODUCT_ID = 0x5500U; + +const char REPORT_ID = 0x00U; + +const char CMD_RESET = 0x00U; +const char CMD_SET_ANALOG_DIGITAL = 0x05U; + +const char IN_PORT1 = 0x10U; +const char IN_PORT2 = 0x20U; +const char IN_PORT3 = 0x01U; +const char IN_PORT4 = 0x40U; +const char IN_PORT5 = 0x80U; + +const char OUT_PORT1 = 0x01U; +const char OUT_PORT2 = 0x02U; +const char OUT_PORT3 = 0x04U; +const char OUT_PORT4 = 0x08U; +const char OUT_PORT5 = 0x10U; +const char OUT_PORT6 = 0x20U; +const char OUT_PORT7 = 0x40U; +const char OUT_PORT8 = 0x80U; + +#if defined(__WINDOWS__) + +#include +#include + +const DWORD USB_BUFSIZE = 9UL; + +CK8055Controller::CK8055Controller(unsigned int address) : +m_address(address), +m_outp1(false), +m_outp2(false), +m_outp3(false), +m_outp4(false), +m_outp5(false), +m_outp6(false), +m_outp7(false), +m_outp8(false), +m_handle(INVALID_HANDLE_VALUE) +{ +} + +CK8055Controller::~CK8055Controller() +{ +} + +bool CK8055Controller::open() +{ + wxASSERT(m_handle == INVALID_HANDLE_VALUE); + + GUID guid; + ::HidD_GetHidGuid(&guid); + + HDEVINFO devInfo = ::SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (devInfo == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from SetupDiGetClassDevs: err=%u"), ::GetLastError()); + return false; + } + + SP_DEVICE_INTERFACE_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (DWORD index = 0U; ::SetupDiEnumDeviceInterfaces(devInfo, NULL, &guid, index, &devInfoData); index++) { + // Find the required length of the device structure + DWORD length; + ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, NULL, 0U, &length, NULL); + + PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = PSP_DEVICE_INTERFACE_DETAIL_DATA(::malloc(length)); + detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // Get the detailed data into the newly allocated device structure + DWORD required; + BOOL res = ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, detailData, length, &required, NULL); + if (!res) { + wxLogError(wxT("Error from SetupDiGetDeviceInterfaceDetail: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + // Get the handle for getting the attributes + HANDLE handle = ::CreateFile(detailData->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (handle == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from CreateFile: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + HIDD_ATTRIBUTES attributes; + attributes.Size = sizeof(HIDD_ATTRIBUTES); + res = ::HidD_GetAttributes(handle, &attributes); + if (!res) { + wxLogError(wxT("Error from HidD_GetAttributes: err=%u"), ::GetLastError()); + ::CloseHandle(handle); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::CloseHandle(handle); + + // Is this a Velleman K8055 and the right one? + if (attributes.VendorID == VELLEMAN_VENDOR_ID && attributes.ProductID == (VELLEMAN_PRODUCT_ID + m_address)) { + m_handle = ::CreateFile(detailData->DevicePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (m_handle == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from CreateFile: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + return true; + } + + ::free(detailData); + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + + return false; +} + +void CK8055Controller::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + char buffer[USB_BUFSIZE]; + buffer[0] = REPORT_ID; + buffer[1] = 0x00U; + buffer[2] = 0x00U; + buffer[3] = 0x00U; + buffer[4] = 0x00U; + buffer[5] = 0x00U; + buffer[6] = 0x00U; + buffer[7] = 0x00U; + buffer[8] = 0x00U; + + DWORD read; + BOOL res = ::ReadFile(m_handle, buffer, USB_BUFSIZE, &read, NULL); + if (!res) { + wxLogError(wxT("Error from ReadFile: err=%u, read=%u"), ::GetLastError(), read); + return; + } + + // Do we have data? + if (buffer[2] != 0x00) { + inp1 = (buffer[1] & IN_PORT1) == IN_PORT1; + inp2 = (buffer[1] & IN_PORT2) == IN_PORT2; + inp3 = (buffer[1] & IN_PORT3) == IN_PORT3; + inp4 = (buffer[1] & IN_PORT4) == IN_PORT4; + inp5 = (buffer[1] & IN_PORT5) == IN_PORT5; + } +} + +void CK8055Controller::setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + if (outp1 == m_outp1 && outp2 == m_outp2 && outp3 == m_outp3 && outp4 == m_outp4 && + outp5 == m_outp5 && outp6 == m_outp6 && outp7 == m_outp7 && outp8 == m_outp8) + return; + + char buffer[USB_BUFSIZE]; + buffer[0] = REPORT_ID; + buffer[1] = CMD_SET_ANALOG_DIGITAL; + buffer[2] = 0x00U; + buffer[3] = 0x00U; + buffer[4] = 0x00U; + buffer[5] = 0x00U; + buffer[6] = 0x00U; + buffer[7] = 0x00U; + buffer[8] = 0x00U; + + if (outp1) + buffer[2] |= OUT_PORT1; + if (outp2) + buffer[2] |= OUT_PORT2; + if (outp3) + buffer[2] |= OUT_PORT3; + if (outp4) + buffer[2] |= OUT_PORT4; + if (outp5) + buffer[2] |= OUT_PORT5; + if (outp6) + buffer[2] |= OUT_PORT6; + if (outp7) + buffer[2] |= OUT_PORT7; + if (outp8) + buffer[2] |= OUT_PORT8; + + DWORD written; + BOOL res = ::WriteFile(m_handle, buffer, USB_BUFSIZE, &written, NULL); + if (!res || written != USB_BUFSIZE) + wxLogError(wxT("Error from WriteFile: err=%u, written=%u"), ::GetLastError(), written); + + m_outp1 = outp1; + m_outp2 = outp2; + m_outp3 = outp3; + m_outp4 = outp4; + m_outp5 = outp5; + m_outp6 = outp6; + m_outp7 = outp7; + m_outp8 = outp8; +} + +void CK8055Controller::close() +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; +} + +#else + +const unsigned int VELLEMAN_HID_INTERFACE = 0U; + +const unsigned int USB_OUTPUT_ENDPOINT = 0x01U; +const unsigned int USB_INPUT_ENDPOINT = 0x81U; + +const int USB_BUFSIZE = 8; + +const int USB_TIMEOUT = 5000; + +CK8055Controller::CK8055Controller(unsigned int address) : +m_address(address), +m_outp1(false), +m_outp2(false), +m_outp3(false), +m_outp4(false), +m_outp5(false), +m_outp6(false), +m_outp7(false), +m_outp8(false), +m_context(NULL), +m_handle(NULL) +{ + ::libusb_init(&m_context); +} + +CK8055Controller::~CK8055Controller() +{ + wxASSERT(m_context != NULL); + + ::libusb_exit(m_context); +} + +bool CK8055Controller::open() +{ + wxASSERT(m_context != NULL); + wxASSERT(m_handle == NULL); + + m_handle = ::libusb_open_device_with_vid_pid(m_context, VELLEMAN_VENDOR_ID, VELLEMAN_PRODUCT_ID + m_address); + if (m_handle == NULL) { + wxLogError(wxT("Could not open the Velleman K8055")); + return false; + } + + int res = ::libusb_claim_interface(m_handle, VELLEMAN_HID_INTERFACE); + if (res != 0) { + res = ::libusb_detach_kernel_driver(m_handle, VELLEMAN_HID_INTERFACE); + if (res != 0) { + wxLogError(wxT("Error from libusb_detach_kernel_driver: err=%d"), res); + ::libusb_close(m_handle); + m_handle = NULL; + return false; + } + + res = ::libusb_claim_interface(m_handle, VELLEMAN_HID_INTERFACE); + if (res != 0) { + wxLogError(wxT("Error from libusb_claim_interface: err=%d"), res); + ::libusb_close(m_handle); + m_handle = NULL; + return false; + } + } + + ::libusb_set_configuration(m_handle, 1); + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + return true; +} + +void CK8055Controller::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + wxASSERT(m_handle != NULL); + + unsigned char buffer[USB_BUFSIZE]; + buffer[0] = 0x00; + buffer[1] = 0x00; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0x00; + buffer[5] = 0x00; + buffer[6] = 0x00; + buffer[7] = 0x00; + + int written; + int res = ::libusb_interrupt_transfer(m_handle, USB_INPUT_ENDPOINT, buffer, USB_BUFSIZE, &written, USB_TIMEOUT); + if (res != 0) { + wxLogError(wxT("Error from libusb_interrupt_transfer: err=%d"), res); + return; + } + + if (written != USB_BUFSIZE) + return; + + // Do we have data? + if (buffer[1] != 0x00) { + inp1 = (buffer[0] & IN_PORT1) == IN_PORT1; + inp2 = (buffer[0] & IN_PORT2) == IN_PORT2; + inp3 = (buffer[0] & IN_PORT3) == IN_PORT3; + inp4 = (buffer[0] & IN_PORT4) == IN_PORT4; + inp5 = (buffer[0] & IN_PORT5) == IN_PORT5; + } +} + +void CK8055Controller::setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8) +{ + wxASSERT(m_handle != NULL); + + if (outp1 == m_outp1 && outp2 == m_outp2 && outp3 == m_outp3 && outp4 == m_outp4 && + outp5 == m_outp5 && outp6 == m_outp6 && outp7 == m_outp7 && outp8 == m_outp8) + return; + + unsigned char buffer[USB_BUFSIZE]; + buffer[0] = CMD_SET_ANALOG_DIGITAL; + buffer[1] = 0x00; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0x00; + buffer[5] = 0x00; + buffer[6] = 0x00; + buffer[7] = 0x00; + + if (outp1) + buffer[1] |= OUT_PORT1; + if (outp2) + buffer[1] |= OUT_PORT2; + if (outp3) + buffer[1] |= OUT_PORT3; + if (outp4) + buffer[1] |= OUT_PORT4; + if (outp5) + buffer[1] |= OUT_PORT5; + if (outp6) + buffer[1] |= OUT_PORT6; + if (outp7) + buffer[1] |= OUT_PORT7; + if (outp8) + buffer[1] |= OUT_PORT8; + + int written; + int res = ::libusb_interrupt_transfer(m_handle, USB_OUTPUT_ENDPOINT, buffer, USB_BUFSIZE, &written, USB_TIMEOUT); + if (res != 0) { + wxLogError(wxT("Error from libusb_interrupt_transfer: err=%d"), res); + return; + } + + if (written != USB_BUFSIZE) + return; + + m_outp1 = outp1; + m_outp2 = outp2; + m_outp3 = outp3; + m_outp4 = outp4; + m_outp5 = outp5; + m_outp6 = outp6; + m_outp7 = outp7; + m_outp8 = outp8; +} + +void CK8055Controller::close() +{ + wxASSERT(m_handle != NULL); + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + ::libusb_release_interface(m_handle, VELLEMAN_HID_INTERFACE); + + ::libusb_close(m_handle); + m_handle = NULL; +} + +#endif + diff --git a/Common/K8055Controller.h b/Common/K8055Controller.h new file mode 100644 index 0000000..619e1d9 --- /dev/null +++ b/Common/K8055Controller.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2009 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef K8055Controller_H +#define K8055Controller_H + +#include "HardwareController.h" + +#include + +#if defined(__WINDOWS__) +#include +#else +#include +#endif + +class CK8055Controller : public IHardwareController { +public: + CK8055Controller(unsigned int address); + virtual ~CK8055Controller(); + + virtual bool open(); + + virtual void getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5); + + virtual void setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8); + + virtual void close(); + +private: + unsigned int m_address; + bool m_outp1; + bool m_outp2; + bool m_outp3; + bool m_outp4; + bool m_outp5; + bool m_outp6; + bool m_outp7; + bool m_outp8; +#if defined(__WINDOWS__) + HANDLE m_handle; +#else + libusb_context* m_context; + libusb_device_handle* m_handle; +#endif +}; + +#endif diff --git a/Common/LogEvent.cpp b/Common/LogEvent.cpp new file mode 100644 index 0000000..2cd850c --- /dev/null +++ b/Common/LogEvent.cpp @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2010 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "LogEvent.h" + +CLogEvent::CLogEvent(const wxString& text, wxEventType type, int id) : +wxEvent(id, type), +m_text(text) +{ +} + +CLogEvent::CLogEvent(const CLogEvent& event) : +wxEvent(event), +m_text(event.m_text) +{ +} + +CLogEvent::~CLogEvent() +{ +} + +wxString CLogEvent::getText() const +{ + return m_text; +} + +wxEvent* CLogEvent::Clone() const +{ + return new CLogEvent(*this); +} diff --git a/Common/LogEvent.h b/Common/LogEvent.h new file mode 100644 index 0000000..4721989 --- /dev/null +++ b/Common/LogEvent.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2010 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef LogEvent_H +#define LogEvent_H + +#include + +class CLogEvent : public wxEvent { +public: + CLogEvent(const wxString& text, wxEventType type, int id = 0); + virtual ~CLogEvent(); + + virtual wxString getText() const; + + virtual wxEvent* Clone() const; + +protected: + CLogEvent(const CLogEvent& event); + +private: + wxString m_text; +}; + +#endif diff --git a/Common/Logger.cpp b/Common/Logger.cpp new file mode 100644 index 0000000..8edd3b1 --- /dev/null +++ b/Common/Logger.cpp @@ -0,0 +1,122 @@ +/* + * Copyright (C) 2002,2003,2009,2011,2012,2018 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "Logger.h" + +CLogger::CLogger(const wxString& directory, const wxString& name) : +#if(defined(__WINDOWS__)) +m_day(0), +#endif +wxLog(), +m_name(name), +m_file(NULL), +m_fileName() +{ + m_file = new wxFFile; + + m_fileName.SetPath(directory); + m_fileName.SetExt(wxT("log")); + +#if(defined(__WINDOWS__)) + time_t timestamp; + ::time(×tamp); + struct tm* tm = ::gmtime(×tamp); + + wxString text; + text.Printf(wxT("%s-%04d-%02d-%02d"), m_name.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + + m_day = tm->tm_yday; + m_fileName.SetName(text); +#else + m_fileName.SetName(m_name); +#endif + + bool ret = m_file->Open(m_fileName.GetFullPath(), wxT("a+t")); + if (!ret) { + wxLogError(wxT("Cannot open %s file for appending"), m_fileName.GetFullPath().c_str()); + return; + } +} + +CLogger::~CLogger() +{ + wxASSERT(m_file != NULL); + + m_file->Close(); + delete m_file; +} + +void CLogger::DoLogRecord(wxLogLevel level, const wxString& msg, const wxLogRecordInfo& info) +{ + wxASSERT(m_file != NULL); + wxASSERT(m_file->IsOpened()); + + wxString letter; + + switch (level) { + case wxLOG_FatalError: letter = wxT("F"); break; + case wxLOG_Error: letter = wxT("E"); break; + case wxLOG_Warning: letter = wxT("W"); break; + case wxLOG_Info: letter = wxT("I"); break; + case wxLOG_Message: letter = wxT("M"); break; + case wxLOG_Status: letter = wxT("M"); break; + case wxLOG_Trace: letter = wxT("T"); break; + case wxLOG_Debug: letter = wxT("D"); break; + default: letter = wxT("U"); break; + } + + struct tm* tm = ::gmtime(&info.timestamp); + + wxString message; + message.Printf(wxT("%s: %04d-%02d-%02d %02d:%02d:%02d: %s\n"), letter.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, msg.c_str()); + + logString(message, info.timestamp); + + if (level == wxLOG_FatalError) + ::abort(); +} + +void CLogger::logString(const wxString& msg, time_t timestamp) +{ + wxASSERT(m_file != NULL); + wxASSERT(m_file->IsOpened()); + +#if(defined(__WINDOWS__)) + struct tm* tm = ::gmtime(×tamp); + + int day = tm->tm_yday; + if (day != m_day) { + wxString text; + text.Printf(wxT("%s-%04d-%02d-%02d"), m_name.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday); + + m_day = day; + m_fileName.SetName(text); + + m_file->Close(); + + bool ret = m_file->Open(m_fileName.GetFullPath(), wxT("a+t")); + if (!ret) { + wxLogError(wxT("Cannot open %s file for appending"), m_fileName.GetFullPath().c_str()); + return; + } + } +#endif + + m_file->Write(msg); + m_file->Flush(); +} diff --git a/Common/Logger.h b/Common/Logger.h new file mode 100644 index 0000000..0d082f0 --- /dev/null +++ b/Common/Logger.h @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2002,2003,2009,2011,2012,2018 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Logger_H +#define Logger_H + +#include +#include +#include +#include + +class CLogger : public wxLog { +public: + CLogger(const wxString& directory, const wxString& name); + virtual ~CLogger(); + + virtual void DoLogRecord(wxLogLevel level, const wxString& msg, const wxLogRecordInfo& info); + +private: + wxString m_name; + wxFFile* m_file; + wxFileName m_fileName; +#if(defined(__WINDOWS__)) + int m_day; +#endif + + void logString(const wxString& msg, time_t timestamp); +}; + +#endif diff --git a/Common/MMDVMController.cpp b/Common/MMDVMController.cpp new file mode 100644 index 0000000..9028b3b --- /dev/null +++ b/Common/MMDVMController.cpp @@ -0,0 +1,866 @@ +/* + * Copyright (C) 2011-2016 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CCITTChecksumReverse.h" +#include "MMDVMController.h" +#include "DStarDefines.h" +#include "Timer.h" + +#if defined(__WINDOWS__) +#include +#else +#include +#endif + +const unsigned char MMDVM_FRAME_START = 0xE0U; + +const unsigned char MMDVM_GET_VERSION = 0x00U; +const unsigned char MMDVM_GET_STATUS = 0x01U; +const unsigned char MMDVM_SET_CONFIG = 0x02U; + +const unsigned char MMDVM_DSTAR_HEADER = 0x10U; +const unsigned char MMDVM_DSTAR_DATA = 0x11U; +const unsigned char MMDVM_DSTAR_LOST = 0x12U; +const unsigned char MMDVM_DSTAR_EOT = 0x13U; + +const unsigned char MMDVM_ACK = 0x70U; +const unsigned char MMDVM_NAK = 0x7FU; + +const unsigned char MMDVM_DUMP = 0xF0U; +const unsigned char MMDVM_DEBUG1 = 0xF1U; +const unsigned char MMDVM_DEBUG2 = 0xF2U; +const unsigned char MMDVM_DEBUG3 = 0xF3U; +const unsigned char MMDVM_DEBUG4 = 0xF4U; +const unsigned char MMDVM_DEBUG5 = 0xF5U; + +const unsigned int MAX_RESPONSES = 30U; + +const unsigned int BUFFER_LENGTH = 200U; + +CMMDVMController::CMMDVMController(const wxString& port, const wxString& path, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int rxLevel, unsigned int txLevel) : +CModem(), +m_port(port), +m_path(path), +m_rxInvert(rxInvert), +m_txInvert(txInvert), +m_pttInvert(pttInvert), +m_txDelay(txDelay), +m_txLevel(txLevel), +m_rxLevel(rxLevel), +m_serial(port, SERIAL_115200, true), +m_buffer(NULL), +m_txData(1000U), +m_rx(false) +{ + wxASSERT(!port.IsEmpty()); + + m_buffer = new unsigned char[BUFFER_LENGTH]; +} + +CMMDVMController::~CMMDVMController() +{ + delete[] m_buffer; +} + +bool CMMDVMController::start() +{ + findPort(); + + bool ret = openModem(); + if (!ret) + return false; + + findPath(); + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CMMDVMController::Entry() +{ + wxLogMessage(wxT("Starting MMDVM Controller thread")); + + // Clock every 5ms-ish + CTimer pollTimer(200U, 0U, 100U); + pollTimer.start(); + + unsigned char writeType = DSMTT_NONE; + unsigned char writeLength = 0U; + unsigned char* writeBuffer = new unsigned char[BUFFER_LENGTH]; + + unsigned int space = 0U; + + while (!m_stopped) { + // Poll the modem status every 100ms + if (pollTimer.hasExpired()) { + bool ret = readStatus(); + if (!ret) { + ret = findModem(); + if (!ret) { + wxLogError(wxT("Stopping MMDVM Controller thread")); + return NULL; + } + } + + pollTimer.start(); + } + + unsigned int length; + RESP_TYPE_MMDVM type = getResponse(m_buffer, length); + + switch (type) { + case RTDVM_TIMEOUT: + break; + + case RTDVM_ERROR: { + bool ret = findModem(); + if (!ret) { + wxLogError(wxT("Stopping MMDVM Controller thread")); + return NULL; + } + } + break; + + case RTDVM_DSTAR_HEADER: { + // CUtils::dump(wxT("RT_DSTAR_HEADER"), m_buffer, length); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 3U, RADIO_HEADER_LENGTH_BYTES); + + m_rx = true; + } + break; + + case RTDVM_DSTAR_DATA: { + // CUtils::dump(wxT("RT_DSTAR_DATA"), m_buffer, length); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_buffer + 3U, DV_FRAME_LENGTH_BYTES); + + m_rx = true; + } + break; + + case RTDVM_DSTAR_EOT: { + // wxLogMessage(wxT("RT_DSTAR_EOT")); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + break; + + case RTDVM_DSTAR_LOST: { + // wxLogMessage(wxT("RT_DSTAR_LOST")); + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_LOST; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + break; + + case RTDVM_GET_STATUS: { + bool dstar = (m_buffer[3U] & 0x01U) == 0x01U; + if (!dstar) { + wxLogError(wxT("D-Star not enabled in the MMDVM!!!")); + wxLogError(wxT("Stopping MMDVM Controller thread")); + return NULL; + } + + m_tx = (m_buffer[5U] & 0x01U) == 0x01U; + + bool adcOverflow = (m_buffer[5U] & 0x02U) == 0x02U; + if (adcOverflow) + wxLogWarning(wxT("MMDVM ADC levels have overflowed")); + + space = m_buffer[6U]; + // CUtils::dump(wxT("GET_STATUS"), m_buffer, length); + // wxLogMessage(wxT("PTT=%d space=%u"), int(m_tx), space); + } + break; + + // These should not be received in this loop, but don't complain if we do + case RTDVM_GET_VERSION: + case RTDVM_ACK: + break; + + case RTDVM_NAK: { + switch (m_buffer[3U]) { + case MMDVM_DSTAR_HEADER: + wxLogWarning(wxT("Received a header NAK from the MMDVM, reason = %u"), m_buffer[4U]); + break; + case MMDVM_DSTAR_DATA: + wxLogWarning(wxT("Received a data NAK from the MMDVM, reason = %u"), m_buffer[4U]); + break; + case MMDVM_DSTAR_EOT: + wxLogWarning(wxT("Received an EOT NAK from the MMDVM, reason = %u"), m_buffer[4U]); + break; + default: + wxLogWarning(wxT("Received a NAK from the MMDVM, command = 0x%02X, reason = %u"), m_buffer[3U], m_buffer[4U]); + break; + } + } + break; + + case RTDVM_DUMP: + CUtils::dump(wxT("Modem dump"), m_buffer + 3U, length - 3U); + break; + + case RTDVM_DEBUG1: + case RTDVM_DEBUG2: + case RTDVM_DEBUG3: + case RTDVM_DEBUG4: + case RTDVM_DEBUG5: + printDebug(); + break; + + default: + wxLogMessage(wxT("Unknown message, type: %02X"), m_buffer[2U]); + CUtils::dump(wxT("Buffer dump"), m_buffer, length); + break; + } + + if (writeType == DSMTT_NONE && m_txData.hasData()) { + wxMutexLocker locker(m_mutex); + + m_txData.getData(&writeType, 1U); + m_txData.getData(&writeLength, 1U); + m_txData.getData(writeBuffer, writeLength); + } + + if (space > 4U && writeType == DSMTT_HEADER) { + // CUtils::dump(wxT("Write Header"), writeBuffer, writeLength); + + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing the header to the MMDVM")); + + writeType = DSMTT_NONE; + space -= 4U; + } + + if (space > 1U && (writeType == DSMTT_DATA || writeType == DSMTT_EOT)) { + // CUtils::dump(wxT("Write Data"), writeBuffer, writeLength); + + int ret = m_serial.write(writeBuffer, writeLength); + if (ret != int(writeLength)) + wxLogWarning(wxT("Error when writing data to the MMDVM")); + + writeType = DSMTT_NONE; + space--; + } + + Sleep(5UL); + + pollTimer.clock(); + } + + wxLogMessage(wxT("Stopping MMDVM Controller thread")); + + delete[] writeBuffer; + + m_serial.close(); + + return NULL; +} + +bool CMMDVMController::writeHeader(const CHeaderData& header) +{ + bool ret = m_txData.hasSpace(46U); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + unsigned char buffer[50U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = RADIO_HEADER_LENGTH_BYTES + 3U; + buffer[2U] = MMDVM_DSTAR_HEADER; + + ::memset(buffer + 3U, ' ', RADIO_HEADER_LENGTH_BYTES); + + buffer[3U] = header.getFlag1(); + buffer[4U] = header.getFlag2(); + buffer[5U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 6U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 14U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 22U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 30U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer[i + 38U] = my2.GetChar(i); + + CCCITTChecksumReverse cksum1; + cksum1.update(buffer + 3U, RADIO_HEADER_LENGTH_BYTES - 2U); + cksum1.result(buffer + 42U); + + wxMutexLocker locker(m_mutex); + + unsigned char type = DSMTT_HEADER; + m_txData.addData(&type, 1U); + + unsigned char len = RADIO_HEADER_LENGTH_BYTES + 3U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, RADIO_HEADER_LENGTH_BYTES + 3U); + + return true; +} + +bool CMMDVMController::writeData(const unsigned char* data, unsigned int length, bool end) +{ + bool ret = m_txData.hasSpace(17U); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + unsigned char buffer[20U]; + + if (end) { + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = MMDVM_DSTAR_EOT; + + wxMutexLocker locker(m_mutex); + + unsigned char type = DSMTT_EOT; + m_txData.addData(&type, 1U); + + unsigned char len = 3U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, 3U); + + return true; + } + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = DV_FRAME_LENGTH_BYTES + 3U; + buffer[2U] = MMDVM_DSTAR_DATA; + ::memcpy(buffer + 3U, data, DV_FRAME_LENGTH_BYTES); + + wxMutexLocker locker(m_mutex); + + unsigned char type = DSMTT_DATA; + m_txData.addData(&type, 1U); + + unsigned char len = DV_FRAME_LENGTH_BYTES + 3U; + m_txData.addData(&len, 1U); + + m_txData.addData(buffer, DV_FRAME_LENGTH_BYTES + 3U); + + return true; +} + +unsigned int CMMDVMController::getSpace() +{ + return m_txData.freeSpace() / (DV_FRAME_LENGTH_BYTES + 5U); +} + +bool CMMDVMController::isTXReady() +{ + if (m_tx) + return false; + + return m_txData.isEmpty(); +} + +bool CMMDVMController::readVersion() +{ + ::wxSleep(2); + + for (unsigned int i = 0U; i < 6U; i++) { + unsigned char buffer[3U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = MMDVM_GET_VERSION; + + // CUtils::dump(wxT("Written"), buffer, 3U); + + int ret = m_serial.write(buffer, 3U); + if (ret != 3) + return false; + + for (unsigned int count = 0U; count < MAX_RESPONSES; count++) { + ::wxMilliSleep(10UL); + + unsigned int length; + RESP_TYPE_MMDVM resp = getResponse(m_buffer, length); + if (resp == RTDVM_GET_VERSION) { + wxString description((char*)(m_buffer + 4U), wxConvLocal, length - 4U); + + wxLogInfo(wxT("MMDVM protocol version: %u, description: %s"), m_buffer[3U], description.c_str()); + + return true; + } + } + + ::wxSleep(1); + } + + wxLogError(wxT("Unable to read the firmware version after six attempts")); + + return false; +} + +bool CMMDVMController::readStatus() +{ + unsigned char buffer[3U]; + + buffer[0U] = MMDVM_FRAME_START; + buffer[1U] = 3U; + buffer[2U] = MMDVM_GET_STATUS; + + return m_serial.write(buffer, 3U) == 3; +} + +bool CMMDVMController::setConfig() +{ + unsigned char buffer[25U]; + + buffer[0U] = MMDVM_FRAME_START; + + buffer[1U] = 19U; + + buffer[2U] = MMDVM_SET_CONFIG; + + buffer[3U] = 0x00U; + if (m_rxInvert) + buffer[3U] |= 0x01U; + if (m_txInvert) + buffer[3U] |= 0x02U; + if (m_pttInvert) + buffer[3U] |= 0x04U; + + buffer[4U] = 0x01U; // D-Star only + + buffer[5U] = m_txDelay / 10U; // In 10ms units + + buffer[6U] = 1U; // STATE_DSTAR + + buffer[7U] = (m_rxLevel * 255U) / 100U; + buffer[8U] = (m_txLevel * 255U) / 100U; + + buffer[9U] = 0U; // DMR Color Code + + buffer[10U] = 0U; // DMR Delay + + buffer[11U] = 0U; // Osc. Offset + + buffer[12U] = (m_txLevel * 255U) / 100U; + buffer[13U] = (m_txLevel * 255U) / 100U; + buffer[14U] = (m_txLevel * 255U) / 100U; + buffer[15U] = (m_txLevel * 255U) / 100U; + + buffer[16U] = 128U; + buffer[17U] = 128U; + + buffer[18U] = (m_txLevel * 255U) / 100U; + + // CUtils::dump(wxT("Written"), buffer, 19U); + + int ret = m_serial.write(buffer, 19U); + if (ret != 19U) + return false; + + unsigned int count = 0U; + unsigned int length; + RESP_TYPE_MMDVM resp; + do { + ::wxMilliSleep(10UL); + + resp = getResponse(m_buffer, length); + + if (resp != RTDVM_ACK && resp != RTDVM_NAK) { + count++; + if (count >= MAX_RESPONSES) { + wxLogError(wxT("The MMDVM is not responding to the SET_CONFIG command")); + return false; + } + } + } while (resp != RTDVM_ACK && resp != RTDVM_NAK); + + // CUtils::dump(wxT("Response"), m_buffer, length); + + if (resp == RTDVM_NAK) { + wxLogError(wxT("Received a NAK to the SET_CONFIG command from the modem")); + return false; + } + + return true; +} + +RESP_TYPE_MMDVM CMMDVMController::getResponse(unsigned char *buffer, unsigned int& length) +{ + // Get the start of the frame or nothing at all + int ret = m_serial.read(buffer + 0U, 1U); + if (ret < 0) { + wxLogError(wxT("Error when reading from the MMDVM")); + return RTDVM_ERROR; + } + + if (ret == 0) + return RTDVM_TIMEOUT; + + if (buffer[0U] != MMDVM_FRAME_START) + return RTDVM_TIMEOUT; + + ret = m_serial.read(buffer + 1U, 1U); + if (ret < 0) { + wxLogError(wxT("Error when reading from the MMDVM")); + return RTDVM_ERROR; + } + + if (ret == 0) + return RTDVM_TIMEOUT; + + length = buffer[1U]; + + if (length >= 100U) { + wxLogError(wxT("Invalid data received from the MMDVM")); + CUtils::dump(wxT("Data"), buffer, 2U); + return RTDVM_TIMEOUT; + } + + unsigned int offset = 2U; + + while (offset < length) { + int ret = m_serial.read(buffer + offset, length - offset); + if (ret < 0) { + wxLogError(wxT("Error when reading from the MMDVM")); + return RTDVM_ERROR; + } + + if (ret > 0) + offset += ret; + + if (ret == 0) + Sleep(5UL); + } + + // CUtils::dump(wxT("Received"), buffer, length); + + switch (buffer[2U]) { + case MMDVM_GET_STATUS: + return RTDVM_GET_STATUS; + case MMDVM_GET_VERSION: + return RTDVM_GET_VERSION; + case MMDVM_DSTAR_HEADER: + return RTDVM_DSTAR_HEADER; + case MMDVM_DSTAR_DATA: + return RTDVM_DSTAR_DATA; + case MMDVM_DSTAR_EOT: + return RTDVM_DSTAR_EOT; + case MMDVM_DSTAR_LOST: + return RTDVM_DSTAR_LOST; + case MMDVM_ACK: + return RTDVM_ACK; + case MMDVM_NAK: + return RTDVM_NAK; + case MMDVM_DUMP: + return RTDVM_DUMP; + case MMDVM_DEBUG1: + return RTDVM_DEBUG1; + case MMDVM_DEBUG2: + return RTDVM_DEBUG2; + case MMDVM_DEBUG3: + return RTDVM_DEBUG3; + case MMDVM_DEBUG4: + return RTDVM_DEBUG4; + case MMDVM_DEBUG5: + return RTDVM_DEBUG5; + default: + return RTDVM_UNKNOWN; + } +} + +wxString CMMDVMController::getPath() const +{ + return m_path; +} + +bool CMMDVMController::findPort() +{ + if (m_path.IsEmpty()) + return false; + +#if defined(__WINDOWS__) +#else + wxDir dir; + bool ret1 = dir.Open(wxT("/sys/class/tty")); + if (!ret1) { + wxLogError(wxT("Cannot open directory /sys/class/tty")); + return false; + } + + wxString fileName; + ret1 = dir.GetFirst(&fileName, wxT("ttyACM*")); + while (ret1) { + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), fileName.c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + ::strcat(cpath, "/device"); + ret2 = ::readlink(cpath, symlink, 255U); + if (ret2 < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret2); + } else { + // Get all but the last section + wxString fullPath = wxString(symlink, wxConvLocal, ret2); + path = fullPath.BeforeLast(wxT('/')); + } + + if (path.IsSameAs(m_path)) { + m_port.Printf(wxT("/dev/%s"), fileName.c_str()); + + wxLogMessage(wxT("Found modem port of %s based on the path"), m_port.c_str()); + + return true; + } + + ret1 = dir.GetNext(&fileName); + } +#endif + + return false; +} + +bool CMMDVMController::findPath() +{ +#if defined(__WINDOWS__) +#ifdef notdef + GUID guids[5U]; + + DWORD count; + BOOL res = ::SetupDiClassGuidsFromName(L"Multifunction", guids, 5U, &count); + if (!res) { + wxLogError(wxT("Error from SetupDiClassGuidsFromName: err=%u"), ::GetLastError()); + return false; + } + + for (DWORD i = 0U; i < count; i++) { + HDEVINFO devInfo = ::SetupDiGetClassDevs(&guids[i], NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (devInfo == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from SetupDiGetClassDevs: err=%u"), ::GetLastError()); + return false; + } + + SP_DEVICE_INTERFACE_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + for (DWORD index = 0U; ::SetupDiEnumDeviceInterfaces(devInfo, NULL, &guids[i], index, &devInfoData); index++) { + // Find the required length of the device structure + DWORD length; + ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, NULL, 0U, &length, NULL); + + PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = PSP_DEVICE_INTERFACE_DETAIL_DATA(::malloc(length)); + detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // Get the detailed data into the newly allocated device structure + DWORD required; + res = ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, detailData, length, &required, NULL); + if (!res) { + wxLogError(wxT("Error from SetupDiGetDeviceInterfaceDetail: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::free(detailData); + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + } + + return false; +#endif +#else + wxString path; + path.Printf(wxT("/sys/class/tty/%s"), m_port.Mid(5U).c_str()); + + char cpath[255U]; + ::memset(cpath, 0x00U, 255U); + + for (unsigned int i = 0U; i < path.Len(); i++) + cpath[i] = path.GetChar(i); + + char symlink[255U]; + int ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + ::strcat(cpath, "/device"); + ret = ::readlink(cpath, symlink, 255U); + if (ret < 0) { + wxLogError(wxT("Error from readlink()")); + return false; + } + + path = wxString(symlink, wxConvLocal, ret); + } else { + wxString fullPath = wxString(symlink, wxConvLocal, ret); + path = fullPath.BeforeLast(wxT('/')); + } + + if (m_path.IsEmpty()) + wxLogMessage(wxT("Found modem path of %s"), path.c_str()); + + m_path = path; +#endif + + return true; +} + +bool CMMDVMController::findModem() +{ + m_serial.close(); + + // Tell the repeater that the signal has gone away + if (m_rx) { + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rx = false; + } + + unsigned int count = 0U; + + // Purge the transmit buffer every 500ms to avoid overflow, but only try and reopen the modem every 2s + while (!m_stopped) { + count++; + if (count >= 4U) { + wxLogMessage(wxT("Trying to reopen the modem")); + + bool ret = findPort(); + if (ret) { + ret = openModem(); + if (ret) + return true; + } + + count = 0U; + } + + Sleep(500UL); + } + + return false; +} + +bool CMMDVMController::openModem() +{ + bool ret = m_serial.open(); + if (!ret) + return false; + + ret = readVersion(); + if (!ret) { + m_serial.close(); + return false; + } + + ret = setConfig(); + if (!ret) { + m_serial.close(); + return false; + } + + return true; +} + +void CMMDVMController::printDebug() +{ + unsigned int length = m_buffer[1U]; + if (m_buffer[2U] == 0xF1U) { + wxString message((char*)(m_buffer + 3U), wxConvLocal, length - 3U); + wxLogMessage(wxT("Debug: %s"), message.c_str()); + } else if (m_buffer[2U] == 0xF2U) { + wxString message((char*)(m_buffer + 3U), wxConvLocal, length - 5U); + short val1 = (m_buffer[length - 2U] << 8) | m_buffer[length - 1U]; + wxLogMessage(wxT("Debug: %s %d"), message.c_str(), val1); + } else if (m_buffer[2U] == 0xF3U) { + wxString message((char*)(m_buffer + 3U), wxConvLocal, length - 7U); + short val1 = (m_buffer[length - 4U] << 8) | m_buffer[length - 3U]; + short val2 = (m_buffer[length - 2U] << 8) | m_buffer[length - 1U]; + wxLogMessage(wxT("Debug: %s %d %d"), message.c_str(), val1, val2); + } else if (m_buffer[2U] == 0xF4U) { + wxString message((char*)(m_buffer + 3U), wxConvLocal, length - 9U); + short val1 = (m_buffer[length - 6U] << 8) | m_buffer[length - 5U]; + short val2 = (m_buffer[length - 4U] << 8) | m_buffer[length - 3U]; + short val3 = (m_buffer[length - 2U] << 8) | m_buffer[length - 1U]; + wxLogMessage(wxT("Debug: %s %d %d %d"), message.c_str(), val1, val2, val3); + } else if (m_buffer[2U] == 0xF5U) { + wxString message((char*)(m_buffer + 3U), wxConvLocal, length - 11U); + short val1 = (m_buffer[length - 8U] << 8) | m_buffer[length - 7U]; + short val2 = (m_buffer[length - 6U] << 8) | m_buffer[length - 5U]; + short val3 = (m_buffer[length - 4U] << 8) | m_buffer[length - 3U]; + short val4 = (m_buffer[length - 2U] << 8) | m_buffer[length - 1U]; + wxLogMessage(wxT("Debug: %s %d %d %d %d"), message.c_str(), val1, val2, val3, val4); + } +} diff --git a/Common/MMDVMController.h b/Common/MMDVMController.h new file mode 100644 index 0000000..437d711 --- /dev/null +++ b/Common/MMDVMController.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef MMDVMController_H +#define MMDVMController_H + +#include "SerialDataController.h" +#include "RingBuffer.h" +#include "Modem.h" +#include "Utils.h" + +#include + +enum RESP_TYPE_MMDVM { + RTDVM_TIMEOUT, + RTDVM_ERROR, + RTDVM_UNKNOWN, + RTDVM_GET_STATUS, + RTDVM_GET_VERSION, + RTDVM_DSTAR_HEADER, + RTDVM_DSTAR_DATA, + RTDVM_DSTAR_EOT, + RTDVM_DSTAR_LOST, + RTDVM_ACK, + RTDVM_NAK, + RTDVM_DUMP, + RTDVM_DEBUG1, + RTDVM_DEBUG2, + RTDVM_DEBUG3, + RTDVM_DEBUG4, + RTDVM_DEBUG5 +}; + +class CMMDVMController : public CModem { +public: + CMMDVMController(const wxString& port, const wxString& path, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int rxLevel, unsigned int txLevel); + virtual ~CMMDVMController(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + + virtual wxString getPath() const; + +private: + wxString m_port; + wxString m_path; + bool m_rxInvert; + bool m_txInvert; + bool m_pttInvert; + unsigned int m_txDelay; + unsigned int m_rxLevel; + unsigned int m_txLevel; + CSerialDataController m_serial; + unsigned char* m_buffer; + CRingBuffer m_txData; + bool m_rx; + + bool readVersion(); + bool readStatus(); + bool setConfig(); + + RESP_TYPE_MMDVM getResponse(unsigned char* buffer, unsigned int& length); + + bool findPort(); + bool findPath(); + + bool findModem(); + bool openModem(); + + void printDebug(); +}; + +#endif + diff --git a/Common/Modem.cpp b/Common/Modem.cpp new file mode 100644 index 0000000..524e15d --- /dev/null +++ b/Common/Modem.cpp @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarDefines.h" +#include "Modem.h" + +const unsigned int BUFFER_LENGTH = 200U; + +CModem::CModem() : +wxThread(wxTHREAD_JOINABLE), +m_rxData(1000U), +m_mutex(), +m_tx(false), +m_stopped(false), +m_readType(DSMTT_NONE), +m_readLength(0U), +m_readBuffer(NULL) +{ + m_readBuffer = new unsigned char[BUFFER_LENGTH]; +} + +CModem::~CModem() +{ + delete[] m_readBuffer; +} + +bool CModem::isTX() +{ + return m_tx; +} + +DSMT_TYPE CModem::read() +{ + m_readLength = 0U; + + if (m_rxData.isEmpty()) + return DSMTT_NONE; + + wxMutexLocker locker(m_mutex); + + unsigned char hdr[2U]; + m_rxData.getData(hdr, 2U); + + m_readType = DSMT_TYPE(hdr[0U]); + m_readLength = hdr[1U]; + m_rxData.getData(m_readBuffer, m_readLength); + + return m_readType; +} + +CHeaderData* CModem::readHeader() +{ + if (m_readType != DSMTT_HEADER) + return NULL; + + return new CHeaderData(m_readBuffer, RADIO_HEADER_LENGTH_BYTES, false); +} + +unsigned int CModem::readData(unsigned char* data, unsigned int length) +{ + if (m_readType != DSMTT_DATA) + return 0U; + + if (length < m_readLength) { + ::memcpy(data, m_readBuffer, length); + return length; + } else { + ::memcpy(data, m_readBuffer, m_readLength); + return m_readLength; + } +} + +void CModem::stop() +{ + m_stopped = true; + + Wait(); +} + diff --git a/Common/Modem.h b/Common/Modem.h new file mode 100644 index 0000000..2351367 --- /dev/null +++ b/Common/Modem.h @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2013,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Modem_H +#define Modem_H + +#include "HeaderData.h" +#include "RingBuffer.h" + +#include + +enum DSMT_TYPE { + DSMTT_NONE, + DSMTT_START, + DSMTT_HEADER, + DSMTT_DATA, + DSMTT_EOT, + DSMTT_LOST +}; + +class CModem : public wxThread { +public: + CModem(); + virtual ~CModem(); + + virtual bool start() = 0; + + virtual bool writeHeader(const CHeaderData& header) = 0; + virtual bool writeData(const unsigned char* data, unsigned int length, bool end) = 0; + + virtual unsigned int getSpace() = 0; + virtual bool isTXReady() = 0; + virtual bool isTX(); + + virtual DSMT_TYPE read(); + virtual CHeaderData* readHeader(); + virtual unsigned int readData(unsigned char* data, unsigned int length); + + virtual void stop(); + +protected: + CRingBuffer m_rxData; + wxMutex m_mutex; + bool m_tx; + bool m_stopped; + +private: + DSMT_TYPE m_readType; + unsigned int m_readLength; + unsigned char* m_readBuffer; +}; + +#endif diff --git a/Common/OutputQueue.cpp b/Common/OutputQueue.cpp new file mode 100644 index 0000000..c1da6c7 --- /dev/null +++ b/Common/OutputQueue.cpp @@ -0,0 +1,138 @@ +/* + * Copyright (C) 2011,2013,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "OutputQueue.h" + +COutputQueue::COutputQueue(unsigned int space, unsigned int threshold) : +m_data(space), +m_threshold(threshold), +m_header(NULL), +m_count(0U) +{ + wxASSERT(space > 0U); + wxASSERT(threshold > 0U); +} + +COutputQueue::~COutputQueue() +{ + delete m_header; +} + +void COutputQueue::setHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + delete m_header; + + m_header = header; +} + +CHeaderData* COutputQueue::getHeader() +{ + CHeaderData* header = m_header; + + m_header = NULL; + + return header; +} + +unsigned int COutputQueue::getData(unsigned char *data, unsigned int length, bool& end) +{ + wxASSERT(data != NULL); + + if (m_data.isEmpty()) { + end = false; + return 0U; + } + + unsigned char hdr[2U]; + m_data.getData(hdr, 2U); + + end = hdr[0U] == 1U; + + if (length < hdr[1U]) { + wxLogWarning(wxT("Output buffer is too short, %u < %u"), length, hdr[1U]); + + unsigned int len = m_data.getData(data, length); + + // Purge the excess data + unsigned int n = hdr[1U] - length; + unsigned char c; + for (unsigned int i = 0U; i < n; i++) + m_data.getData(&c, 1U); + + return len; + } else { + return m_data.getData(data, hdr[1U]); + } +} + +unsigned int COutputQueue::addData(const unsigned char *data, unsigned int length, bool end) +{ + wxASSERT(data != NULL); + + bool ret = m_data.hasSpace(length + 2U); + if (!ret) { + // XXX wxLogWarning(wxT("Not enough space in the output queue")); + return 0U; + } + + unsigned char hdr[2U]; + hdr[0U] = end ? 1U : 0U; + hdr[1U] = length; + m_data.addData(hdr, 2U); + + if (end) + m_count = m_threshold; + else + m_count++; + + return m_data.addData(data, length); +} + +bool COutputQueue::headerReady() const +{ + return m_header != NULL && m_count >= m_threshold; +} + +bool COutputQueue::dataReady() const +{ + return m_header == NULL && m_count >= m_threshold; +} + +bool COutputQueue::isEmpty() +{ + return m_header == NULL && m_data.isEmpty(); +} + +void COutputQueue::reset() +{ + m_data.clear(); + + delete m_header; + m_header = NULL; + + m_count = 0U; +} + +void COutputQueue::setThreshold(unsigned int threshold) +{ + wxASSERT(threshold > 0U); + + m_threshold = threshold; +} diff --git a/Common/OutputQueue.h b/Common/OutputQueue.h new file mode 100644 index 0000000..f4f5e8b --- /dev/null +++ b/Common/OutputQueue.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2011,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef OutputQueue_H +#define OutputQueue_H + +#include "RingBuffer.h" +#include "HeaderData.h" + +#include + +class COutputQueue { +public: + COutputQueue(unsigned int space, unsigned int threshold); + ~COutputQueue(); + + void setHeader(CHeaderData* header); + CHeaderData* getHeader(); + + unsigned int getData(unsigned char* data, unsigned int length, bool& end); + unsigned int addData(const unsigned char* data, unsigned int length, bool end); + + bool headerReady() const; + bool dataReady() const; + + bool isEmpty(); + + void reset(); + + void setThreshold(unsigned int threshold); + +private: + CRingBuffer m_data; + unsigned int m_threshold; + CHeaderData* m_header; + unsigned int m_count; +}; + +#endif diff --git a/Common/RepeaterProtocolHandler.cpp b/Common/RepeaterProtocolHandler.cpp new file mode 100644 index 0000000..2e9e963 --- /dev/null +++ b/Common/RepeaterProtocolHandler.cpp @@ -0,0 +1,539 @@ +/* + * Copyright (C) 2009-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "RepeaterProtocolHandler.h" +#include "CCITTChecksumReverse.h" +#include "DStarDefines.h" +#include "Utils.h" + +// #define DUMP_TX + +const unsigned int BUFFER_LENGTH = 255U; + +CRepeaterProtocolHandler::CRepeaterProtocolHandler(const wxString& gatewayAddress, unsigned int gatewayPort, const wxString& localAddress, unsigned int localPort, const wxString& name) : +m_socket(localAddress, localPort), +m_address(), +m_port(gatewayPort), +m_name(name), +m_outId(0U), +m_outSeq(0U), +m_type(NETWORK_NONE), +m_inId(0U), +m_buffer(NULL), +m_length(0U) +{ + m_address = CUDPReaderWriter::lookup(gatewayAddress); + + m_buffer = new unsigned char[BUFFER_LENGTH]; + + wxDateTime now = wxDateTime::UNow(); + ::srand(now.GetMillisecond()); +} + +CRepeaterProtocolHandler::~CRepeaterProtocolHandler() +{ + delete[] m_buffer; +} + +bool CRepeaterProtocolHandler::open() +{ + if (m_address.s_addr == INADDR_NONE) + return false; + + return m_socket.open(); +} + +bool CRepeaterProtocolHandler::writeHeader(const CHeaderData& header) +{ + unsigned char buffer[50U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x20U; + + // Create a random id for this transmission + m_outId = (::rand() % 65535U) + 1U; + + buffer[5] = m_outId / 256U; // Unique session id + buffer[6] = m_outId % 256U; + + buffer[7] = 0U; + + buffer[8] = header.getFlag1(); + buffer[9] = header.getFlag2(); + buffer[10] = header.getFlag3(); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[11 + i] = header.getRptCall1().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[19 + i] = header.getRptCall2().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[27 + i] = header.getYourCall().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[35 + i] = header.getMyCall1().GetChar(i); + + for (unsigned int i = 0U; i < SHORT_CALLSIGN_LENGTH; i++) + buffer[43 + i] = header.getMyCall2().GetChar(i); + + // Get the checksum for the header + CCCITTChecksumReverse csum; + csum.update(buffer + 8U, 4U * LONG_CALLSIGN_LENGTH + SHORT_CALLSIGN_LENGTH + 3U); + csum.result(buffer + 47U); + + m_outSeq = 0U; + +#if defined(DUMP_TX) + CUtils::dump(wxT("Sending Header"), buffer, 49U); +#endif + + for (unsigned int i = 0U; i < 2U; i++) { + bool ret = m_socket.write(buffer, 49U, m_address, m_port); + if (!ret) + return false; + } + + return true; +} + +bool CRepeaterProtocolHandler::writeData(const unsigned char* data, unsigned int length, unsigned int errors, bool end) +{ + wxASSERT(data != NULL); + wxASSERT(length == DV_FRAME_LENGTH_BYTES || length == DV_FRAME_MAX_LENGTH_BYTES); + + unsigned char buffer[30U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x21U; + + buffer[5] = m_outId / 256U; // Unique session id + buffer[6] = m_outId % 256U; + + // If this is a data sync, reset the sequence to zero + if (data[9] == 0x55 && data[10] == 0x2D && data[11] == 0x16) + m_outSeq = 0U; + + buffer[7] = m_outSeq; + if (end) + buffer[7] |= 0x40U; // End of data marker + + buffer[8] = errors; + + m_outSeq++; + if (m_outSeq > 0x14U) + m_outSeq = 0U; + + ::memcpy(buffer + 9U, data, length); + +#if defined(DUMP_TX) + CUtils::dump(wxT("Sending Data"), buffer, length + 9U); +#endif + + return m_socket.write(buffer, length + 9U, m_address, m_port); +} + +bool CRepeaterProtocolHandler::writeBusyHeader(const CHeaderData& header) +{ + unsigned char buffer[50U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x22U; + + // Create a random id for this header + m_outId = (::rand() % 65535U) + 1U; + + buffer[5] = m_outId / 256U; // Unique session id + buffer[6] = m_outId % 256U; + + buffer[7] = 0U; + + buffer[8] = header.getFlag1(); + buffer[9] = header.getFlag2(); + buffer[10] = header.getFlag3(); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[11 + i] = header.getRptCall1().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[19 + i] = header.getRptCall2().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[27 + i] = header.getYourCall().GetChar(i); + + for (unsigned int i = 0U; i < LONG_CALLSIGN_LENGTH; i++) + buffer[35 + i] = header.getMyCall1().GetChar(i); + + for (unsigned int i = 0U; i < SHORT_CALLSIGN_LENGTH; i++) + buffer[43 + i] = header.getMyCall2().GetChar(i); + + // Get the checksum for the header + CCCITTChecksumReverse csum; + csum.update(buffer + 8U, 4U * LONG_CALLSIGN_LENGTH + SHORT_CALLSIGN_LENGTH + 3U); + csum.result(buffer + 47U); + + m_outSeq = 0U; + +#if defined(DUMP_TX) + CUtils::dump(wxT("Sending Busy Header"), buffer, 49U); +#endif + + return m_socket.write(buffer, 49U, m_address, m_port); +} + +bool CRepeaterProtocolHandler::writeBusyData(const unsigned char* data, unsigned int length, unsigned int errors, bool end) +{ + wxASSERT(data != NULL); + wxASSERT(length == DV_FRAME_LENGTH_BYTES || length == DV_FRAME_MAX_LENGTH_BYTES); + + unsigned char buffer[30U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x23U; + + buffer[5] = m_outId / 256U; // Unique session id + buffer[6] = m_outId % 256U; + + // If this is a data sync, reset the sequence to zero + if (data[9] == 0x55 && data[10] == 0x2D && data[11] == 0x16) + m_outSeq = 0U; + + buffer[7] = m_outSeq; + if (end) + buffer[7] |= 0x40U; // End of data marker + + buffer[8] = errors; + + m_outSeq++; + if (m_outSeq > 0x14U) + m_outSeq = 0U; + + ::memcpy(buffer + 9U, data, length); + +#if defined(DUMP_TX) + CUtils::dump(wxT("Sending Busy Data"), buffer, length + 9U); +#endif + + return m_socket.write(buffer, length + 9U, m_address, m_port); +} + +bool CRepeaterProtocolHandler::writePoll(const wxString& text) +{ + unsigned char buffer[40U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x0A; // Poll with text + + unsigned int length = text.Length(); + + for (unsigned int i = 0U; i < length; i++) + buffer[5U + i] = text.GetChar(i); + + buffer[5U + length] = 0x00; + +#if defined(DUMP_TX) + CUtils::dump(wxT("Sending Poll"), buffer, 6U + length); +#endif + + return m_socket.write(buffer, 6U + length, m_address, m_port); +} + +bool CRepeaterProtocolHandler::writeRegister() +{ + unsigned char buffer[40U]; + + buffer[0] = 'D'; + buffer[1] = 'S'; + buffer[2] = 'R'; + buffer[3] = 'P'; + + buffer[4] = 0x0B; // Register with name + + unsigned int length = m_name.Length(); + + for (unsigned int i = 0U; i < length; i++) + buffer[5U + i] = m_name.GetChar(i); + + buffer[5U + length] = 0x00; + +#if defined(DUMP_TX) + CUtils::dump(wxT("Sending Register"), buffer, 6U + length); +#endif + + return m_socket.write(buffer, 6U + length, m_address, m_port); +} + +NETWORK_TYPE CRepeaterProtocolHandler::read() +{ + bool res = true; + + // Loop until we have no more data from the socket or we have data for the higher layers + while (res) + res = readPackets(); + + return m_type; +} + +bool CRepeaterProtocolHandler::readPackets() +{ + m_type = NETWORK_NONE; + + // No more data? + in_addr address; + unsigned int port; + int length = m_socket.read(m_buffer, BUFFER_LENGTH, address, port); + if (length <= 0) + return false; + + // Check if the data is for us + if (m_address.s_addr != address.s_addr || m_port != port) { + wxLogMessage(wxT("Packet received from an invalid source, %08X != %08X and/or %u != %u"), m_address.s_addr, address.s_addr, m_port, port); + CUtils::dump(wxT("Data"), m_buffer, length); + return false; + } + + m_length = length; + + // Invalid packet type? + if (m_buffer[0] == 'D' && m_buffer[1] == 'S' && m_buffer[2] == 'R' && m_buffer[3] == 'P') { + if (m_buffer[4] == 0x00U) { + m_type = NETWORK_TEXT; + return false; + } + + if (m_buffer[4] == 0x01U) { + m_type = NETWORK_TEMPTEXT; + return false; + } + + // Status data 1 + else if (m_buffer[4] == 0x04U && m_buffer[5] == 0x00U) { + m_type = NETWORK_STATUS1; + return false; + } + + // Status data 2 + else if (m_buffer[4] == 0x04U && m_buffer[5] == 0x01U) { + m_type = NETWORK_STATUS2; + return false; + } + + // Status data 3 + else if (m_buffer[4] == 0x04U && m_buffer[5] == 0x02U) { + m_type = NETWORK_STATUS3; + return false; + } + + // Status data 4 + else if (m_buffer[4] == 0x04U && m_buffer[5] == 0x03U) { + m_type = NETWORK_STATUS4; + return false; + } + + // Status data 5 + else if (m_buffer[4] == 0x04U && m_buffer[5] == 0x04U) { + m_type = NETWORK_STATUS5; + return false; + } + + // Header data + else if (m_buffer[4] == 0x20U) { + wxUint16 id = m_buffer[5] * 256U + m_buffer[6]; + + // Are we listening for headers? + if (m_inId != 0U) + return true; + + m_inId = id; // Take the stream id + m_type = NETWORK_HEADER; + return false; + } + + // User data + else if (m_buffer[4] == 0x21U) { + wxUint16 id = m_buffer[5] * 256U + m_buffer[6]; + + // Check that the stream id matches the valid header, reject otherwise + if (id != m_inId) + return true; + + // Is this the last packet in the stream? + if ((m_buffer[7] & 0x40) == 0x40) + m_inId = 0U; + + m_type = NETWORK_DATA; + return false; + } + + else if (m_buffer[4] == 0x24U) { + // Silently ignore DD data + } + } + + CUtils::dump(wxT("Unknown packet from the Gateway"), m_buffer, m_length); + + return true; +} + +CHeaderData* CRepeaterProtocolHandler::readHeader() +{ + if (m_type != NETWORK_HEADER) + return NULL; + + // If the checksum is 0xFFFF then we accept the header without testing the checksum + if (m_buffer[47U] == 0xFFU && m_buffer[48U] == 0xFFU) + return new CHeaderData(m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES, false); + + // Header checksum testing is enabled + CHeaderData* header = new CHeaderData(m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES, true); + + if (!header->isValid()) { + CUtils::dump(wxT("Header checksum failure from the Gateway"), m_buffer + 8U, RADIO_HEADER_LENGTH_BYTES); + delete header; + return NULL; + } + + return header; +} + +unsigned int CRepeaterProtocolHandler::readData(unsigned char* buffer, unsigned int length, unsigned char& seqNo) +{ + if (m_type != NETWORK_DATA) + return 0U; + + unsigned int dataLen = m_length - 9U; + + // Is our buffer too small? + if (dataLen > length) + dataLen = length; + + seqNo = m_buffer[7U]; + + ::memcpy(buffer, m_buffer + 9U, dataLen); + + // Simple sanity checks of the incoming sync bits + if (seqNo == 0U) { + // Regenerate sync bytes + buffer[9U] = DATA_SYNC_BYTES[0U]; + buffer[10U] = DATA_SYNC_BYTES[1U]; + buffer[11U] = DATA_SYNC_BYTES[2U]; + } else if (::memcmp(buffer + 9U, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES) == 0) { + // Sync bytes appearing where they shouldn't! + buffer[9U] = 0x70U; + buffer[10U] = 0x4FU; + buffer[11U] = 0x93U; + } + + return dataLen; +} + +void CRepeaterProtocolHandler::readText(wxString& text, LINK_STATUS& status, wxString& reflector) +{ + if (m_type != NETWORK_TEXT) { + text = wxT(" "); + reflector = wxT(" "); + status = LS_NONE; + return; + } + + text = wxString((char*)(m_buffer + 5U), wxConvLocal, 20U); + + status = LINK_STATUS(m_buffer[25U]); + + reflector = wxString((char*)(m_buffer + 26U), wxConvLocal, 8U); +} + +void CRepeaterProtocolHandler::readTempText(wxString& text) +{ + if (m_type != NETWORK_TEMPTEXT) { + text = wxT(" "); + return; + } + + text = wxString((char*)(m_buffer + 5U), wxConvLocal, 20U); +} + +wxString CRepeaterProtocolHandler::readStatus1() +{ + if (m_type != NETWORK_STATUS1) + return wxEmptyString; + + return wxString((char*)(m_buffer + 6U), wxConvLocal, 20U); +} + +wxString CRepeaterProtocolHandler::readStatus2() +{ + if (m_type != NETWORK_STATUS2) + return wxEmptyString; + + return wxString((char*)(m_buffer + 6U), wxConvLocal, 20U); +} + +wxString CRepeaterProtocolHandler::readStatus3() +{ + if (m_type != NETWORK_STATUS3) + return wxEmptyString; + + return wxString((char*)(m_buffer + 6U), wxConvLocal, 20U); +} + +wxString CRepeaterProtocolHandler::readStatus4() +{ + if (m_type != NETWORK_STATUS4) + return wxEmptyString; + + return wxString((char*)(m_buffer + 6U), wxConvLocal, 20U); +} + +wxString CRepeaterProtocolHandler::readStatus5() +{ + if (m_type != NETWORK_STATUS5) + return wxEmptyString; + + return wxString((char*)(m_buffer + 6U), wxConvLocal, 20U); +} + +void CRepeaterProtocolHandler::reset() +{ + m_inId = 0U; +} + +void CRepeaterProtocolHandler::close() +{ + m_socket.close(); +} diff --git a/Common/RepeaterProtocolHandler.h b/Common/RepeaterProtocolHandler.h new file mode 100644 index 0000000..1a74cac --- /dev/null +++ b/Common/RepeaterProtocolHandler.h @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2009-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef RepeaterProtocolHander_H +#define RepeaterProtocolHander_H + +#include "UDPReaderWriter.h" +#include "DStarDefines.h" +#include "HeaderData.h" + +#include + +class CRepeaterProtocolHandler { +public: + CRepeaterProtocolHandler(const wxString& gatewayAddress, unsigned int gatewayPort, const wxString& localAddress, unsigned int localPort, const wxString& name); + ~CRepeaterProtocolHandler(); + + bool open(); + + bool writeHeader(const CHeaderData& header); + bool writeBusyHeader(const CHeaderData& header); + bool writeData(const unsigned char* data, unsigned int length, unsigned int errors, bool end); + bool writeBusyData(const unsigned char* data, unsigned int length, unsigned int errors, bool end); + bool writePoll(const wxString& text); + bool writeRegister(); + + NETWORK_TYPE read(); + void readText(wxString& text, LINK_STATUS& status, wxString& reflector); + void readTempText(wxString& text); + wxString readStatus1(); + wxString readStatus2(); + wxString readStatus3(); + wxString readStatus4(); + wxString readStatus5(); + CHeaderData* readHeader(); + unsigned int readData(unsigned char* data, unsigned int length, unsigned char& seqNo); + + void reset(); + + void close(); + +private: + CUDPReaderWriter m_socket; + in_addr m_address; + unsigned int m_port; + wxString m_name; + wxUint16 m_outId; + wxUint8 m_outSeq; + NETWORK_TYPE m_type; + wxUint16 m_inId; + unsigned char* m_buffer; + unsigned int m_length; + + bool readPackets(); +}; + +#endif diff --git a/Common/RingBuffer.h b/Common/RingBuffer.h new file mode 100644 index 0000000..3c40af0 --- /dev/null +++ b/Common/RingBuffer.h @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2006-2009,2012,2013,2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef RingBuffer_H +#define RingBuffer_H + +#include + +template class CRingBuffer { +public: + CRingBuffer(unsigned int length) : + m_length(length), + m_buffer(NULL), + m_iPtr(0U), + m_oPtr(0U) + { + wxASSERT(length > 0U); + + m_buffer = new T[length]; + + ::memset(m_buffer, 0x00, m_length * sizeof(T)); + } + + ~CRingBuffer() + { + delete[] m_buffer; + } + + unsigned int addData(const T* buffer, unsigned int nSamples) + { + if (nSamples > freeSpace()) + return 0U; + + for (unsigned int i = 0U; i < nSamples; i++) { + m_buffer[m_iPtr++] = buffer[i]; + + if (m_iPtr == m_length) + m_iPtr = 0U; + } + + return nSamples; + } + + unsigned int getData(T* buffer, unsigned int nSamples) + { + unsigned int data = dataSize(); + + if (data < nSamples) + nSamples = data; + + for (unsigned int i = 0U; i < nSamples; i++) { + buffer[i] = m_buffer[m_oPtr++]; + + if (m_oPtr == m_length) + m_oPtr = 0U; + } + + return nSamples; + } + + unsigned int peek(T* buffer, unsigned int nSamples) + { + unsigned int data = dataSize(); + + if (data < nSamples) + nSamples = data; + + unsigned int ptr = m_oPtr; + for (unsigned int i = 0U; i < nSamples; i++) { + buffer[i] = m_buffer[ptr++]; + + if (ptr == m_length) + ptr = 0U; + } + + return nSamples; + } + + void clear() + { + m_iPtr = 0U; + m_oPtr = 0U; + + ::memset(m_buffer, 0x00, m_length * sizeof(T)); + } + + unsigned int freeSpace() const + { + if (m_oPtr == m_iPtr) + return m_length - 1U; + + if (m_oPtr > m_iPtr) + return m_oPtr - m_iPtr - 1U; + + return m_length - (m_iPtr - m_oPtr) - 1U; + } + + bool hasSpace(unsigned int length) const + { + return freeSpace() > length; + } + + bool hasData() const + { + return m_oPtr != m_iPtr; + } + + bool isEmpty() const + { + return m_oPtr == m_iPtr; + } + +private: + unsigned int m_length; + T* m_buffer; + volatile unsigned int m_iPtr; + volatile unsigned int m_oPtr; + + unsigned int dataSize() const + { + if (m_iPtr >= m_oPtr) + return m_iPtr - m_oPtr; + + return m_length - (m_oPtr - m_iPtr); + } +}; + +#endif diff --git a/Common/SerialDataController.cpp b/Common/SerialDataController.cpp new file mode 100644 index 0000000..fe8c5e6 --- /dev/null +++ b/Common/SerialDataController.cpp @@ -0,0 +1,464 @@ +/* + * Copyright (C) 2002-2004,2007-2011,2013,2014,2015 by Jonathan Naylor G4KLX + * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "SerialDataController.h" + +#include + +#if defined(__WINDOWS__) +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + + +#if defined(__WINDOWS__) + +const unsigned int BUFFER_LENGTH = 1000U; + +CSerialDataController::CSerialDataController(const wxString& device, SERIAL_SPEED speed, bool assertRTS) : +m_device(device), +m_speed(speed), +m_assertRTS(assertRTS), +m_handle(INVALID_HANDLE_VALUE), +m_readOverlapped(), +m_writeOverlapped(), +m_readBuffer(NULL), +m_readLength(0U), +m_readPending(false) +{ + wxASSERT(!device.IsEmpty()); + + m_readBuffer = new unsigned char[BUFFER_LENGTH]; +} + +CSerialDataController::~CSerialDataController() +{ + delete[] m_readBuffer; +} + +bool CSerialDataController::open() +{ + wxASSERT(m_handle == INVALID_HANDLE_VALUE); + + DWORD errCode; + + wxString baseName = m_device.Mid(4U); // Convert "\\.\COM10" to "COM10" + + m_handle = ::CreateFile(m_device.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); + if (m_handle == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Cannot open device - %s, err=%04lx"), m_device.c_str(), ::GetLastError()); + return false; + } + + DCB dcb; + if (::GetCommState(m_handle, &dcb) == 0) { + wxLogError(wxT("Cannot get the attributes for %s, err=%04lx"), m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + dcb.BaudRate = DWORD(m_speed); + dcb.ByteSize = 8; + dcb.Parity = NOPARITY; + dcb.fParity = FALSE; + dcb.StopBits = ONESTOPBIT; + dcb.fInX = FALSE; + dcb.fOutX = FALSE; + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; + + if (::SetCommState(m_handle, &dcb) == 0) { + wxLogError(wxT("Cannot set the attributes for %s, err=%04lx"), m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + COMMTIMEOUTS timeouts; + if (!::GetCommTimeouts(m_handle, &timeouts)) { + wxLogError(wxT("Cannot get the timeouts for %s, err=%04lx"), m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + timeouts.ReadIntervalTimeout = MAXDWORD; + timeouts.ReadTotalTimeoutMultiplier = 0UL; + timeouts.ReadTotalTimeoutConstant = 0UL; + + if (!::SetCommTimeouts(m_handle, &timeouts)) { + wxLogError(wxT("Cannot set the timeouts for %s, err=%04lx"), m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + if (::EscapeCommFunction(m_handle, CLRDTR) == 0) { + wxLogError(wxT("Cannot clear DTR for %s, err=%04lx"), m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + if (::EscapeCommFunction(m_handle, m_assertRTS ? SETRTS : CLRRTS) == 0) { + wxLogError(wxT("Cannot set/clear RTS for %s, err=%04lx"), m_device.c_str(), ::GetLastError()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + ::ClearCommError(m_handle, &errCode, NULL); + + ::memset(&m_readOverlapped, 0x00U, sizeof(OVERLAPPED)); + ::memset(&m_writeOverlapped, 0x00U, sizeof(OVERLAPPED)); + + m_readOverlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); + m_writeOverlapped.hEvent = ::CreateEvent(NULL, TRUE, FALSE, NULL); + + m_readLength = 0U; + m_readPending = false; + ::memset(m_readBuffer, 0x00U, BUFFER_LENGTH); + + return true; +} + +int CSerialDataController::read(unsigned char* buffer, unsigned int length) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + wxASSERT(buffer != NULL); + + unsigned int ptr = 0U; + + while (ptr < length) { + int ret = readNonblock(buffer + ptr, length - ptr); + if (ret < 0) { + return ret; + } else if (ret == 0) { + if (ptr == 0U) + return 0; + } else { + ptr += ret; + } + } + + return int(length); +} + +int CSerialDataController::readNonblock(unsigned char* buffer, unsigned int length) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + wxASSERT(buffer != NULL); + + if (length > BUFFER_LENGTH) + length = BUFFER_LENGTH; + + if (m_readPending && length != m_readLength) { + ::CancelIo(m_handle); + m_readPending = false; + } + + m_readLength = length; + + if (length == 0U) + return 0; + + if (!m_readPending) { + DWORD bytes = 0UL; + BOOL res = ::ReadFile(m_handle, m_readBuffer, m_readLength, &bytes, &m_readOverlapped); + if (res) { + ::memcpy(buffer, m_readBuffer, bytes); + return int(bytes); + } + + DWORD error = ::GetLastError(); + if (error != ERROR_IO_PENDING) { + wxLogError(wxT("Error from ReadFile: %04lx"), error); + return -1; + } + + m_readPending = true; + } + + BOOL res = HasOverlappedIoCompleted(&m_readOverlapped); + if (!res) + return 0; + + DWORD bytes = 0UL; + res = ::GetOverlappedResult(m_handle, &m_readOverlapped, &bytes, TRUE); + if (!res) { + wxLogError(wxT("Error from GetOverlappedResult (ReadFile): %04lx"), ::GetLastError()); + return -1; + } + + ::memcpy(buffer, m_readBuffer, bytes); + m_readPending = false; + + return int(bytes); +} + +int CSerialDataController::write(const unsigned char* buffer, unsigned int length) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + wxASSERT(buffer != NULL); + + if (length == 0U) + return 0; + + unsigned int ptr = 0U; + + while (ptr < length) { + DWORD bytes = 0UL; + BOOL res = ::WriteFile(m_handle, buffer + ptr, length - ptr, &bytes, &m_writeOverlapped); + if (!res) { + DWORD error = ::GetLastError(); + if (error != ERROR_IO_PENDING) { + wxLogError(wxT("Error from WriteFile: %04lx"), error); + return -1; + } + + res = ::GetOverlappedResult(m_handle, &m_writeOverlapped, &bytes, TRUE); + if (!res) { + wxLogError(wxT("Error from GetOverlappedResult (WriteFile): %04lx"), ::GetLastError()); + return -1; + } + } + + ptr += bytes; + } + + return int(length); +} + +void CSerialDataController::close() +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; + + ::CloseHandle(m_readOverlapped.hEvent); + ::CloseHandle(m_writeOverlapped.hEvent); +} + +#else + +CSerialDataController::CSerialDataController(const wxString& device, SERIAL_SPEED speed, bool assertRTS) : +m_device(device), +m_speed(speed), +m_assertRTS(assertRTS), +m_fd(-1) +{ + wxASSERT(!device.IsEmpty()); +} + +CSerialDataController::~CSerialDataController() +{ +} + +bool CSerialDataController::open() +{ + wxASSERT(m_fd == -1); + + m_fd = ::open(m_device.mb_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (m_fd < 0) { + wxLogError(wxT("Cannot open device - %s"), m_device.c_str()); + return false; + } + + if (::isatty(m_fd) == 0) { + wxLogError(wxT("%s is not a TTY device"), m_device.c_str()); + ::close(m_fd); + return false; + } + + termios termios; + if (::tcgetattr(m_fd, &termios) < 0) { + wxLogError(wxT("Cannot get the attributes for %s"), m_device.c_str()); + ::close(m_fd); + return false; + } + + termios.c_lflag &= ~(ECHO | ECHOE | ICANON | IEXTEN | ISIG); + termios.c_iflag &= ~(BRKINT | ICRNL | INPCK | ISTRIP | IXON | IXOFF | IXANY); + termios.c_cflag &= ~(CSIZE | CSTOPB | PARENB | CRTSCTS); + termios.c_cflag |= CS8; + termios.c_oflag &= ~(OPOST); + termios.c_cc[VMIN] = 0; + termios.c_cc[VTIME] = 10; + + switch (m_speed) { + case SERIAL_1200: + ::cfsetospeed(&termios, B1200); + ::cfsetispeed(&termios, B1200); + break; + case SERIAL_2400: + ::cfsetospeed(&termios, B2400); + ::cfsetispeed(&termios, B2400); + break; + case SERIAL_4800: + ::cfsetospeed(&termios, B4800); + ::cfsetispeed(&termios, B4800); + break; + case SERIAL_9600: + ::cfsetospeed(&termios, B9600); + ::cfsetispeed(&termios, B9600); + break; + case SERIAL_19200: + ::cfsetospeed(&termios, B19200); + ::cfsetispeed(&termios, B19200); + break; + case SERIAL_38400: + ::cfsetospeed(&termios, B38400); + ::cfsetispeed(&termios, B38400); + break; + case SERIAL_115200: + ::cfsetospeed(&termios, B115200); + ::cfsetispeed(&termios, B115200); + break; + case SERIAL_230400: + ::cfsetospeed(&termios, B230400); + ::cfsetispeed(&termios, B230400); + break; + default: + wxLogError(wxT("Unsupported serial port speed - %d"), int(m_speed)); + ::close(m_fd); + return false; + } + + if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { + wxLogError(wxT("Cannot set the attributes for %s"), m_device.c_str()); + ::close(m_fd); + return false; + } + + if (m_assertRTS) { + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + wxLogError(wxT("Cannot get the control attributes for %s"), m_device.c_str()); + ::close(m_fd); + return false; + } + + y |= TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + wxLogError(wxT("Cannot set the control attributes for %s"), m_device.c_str()); + ::close(m_fd); + return false; + } + } + + return true; +} + +int CSerialDataController::read(unsigned char* buffer, unsigned int length) +{ + wxASSERT(buffer != NULL); + wxASSERT(m_fd != -1); + + if (length == 0U) + return 0; + + unsigned int offset = 0U; + + while (offset < length) { + fd_set fds; + FD_ZERO(&fds); + FD_SET(m_fd, &fds); + + int n; + if (offset == 0U) { + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = 0; + + n = ::select(m_fd + 1, &fds, NULL, NULL, &tv); + if (n == 0) + return 0; + } else { + n = ::select(m_fd + 1, &fds, NULL, NULL, NULL); + } + + if (n < 0) { + wxLogError(wxT("Error from select(), errno=%d"), errno); + return -1; + } + + if (n > 0) { + ssize_t len = ::read(m_fd, buffer + offset, length - offset); + if (len < 0) { + if (errno != EAGAIN) { + wxLogError(wxT("Error from read(), errno=%d"), errno); + return -1; + } + } + + if (len > 0) + offset += len; + } + } + + return length; +} + +int CSerialDataController::write(const unsigned char* buffer, unsigned int length) +{ + wxASSERT(buffer != NULL); + wxASSERT(m_fd != -1); + + if (length == 0U) + return 0; + + unsigned int ptr = 0U; + + while (ptr < length) { + ssize_t n = ::write(m_fd, buffer + ptr, length - ptr); + if (n < 0) { + if (errno != EAGAIN) { + wxLogError(wxT("Error returned from write(), errno=%d"), errno); + return -1; + } + } + + if (n > 0) + ptr += n; + } + + return length; +} + +void CSerialDataController::close() +{ + wxASSERT(m_fd != -1); + + ::close(m_fd); + m_fd = -1; +} + +#endif diff --git a/Common/SerialDataController.h b/Common/SerialDataController.h new file mode 100644 index 0000000..8c154fb --- /dev/null +++ b/Common/SerialDataController.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2002-2004,2007-2009,2011-2013,2015 by Jonathan Naylor G4KLX + * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SerialDataController_H +#define SerialDataController_H + +#include + +#if defined(__WINDOWS__) +#include +#endif + +enum SERIAL_SPEED { + SERIAL_1200 = 1200, + SERIAL_2400 = 2400, + SERIAL_4800 = 4800, + SERIAL_9600 = 9600, + SERIAL_19200 = 19200, + SERIAL_38400 = 38400, + SERIAL_76800 = 76800, + SERIAL_115200 = 115200, + SERIAL_230400 = 230400 +}; + +class CSerialDataController { +public: + CSerialDataController(const wxString& device, SERIAL_SPEED speed, bool assertRTS = false); + ~CSerialDataController(); + + bool open(); + + int read(unsigned char* buffer, unsigned int length); + int write(const unsigned char* buffer, unsigned int length); + + void close(); + +private: + wxString m_device; + SERIAL_SPEED m_speed; + bool m_assertRTS; +#if defined(__WINDOWS__) + HANDLE m_handle; + OVERLAPPED m_readOverlapped; + OVERLAPPED m_writeOverlapped; + unsigned char* m_readBuffer; + unsigned int m_readLength; + bool m_readPending; +#else + int m_fd; +#endif + +#if defined(__WINDOWS__) + int readNonblock(unsigned char* buffer, unsigned int length); +#endif +}; + +#endif diff --git a/Common/SerialLineController.cpp b/Common/SerialLineController.cpp new file mode 100644 index 0000000..a42324a --- /dev/null +++ b/Common/SerialLineController.cpp @@ -0,0 +1,533 @@ +/* + * Copyright (C) 2002-2004,2007-2011,2013,2015 by Jonathan Naylor G4KLX + * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "SerialLineController.h" + +#include + +#if defined(__WINDOWS__) +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + + +#if defined(__WINDOWS__) + +CSerialLineController::CSerialLineController(const wxString& device, unsigned int config) : +m_device(device), +m_config(config), +m_rts(false), +m_dtr(false), +m_handle(INVALID_HANDLE_VALUE) +{ + wxASSERT(!device.IsEmpty()); + wxASSERT(config == 1U || config == 2U || config == 3U); +} + +CSerialLineController::~CSerialLineController() +{ +} + +bool CSerialLineController::open() +{ + wxASSERT(m_handle == INVALID_HANDLE_VALUE); + + DWORD errCode; + + m_handle = ::CreateFile(m_device.c_str(), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL); + if (m_handle == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Cannot open device - %s"), m_device.c_str()); + return false; + } + + DCB dcb; + if (::GetCommState(m_handle, &dcb) == 0) { + wxLogError(wxT("Cannot get the attributes for %s"), m_device.c_str()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + dcb.fOutxCtsFlow = FALSE; + dcb.fOutxDsrFlow = FALSE; + dcb.fDtrControl = DTR_CONTROL_DISABLE; + dcb.fRtsControl = RTS_CONTROL_DISABLE; + + if (::SetCommState(m_handle, &dcb) == 0) { + wxLogError(wxT("Cannot set the attributes for %s"), m_device.c_str()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + if (::EscapeCommFunction(m_handle, CLRDTR) == 0) { + wxLogError(wxT("Cannot clear DTR for %s"), m_device.c_str()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + if (::EscapeCommFunction(m_handle, CLRRTS) == 0) { + wxLogError(wxT("Cannot clear RTS for %s"), m_device.c_str()); + ::ClearCommError(m_handle, &errCode, NULL); + ::CloseHandle(m_handle); + return false; + } + + ::ClearCommError(m_handle, &errCode, NULL); + + return true; +} + +bool CSerialLineController::getCD() const +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + DWORD status; + DWORD errCode; + if (::GetCommModemStatus(m_handle, &status) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return false; + } + + return (status & MS_RLSD_ON) == MS_RLSD_ON; +} + +bool CSerialLineController::getCTS() const +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + DWORD status; + DWORD errCode; + if (::GetCommModemStatus(m_handle, &status) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return false; + } + + return (status & MS_CTS_ON) == MS_CTS_ON; +} + +bool CSerialLineController::getDSR() const +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + DWORD status; + DWORD errCode; + if (::GetCommModemStatus(m_handle, &status) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return false; + } + + return (status & MS_DSR_ON) == MS_DSR_ON; +} + +bool CSerialLineController::setRTS(bool set) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + if (set == m_rts) + return true; + + DWORD rts = (set) ? SETRTS : CLRRTS; + DWORD errCode; + + if (::EscapeCommFunction(m_handle, rts) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return false; + } + + m_rts = set; + + return true; +} + +bool CSerialLineController::setDTR(bool set) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + if (set == m_dtr) + return true; + + DWORD dtr = (set) ? SETDTR : CLRDTR; + DWORD errCode; + + if (::EscapeCommFunction(m_handle, dtr) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return false; + } + + m_dtr = set; + + return true; +} + +void CSerialLineController::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + inp1 = inp2 = inp3 = inp4 = inp5 = false; + + DWORD status; + DWORD errCode; + if (::GetCommModemStatus(m_handle, &status) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return; + } + + switch (m_config) { + case 1U: + inp1 = (status & MS_DSR_ON) == MS_DSR_ON; + inp2 = (status & MS_CTS_ON) == MS_CTS_ON; + break; + case 2U: + inp1 = (status & MS_RLSD_ON) == MS_RLSD_ON; + inp2 = (status & MS_RLSD_ON) == MS_RLSD_ON; + break; + case 3U: + inp1 = (status & MS_RLSD_ON) == MS_RLSD_ON; + break; + default: + wxLogError(wxT("Unknown serial config - %u"), m_config); + break; + } +} + +void CSerialLineController::setDigitalOutputs(bool outp1, bool, bool outp3, bool, bool, bool, bool, bool) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + switch (m_config) { + case 1U: + if (outp1 != m_dtr) { + DWORD dtr = (outp1) ? SETDTR : CLRDTR; + DWORD errCode; + + if (::EscapeCommFunction(m_handle, dtr) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return; + } + + m_dtr = outp1; + } + if (outp3 != m_rts) { + DWORD rts = (outp3) ? SETRTS : CLRRTS; + DWORD errCode; + + if (::EscapeCommFunction(m_handle, rts) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return; + } + + m_rts = outp3; + } + break; + + case 2U: + case 3U: + if (outp1 != m_rts) { + DWORD rts = (outp1) ? SETRTS : CLRRTS; + DWORD errCode; + + if (::EscapeCommFunction(m_handle, rts) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return; + } + + m_rts = outp1; + } + if (outp3 != m_dtr) { + DWORD dtr = (outp3) ? SETDTR : CLRDTR; + DWORD errCode; + + if (::EscapeCommFunction(m_handle, dtr) == 0) { + ::ClearCommError(m_handle, &errCode, NULL); + return; + } + + m_dtr = outp3; + } + break; + + default: + wxLogError(wxT("Unknown serial config - %u"), m_config); + break; + } +} + +void CSerialLineController::close() +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; +} + +#else + +CSerialLineController::CSerialLineController(const wxString& device, unsigned int config) : +m_device(device), +m_config(config), +m_rts(false), +m_dtr(false), +m_fd(-1) +{ + wxASSERT(!device.IsEmpty()); + wxASSERT(config == 1U || config == 2U || config == 3U); +} + +CSerialLineController::~CSerialLineController() +{ +} + +bool CSerialLineController::open() +{ + wxASSERT(m_fd == -1); + + m_fd = ::open(m_device.mb_str(), O_RDWR | O_NOCTTY | O_NDELAY, 0); + if (m_fd < 0) { + wxLogError(wxT("Cannot open device - %s"), m_device.c_str()); + return false; + } + + if (::isatty(m_fd) == 0) { + wxLogError(wxT("%s is not a TTY device"), m_device.c_str()); + ::close(m_fd); + return false; + } + + termios termios; + if (::tcgetattr(m_fd, &termios) < 0) { + wxLogError(wxT("Cannot get the attributes for %s"), m_device.c_str()); + ::close(m_fd); + return false; + } + + // FIXME XXX unfinished + termios.c_cflag |= (CLOCAL | CREAD); + termios.c_cflag &= ~CRTSCTS; + termios.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); + termios.c_oflag &= ~OPOST; + termios.c_cc[VMIN] = 0; + termios.c_cc[VTIME] = 10; + + if (::tcsetattr(m_fd, TCSANOW, &termios) < 0) { + wxLogError(wxT("Cannot set the attributes for %s"), m_device.c_str()); + ::close(m_fd); + return false; + } + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) { + wxLogError(wxT("Cannot get the modem status bits for %s"), m_device.c_str()); + ::close(m_fd); + return false; + } + + y &= ~TIOCM_DTR; + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) { + wxLogError(wxT("Cannot set the modem status bits for %s"), m_device.c_str()); + ::close(m_fd); + return false; + } + + return true; +} + +bool CSerialLineController::getCD() const +{ + wxASSERT(m_fd != -1); + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) + return false; + + return (y & TIOCM_CD) == TIOCM_CD; +} + +bool CSerialLineController::getCTS() const +{ + wxASSERT(m_fd != -1); + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) + return false; + + return (y & TIOCM_CTS) == TIOCM_CTS; +} + +bool CSerialLineController::getDSR() const +{ + wxASSERT(m_fd != -1); + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) + return false; + + return (y & TIOCM_DSR) == TIOCM_DSR; +} + +bool CSerialLineController::setRTS(bool set) +{ + wxASSERT(m_fd != -1); + + if (set == m_rts) + return true; + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) + return false; + + if (set) + y |= TIOCM_RTS; + else + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) + return false; + + m_rts = set; + + return true; +} + +bool CSerialLineController::setDTR(bool set) +{ + wxASSERT(m_fd != -1); + + if (set == m_dtr) + return true; + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) + return false; + + if (set) + y |= TIOCM_DTR; + else + y &= ~TIOCM_DTR; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) + return false; + + m_dtr = set; + + return true; +} + +void CSerialLineController::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + wxASSERT(m_fd != -1); + + inp1 = inp2 = inp3 = inp4 = inp5 = false; + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) + return; + + switch (m_config) { + case 1U: + inp1 = (y & TIOCM_DSR) == TIOCM_DSR; + inp2 = (y & TIOCM_CTS) == TIOCM_CTS; + break; + case 2U: + inp1 = (y & TIOCM_CD) == TIOCM_CD; + inp2 = (y & TIOCM_CD) == TIOCM_CD; + break; + case 3U: + inp1 = (y & TIOCM_CD) == TIOCM_CD; + break; + default: + wxLogError(wxT("Unknown serial config - %u"), m_config); + break; + } +} + +void CSerialLineController::setDigitalOutputs(bool outp1, bool, bool outp3, bool, bool, bool, bool, bool) +{ + wxASSERT(m_fd != -1); + + if (m_config == 1U) { + if (outp1 == m_dtr && outp3 == m_rts) + return; + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) + return; + + if (outp1) + y |= TIOCM_DTR; + else + y &= ~TIOCM_DTR; + + if (outp3) + y |= TIOCM_RTS; + else + y &= ~TIOCM_RTS; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) + return; + + m_dtr = outp1; + m_rts = outp3; + } else if (m_config == 2U || m_config == 3U) { + if (outp1 == m_rts && outp3 == m_dtr) + return; + + unsigned int y; + if (::ioctl(m_fd, TIOCMGET, &y) < 0) + return; + + if (outp1) + y |= TIOCM_RTS; + else + y &= ~TIOCM_RTS; + + if (outp3) + y |= TIOCM_DTR; + else + y &= ~TIOCM_DTR; + + if (::ioctl(m_fd, TIOCMSET, &y) < 0) + return; + + m_rts = outp1; + m_dtr = outp3; + } else { + wxLogError(wxT("Unknown serial config - %u"), m_config); + } +} + +void CSerialLineController::close() +{ + wxASSERT(m_fd != -1); + + ::close(m_fd); + m_fd = -1; +} + +#endif diff --git a/Common/SerialLineController.h b/Common/SerialLineController.h new file mode 100644 index 0000000..827af7a --- /dev/null +++ b/Common/SerialLineController.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2002-2004,2007-2009,2011-2013,2015 by Jonathan Naylor G4KLX + * Copyright (C) 1999-2001 by Thomas Sailor HB9JNX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SerialLineController_H +#define SerialLineController_H + +#include "HardwareController.h" + +#include + +#if defined(__WINDOWS__) +#include +#endif + +enum SERIALPIN { + SERIAL_CD, + SERIAL_CTS, + SERIAL_DSR, + SERIAL_DTR, + SERIAL_RTS, + SERIAL_ECHOLINK +}; + +const unsigned int MAX_DEVICE_NAME = 255U; + +class CSerialLineController : public IHardwareController { +public: + CSerialLineController(const wxString& device, unsigned int config = 1U); + virtual ~CSerialLineController(); + + virtual bool open(); + + virtual bool setRTS(bool set); + virtual bool setDTR(bool set); + + virtual bool getCD() const; + virtual bool getCTS() const; + virtual bool getDSR() const; + + virtual void getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5); + virtual void setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8); + + virtual void close(); + +private: + wxString m_device; + unsigned int m_config; + bool m_rts; + bool m_dtr; +#if defined(__WINDOWS__) + HANDLE m_handle; +#else + int m_fd; +#endif +}; + +#endif diff --git a/Common/SerialPortSelector.cpp b/Common/SerialPortSelector.cpp new file mode 100644 index 0000000..bc7ad1c --- /dev/null +++ b/Common/SerialPortSelector.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2002-2004,2007-2011,2013,2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "SerialPortSelector.h" + +#include + +#if defined(__APPLE__) && defined(__MACH__) +#include +#include +#include +#include +#include +#include +#include +#include +#elif defined(__WINDOWS__) +#include +#include +#else +#include +#endif + + +wxArrayString CSerialPortSelector::getDevices() +{ + wxArrayString devices; + + devices.Alloc(10); + +#if defined(__APPLE__) && defined(__MACH__) + mach_port_t masterPort; + kern_return_t ret = ::IOMasterPort(MACH_PORT_NULL, &masterPort); + if (ret != KERN_SUCCESS) + return devices; + + CFMutableDictionaryRef match = ::IOServiceMatching(kIOSerialBSDServiceValue); + if (match == NULL) + wxLogWarning(wxT("IOServiceMatching() returned NULL")); + else + ::CFDictionarySetValue(match, CFSTR(kIOSerialBSDTypeKey), CFSTR(kIOSerialBSDRS232Type)); + + io_iterator_t services; + ret = ::IOServiceGetMatchingServices(masterPort, match, &services); + if (ret != KERN_SUCCESS) + return devices; + + io_object_t modem; + while ((modem = ::IOIteratorNext(services))) { + CFTypeRef filePath = ::IORegistryEntryCreateCFProperty(modem, CFSTR(kIOCalloutDeviceKey), kCFAllocatorDefault, 0); + if (filePath != NULL) { + char port[MAXPATHLEN]; + Boolean result = ::CFStringGetCString((const __CFString*)filePath, port, MAXPATHLEN, kCFStringEncodingASCII); + ::CFRelease(filePath); + + if (result) + devices.Add(port); + + ::IOObjectRelease(modem); + } + } + + ::IOObjectRelease(services); +#elif defined(__WINDOWS__) + devices.Add(wxT("\\\\.\\COM1")); + devices.Add(wxT("\\\\.\\COM2")); + devices.Add(wxT("\\\\.\\COM3")); + devices.Add(wxT("\\\\.\\COM4")); + devices.Add(wxT("\\\\.\\COM5")); + devices.Add(wxT("\\\\.\\COM6")); + devices.Add(wxT("\\\\.\\COM7")); + devices.Add(wxT("\\\\.\\COM8")); + devices.Add(wxT("\\\\.\\COM9")); + devices.Add(wxT("\\\\.\\COM10")); + devices.Add(wxT("\\\\.\\COM11")); + devices.Add(wxT("\\\\.\\COM12")); + devices.Add(wxT("\\\\.\\COM13")); + devices.Add(wxT("\\\\.\\COM14")); + devices.Add(wxT("\\\\.\\COM15")); + devices.Add(wxT("\\\\.\\COM16")); + devices.Add(wxT("\\\\.\\COM17")); + devices.Add(wxT("\\\\.\\COM18")); + devices.Add(wxT("\\\\.\\COM19")); +#else + wxDir devDir; + bool ret = devDir.Open(wxT("/dev")); + + if (ret) { + wxString fileName; + ret = devDir.GetFirst(&fileName, wxT("ttyACM*"), wxDIR_FILES); + + while (ret) { + fileName.Prepend(wxT("/dev/")); + devices.Add(fileName); + + ret = devDir.GetNext(&fileName); + } + + ret = devDir.GetFirst(&fileName, wxT("ttyAMA*"), wxDIR_FILES); + + while (ret) { + fileName.Prepend(wxT("/dev/")); + devices.Add(fileName); + + ret = devDir.GetNext(&fileName); + } + + ret = devDir.GetFirst(&fileName, wxT("ttyS*"), wxDIR_FILES); + + while (ret) { + fileName.Prepend(wxT("/dev/")); + devices.Add(fileName); + + ret = devDir.GetNext(&fileName); + } + + ret = devDir.GetFirst(&fileName, wxT("ttyUSB*"), wxDIR_FILES); + + while (ret) { + fileName.Prepend(wxT("/dev/")); + devices.Add(fileName); + + ret = devDir.GetNext(&fileName); + } + } +#endif + + return devices; +} diff --git a/Common/SerialPortSelector.h b/Common/SerialPortSelector.h new file mode 100644 index 0000000..0d7c412 --- /dev/null +++ b/Common/SerialPortSelector.h @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2002-2004,2007-2009,2011-2013,2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SerialPortSelector_H +#define SerialPortSelector_H + +#include + +class CSerialPortSelector { +public: + static wxArrayString getDevices(); + +private: +}; + +#endif diff --git a/Common/SlowDataDecoder.cpp b/Common/SlowDataDecoder.cpp new file mode 100644 index 0000000..e75eb37 --- /dev/null +++ b/Common/SlowDataDecoder.cpp @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2009,2010,2011 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "CCITTChecksumReverse.h" +#include "SlowDataDecoder.h" +#include "DStarDefines.h" + +const unsigned int SLOW_DATA_BLOCK_SIZE = 6U; + +const unsigned int SLOW_DATA_FULL_BLOCK_SIZE = (SLOW_DATA_BLOCK_SIZE - 1U) * 10U; + +CSlowDataDecoder::CSlowDataDecoder() : +m_buffer(NULL), +m_state(SDD_FIRST), +m_headerData(NULL), +m_headerPtr(0U), +m_header(NULL) +{ + m_buffer = new unsigned char[SLOW_DATA_BLOCK_SIZE]; + m_headerData = new unsigned char[SLOW_DATA_FULL_BLOCK_SIZE]; +} + +CSlowDataDecoder::~CSlowDataDecoder() +{ + delete[] m_buffer; + delete[] m_headerData; + + delete m_header; +} + +void CSlowDataDecoder::addData(const unsigned char* data) +{ + wxASSERT(data != NULL); + + switch (m_state) { + case SDD_FIRST: + m_buffer[0] = data[0] ^ SCRAMBLER_BYTE1; + m_buffer[1] = data[1] ^ SCRAMBLER_BYTE2; + m_buffer[2] = data[2] ^ SCRAMBLER_BYTE3; + m_state = SDD_SECOND; + return; + + case SDD_SECOND: + m_buffer[3] = data[0] ^ SCRAMBLER_BYTE1; + m_buffer[4] = data[1] ^ SCRAMBLER_BYTE2; + m_buffer[5] = data[2] ^ SCRAMBLER_BYTE3; + m_state = SDD_FIRST; + processHeader(); + break; + } +} + +CHeaderData* CSlowDataDecoder::getHeaderData() +{ + if (m_header == NULL) + return NULL; + + CHeaderData* temp = new CHeaderData(*m_header); + + reset(); + + return temp; +} + +void CSlowDataDecoder::sync() +{ + m_headerPtr = 0U; + + m_state = SDD_FIRST; +} + +void CSlowDataDecoder::reset() +{ + m_headerPtr = 0U; + + m_state = SDD_FIRST; + + ::memset(m_headerData, 0x00, SLOW_DATA_FULL_BLOCK_SIZE * sizeof(unsigned char)); + + delete m_header; + m_header = NULL; +} + +void CSlowDataDecoder::processHeader() +{ + // Do we have a complete and valid header already? + if (m_header != NULL) + return; + + for (unsigned int i = 1U; i <= (SLOW_DATA_BLOCK_SIZE - 1U); i++, m_headerPtr++) { + m_headerData[m_headerPtr] = m_buffer[i]; + + if (m_headerPtr >= SLOW_DATA_FULL_BLOCK_SIZE) + m_headerPtr = 0U; + } + + // Look for a header starting immediately after the data sync + bool res = processHeader(m_headerData); + if (res) + return; + + // Look for a header starting one block after the data sync + processHeader(m_headerData + SLOW_DATA_BLOCK_SIZE - 1U); +} + +bool CSlowDataDecoder::processHeader(const unsigned char* bytes) +{ + unsigned char buffer[RADIO_HEADER_LENGTH_BYTES]; + + // Clean up the header data + for (unsigned int n = 0U; n < RADIO_HEADER_LENGTH_BYTES; n++) { + switch (n) { + case 0U: // Flag 1 + // Only allow BK and EMR bits and always assert the repeater bit + buffer[n] = (bytes[n] & (INTERRUPTED_MASK | URGENT_MASK)) | REPEATER_MASK; + break; + case 1U: // Flag 2 + case 2U: // Flag 3 + // Always 0x00 in current implementations + buffer[n] = 0x00; + break; + case 39U: // Checksum 1 + case 40U: // Checksum 2 + // These bytes can be any value + buffer[n] = bytes[n]; + break; + default: // Callsign text + // Only ASCII text so MSB is always clear + buffer[n] = bytes[n] & 0x7F; + break; + } + } + + CCCITTChecksumReverse cksum; + cksum.update(buffer, RADIO_HEADER_LENGTH_BYTES - 2U); + + bool valid = cksum.check(buffer + RADIO_HEADER_LENGTH_BYTES - 2U); + if (!valid) + return false; + + // The checksum has already been checked + m_header = new CHeaderData(buffer, RADIO_HEADER_LENGTH_BYTES, false); + + return true; +} diff --git a/Common/SlowDataDecoder.h b/Common/SlowDataDecoder.h new file mode 100644 index 0000000..b628116 --- /dev/null +++ b/Common/SlowDataDecoder.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2009,2010 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef SlowDataDecoder_H +#define SlowDataDecoder_H + +#include "HeaderData.h" + +#include + +enum SDD_STATE { + SDD_FIRST, + SDD_SECOND +}; + +class CSlowDataDecoder { +public: + CSlowDataDecoder(); + ~CSlowDataDecoder(); + + void addData(const unsigned char* data); + + CHeaderData* getHeaderData(); + + void sync(); + void reset(); + +private: + unsigned char* m_buffer; + SDD_STATE m_state; + unsigned char* m_headerData; + unsigned int m_headerPtr; + CHeaderData* m_header; + + void processHeader(); + bool processHeader(const unsigned char* bytes); +}; + +#endif diff --git a/Common/SlowDataEncoder.cpp b/Common/SlowDataEncoder.cpp new file mode 100644 index 0000000..74bbd4b --- /dev/null +++ b/Common/SlowDataEncoder.cpp @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2010,2011,2015 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "CCITTChecksumReverse.h" +#include "SlowDataEncoder.h" +#include "DStarDefines.h" + +const unsigned int SLOW_DATA_BLOCK_SIZE = 6U; + +const unsigned int SLOW_DATA_FULL_BLOCK_SIZE = SLOW_DATA_BLOCK_SIZE * 10U; + +CSlowDataEncoder::CSlowDataEncoder() : +m_headerData(NULL), +m_textData(NULL), +m_headerPtr(0U), +m_textPtr(0U) +{ + m_headerData = new unsigned char[SLOW_DATA_FULL_BLOCK_SIZE]; + m_textData = new unsigned char[SLOW_DATA_FULL_BLOCK_SIZE]; + + ::memset(m_headerData, 'f', SLOW_DATA_FULL_BLOCK_SIZE); + ::memset(m_textData, 'f', SLOW_DATA_FULL_BLOCK_SIZE); +} + +CSlowDataEncoder::~CSlowDataEncoder() +{ + delete[] m_headerData; + delete[] m_textData; +} + +void CSlowDataEncoder::getHeaderData(unsigned char* data) +{ + wxASSERT(data != NULL); + + data[0U] = m_headerData[m_headerPtr++] ^ SCRAMBLER_BYTE1; + data[1U] = m_headerData[m_headerPtr++] ^ SCRAMBLER_BYTE2; + data[2U] = m_headerData[m_headerPtr++] ^ SCRAMBLER_BYTE3; + + if (m_headerPtr >= SLOW_DATA_FULL_BLOCK_SIZE) + m_headerPtr = 0U; +} + +void CSlowDataEncoder::getTextData(unsigned char* data) +{ + wxASSERT(data != NULL); + + data[0U] = m_textData[m_textPtr++] ^ SCRAMBLER_BYTE1; + data[1U] = m_textData[m_textPtr++] ^ SCRAMBLER_BYTE2; + data[2U] = m_textData[m_textPtr++] ^ SCRAMBLER_BYTE3; + + if (m_textPtr >= SLOW_DATA_FULL_BLOCK_SIZE) + m_textPtr = 0U; +} + +void CSlowDataEncoder::sync() +{ + m_headerPtr = 0U; + m_textPtr = 0U; +} + +void CSlowDataEncoder::reset() +{ + m_headerPtr = 0U; + m_textPtr = 0U; + + ::memset(m_headerData, 'f', SLOW_DATA_FULL_BLOCK_SIZE); + ::memset(m_textData, 'f', SLOW_DATA_FULL_BLOCK_SIZE); +} + +void CSlowDataEncoder::setHeaderData(const CHeaderData& header) +{ + ::memset(m_headerData, 'f', SLOW_DATA_FULL_BLOCK_SIZE); + + m_headerData[0U] = SLOW_DATA_TYPE_HEADER | 5U; + m_headerData[1U] = header.getFlag1(); + m_headerData[2U] = header.getFlag2(); + m_headerData[3U] = header.getFlag3(); + m_headerData[4U] = header.getRptCall2().GetChar(0); + m_headerData[5U] = header.getRptCall2().GetChar(1); + + m_headerData[6U] = SLOW_DATA_TYPE_HEADER | 5U; + m_headerData[7U] = header.getRptCall2().GetChar(2); + m_headerData[8U] = header.getRptCall2().GetChar(3); + m_headerData[9U] = header.getRptCall2().GetChar(4); + m_headerData[10U] = header.getRptCall2().GetChar(5); + m_headerData[11U] = header.getRptCall2().GetChar(6); + + m_headerData[12U] = SLOW_DATA_TYPE_HEADER | 5U; + m_headerData[13U] = header.getRptCall2().GetChar(7); + m_headerData[14U] = header.getRptCall1().GetChar(0); + m_headerData[15U] = header.getRptCall1().GetChar(1); + m_headerData[16U] = header.getRptCall1().GetChar(2); + m_headerData[17U] = header.getRptCall1().GetChar(3); + + m_headerData[18U] = SLOW_DATA_TYPE_HEADER | 5U; + m_headerData[19U] = header.getRptCall1().GetChar(4); + m_headerData[20U] = header.getRptCall1().GetChar(5); + m_headerData[21U] = header.getRptCall1().GetChar(6); + m_headerData[22U] = header.getRptCall1().GetChar(7); + m_headerData[23U] = header.getYourCall().GetChar(0); + + m_headerData[24U] = SLOW_DATA_TYPE_HEADER | 5U; + m_headerData[25U] = header.getYourCall().GetChar(1); + m_headerData[26U] = header.getYourCall().GetChar(2); + m_headerData[27U] = header.getYourCall().GetChar(3); + m_headerData[28U] = header.getYourCall().GetChar(4); + m_headerData[29U] = header.getYourCall().GetChar(5); + + m_headerData[30U] = SLOW_DATA_TYPE_HEADER | 5U; + m_headerData[31U] = header.getYourCall().GetChar(6); + m_headerData[32U] = header.getYourCall().GetChar(7); + m_headerData[33U] = header.getMyCall1().GetChar(0); + m_headerData[34U] = header.getMyCall1().GetChar(1); + m_headerData[35U] = header.getMyCall1().GetChar(2); + + m_headerData[36U] = SLOW_DATA_TYPE_HEADER | 5U; + m_headerData[37U] = header.getMyCall1().GetChar(3); + m_headerData[38U] = header.getMyCall1().GetChar(4); + m_headerData[39U] = header.getMyCall1().GetChar(5); + m_headerData[40U] = header.getMyCall1().GetChar(6); + m_headerData[41U] = header.getMyCall1().GetChar(7); + + m_headerData[42U] = SLOW_DATA_TYPE_HEADER | 5U; + m_headerData[43U] = header.getMyCall2().GetChar(0); + m_headerData[44U] = header.getMyCall2().GetChar(1); + m_headerData[45U] = header.getMyCall2().GetChar(2); + m_headerData[46U] = header.getMyCall2().GetChar(3); + + CCCITTChecksumReverse cksum; + cksum.update(m_headerData + 1U, 5U); + cksum.update(m_headerData + 7U, 5U); + cksum.update(m_headerData + 13U, 5U); + cksum.update(m_headerData + 19U, 5U); + cksum.update(m_headerData + 25U, 5U); + cksum.update(m_headerData + 31U, 5U); + cksum.update(m_headerData + 37U, 5U); + cksum.update(m_headerData + 43U, 4U); + + unsigned char checkSum[2U]; + cksum.result(checkSum); + + m_headerData[47U] = checkSum[0]; + + m_headerData[48U] = SLOW_DATA_TYPE_HEADER | 1U; + m_headerData[49U] = checkSum[1]; + + m_headerPtr = 0U; +} + +void CSlowDataEncoder::setTextData(const wxString& text) +{ + if (text.IsEmpty()) + return; + + ::memset(m_textData, 'f', SLOW_DATA_FULL_BLOCK_SIZE); + + m_textData[0U] = SLOW_DATA_TYPE_TEXT | 0U; + m_textData[1U] = text.GetChar(0); + m_textData[2U] = text.GetChar(1); + m_textData[3U] = text.GetChar(2); + m_textData[4U] = text.GetChar(3); + m_textData[5U] = text.GetChar(4); + + m_textData[6U] = SLOW_DATA_TYPE_TEXT | 1U; + m_textData[7U] = text.GetChar(5); + m_textData[8U] = text.GetChar(6); + m_textData[9U] = text.GetChar(7); + m_textData[10U] = text.GetChar(8); + m_textData[11U] = text.GetChar(9); + + m_textData[12U] = SLOW_DATA_TYPE_TEXT | 2U; + m_textData[13U] = text.GetChar(10); + m_textData[14U] = text.GetChar(11); + m_textData[15U] = text.GetChar(12); + m_textData[16U] = text.GetChar(13); + m_textData[17U] = text.GetChar(14); + + m_textData[18U] = SLOW_DATA_TYPE_TEXT | 3U; + m_textData[19U] = text.GetChar(15); + m_textData[20U] = text.GetChar(16); + m_textData[21U] = text.GetChar(17); + m_textData[22U] = text.GetChar(18); + m_textData[23U] = text.GetChar(19); + + m_textPtr = 0U; +} + diff --git a/Common/SlowDataEncoder.h b/Common/SlowDataEncoder.h new file mode 100644 index 0000000..f3c534e --- /dev/null +++ b/Common/SlowDataEncoder.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2010 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef SlowDataEncoder_H +#define SlowDataEncoder_H + +#include "HeaderData.h" + +#include + +class CSlowDataEncoder { +public: + CSlowDataEncoder(); + ~CSlowDataEncoder(); + + void setHeaderData(const CHeaderData& header); + void setTextData(const wxString& text); + + void getHeaderData(unsigned char* data); + void getTextData(unsigned char* data); + + void reset(); + void sync(); + +private: + unsigned char* m_headerData; + unsigned char* m_textData; + unsigned int m_headerPtr; + unsigned int m_textPtr; +}; + +#endif diff --git a/Common/SoundCardController.cpp b/Common/SoundCardController.cpp new file mode 100644 index 0000000..5e83c8a --- /dev/null +++ b/Common/SoundCardController.cpp @@ -0,0 +1,1095 @@ +/* + * Copyright (C) 2009-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CCITTChecksumReverse.h" +#include "SoundCardController.h" +#include "DStarDefines.h" + +// #define AUDIO_LOOPBACK + +const unsigned int BUFFER_LENGTH = 200U; + +const unsigned int NIBBLE_BITS[] = {0U, 1U, 1U, 2U, 1U, 2U, 2U, 3U, 1U, 2U, 2U, 3U, 2U, 3U, 3U, 4U}; + +const unsigned int DV_FRAME_LENGTH_BITS = DV_FRAME_LENGTH_BYTES * 8U; + +const unsigned int MAX_SYNC_BITS = 50U * DV_FRAME_LENGTH_BITS; + +const unsigned int FEC_SECTION_LENGTH_BITS = 660U; + +// D-Star bit order version of 0x55 0x55 0x6E 0x0A +const wxUint32 FRAME_SYNC_DATA = 0x00557650U; +const wxUint32 FRAME_SYNC_MASK = 0x00FFFFFFU; +const unsigned int FRAME_SYNC_ERRS = 2U; + +// D-Star bit order version of 0x55 0x2D 0x16 +const wxUint32 DATA_SYNC_DATA = 0x00AAB468U; +const wxUint32 DATA_SYNC_MASK = 0x00FFFFFFU; +const unsigned int DATA_SYNC_ERRS = 2U; + +// D-Star bit order version of 0x55 0x55 0xC8 0x7A +const wxUint32 END_SYNC_DATA = 0xAAAA135EU; +const wxUint32 END_SYNC_MASK = 0xFFFFFFFFU; +const unsigned int END_SYNC_ERRS = 3U; + +const unsigned char BIT_SYNC = 0xAAU; + +const unsigned char FRAME_SYNC0 = 0xEAU; +const unsigned char FRAME_SYNC1 = 0xA6U; +const unsigned char FRAME_SYNC2 = 0x00U; + +const unsigned char BIT_MASK_TABLE0[] = {0x7FU, 0xBFU, 0xDFU, 0xEFU, 0xF7U, 0xFBU, 0xFDU, 0xFEU}; +const unsigned char BIT_MASK_TABLE1[] = {0x80U, 0x40U, 0x20U, 0x10U, 0x08U, 0x04U, 0x02U, 0x01U}; +const unsigned char BIT_MASK_TABLE2[] = {0xFEU, 0xFDU, 0xFBU, 0xF7U, 0xEFU, 0xDFU, 0xBFU, 0x7FU}; +const unsigned char BIT_MASK_TABLE3[] = {0x01U, 0x02U, 0x04U, 0x08U, 0x10U, 0x20U, 0x40U, 0x80U}; + +#define WRITE_BIT1(p,i,b) p[(i)/8] = (b) ? (p[(i)/8] | BIT_MASK_TABLE1[(i)%8]) : (p[(i)/8] & BIT_MASK_TABLE0[(i)%8]) +#define READ_BIT1(p,i) (p[(i)/8] & BIT_MASK_TABLE1[(i)%8]) + +#define WRITE_BIT2(p,i,b) p[(i)/8] = (b) ? (p[(i)/8] | BIT_MASK_TABLE3[(i)%8]) : (p[(i)/8] & BIT_MASK_TABLE2[(i)%8]) +#define READ_BIT2(p,i) (p[(i)/8] & BIT_MASK_TABLE3[(i)%8]) + +const unsigned char INTERLEAVE_TABLE_TX[] = { + 0x00U, 0x04U, 0x04U, 0x00U, 0x07U, 0x04U, 0x0BU, 0x00U, 0x0EU, 0x04U, + 0x12U, 0x00U, 0x15U, 0x04U, 0x19U, 0x00U, 0x1CU, 0x04U, 0x20U, 0x00U, + 0x23U, 0x04U, 0x27U, 0x00U, 0x2AU, 0x04U, 0x2DU, 0x07U, 0x31U, 0x02U, + 0x34U, 0x05U, 0x38U, 0x00U, 0x3BU, 0x03U, 0x3EU, 0x06U, 0x42U, 0x01U, + 0x45U, 0x04U, 0x48U, 0x07U, 0x4CU, 0x02U, 0x4FU, 0x05U, 0x00U, 0x05U, + 0x04U, 0x01U, 0x07U, 0x05U, 0x0BU, 0x01U, 0x0EU, 0x05U, 0x12U, 0x01U, + 0x15U, 0x05U, 0x19U, 0x01U, 0x1CU, 0x05U, 0x20U, 0x01U, 0x23U, 0x05U, + 0x27U, 0x01U, 0x2AU, 0x05U, 0x2EU, 0x00U, 0x31U, 0x03U, 0x34U, 0x06U, + 0x38U, 0x01U, 0x3BU, 0x04U, 0x3EU, 0x07U, 0x42U, 0x02U, 0x45U, 0x05U, + 0x49U, 0x00U, 0x4CU, 0x03U, 0x4FU, 0x06U, 0x00U, 0x06U, 0x04U, 0x02U, + 0x07U, 0x06U, 0x0BU, 0x02U, 0x0EU, 0x06U, 0x12U, 0x02U, 0x15U, 0x06U, + 0x19U, 0x02U, 0x1CU, 0x06U, 0x20U, 0x02U, 0x23U, 0x06U, 0x27U, 0x02U, + 0x2AU, 0x06U, 0x2EU, 0x01U, 0x31U, 0x04U, 0x34U, 0x07U, 0x38U, 0x02U, + 0x3BU, 0x05U, 0x3FU, 0x00U, 0x42U, 0x03U, 0x45U, 0x06U, 0x49U, 0x01U, + 0x4CU, 0x04U, 0x4FU, 0x07U, 0x00U, 0x07U, 0x04U, 0x03U, 0x07U, 0x07U, + 0x0BU, 0x03U, 0x0EU, 0x07U, 0x12U, 0x03U, 0x15U, 0x07U, 0x19U, 0x03U, + 0x1CU, 0x07U, 0x20U, 0x03U, 0x23U, 0x07U, 0x27U, 0x03U, 0x2AU, 0x07U, + 0x2EU, 0x02U, 0x31U, 0x05U, 0x35U, 0x00U, 0x38U, 0x03U, 0x3BU, 0x06U, + 0x3FU, 0x01U, 0x42U, 0x04U, 0x45U, 0x07U, 0x49U, 0x02U, 0x4CU, 0x05U, + 0x50U, 0x00U, 0x01U, 0x00U, 0x04U, 0x04U, 0x08U, 0x00U, 0x0BU, 0x04U, + 0x0FU, 0x00U, 0x12U, 0x04U, 0x16U, 0x00U, 0x19U, 0x04U, 0x1DU, 0x00U, + 0x20U, 0x04U, 0x24U, 0x00U, 0x27U, 0x04U, 0x2BU, 0x00U, 0x2EU, 0x03U, + 0x31U, 0x06U, 0x35U, 0x01U, 0x38U, 0x04U, 0x3BU, 0x07U, 0x3FU, 0x02U, + 0x42U, 0x05U, 0x46U, 0x00U, 0x49U, 0x03U, 0x4CU, 0x06U, 0x50U, 0x01U, + 0x01U, 0x01U, 0x04U, 0x05U, 0x08U, 0x01U, 0x0BU, 0x05U, 0x0FU, 0x01U, + 0x12U, 0x05U, 0x16U, 0x01U, 0x19U, 0x05U, 0x1DU, 0x01U, 0x20U, 0x05U, + 0x24U, 0x01U, 0x27U, 0x05U, 0x2BU, 0x01U, 0x2EU, 0x04U, 0x31U, 0x07U, + 0x35U, 0x02U, 0x38U, 0x05U, 0x3CU, 0x00U, 0x3FU, 0x03U, 0x42U, 0x06U, + 0x46U, 0x01U, 0x49U, 0x04U, 0x4CU, 0x07U, 0x50U, 0x02U, 0x01U, 0x02U, + 0x04U, 0x06U, 0x08U, 0x02U, 0x0BU, 0x06U, 0x0FU, 0x02U, 0x12U, 0x06U, + 0x16U, 0x02U, 0x19U, 0x06U, 0x1DU, 0x02U, 0x20U, 0x06U, 0x24U, 0x02U, + 0x27U, 0x06U, 0x2BU, 0x02U, 0x2EU, 0x05U, 0x32U, 0x00U, 0x35U, 0x03U, + 0x38U, 0x06U, 0x3CU, 0x01U, 0x3FU, 0x04U, 0x42U, 0x07U, 0x46U, 0x02U, + 0x49U, 0x05U, 0x4DU, 0x00U, 0x50U, 0x03U, 0x01U, 0x03U, 0x04U, 0x07U, + 0x08U, 0x03U, 0x0BU, 0x07U, 0x0FU, 0x03U, 0x12U, 0x07U, 0x16U, 0x03U, + 0x19U, 0x07U, 0x1DU, 0x03U, 0x20U, 0x07U, 0x24U, 0x03U, 0x27U, 0x07U, + 0x2BU, 0x03U, 0x2EU, 0x06U, 0x32U, 0x01U, 0x35U, 0x04U, 0x38U, 0x07U, + 0x3CU, 0x02U, 0x3FU, 0x05U, 0x43U, 0x00U, 0x46U, 0x03U, 0x49U, 0x06U, + 0x4DU, 0x01U, 0x50U, 0x04U, 0x01U, 0x04U, 0x05U, 0x00U, 0x08U, 0x04U, + 0x0CU, 0x00U, 0x0FU, 0x04U, 0x13U, 0x00U, 0x16U, 0x04U, 0x1AU, 0x00U, + 0x1DU, 0x04U, 0x21U, 0x00U, 0x24U, 0x04U, 0x28U, 0x00U, 0x2BU, 0x04U, + 0x2EU, 0x07U, 0x32U, 0x02U, 0x35U, 0x05U, 0x39U, 0x00U, 0x3CU, 0x03U, + 0x3FU, 0x06U, 0x43U, 0x01U, 0x46U, 0x04U, 0x49U, 0x07U, 0x4DU, 0x02U, + 0x50U, 0x05U, 0x01U, 0x05U, 0x05U, 0x01U, 0x08U, 0x05U, 0x0CU, 0x01U, + 0x0FU, 0x05U, 0x13U, 0x01U, 0x16U, 0x05U, 0x1AU, 0x01U, 0x1DU, 0x05U, + 0x21U, 0x01U, 0x24U, 0x05U, 0x28U, 0x01U, 0x2BU, 0x05U, 0x2FU, 0x00U, + 0x32U, 0x03U, 0x35U, 0x06U, 0x39U, 0x01U, 0x3CU, 0x04U, 0x3FU, 0x07U, + 0x43U, 0x02U, 0x46U, 0x05U, 0x4AU, 0x00U, 0x4DU, 0x03U, 0x50U, 0x06U, + 0x01U, 0x06U, 0x05U, 0x02U, 0x08U, 0x06U, 0x0CU, 0x02U, 0x0FU, 0x06U, + 0x13U, 0x02U, 0x16U, 0x06U, 0x1AU, 0x02U, 0x1DU, 0x06U, 0x21U, 0x02U, + 0x24U, 0x06U, 0x28U, 0x02U, 0x2BU, 0x06U, 0x2FU, 0x01U, 0x32U, 0x04U, + 0x35U, 0x07U, 0x39U, 0x02U, 0x3CU, 0x05U, 0x40U, 0x00U, 0x43U, 0x03U, + 0x46U, 0x06U, 0x4AU, 0x01U, 0x4DU, 0x04U, 0x50U, 0x07U, 0x01U, 0x07U, + 0x05U, 0x03U, 0x08U, 0x07U, 0x0CU, 0x03U, 0x0FU, 0x07U, 0x13U, 0x03U, + 0x16U, 0x07U, 0x1AU, 0x03U, 0x1DU, 0x07U, 0x21U, 0x03U, 0x24U, 0x07U, + 0x28U, 0x03U, 0x2BU, 0x07U, 0x2FU, 0x02U, 0x32U, 0x05U, 0x36U, 0x00U, + 0x39U, 0x03U, 0x3CU, 0x06U, 0x40U, 0x01U, 0x43U, 0x04U, 0x46U, 0x07U, + 0x4AU, 0x02U, 0x4DU, 0x05U, 0x51U, 0x00U, 0x02U, 0x00U, 0x05U, 0x04U, + 0x09U, 0x00U, 0x0CU, 0x04U, 0x10U, 0x00U, 0x13U, 0x04U, 0x17U, 0x00U, + 0x1AU, 0x04U, 0x1EU, 0x00U, 0x21U, 0x04U, 0x25U, 0x00U, 0x28U, 0x04U, + 0x2CU, 0x00U, 0x2FU, 0x03U, 0x32U, 0x06U, 0x36U, 0x01U, 0x39U, 0x04U, + 0x3CU, 0x07U, 0x40U, 0x02U, 0x43U, 0x05U, 0x47U, 0x00U, 0x4AU, 0x03U, + 0x4DU, 0x06U, 0x51U, 0x01U, 0x02U, 0x01U, 0x05U, 0x05U, 0x09U, 0x01U, + 0x0CU, 0x05U, 0x10U, 0x01U, 0x13U, 0x05U, 0x17U, 0x01U, 0x1AU, 0x05U, + 0x1EU, 0x01U, 0x21U, 0x05U, 0x25U, 0x01U, 0x28U, 0x05U, 0x2CU, 0x01U, + 0x2FU, 0x04U, 0x32U, 0x07U, 0x36U, 0x02U, 0x39U, 0x05U, 0x3DU, 0x00U, + 0x40U, 0x03U, 0x43U, 0x06U, 0x47U, 0x01U, 0x4AU, 0x04U, 0x4DU, 0x07U, + 0x51U, 0x02U, 0x02U, 0x02U, 0x05U, 0x06U, 0x09U, 0x02U, 0x0CU, 0x06U, + 0x10U, 0x02U, 0x13U, 0x06U, 0x17U, 0x02U, 0x1AU, 0x06U, 0x1EU, 0x02U, + 0x21U, 0x06U, 0x25U, 0x02U, 0x28U, 0x06U, 0x2CU, 0x02U, 0x2FU, 0x05U, + 0x33U, 0x00U, 0x36U, 0x03U, 0x39U, 0x06U, 0x3DU, 0x01U, 0x40U, 0x04U, + 0x43U, 0x07U, 0x47U, 0x02U, 0x4AU, 0x05U, 0x4EU, 0x00U, 0x51U, 0x03U, + 0x02U, 0x03U, 0x05U, 0x07U, 0x09U, 0x03U, 0x0CU, 0x07U, 0x10U, 0x03U, + 0x13U, 0x07U, 0x17U, 0x03U, 0x1AU, 0x07U, 0x1EU, 0x03U, 0x21U, 0x07U, + 0x25U, 0x03U, 0x28U, 0x07U, 0x2CU, 0x03U, 0x2FU, 0x06U, 0x33U, 0x01U, + 0x36U, 0x04U, 0x39U, 0x07U, 0x3DU, 0x02U, 0x40U, 0x05U, 0x44U, 0x00U, + 0x47U, 0x03U, 0x4AU, 0x06U, 0x4EU, 0x01U, 0x51U, 0x04U, 0x02U, 0x04U, + 0x06U, 0x00U, 0x09U, 0x04U, 0x0DU, 0x00U, 0x10U, 0x04U, 0x14U, 0x00U, + 0x17U, 0x04U, 0x1BU, 0x00U, 0x1EU, 0x04U, 0x22U, 0x00U, 0x25U, 0x04U, + 0x29U, 0x00U, 0x2CU, 0x04U, 0x2FU, 0x07U, 0x33U, 0x02U, 0x36U, 0x05U, + 0x3AU, 0x00U, 0x3DU, 0x03U, 0x40U, 0x06U, 0x44U, 0x01U, 0x47U, 0x04U, + 0x4AU, 0x07U, 0x4EU, 0x02U, 0x51U, 0x05U, 0x02U, 0x05U, 0x06U, 0x01U, + 0x09U, 0x05U, 0x0DU, 0x01U, 0x10U, 0x05U, 0x14U, 0x01U, 0x17U, 0x05U, + 0x1BU, 0x01U, 0x1EU, 0x05U, 0x22U, 0x01U, 0x25U, 0x05U, 0x29U, 0x01U, + 0x2CU, 0x05U, 0x30U, 0x00U, 0x33U, 0x03U, 0x36U, 0x06U, 0x3AU, 0x01U, + 0x3DU, 0x04U, 0x40U, 0x07U, 0x44U, 0x02U, 0x47U, 0x05U, 0x4BU, 0x00U, + 0x4EU, 0x03U, 0x51U, 0x06U, 0x02U, 0x06U, 0x06U, 0x02U, 0x09U, 0x06U, + 0x0DU, 0x02U, 0x10U, 0x06U, 0x14U, 0x02U, 0x17U, 0x06U, 0x1BU, 0x02U, + 0x1EU, 0x06U, 0x22U, 0x02U, 0x25U, 0x06U, 0x29U, 0x02U, 0x2CU, 0x06U, + 0x30U, 0x01U, 0x33U, 0x04U, 0x36U, 0x07U, 0x3AU, 0x02U, 0x3DU, 0x05U, + 0x41U, 0x00U, 0x44U, 0x03U, 0x47U, 0x06U, 0x4BU, 0x01U, 0x4EU, 0x04U, + 0x51U, 0x07U, 0x02U, 0x07U, 0x06U, 0x03U, 0x09U, 0x07U, 0x0DU, 0x03U, + 0x10U, 0x07U, 0x14U, 0x03U, 0x17U, 0x07U, 0x1BU, 0x03U, 0x1EU, 0x07U, + 0x22U, 0x03U, 0x25U, 0x07U, 0x29U, 0x03U, 0x2CU, 0x07U, 0x30U, 0x02U, + 0x33U, 0x05U, 0x37U, 0x00U, 0x3AU, 0x03U, 0x3DU, 0x06U, 0x41U, 0x01U, + 0x44U, 0x04U, 0x47U, 0x07U, 0x4BU, 0x02U, 0x4EU, 0x05U, 0x52U, 0x00U, + 0x03U, 0x00U, 0x06U, 0x04U, 0x0AU, 0x00U, 0x0DU, 0x04U, 0x11U, 0x00U, + 0x14U, 0x04U, 0x18U, 0x00U, 0x1BU, 0x04U, 0x1FU, 0x00U, 0x22U, 0x04U, + 0x26U, 0x00U, 0x29U, 0x04U, 0x2DU, 0x00U, 0x30U, 0x03U, 0x33U, 0x06U, + 0x37U, 0x01U, 0x3AU, 0x04U, 0x3DU, 0x07U, 0x41U, 0x02U, 0x44U, 0x05U, + 0x48U, 0x00U, 0x4BU, 0x03U, 0x4EU, 0x06U, 0x52U, 0x01U, 0x03U, 0x01U, + 0x06U, 0x05U, 0x0AU, 0x01U, 0x0DU, 0x05U, 0x11U, 0x01U, 0x14U, 0x05U, + 0x18U, 0x01U, 0x1BU, 0x05U, 0x1FU, 0x01U, 0x22U, 0x05U, 0x26U, 0x01U, + 0x29U, 0x05U, 0x2DU, 0x01U, 0x30U, 0x04U, 0x33U, 0x07U, 0x37U, 0x02U, + 0x3AU, 0x05U, 0x3EU, 0x00U, 0x41U, 0x03U, 0x44U, 0x06U, 0x48U, 0x01U, + 0x4BU, 0x04U, 0x4EU, 0x07U, 0x52U, 0x02U, 0x03U, 0x02U, 0x06U, 0x06U, + 0x0AU, 0x02U, 0x0DU, 0x06U, 0x11U, 0x02U, 0x14U, 0x06U, 0x18U, 0x02U, + 0x1BU, 0x06U, 0x1FU, 0x02U, 0x22U, 0x06U, 0x26U, 0x02U, 0x29U, 0x06U, + 0x2DU, 0x02U, 0x30U, 0x05U, 0x34U, 0x00U, 0x37U, 0x03U, 0x3AU, 0x06U, + 0x3EU, 0x01U, 0x41U, 0x04U, 0x44U, 0x07U, 0x48U, 0x02U, 0x4BU, 0x05U, + 0x4FU, 0x00U, 0x52U, 0x03U, 0x03U, 0x03U, 0x06U, 0x07U, 0x0AU, 0x03U, + 0x0DU, 0x07U, 0x11U, 0x03U, 0x14U, 0x07U, 0x18U, 0x03U, 0x1BU, 0x07U, + 0x1FU, 0x03U, 0x22U, 0x07U, 0x26U, 0x03U, 0x29U, 0x07U, 0x2DU, 0x03U, + 0x30U, 0x06U, 0x34U, 0x01U, 0x37U, 0x04U, 0x3AU, 0x07U, 0x3EU, 0x02U, + 0x41U, 0x05U, 0x45U, 0x00U, 0x48U, 0x03U, 0x4BU, 0x06U, 0x4FU, 0x01U, + 0x52U, 0x04U, 0x03U, 0x04U, 0x07U, 0x00U, 0x0AU, 0x04U, 0x0EU, 0x00U, + 0x11U, 0x04U, 0x15U, 0x00U, 0x18U, 0x04U, 0x1CU, 0x00U, 0x1FU, 0x04U, + 0x23U, 0x00U, 0x26U, 0x04U, 0x2AU, 0x00U, 0x2DU, 0x04U, 0x30U, 0x07U, + 0x34U, 0x02U, 0x37U, 0x05U, 0x3BU, 0x00U, 0x3EU, 0x03U, 0x41U, 0x06U, + 0x45U, 0x01U, 0x48U, 0x04U, 0x4BU, 0x07U, 0x4FU, 0x02U, 0x52U, 0x05U, + 0x03U, 0x05U, 0x07U, 0x01U, 0x0AU, 0x05U, 0x0EU, 0x01U, 0x11U, 0x05U, + 0x15U, 0x01U, 0x18U, 0x05U, 0x1CU, 0x01U, 0x1FU, 0x05U, 0x23U, 0x01U, + 0x26U, 0x05U, 0x2AU, 0x01U, 0x2DU, 0x05U, 0x31U, 0x00U, 0x34U, 0x03U, + 0x37U, 0x06U, 0x3BU, 0x01U, 0x3EU, 0x04U, 0x41U, 0x07U, 0x45U, 0x02U, + 0x48U, 0x05U, 0x4CU, 0x00U, 0x4FU, 0x03U, 0x52U, 0x06U, 0x03U, 0x06U, + 0x07U, 0x02U, 0x0AU, 0x06U, 0x0EU, 0x02U, 0x11U, 0x06U, 0x15U, 0x02U, + 0x18U, 0x06U, 0x1CU, 0x02U, 0x1FU, 0x06U, 0x23U, 0x02U, 0x26U, 0x06U, + 0x2AU, 0x02U, 0x2DU, 0x06U, 0x31U, 0x01U, 0x34U, 0x04U, 0x37U, 0x07U, + 0x3BU, 0x02U, 0x3EU, 0x05U, 0x42U, 0x00U, 0x45U, 0x03U, 0x48U, 0x06U, + 0x4CU, 0x01U, 0x4FU, 0x04U, 0x52U, 0x07U, 0x03U, 0x07U, 0x07U, 0x03U, + 0x0AU, 0x07U, 0x0EU, 0x03U, 0x11U, 0x07U, 0x15U, 0x03U, 0x18U, 0x07U, + 0x1CU, 0x03U, 0x1FU, 0x07U, 0x23U, 0x03U, 0x26U, 0x07U, 0x2AU, 0x03U +}; + +const unsigned char INTERLEAVE_TABLE_RX[] = { + 0x00U, 0x00U, 0x03U, 0x00U, 0x06U, 0x00U, 0x09U, 0x00U, 0x0CU, 0x00U, + 0x0FU, 0x00U, 0x12U, 0x00U, 0x15U, 0x00U, 0x18U, 0x00U, 0x1BU, 0x00U, + 0x1EU, 0x00U, 0x21U, 0x00U, 0x24U, 0x00U, 0x27U, 0x00U, 0x2AU, 0x00U, + 0x2DU, 0x00U, 0x30U, 0x00U, 0x33U, 0x00U, 0x36U, 0x00U, 0x39U, 0x00U, + 0x3CU, 0x00U, 0x3FU, 0x00U, 0x42U, 0x00U, 0x45U, 0x00U, 0x48U, 0x00U, + 0x4BU, 0x00U, 0x4EU, 0x00U, 0x51U, 0x00U, 0x00U, 0x01U, 0x03U, 0x01U, + 0x06U, 0x01U, 0x09U, 0x01U, 0x0CU, 0x01U, 0x0FU, 0x01U, 0x12U, 0x01U, + 0x15U, 0x01U, 0x18U, 0x01U, 0x1BU, 0x01U, 0x1EU, 0x01U, 0x21U, 0x01U, + 0x24U, 0x01U, 0x27U, 0x01U, 0x2AU, 0x01U, 0x2DU, 0x01U, 0x30U, 0x01U, + 0x33U, 0x01U, 0x36U, 0x01U, 0x39U, 0x01U, 0x3CU, 0x01U, 0x3FU, 0x01U, + 0x42U, 0x01U, 0x45U, 0x01U, 0x48U, 0x01U, 0x4BU, 0x01U, 0x4EU, 0x01U, + 0x51U, 0x01U, 0x00U, 0x02U, 0x03U, 0x02U, 0x06U, 0x02U, 0x09U, 0x02U, + 0x0CU, 0x02U, 0x0FU, 0x02U, 0x12U, 0x02U, 0x15U, 0x02U, 0x18U, 0x02U, + 0x1BU, 0x02U, 0x1EU, 0x02U, 0x21U, 0x02U, 0x24U, 0x02U, 0x27U, 0x02U, + 0x2AU, 0x02U, 0x2DU, 0x02U, 0x30U, 0x02U, 0x33U, 0x02U, 0x36U, 0x02U, + 0x39U, 0x02U, 0x3CU, 0x02U, 0x3FU, 0x02U, 0x42U, 0x02U, 0x45U, 0x02U, + 0x48U, 0x02U, 0x4BU, 0x02U, 0x4EU, 0x02U, 0x51U, 0x02U, 0x00U, 0x03U, + 0x03U, 0x03U, 0x06U, 0x03U, 0x09U, 0x03U, 0x0CU, 0x03U, 0x0FU, 0x03U, + 0x12U, 0x03U, 0x15U, 0x03U, 0x18U, 0x03U, 0x1BU, 0x03U, 0x1EU, 0x03U, + 0x21U, 0x03U, 0x24U, 0x03U, 0x27U, 0x03U, 0x2AU, 0x03U, 0x2DU, 0x03U, + 0x30U, 0x03U, 0x33U, 0x03U, 0x36U, 0x03U, 0x39U, 0x03U, 0x3CU, 0x03U, + 0x3FU, 0x03U, 0x42U, 0x03U, 0x45U, 0x03U, 0x48U, 0x03U, 0x4BU, 0x03U, + 0x4EU, 0x03U, 0x51U, 0x03U, 0x00U, 0x04U, 0x03U, 0x04U, 0x06U, 0x04U, + 0x09U, 0x04U, 0x0CU, 0x04U, 0x0FU, 0x04U, 0x12U, 0x04U, 0x15U, 0x04U, + 0x18U, 0x04U, 0x1BU, 0x04U, 0x1EU, 0x04U, 0x21U, 0x04U, 0x24U, 0x04U, + 0x27U, 0x04U, 0x2AU, 0x04U, 0x2DU, 0x04U, 0x30U, 0x04U, 0x33U, 0x04U, + 0x36U, 0x04U, 0x39U, 0x04U, 0x3CU, 0x04U, 0x3FU, 0x04U, 0x42U, 0x04U, + 0x45U, 0x04U, 0x48U, 0x04U, 0x4BU, 0x04U, 0x4EU, 0x04U, 0x51U, 0x04U, + 0x00U, 0x05U, 0x03U, 0x05U, 0x06U, 0x05U, 0x09U, 0x05U, 0x0CU, 0x05U, + 0x0FU, 0x05U, 0x12U, 0x05U, 0x15U, 0x05U, 0x18U, 0x05U, 0x1BU, 0x05U, + 0x1EU, 0x05U, 0x21U, 0x05U, 0x24U, 0x05U, 0x27U, 0x05U, 0x2AU, 0x05U, + 0x2DU, 0x05U, 0x30U, 0x05U, 0x33U, 0x05U, 0x36U, 0x05U, 0x39U, 0x05U, + 0x3CU, 0x05U, 0x3FU, 0x05U, 0x42U, 0x05U, 0x45U, 0x05U, 0x48U, 0x05U, + 0x4BU, 0x05U, 0x4EU, 0x05U, 0x51U, 0x05U, 0x00U, 0x06U, 0x03U, 0x06U, + 0x06U, 0x06U, 0x09U, 0x06U, 0x0CU, 0x06U, 0x0FU, 0x06U, 0x12U, 0x06U, + 0x15U, 0x06U, 0x18U, 0x06U, 0x1BU, 0x06U, 0x1EU, 0x06U, 0x21U, 0x06U, + 0x24U, 0x06U, 0x27U, 0x06U, 0x2AU, 0x06U, 0x2DU, 0x06U, 0x30U, 0x06U, + 0x33U, 0x06U, 0x36U, 0x06U, 0x39U, 0x06U, 0x3CU, 0x06U, 0x3FU, 0x06U, + 0x42U, 0x06U, 0x45U, 0x06U, 0x48U, 0x06U, 0x4BU, 0x06U, 0x4EU, 0x06U, + 0x51U, 0x06U, 0x00U, 0x07U, 0x03U, 0x07U, 0x06U, 0x07U, 0x09U, 0x07U, + 0x0CU, 0x07U, 0x0FU, 0x07U, 0x12U, 0x07U, 0x15U, 0x07U, 0x18U, 0x07U, + 0x1BU, 0x07U, 0x1EU, 0x07U, 0x21U, 0x07U, 0x24U, 0x07U, 0x27U, 0x07U, + 0x2AU, 0x07U, 0x2DU, 0x07U, 0x30U, 0x07U, 0x33U, 0x07U, 0x36U, 0x07U, + 0x39U, 0x07U, 0x3CU, 0x07U, 0x3FU, 0x07U, 0x42U, 0x07U, 0x45U, 0x07U, + 0x48U, 0x07U, 0x4BU, 0x07U, 0x4EU, 0x07U, 0x51U, 0x07U, 0x01U, 0x00U, + 0x04U, 0x00U, 0x07U, 0x00U, 0x0AU, 0x00U, 0x0DU, 0x00U, 0x10U, 0x00U, + 0x13U, 0x00U, 0x16U, 0x00U, 0x19U, 0x00U, 0x1CU, 0x00U, 0x1FU, 0x00U, + 0x22U, 0x00U, 0x25U, 0x00U, 0x28U, 0x00U, 0x2BU, 0x00U, 0x2EU, 0x00U, + 0x31U, 0x00U, 0x34U, 0x00U, 0x37U, 0x00U, 0x3AU, 0x00U, 0x3DU, 0x00U, + 0x40U, 0x00U, 0x43U, 0x00U, 0x46U, 0x00U, 0x49U, 0x00U, 0x4CU, 0x00U, + 0x4FU, 0x00U, 0x52U, 0x00U, 0x01U, 0x01U, 0x04U, 0x01U, 0x07U, 0x01U, + 0x0AU, 0x01U, 0x0DU, 0x01U, 0x10U, 0x01U, 0x13U, 0x01U, 0x16U, 0x01U, + 0x19U, 0x01U, 0x1CU, 0x01U, 0x1FU, 0x01U, 0x22U, 0x01U, 0x25U, 0x01U, + 0x28U, 0x01U, 0x2BU, 0x01U, 0x2EU, 0x01U, 0x31U, 0x01U, 0x34U, 0x01U, + 0x37U, 0x01U, 0x3AU, 0x01U, 0x3DU, 0x01U, 0x40U, 0x01U, 0x43U, 0x01U, + 0x46U, 0x01U, 0x49U, 0x01U, 0x4CU, 0x01U, 0x4FU, 0x01U, 0x52U, 0x01U, + 0x01U, 0x02U, 0x04U, 0x02U, 0x07U, 0x02U, 0x0AU, 0x02U, 0x0DU, 0x02U, + 0x10U, 0x02U, 0x13U, 0x02U, 0x16U, 0x02U, 0x19U, 0x02U, 0x1CU, 0x02U, + 0x1FU, 0x02U, 0x22U, 0x02U, 0x25U, 0x02U, 0x28U, 0x02U, 0x2BU, 0x02U, + 0x2EU, 0x02U, 0x31U, 0x02U, 0x34U, 0x02U, 0x37U, 0x02U, 0x3AU, 0x02U, + 0x3DU, 0x02U, 0x40U, 0x02U, 0x43U, 0x02U, 0x46U, 0x02U, 0x49U, 0x02U, + 0x4CU, 0x02U, 0x4FU, 0x02U, 0x52U, 0x02U, 0x01U, 0x03U, 0x04U, 0x03U, + 0x07U, 0x03U, 0x0AU, 0x03U, 0x0DU, 0x03U, 0x10U, 0x03U, 0x13U, 0x03U, + 0x16U, 0x03U, 0x19U, 0x03U, 0x1CU, 0x03U, 0x1FU, 0x03U, 0x22U, 0x03U, + 0x25U, 0x03U, 0x28U, 0x03U, 0x2BU, 0x03U, 0x2EU, 0x03U, 0x31U, 0x03U, + 0x34U, 0x03U, 0x37U, 0x03U, 0x3AU, 0x03U, 0x3DU, 0x03U, 0x40U, 0x03U, + 0x43U, 0x03U, 0x46U, 0x03U, 0x49U, 0x03U, 0x4CU, 0x03U, 0x4FU, 0x03U, + 0x52U, 0x03U, 0x01U, 0x04U, 0x04U, 0x04U, 0x07U, 0x04U, 0x0AU, 0x04U, + 0x0DU, 0x04U, 0x10U, 0x04U, 0x13U, 0x04U, 0x16U, 0x04U, 0x19U, 0x04U, + 0x1CU, 0x04U, 0x1FU, 0x04U, 0x22U, 0x04U, 0x25U, 0x04U, 0x28U, 0x04U, + 0x2BU, 0x04U, 0x2EU, 0x04U, 0x31U, 0x04U, 0x34U, 0x04U, 0x37U, 0x04U, + 0x3AU, 0x04U, 0x3DU, 0x04U, 0x40U, 0x04U, 0x43U, 0x04U, 0x46U, 0x04U, + 0x49U, 0x04U, 0x4CU, 0x04U, 0x4FU, 0x04U, 0x01U, 0x05U, 0x04U, 0x05U, + 0x07U, 0x05U, 0x0AU, 0x05U, 0x0DU, 0x05U, 0x10U, 0x05U, 0x13U, 0x05U, + 0x16U, 0x05U, 0x19U, 0x05U, 0x1CU, 0x05U, 0x1FU, 0x05U, 0x22U, 0x05U, + 0x25U, 0x05U, 0x28U, 0x05U, 0x2BU, 0x05U, 0x2EU, 0x05U, 0x31U, 0x05U, + 0x34U, 0x05U, 0x37U, 0x05U, 0x3AU, 0x05U, 0x3DU, 0x05U, 0x40U, 0x05U, + 0x43U, 0x05U, 0x46U, 0x05U, 0x49U, 0x05U, 0x4CU, 0x05U, 0x4FU, 0x05U, + 0x01U, 0x06U, 0x04U, 0x06U, 0x07U, 0x06U, 0x0AU, 0x06U, 0x0DU, 0x06U, + 0x10U, 0x06U, 0x13U, 0x06U, 0x16U, 0x06U, 0x19U, 0x06U, 0x1CU, 0x06U, + 0x1FU, 0x06U, 0x22U, 0x06U, 0x25U, 0x06U, 0x28U, 0x06U, 0x2BU, 0x06U, + 0x2EU, 0x06U, 0x31U, 0x06U, 0x34U, 0x06U, 0x37U, 0x06U, 0x3AU, 0x06U, + 0x3DU, 0x06U, 0x40U, 0x06U, 0x43U, 0x06U, 0x46U, 0x06U, 0x49U, 0x06U, + 0x4CU, 0x06U, 0x4FU, 0x06U, 0x01U, 0x07U, 0x04U, 0x07U, 0x07U, 0x07U, + 0x0AU, 0x07U, 0x0DU, 0x07U, 0x10U, 0x07U, 0x13U, 0x07U, 0x16U, 0x07U, + 0x19U, 0x07U, 0x1CU, 0x07U, 0x1FU, 0x07U, 0x22U, 0x07U, 0x25U, 0x07U, + 0x28U, 0x07U, 0x2BU, 0x07U, 0x2EU, 0x07U, 0x31U, 0x07U, 0x34U, 0x07U, + 0x37U, 0x07U, 0x3AU, 0x07U, 0x3DU, 0x07U, 0x40U, 0x07U, 0x43U, 0x07U, + 0x46U, 0x07U, 0x49U, 0x07U, 0x4CU, 0x07U, 0x4FU, 0x07U, 0x02U, 0x00U, + 0x05U, 0x00U, 0x08U, 0x00U, 0x0BU, 0x00U, 0x0EU, 0x00U, 0x11U, 0x00U, + 0x14U, 0x00U, 0x17U, 0x00U, 0x1AU, 0x00U, 0x1DU, 0x00U, 0x20U, 0x00U, + 0x23U, 0x00U, 0x26U, 0x00U, 0x29U, 0x00U, 0x2CU, 0x00U, 0x2FU, 0x00U, + 0x32U, 0x00U, 0x35U, 0x00U, 0x38U, 0x00U, 0x3BU, 0x00U, 0x3EU, 0x00U, + 0x41U, 0x00U, 0x44U, 0x00U, 0x47U, 0x00U, 0x4AU, 0x00U, 0x4DU, 0x00U, + 0x50U, 0x00U, 0x02U, 0x01U, 0x05U, 0x01U, 0x08U, 0x01U, 0x0BU, 0x01U, + 0x0EU, 0x01U, 0x11U, 0x01U, 0x14U, 0x01U, 0x17U, 0x01U, 0x1AU, 0x01U, + 0x1DU, 0x01U, 0x20U, 0x01U, 0x23U, 0x01U, 0x26U, 0x01U, 0x29U, 0x01U, + 0x2CU, 0x01U, 0x2FU, 0x01U, 0x32U, 0x01U, 0x35U, 0x01U, 0x38U, 0x01U, + 0x3BU, 0x01U, 0x3EU, 0x01U, 0x41U, 0x01U, 0x44U, 0x01U, 0x47U, 0x01U, + 0x4AU, 0x01U, 0x4DU, 0x01U, 0x50U, 0x01U, 0x02U, 0x02U, 0x05U, 0x02U, + 0x08U, 0x02U, 0x0BU, 0x02U, 0x0EU, 0x02U, 0x11U, 0x02U, 0x14U, 0x02U, + 0x17U, 0x02U, 0x1AU, 0x02U, 0x1DU, 0x02U, 0x20U, 0x02U, 0x23U, 0x02U, + 0x26U, 0x02U, 0x29U, 0x02U, 0x2CU, 0x02U, 0x2FU, 0x02U, 0x32U, 0x02U, + 0x35U, 0x02U, 0x38U, 0x02U, 0x3BU, 0x02U, 0x3EU, 0x02U, 0x41U, 0x02U, + 0x44U, 0x02U, 0x47U, 0x02U, 0x4AU, 0x02U, 0x4DU, 0x02U, 0x50U, 0x02U, + 0x02U, 0x03U, 0x05U, 0x03U, 0x08U, 0x03U, 0x0BU, 0x03U, 0x0EU, 0x03U, + 0x11U, 0x03U, 0x14U, 0x03U, 0x17U, 0x03U, 0x1AU, 0x03U, 0x1DU, 0x03U, + 0x20U, 0x03U, 0x23U, 0x03U, 0x26U, 0x03U, 0x29U, 0x03U, 0x2CU, 0x03U, + 0x2FU, 0x03U, 0x32U, 0x03U, 0x35U, 0x03U, 0x38U, 0x03U, 0x3BU, 0x03U, + 0x3EU, 0x03U, 0x41U, 0x03U, 0x44U, 0x03U, 0x47U, 0x03U, 0x4AU, 0x03U, + 0x4DU, 0x03U, 0x50U, 0x03U, 0x02U, 0x04U, 0x05U, 0x04U, 0x08U, 0x04U, + 0x0BU, 0x04U, 0x0EU, 0x04U, 0x11U, 0x04U, 0x14U, 0x04U, 0x17U, 0x04U, + 0x1AU, 0x04U, 0x1DU, 0x04U, 0x20U, 0x04U, 0x23U, 0x04U, 0x26U, 0x04U, + 0x29U, 0x04U, 0x2CU, 0x04U, 0x2FU, 0x04U, 0x32U, 0x04U, 0x35U, 0x04U, + 0x38U, 0x04U, 0x3BU, 0x04U, 0x3EU, 0x04U, 0x41U, 0x04U, 0x44U, 0x04U, + 0x47U, 0x04U, 0x4AU, 0x04U, 0x4DU, 0x04U, 0x50U, 0x04U, 0x02U, 0x05U, + 0x05U, 0x05U, 0x08U, 0x05U, 0x0BU, 0x05U, 0x0EU, 0x05U, 0x11U, 0x05U, + 0x14U, 0x05U, 0x17U, 0x05U, 0x1AU, 0x05U, 0x1DU, 0x05U, 0x20U, 0x05U, + 0x23U, 0x05U, 0x26U, 0x05U, 0x29U, 0x05U, 0x2CU, 0x05U, 0x2FU, 0x05U, + 0x32U, 0x05U, 0x35U, 0x05U, 0x38U, 0x05U, 0x3BU, 0x05U, 0x3EU, 0x05U, + 0x41U, 0x05U, 0x44U, 0x05U, 0x47U, 0x05U, 0x4AU, 0x05U, 0x4DU, 0x05U, + 0x50U, 0x05U, 0x02U, 0x06U, 0x05U, 0x06U, 0x08U, 0x06U, 0x0BU, 0x06U, + 0x0EU, 0x06U, 0x11U, 0x06U, 0x14U, 0x06U, 0x17U, 0x06U, 0x1AU, 0x06U, + 0x1DU, 0x06U, 0x20U, 0x06U, 0x23U, 0x06U, 0x26U, 0x06U, 0x29U, 0x06U, + 0x2CU, 0x06U, 0x2FU, 0x06U, 0x32U, 0x06U, 0x35U, 0x06U, 0x38U, 0x06U, + 0x3BU, 0x06U, 0x3EU, 0x06U, 0x41U, 0x06U, 0x44U, 0x06U, 0x47U, 0x06U, + 0x4AU, 0x06U, 0x4DU, 0x06U, 0x50U, 0x06U, 0x02U, 0x07U, 0x05U, 0x07U, + 0x08U, 0x07U, 0x0BU, 0x07U, 0x0EU, 0x07U, 0x11U, 0x07U, 0x14U, 0x07U, + 0x17U, 0x07U, 0x1AU, 0x07U, 0x1DU, 0x07U, 0x20U, 0x07U, 0x23U, 0x07U, + 0x26U, 0x07U, 0x29U, 0x07U, 0x2CU, 0x07U, 0x2FU, 0x07U, 0x32U, 0x07U, + 0x35U, 0x07U, 0x38U, 0x07U, 0x3BU, 0x07U, 0x3EU, 0x07U, 0x41U, 0x07U, + 0x44U, 0x07U, 0x47U, 0x07U, 0x4AU, 0x07U, 0x4DU, 0x07U, 0x50U, 0x07U, +}; + +const unsigned char SCRAMBLE_TABLE_TX[] = { + 0x00U, 0xF7U, 0x34U, 0x09U, 0x44U, 0x46U, 0xD7U, 0x06U, 0xB3U, 0x72U, + 0xDEU, 0x42U, 0xF5U, 0xA5U, 0xD8U, 0xF1U, 0x87U, 0x7BU, 0x9AU, 0x04U, + 0x22U, 0xA3U, 0x6BU, 0x83U, 0x59U, 0x39U, 0x6FU, 0xA1U, 0xFAU, 0x52U, + 0xECU, 0xF8U, 0xC3U, 0x3DU, 0x4DU, 0x02U, 0x91U, 0xD1U, 0xB5U, 0xC1U, + 0xACU, 0x9CU, 0xB7U, 0x50U, 0x7DU, 0x29U, 0x76U, 0xFCU, 0xE1U, 0x9EU, + 0x26U, 0x81U, 0xC8U, 0xE8U, 0xDAU, 0x60U, 0x56U, 0xCEU, 0x5BU, 0xA8U, + 0xBEU, 0x14U, 0x3BU, 0xFEU, 0x70U, 0x4FU, 0x93U, 0x40U, 0x64U, 0x74U, + 0x6DU, 0x30U, 0x2BU, 0xE7U, 0x2DU, 0x54U, 0x5FU, 0x8AU, 0x1DU, 0x7FU, + 0xB8U, 0xA7U, 0x49U, 0x20U, 0x32U, 0xBAU, 0x36U, 0x98U, 0x95U, 0xF3U, + 0x06U}; + +const unsigned char SCRAMBLE_TABLE_RX[] = { + 0x70U, 0x4FU, 0x93U, 0x40U, 0x64U, 0x74U, 0x6DU, 0x30U, 0x2BU, 0xE7U, + 0x2DU, 0x54U, 0x5FU, 0x8AU, 0x1DU, 0x7FU, 0xB8U, 0xA7U, 0x49U, 0x20U, + 0x32U, 0xBAU, 0x36U, 0x98U, 0x95U, 0xF3U, 0x16U, 0xAAU, 0x2FU, 0xC5U, + 0x8EU, 0x3FU, 0xDCU, 0xD3U, 0x24U, 0x10U, 0x19U, 0x5DU, 0x1BU, 0xCCU, + 0xCAU, 0x79U, 0x0BU, 0xD5U, 0x97U, 0x62U, 0xC7U, 0x1FU, 0xEEU, 0x69U, + 0x12U, 0x88U, 0x8CU, 0xAEU, 0x0DU, 0x66U, 0xE5U, 0xBCU, 0x85U, 0xEAU, + 0x4BU, 0xB1U, 0xE3U, 0x0FU, 0xF7U, 0x34U, 0x09U, 0x44U, 0x46U, 0xD7U, + 0x06U, 0xB3U, 0x72U, 0xDEU, 0x42U, 0xF5U, 0xA5U, 0xD8U, 0xF1U, 0x87U, + 0x7BU, 0x9AU, 0x04U, 0x22U, 0xA3U, 0x6BU, 0x83U, 0x59U, 0x39U, 0x6FU, + 0x00U}; + +CSoundCardController::CSoundCardController(const wxString& rxDevice, const wxString& txDevice, bool rxInvert, bool txInvert, wxFloat32 rxLevel, wxFloat32 txLevel, unsigned int txDelay, unsigned int txTail) : +CModem(), +m_sound(rxDevice, txDevice, DSTAR_RADIO_SAMPLE_RATE, DSTAR_RADIO_BLOCK_SIZE), +m_rxLevel(rxLevel), +m_txLevel(txLevel), +m_txDelay(txDelay), +m_txTail(txTail), +m_txAudio(48000U), +m_rxAudio(4800U), +m_rxState(DSRSCCS_NONE), +m_patternBuffer(0x00U), +m_demodulator(), +m_modulator(), +m_rxBuffer(NULL), +m_rxBufferBits(0U), +m_dataBits(0U), +m_mar(0U), +m_pathMetric(NULL), +m_pathMemory0(NULL), +m_pathMemory1(NULL), +m_pathMemory2(NULL), +m_pathMemory3(NULL), +m_fecOutput(NULL) +{ + wxASSERT(!rxDevice.IsEmpty()); + wxASSERT(!txDevice.IsEmpty()); + + m_modulator.setInvert(txInvert); + m_demodulator.setInvert(rxInvert); + + m_sound.setCallback(this, 0U); + + m_rxBuffer = new unsigned char[FEC_SECTION_LENGTH_BYTES]; + + m_pathMetric = new int[4U]; + m_pathMemory0 = new unsigned int[42U]; + m_pathMemory1 = new unsigned int[42U]; + m_pathMemory2 = new unsigned int[42U]; + m_pathMemory3 = new unsigned int[42U]; + m_fecOutput = new unsigned char[42U]; +} + +CSoundCardController::~CSoundCardController() +{ + delete[] m_rxBuffer; + delete[] m_pathMetric; + delete[] m_pathMemory0; + delete[] m_pathMemory1; + delete[] m_pathMemory2; + delete[] m_pathMemory3; + delete[] m_fecOutput; +} + +bool CSoundCardController::start() +{ + bool ret = m_sound.open(); + if (!ret) + return false; + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CSoundCardController::Entry() +{ + wxLogMessage(wxT("Starting Sound Card Controller thread")); + + while (!m_stopped) { + wxFloat32 val; + while (m_rxAudio.getData(&val, 1U) == 1U) { + TRISTATE state = m_demodulator.decode(val * m_rxLevel); + switch (state) { + case STATE_TRUE: + switch (m_rxState) { + case DSRSCCS_NONE: + processNone(true); + break; + case DSRSCCS_HEADER: + processHeader(true); + break; + case DSRSCCS_DATA: + processData(true); + break; + default: + break; + } + break; + case STATE_FALSE: + switch (m_rxState) { + case DSRSCCS_NONE: + processNone(false); + break; + case DSRSCCS_HEADER: + processHeader(false); + break; + case DSRSCCS_DATA: + processData(false); + break; + default: + break; + } + break; + default: + break; + } + } + + Sleep(10UL); + } + + wxLogMessage(wxT("Stopping Sound Card Controller thread")); + + m_sound.close(); + + return NULL; +} + +bool CSoundCardController::writeHeader(const CHeaderData& header) +{ + bool ret = m_txAudio.hasSpace((m_txDelay + 60U + 85U) * 8U * DSTAR_RADIO_BIT_LENGTH); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + unsigned char buffer1[RADIO_HEADER_LENGTH_BYTES]; + + ::memset(buffer1, ' ', RADIO_HEADER_LENGTH_BYTES); + + buffer1[0U] = header.getFlag1(); + buffer1[1U] = header.getFlag2(); + buffer1[2U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer1[i + 3U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer1[i + 11U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer1[i + 19U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer1[i + 27U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer1[i + 35U] = my2.GetChar(i); + + CCCITTChecksumReverse cksum1; + cksum1.update(buffer1 + 0U, RADIO_HEADER_LENGTH_BYTES - 2U); + cksum1.result(buffer1 + 39U); + + unsigned int bytes = m_txDelay + 60U; + for (unsigned int i = 0U; i < bytes; i++) + writeBits(BIT_SYNC); + + unsigned char buffer2[86U]; + txHeader(buffer1, buffer2 + 2U); + + buffer2[0U] = FRAME_SYNC0; + buffer2[1U] = FRAME_SYNC1; + buffer2[2U] |= FRAME_SYNC2; + + for (unsigned int i = 0U; i < 85U; i++) + writeBits(buffer2[i]); + + return true; +} + +bool CSoundCardController::writeData(const unsigned char* data, unsigned int length, bool end) +{ + if (end) { + unsigned int tailBlocks = 3U + m_txTail / 10U; + + bool ret = m_txAudio.hasSpace(tailBlocks * END_PATTERN_LENGTH_BYTES * 8U * DSTAR_RADIO_BIT_LENGTH); + if (!ret) { + wxLogWarning(wxT("No space to write end data")); + return false; + } + + for (unsigned int j = 0U; j < tailBlocks; j++) { + for (unsigned int i = 0U; i < END_PATTERN_LENGTH_BYTES; i++) + writeBits(END_PATTERN_BYTES[i]); + } + } else { + bool ret = m_txAudio.hasSpace(length * 8U * DSTAR_RADIO_BIT_LENGTH); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + for (unsigned int i = 0U; i < length; i++) + writeBits(data[i]); + } + + return true; +} + +unsigned int CSoundCardController::getSpace() +{ + return m_txAudio.freeSpace() / (DV_FRAME_LENGTH_BYTES * 8U * DSTAR_RADIO_BIT_LENGTH); +} + +bool CSoundCardController::isTXReady() +{ + return m_txAudio.isEmpty(); +} + +bool CSoundCardController::isTX() +{ +#if (defined(__APPLE__) && defined(__MACH__)) || defined(__WINDOWS__) + return m_txAudio.hasData(); +#else + return m_sound.isWriterBusy() || m_txAudio.hasData(); +#endif +} + +void CSoundCardController::readCallback(const wxFloat32* input, unsigned int n, int id) +{ +#if !defined(AUDIO_LOOPBACK) + if (!m_stopped) + m_rxAudio.addData(input, n); +#endif +} + +void CSoundCardController::writeCallback(wxFloat32* output, unsigned int& n, int id) +{ + if (n == 0U) + return; + + ::memset(output, 0x00, n * sizeof(wxFloat32)); + + if (!m_stopped) { + n = m_txAudio.getData(output, n); +#if defined(AUDIO_LOOPBACK) + n = m_rxAudio.addData(output, n); +#endif + } +} + +void CSoundCardController::txHeader(const unsigned char* in, unsigned char* out) +{ + unsigned char intermediate[84U]; + unsigned int i; + + for (i = 0U; i < 83U; i++) { + intermediate[i] = 0x00U; + out[i] = 0x00U; + } + + // Convolve the header + unsigned char d, d1 = 0U, d2 = 0U, g0, g1; + unsigned int k = 0U; + for (i = 0U; i < 42U; i++) { + for (unsigned int j = 0U; j < 8U; j++) { + unsigned char mask = (0x01U << j); + d = 0U; + + if (in[i] & mask) + d = 1U; + + g0 = (d + d2) % 2U; + g1 = (d + d1 + d2) % 2U; + d2 = d1; + d1 = d; + + if (g1) + intermediate[k / 8U] |= BIT_MASK_TABLE1[k % 8U]; + + k++; + + if (g0) + intermediate[k / 8U] |= BIT_MASK_TABLE1[k % 8U]; + + k++; + } + } + + // Interleave the header + i = 0U; + while (i < 660U) { + unsigned char d = intermediate[i / 8U]; + + if (d & 0x80U) + out[INTERLEAVE_TABLE_TX[i * 2U]] |= (0x01U << INTERLEAVE_TABLE_TX[i * 2U + 1U]); + i++; + + if (d & 0x40U) + out[INTERLEAVE_TABLE_TX[i * 2U]] |= (0x01U << INTERLEAVE_TABLE_TX[i * 2U + 1U]); + i++; + + if (d & 0x20U) + out[INTERLEAVE_TABLE_TX[i * 2U]] |= (0x01U << INTERLEAVE_TABLE_TX[i * 2U + 1U]); + i++; + + if (d & 0x10U) + out[INTERLEAVE_TABLE_TX[i * 2U]] |= (0x01U << INTERLEAVE_TABLE_TX[i * 2U + 1U]); + i++; + + if (i < 660U) { + if (d & 0x08U) + out[INTERLEAVE_TABLE_TX[i * 2U]] |= (0x01U << INTERLEAVE_TABLE_TX[i * 2U + 1U]); + i++; + + if (d & 0x04U) + out[INTERLEAVE_TABLE_TX[i * 2U]] |= (0x01U << INTERLEAVE_TABLE_TX[i * 2U + 1U]); + i++; + + if (d & 0x02U) + out[INTERLEAVE_TABLE_TX[i * 2U]] |= (0x01U << INTERLEAVE_TABLE_TX[i * 2U + 1U]); + i++; + + if (d & 0x01U) + out[INTERLEAVE_TABLE_TX[i * 2U]] |= (0x01U << INTERLEAVE_TABLE_TX[i * 2U + 1U]); + i++; + } + } + + // Scramble the header + for (i = 0U; i < 83U; i++) + out[i] ^= SCRAMBLE_TABLE_TX[i]; +} + +void CSoundCardController::writeBits(unsigned char c) +{ + wxFloat32 buffer[DSTAR_RADIO_BIT_LENGTH]; + + unsigned char mask = 0x01U; + for (unsigned int i = 0U; i < 8U; i++) { + bool bit = (c & mask) == mask; + + m_modulator.code(bit, buffer, DSTAR_RADIO_BIT_LENGTH); + + for (unsigned int j = 0U; j < DSTAR_RADIO_BIT_LENGTH; j++) + buffer[j] *= m_txLevel; + + m_txAudio.addData(buffer, DSTAR_RADIO_BIT_LENGTH); + + mask <<= 1; + } +} + +void CSoundCardController::processNone(bool bit) +{ + m_patternBuffer <<= 1; + if (bit) + m_patternBuffer |= 0x01U; + + // Exact matching of the frame sync sequence + unsigned int errs = countBits((m_patternBuffer & FRAME_SYNC_MASK) ^ FRAME_SYNC_DATA); + if (errs == 0U) { + // Lock the GMSK PLL to this signal + m_demodulator.lock(true); + + ::memset(m_rxBuffer, 0x00U, FEC_SECTION_LENGTH_BYTES); + m_rxBufferBits = 0U; + m_rxState = DSRSCCS_HEADER; + return; + } + + // Exact matching of the data sync bit sequence + errs = countBits((m_patternBuffer & DATA_SYNC_MASK) ^ DATA_SYNC_DATA); + if (errs == 0U) { + // Lock the GMSK PLL to this signal + m_demodulator.lock(true); + + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + m_rxData.addData(DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + ::memset(m_rxBuffer, 0x00U, DV_FRAME_LENGTH_BYTES); + m_rxBufferBits = 0U; + + m_dataBits = MAX_SYNC_BITS; + m_rxState = DSRSCCS_DATA; + return; + } +} + +void CSoundCardController::processHeader(bool bit) +{ + m_patternBuffer <<= 1; + if (bit) + m_patternBuffer |= 0x01U; + + WRITE_BIT2(m_rxBuffer, m_rxBufferBits, bit); + m_rxBufferBits++; + + // A full FEC header + if (m_rxBufferBits == FEC_SECTION_LENGTH_BITS) { + // Process the scrambling, interleaving and FEC, then return if the chcksum was correct + unsigned char header[RADIO_HEADER_LENGTH_BYTES]; + bool ok = rxHeader(m_rxBuffer, header); + if (ok) { + // The checksum is correct + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(header, RADIO_HEADER_LENGTH_BYTES); + + ::memset(m_rxBuffer, 0x00U, DV_FRAME_LENGTH_BYTES); + m_rxBufferBits = 0U; + + m_rxState = DSRSCCS_DATA; + m_dataBits = MAX_SYNC_BITS; + } else { + // Release PLL ACQ + m_demodulator.lock(false); + + // The checksum failed, return to looking for syncs + m_rxState = DSRSCCS_NONE; + } + } +} + +void CSoundCardController::processData(bool bit) +{ + m_patternBuffer <<= 1; + if (bit) + m_patternBuffer |= 0x01U; + + WRITE_BIT2(m_rxBuffer, m_rxBufferBits, bit); + m_rxBufferBits++; + + // Fuzzy matching of the end frame sequences + unsigned int errs = countBits((m_patternBuffer & END_SYNC_MASK) ^ END_SYNC_DATA); + if (errs <= END_SYNC_ERRS) { + // Release the GMSK PLL + m_demodulator.lock(false); + + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rxState = DSRSCCS_NONE; + return; + } + + // Fuzzy matching of the data sync bit sequence + bool syncSeen = false; + if (m_rxBufferBits >= (DV_FRAME_LENGTH_BITS - 3U)) { + errs = countBits((m_patternBuffer & DATA_SYNC_MASK) ^ DATA_SYNC_DATA); + if (errs <= DATA_SYNC_ERRS) { + m_rxBufferBits = DV_FRAME_LENGTH_BITS; + m_dataBits = MAX_SYNC_BITS; + syncSeen = true; + } + } + + // We've not seen a data sync for too long, signal RXLOST and change to RX_NONE + m_dataBits--; + if (m_dataBits == 0U) { + // Release the GMSK PLL + m_demodulator.lock(false); + + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_LOST; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_rxState = DSRSCCS_NONE; + return; + } + + // Check to see if the sync is arriving late + if (m_rxBufferBits == DV_FRAME_LENGTH_BITS && !syncSeen) { + for (unsigned int i = 1U; i <= 3U; i++) { + wxUint32 syncMask = DATA_SYNC_MASK >> i; + wxUint32 syncData = DATA_SYNC_DATA >> i; + errs = countBits((m_patternBuffer & syncMask) ^ syncData); + if (errs <= DATA_SYNC_ERRS) { + m_rxBufferBits -= i; + break; + } + } + } + + // Send a data frame to the host if the required number of bits have been received, or if a data sync has been seen + if (m_rxBufferBits == DV_FRAME_LENGTH_BITS) { + if (syncSeen) { + m_rxBuffer[9U] = DATA_SYNC_BYTES[0U]; + m_rxBuffer[10U] = DATA_SYNC_BYTES[1U]; + m_rxBuffer[11U] = DATA_SYNC_BYTES[2U]; + } + + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_rxBuffer, DV_FRAME_LENGTH_BYTES); + + // Start the next frame + ::memset(m_rxBuffer, 0x00U, DV_FRAME_LENGTH_BYTES); + m_rxBufferBits = 0U; + } +} + +unsigned int CSoundCardController::countBits(wxUint32 num) +{ + unsigned int count = 0U; + + for (unsigned int i = 0U; i < 8U; i++) + count += NIBBLE_BITS[(num >> (i * 4U)) & 0x0FU]; + + return count; +} + +bool CSoundCardController::rxHeader(unsigned char* in, unsigned char* out) +{ + int i; + + // Descramble the header + for (i = 0; i < int(FEC_SECTION_LENGTH_BYTES); i++) + in[i] ^= SCRAMBLE_TABLE_RX[i]; + + unsigned char intermediate[84U]; + for (i = 0; i < 84; i++) + intermediate[i] = 0x00U; + + // Deinterleave the header + i = 0; + while (i < 660) { + unsigned char d = in[i / 8]; + + if (d & 0x01U) + intermediate[INTERLEAVE_TABLE_RX[i * 2U]] |= (0x80U >> INTERLEAVE_TABLE_RX[i * 2U + 1U]); + i++; + + if (d & 0x02U) + intermediate[INTERLEAVE_TABLE_RX[i * 2U]] |= (0x80U >> INTERLEAVE_TABLE_RX[i * 2U + 1U]); + i++; + + if (d & 0x04U) + intermediate[INTERLEAVE_TABLE_RX[i * 2U]] |= (0x80U >> INTERLEAVE_TABLE_RX[i * 2U + 1U]); + i++; + + if (d & 0x08U) + intermediate[INTERLEAVE_TABLE_RX[i * 2U]] |= (0x80U >> INTERLEAVE_TABLE_RX[i * 2U + 1U]); + i++; + + if (i < 660) { + if (d & 0x10U) + intermediate[INTERLEAVE_TABLE_RX[i * 2U]] |= (0x80U >> INTERLEAVE_TABLE_RX[i * 2U + 1U]); + i++; + + if (d & 0x20U) + intermediate[INTERLEAVE_TABLE_RX[i * 2U]] |= (0x80U >> INTERLEAVE_TABLE_RX[i * 2U + 1U]); + i++; + + if (d & 0x40U) + intermediate[INTERLEAVE_TABLE_RX[i * 2U]] |= (0x80U >> INTERLEAVE_TABLE_RX[i * 2U + 1U]); + i++; + + if (d & 0x80U) + intermediate[INTERLEAVE_TABLE_RX[i * 2U]] |= (0x80U >> INTERLEAVE_TABLE_RX[i * 2U + 1U]); + i++; + } + } + + for (i = 0; i < 4; i++) + m_pathMetric[i] = 0; + + int decodeData[2U]; + + m_mar = 0U; + for (i = 0; i < 660; i += 2) { + if (intermediate[i / 8] & (0x80U >> i % 8)) + decodeData[1U] = 1U; + else + decodeData[1U] = 0U; + + if (intermediate[i / 8] & (0x40U >> i % 8)) + decodeData[0U] = 1U; + else + decodeData[0U] = 0U; + + viterbiDecode(decodeData); + } + + traceBack(); + + for (i = 0; i < int(RADIO_HEADER_LENGTH_BYTES); i++) + out[i] = 0x00U; + + unsigned int j = 0; + for (i = 329; i >= 0; i--) { + if (READ_BIT1(m_fecOutput, i)) + out[j / 8] |= (0x01U << (j % 8)); + + j++; + } + + CCCITTChecksumReverse cksum; + cksum.update(out, RADIO_HEADER_LENGTH_BYTES - 2U); + + return cksum.check(out + RADIO_HEADER_LENGTH_BYTES - 2U); +} + +void CSoundCardController::acs(int* metric) +{ + int tempMetric[4U]; + + unsigned int j = m_mar / 8U; + unsigned int k = m_mar % 8U; + + // Pres. state = S0, Prev. state = S0 & S2 + int m1 = metric[0U] + m_pathMetric[0U]; + int m2 = metric[4U] + m_pathMetric[2U]; + tempMetric[0U] = m1 < m2 ? m1 : m2; + if (m1 < m2) + m_pathMemory0[j] &= BIT_MASK_TABLE0[k]; + else + m_pathMemory0[j] |= BIT_MASK_TABLE1[k]; + + // Pres. state = S1, Prev. state = S0 & S2 + m1 = metric[1U] + m_pathMetric[0U]; + m2 = metric[5U] + m_pathMetric[2U]; + tempMetric[1U] = m1 < m2 ? m1 : m2; + if (m1 < m2) + m_pathMemory1[j] &= BIT_MASK_TABLE0[k]; + else + m_pathMemory1[j] |= BIT_MASK_TABLE1[k]; + + // Pres. state = S2, Prev. state = S2 & S3 + m1 = metric[2U] + m_pathMetric[1U]; + m2 = metric[6U] + m_pathMetric[3U]; + tempMetric[2U] = m1 < m2 ? m1 : m2; + if (m1 < m2) + m_pathMemory2[j] &= BIT_MASK_TABLE0[k]; + else + m_pathMemory2[j] |= BIT_MASK_TABLE1[k]; + + // Pres. state = S3, Prev. state = S1 & S3 + m1 = metric[3U] + m_pathMetric[1U]; + m2 = metric[7U] + m_pathMetric[3U]; + tempMetric[3U] = m1 < m2 ? m1 : m2; + if (m1 < m2) + m_pathMemory3[j] &= BIT_MASK_TABLE0[k]; + else + m_pathMemory3[j] |= BIT_MASK_TABLE1[k]; + + for (unsigned int i = 0U; i < 4U; i++) + m_pathMetric[i] = tempMetric[i]; + + m_mar++; +} + +void CSoundCardController::viterbiDecode(int* data) +{ + int metric[8U]; + + metric[0] = (data[1] ^ 0) + (data[0] ^ 0); + metric[1] = (data[1] ^ 1) + (data[0] ^ 1); + metric[2] = (data[1] ^ 1) + (data[0] ^ 0); + metric[3] = (data[1] ^ 0) + (data[0] ^ 1); + metric[4] = (data[1] ^ 1) + (data[0] ^ 1); + metric[5] = (data[1] ^ 0) + (data[0] ^ 0); + metric[6] = (data[1] ^ 0) + (data[0] ^ 1); + metric[7] = (data[1] ^ 1) + (data[0] ^ 0); + + acs(metric); +} + +void CSoundCardController::traceBack() +{ + // Start from the S0, t=31 + unsigned int j = 0U; + unsigned int k = 0U; + for (int i = 329; i >= 0; i--) { + switch (j) { + case 0U: // if state = S0 + if (READ_BIT1(m_pathMemory0, i) == 0) + j = 0U; + else + j = 2U; + WRITE_BIT1(m_fecOutput, k, 0); + k++; + break; + + + case 1U: // if state = S1 + if (READ_BIT1(m_pathMemory1, i) == 0) + j = 0U; + else + j = 2U; + WRITE_BIT1(m_fecOutput, k, 1); + k++; + break; + + case 2: // if state = S1 + if (READ_BIT1(m_pathMemory2, i) == 0) + j = 1U; + else + j = 3U; + WRITE_BIT1(m_fecOutput, k, 0); + k++; + break; + + case 3U: // if state = S1 + if (READ_BIT1(m_pathMemory3, i) == 0) + j = 1U; + else + j = 3U; + WRITE_BIT1(m_fecOutput, k, 1); + k++; + break; + } + } +} diff --git a/Common/SoundCardController.h b/Common/SoundCardController.h new file mode 100644 index 0000000..087a939 --- /dev/null +++ b/Common/SoundCardController.h @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2009-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SoundCardController_H +#define SoundCardController_H + +#include "SoundCardReaderWriter.h" +#include "DStarGMSKDemodulator.h" +#include "DStarGMSKModulator.h" +#include "AudioCallback.h" +#include "RingBuffer.h" +#include "Modem.h" +#include "Utils.h" + +#include + +enum DSRSCC_STATE { + DSRSCCS_NONE, + DSRSCCS_HEADER, + DSRSCCS_DATA +}; + +class CSoundCardController : public CModem, public IAudioCallback { +public: + CSoundCardController(const wxString& rxDevice, const wxString& txDevice, bool rxInvert, bool txInvert, wxFloat32 rxLevel, wxFloat32 txLevel, unsigned int txDelay, unsigned int txTail); + virtual ~CSoundCardController(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + virtual bool isTX(); + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + + virtual void readCallback(const wxFloat32* input, unsigned int n, int id); + virtual void writeCallback(wxFloat32* output, unsigned int& n, int id); + +private: + CSoundCardReaderWriter m_sound; + wxFloat32 m_rxLevel; + wxFloat32 m_txLevel; + unsigned int m_txDelay; + unsigned int m_txTail; + CRingBuffer m_txAudio; + CRingBuffer m_rxAudio; + DSRSCC_STATE m_rxState; + wxUint32 m_patternBuffer; + CDStarGMSKDemodulator m_demodulator; + CDStarGMSKModulator m_modulator; + unsigned char* m_rxBuffer; + unsigned int m_rxBufferBits; + unsigned int m_dataBits; + unsigned int m_mar; + int* m_pathMetric; + unsigned int* m_pathMemory0; + unsigned int* m_pathMemory1; + unsigned int* m_pathMemory2; + unsigned int* m_pathMemory3; + unsigned char* m_fecOutput; + + void processNone(bool bit); + void processHeader(bool bit); + void processData(bool bit); + + bool rxHeader(unsigned char* in, unsigned char* out); + void acs(int* metric); + void viterbiDecode(int* data); + void traceBack(); + + void txHeader(const unsigned char* in, unsigned char* out); + void writeBits(unsigned char c); + + unsigned int countBits(wxUint32 num); +}; + +#endif + diff --git a/Common/SoundCardReaderWriter.cpp b/Common/SoundCardReaderWriter.cpp new file mode 100644 index 0000000..62a15f4 --- /dev/null +++ b/Common/SoundCardReaderWriter.cpp @@ -0,0 +1,787 @@ +/* + * Copyright (C) 2006-2010,2015 by Jonathan Naylor G4KLX + * Copyright (C) 2014 by John Wiseman, G8BPQ + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "SoundCardReaderWriter.h" + +#if (defined(__APPLE__) && defined(__MACH__)) || defined(__WINDOWS__) + +static int scrwCallback(const void* input, void* output, unsigned long nSamples, const PaStreamCallbackTimeInfo* timeInfo, PaStreamCallbackFlags statusFlags, void* userData) +{ + wxASSERT(userData != NULL); + + CSoundCardReaderWriter* object = reinterpret_cast(userData); + + object->callback(static_cast(input), static_cast(output), nSamples); + + return paContinue; +} + +CSoundCardReaderWriter::CSoundCardReaderWriter(const wxString& readDevice, const wxString& writeDevice, unsigned int sampleRate, unsigned int blockSize) : +m_readDevice(readDevice), +m_writeDevice(writeDevice), +m_sampleRate(sampleRate), +m_blockSize(blockSize), +m_callback(NULL), +m_id(-1), +m_stream(NULL) +{ +} + +CSoundCardReaderWriter::~CSoundCardReaderWriter() +{ +} + +wxArrayString CSoundCardReaderWriter::getReadDevices() +{ + wxArrayString devices; + + devices.Alloc(10); + + PaError error = ::Pa_Initialize(); + if (error != paNoError) + return devices; + +#if defined(__WINDOWS__) + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paDirectSound); +#elif defined(__APPLE__) && defined(__MACH__) + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paCoreAudio); +#else + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paALSA); +#endif + if (apiIndex == paHostApiNotFound) { + ::Pa_Terminate(); + return devices; + } + + PaDeviceIndex n = ::Pa_GetDeviceCount(); + if (n <= 0) { + ::Pa_Terminate(); + return devices; + } + + for (PaDeviceIndex i = 0; i < n; i++) { + const PaDeviceInfo* device = ::Pa_GetDeviceInfo(i); + + if (device->hostApi != apiIndex) + continue; + + if (device->maxInputChannels > 0) { + wxString name(device->name, wxConvLocal); + devices.Add(name); + } + } + + ::Pa_Terminate(); + + return devices; +} + +wxArrayString CSoundCardReaderWriter::getWriteDevices() +{ + wxArrayString devices; + + devices.Alloc(10); + + PaError error = ::Pa_Initialize(); + if (error != paNoError) + return devices; + +#if defined(__WINDOWS__) + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paDirectSound); +#elif defined(__APPLE__) && defined(__MACH__) + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paCoreAudio); +#else + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paALSA); +#endif + if (apiIndex == paHostApiNotFound) { + ::Pa_Terminate(); + return devices; + } + + PaDeviceIndex n = ::Pa_GetDeviceCount(); + if (n <= 0) { + ::Pa_Terminate(); + return devices; + } + + for (PaDeviceIndex i = 0; i < n; i++) { + const PaDeviceInfo* device = ::Pa_GetDeviceInfo(i); + + if (device->hostApi != apiIndex) + continue; + + if (device->maxOutputChannels > 0) { + wxString name(device->name, wxConvLocal); + devices.Add(name); + } + } + + ::Pa_Terminate(); + + return devices; +} + +void CSoundCardReaderWriter::setCallback(IAudioCallback* callback, int id) +{ + wxASSERT(callback != NULL); + + m_callback = callback; + m_id = id; +} + +bool CSoundCardReaderWriter::open() +{ + PaError error = ::Pa_Initialize(); + if (error != paNoError) { + wxLogError(wxT("Cannot initialise PortAudio")); + return false; + } + + PaStreamParameters* pParamsIn = NULL; + PaStreamParameters* pParamsOut = NULL; + + PaStreamParameters paramsIn; + PaStreamParameters paramsOut; + + PaDeviceIndex inDev, outDev; + bool res = convertNameToDevices(inDev, outDev); + if (!res) { + wxLogError(wxT("Cannot convert name to device")); + return false; + } + + if (inDev != -1) { + const PaDeviceInfo* inInfo = ::Pa_GetDeviceInfo(inDev); + if (inInfo == NULL) { + wxLogError(wxT("Cannot get device information for the input device")); + return false; + } + + paramsIn.device = inDev; + paramsIn.channelCount = 1; + paramsIn.sampleFormat = paFloat32; + paramsIn.hostApiSpecificStreamInfo = NULL; + paramsIn.suggestedLatency = inInfo->defaultLowInputLatency; + + pParamsIn = ¶msIn; + } + + if (outDev != -1) { + const PaDeviceInfo* outInfo = ::Pa_GetDeviceInfo(outDev); + if (outInfo == NULL) { + wxLogError(wxT("Cannot get device information for the output device")); + return false; + } + + paramsOut.device = outDev; + paramsOut.channelCount = 1; + paramsOut.sampleFormat = paFloat32; + paramsOut.hostApiSpecificStreamInfo = NULL; + paramsOut.suggestedLatency = outInfo->defaultLowOutputLatency; + + pParamsOut = ¶msOut; + } + + error = ::Pa_OpenStream(&m_stream, pParamsIn, pParamsOut, double(m_sampleRate), m_blockSize, paNoFlag, &scrwCallback, this); + if (error != paNoError) { + wxLogError(wxT("Cannot open the audios stream(s)")); + ::Pa_Terminate(); + return false; + } + + error = ::Pa_StartStream(m_stream); + if (error != paNoError) { + wxLogError(wxT("Cannot start the audio stream(s)")); + ::Pa_CloseStream(m_stream); + m_stream = NULL; + + ::Pa_Terminate(); + return false; + } + + return true; +} + +void CSoundCardReaderWriter::close() +{ + wxASSERT(m_stream != NULL); + + ::Pa_AbortStream(m_stream); + + ::Pa_CloseStream(m_stream); + + ::Pa_Terminate(); +} + +void CSoundCardReaderWriter::callback(const wxFloat32* input, wxFloat32* output, unsigned int nSamples) +{ + if (m_callback != NULL) { + m_callback->readCallback(input, nSamples, m_id); + m_callback->writeCallback(output, nSamples, m_id); + } +} + +bool CSoundCardReaderWriter::convertNameToDevices(PaDeviceIndex& inDev, PaDeviceIndex& outDev) +{ + inDev = outDev = -1; + +#if defined(__WINDOWS__) + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paDirectSound); +#elif defined(__APPLE__) && defined(__MACH__) + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paCoreAudio); +#else + PaHostApiIndex apiIndex = ::Pa_HostApiTypeIdToHostApiIndex(paALSA); +#endif + if (apiIndex == paHostApiNotFound) + return false; + + PaDeviceIndex n = ::Pa_GetDeviceCount(); + if (n <= 0) + return false; + + for (PaDeviceIndex i = 0; i < n; i++) { + const PaDeviceInfo* device = ::Pa_GetDeviceInfo(i); + + if (device->hostApi != apiIndex) + continue; + + wxString name(device->name, wxConvLocal); + + if (!m_readDevice.IsEmpty() && m_readDevice.IsSameAs(name) && device->maxInputChannels > 0) + inDev = i; + + if (!m_writeDevice.IsEmpty() && m_writeDevice.IsSameAs(name) && device->maxOutputChannels > 0) + outDev = i; + } + + if (inDev == -1 && outDev == -1) + return false; + + return true; +} + +#else + +wxArrayString CSoundCardReaderWriter::m_readDevices; +wxArrayString CSoundCardReaderWriter::m_writeDevices; + +CSoundCardReaderWriter::CSoundCardReaderWriter(const wxString& readDevice, const wxString& writeDevice, unsigned int sampleRate, unsigned int blockSize) : +m_readDevice(readDevice), +m_writeDevice(writeDevice), +m_sampleRate(sampleRate), +m_blockSize(blockSize), +m_callback(NULL), +m_id(-1), +m_reader(NULL), +m_writer(NULL) +{ + wxASSERT(sampleRate > 0U); + wxASSERT(blockSize > 0U); +} + +CSoundCardReaderWriter::~CSoundCardReaderWriter() +{ +} + +wxArrayString CSoundCardReaderWriter::getReadDevices() +{ + snd_ctl_t *handle = NULL; + snd_pcm_t *pcm = NULL; + char NameString[256]; + + wxArrayString devices(m_readDevices); + + snd_ctl_card_info_t* info; + snd_ctl_card_info_alloca(&info); + + snd_pcm_info_t* pcminfo; + snd_pcm_info_alloca(&pcminfo); + + snd_pcm_hw_params_t* pars; + snd_pcm_hw_params_alloca(&pars); + + unsigned min, max; + int err; + snd_pcm_stream_t stream = SND_PCM_STREAM_CAPTURE; + + int card = -1; + while (::snd_card_next(&card) == 0 && card >= 0) { + char hwdev[80]; + ::sprintf(hwdev, "hw:%d", card); + + if (::snd_ctl_open(&handle, hwdev, 0) < 0) + continue; + + ::snd_ctl_card_info(handle, info); + ::snd_ctl_card_info_get_name(info); + + int dev = -1; + while (::snd_ctl_pcm_next_device(handle, &dev) == 0 && dev >= 0) { + ::snd_pcm_info_set_device(pcminfo, dev); + ::snd_pcm_info_set_subdevice(pcminfo, 0); + ::snd_pcm_info_set_stream(pcminfo, stream); + + err = ::snd_ctl_pcm_info(handle, pcminfo); + if (err != -ENOENT) { + ::sprintf(hwdev, "hw:%d,%d", card, dev); + + if (::snd_pcm_open(&pcm, hwdev, stream, SND_PCM_NONBLOCK) < 0) + continue; + + ::snd_pcm_hw_params_any(pcm, pars); + ::snd_pcm_hw_params_get_channels_min(pars, &min); + ::snd_pcm_hw_params_get_channels_max(pars, &max); + + ::snd_pcm_hw_params_get_rate_min(pars, &min, NULL); + ::snd_pcm_hw_params_get_rate_max(pars, &max, NULL); + + ::sprintf(NameString, "hw:%d,%d %s(%s)", + card, dev, + ::snd_pcm_info_get_name(pcminfo), + snd_ctl_card_info_get_name(info)); + + wxString name(NameString, wxConvLocal); + devices.Add(name); + + ::snd_pcm_close(pcm); + pcm = NULL; + } + } + + ::snd_ctl_close(handle); + } + + return devices; +} + +wxArrayString CSoundCardReaderWriter::getWriteDevices() +{ + snd_ctl_t *handle = NULL; + snd_pcm_t *pcm = NULL; + char NameString[256]; + + wxArrayString devices(m_writeDevices); + + snd_ctl_card_info_t* info; + snd_ctl_card_info_alloca(&info); + + snd_pcm_info_t* pcminfo; + snd_pcm_info_alloca(&pcminfo); + + snd_pcm_hw_params_t* pars; + snd_pcm_hw_params_alloca(&pars); + + unsigned min, max; + int err; + snd_pcm_stream_t stream = SND_PCM_STREAM_PLAYBACK; + + int card = -1; + while (::snd_card_next(&card) == 0 && card >= 0) { + char hwdev[80]; + ::sprintf(hwdev, "hw:%d", card); + + if (::snd_ctl_open(&handle, hwdev, 0) < 0) + continue; + + ::snd_ctl_card_info(handle, info); + ::snd_ctl_card_info_get_name(info); + + int dev = -1; + while (::snd_ctl_pcm_next_device(handle, &dev) == 0 && dev >= 0) { + ::snd_pcm_info_set_device(pcminfo, dev); + ::snd_pcm_info_set_subdevice(pcminfo, 0); + ::snd_pcm_info_set_stream(pcminfo, stream); + + err= ::snd_ctl_pcm_info(handle, pcminfo); + if (err != -ENOENT) { + ::sprintf(hwdev, "hw:%d,%d", card, dev); + + if (::snd_pcm_open(&pcm, hwdev, stream, SND_PCM_NONBLOCK) < 0) + continue; + + ::snd_pcm_hw_params_any(pcm, pars); + ::snd_pcm_hw_params_get_channels_min(pars, &min); + ::snd_pcm_hw_params_get_channels_max(pars, &max); + + ::snd_pcm_hw_params_get_rate_min(pars, &min, NULL); + ::snd_pcm_hw_params_get_rate_max(pars, &max, NULL); + + ::sprintf(NameString, "hw:%d,%d %s(%s)", + card, dev, + ::snd_pcm_info_get_name(pcminfo), + ::snd_ctl_card_info_get_name(info)); + + wxString name(NameString, wxConvLocal); + devices.Add(name); + + ::snd_pcm_close(pcm); + pcm = NULL; + } + } + + ::snd_ctl_close(handle); + } + + return devices; +} + +void CSoundCardReaderWriter::setCallback(IAudioCallback* callback, int id) +{ + wxASSERT(callback != NULL); + + m_callback = callback; + + m_id = id; +} + +bool CSoundCardReaderWriter::open() +{ + int err = 0; + + char buf1[100]; + char buf2[100]; + char* ptr; + + // Store the opened devices because ALSA won't enumerate them + m_readDevices.Add(m_readDevice); + m_writeDevices.Add(m_writeDevice); + + ::strcpy(buf1, (const char*)m_writeDevice.mb_str(wxConvUTF8)); + ::strcpy(buf2, (const char*)m_readDevice.mb_str(wxConvUTF8)); + + ptr = ::strchr(buf1, ' '); + if (ptr) *ptr = 0; // Get Device part of name + + ptr = ::strchr(buf2, ' '); + if (ptr) *ptr = 0; // Get Device part of name + + wxString writeDevice(buf1, wxConvLocal); + wxString readDevice(buf2, wxConvLocal); + + snd_pcm_t* playHandle = NULL; + if ((err = ::snd_pcm_open(&playHandle, buf1, SND_PCM_STREAM_PLAYBACK, 0)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot open playback audio device %s (%s)"), writeDevice.c_str(), error.c_str()); + return false; + } + + snd_pcm_hw_params_t* hw_params; + if ((err = ::snd_pcm_hw_params_malloc(&hw_params)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot allocate hardware parameter structure (%s)"), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_any(playHandle, hw_params)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot initialize hardware parameter structure (%s)"), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_set_access(playHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot set access type (%s)"), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_set_format(playHandle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot set sample format (%s)"), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_set_rate(playHandle, hw_params, m_sampleRate, 0)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot set sample rate (%s)"), error.c_str()); + return false; + } + + unsigned int playChannels = 1U; + + if ((err = ::snd_pcm_hw_params_set_channels(playHandle, hw_params, 1)) < 0) { + playChannels = 2U; + + if ((err = ::snd_pcm_hw_params_set_channels(playHandle, hw_params, 2)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot play set channel count (%s)"), error.c_str()); + return false; + } + } + + if ((err = ::snd_pcm_hw_params(playHandle, hw_params)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot set parameters (%s)"), error.c_str()); + return false; + } + + ::snd_pcm_hw_params_free(hw_params); + + if ((err = ::snd_pcm_prepare(playHandle)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot prepare audio interface for use (%s)"), error.c_str()); + return false; + } + + // Open Capture + snd_pcm_t* recHandle = NULL; + if ((err = ::snd_pcm_open(&recHandle, buf2, SND_PCM_STREAM_CAPTURE, 0)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot open capture audio device %s (%s)"), readDevice.c_str(), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_malloc(&hw_params)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot allocate hardware parameter structure (%s)"), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_any(recHandle, hw_params)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot initialize hardware parameter structure (%s)"), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_set_access(recHandle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot set access type (%s)"), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_set_format(recHandle, hw_params, SND_PCM_FORMAT_S16_LE)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot set sample format (%s)"), error.c_str()); + return false; + } + + if ((err = ::snd_pcm_hw_params_set_rate(recHandle, hw_params, m_sampleRate, 0)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot set sample rate (%s)"), error.c_str()); + return false; + } + + unsigned int recChannels = 1U; + + if ((err = ::snd_pcm_hw_params_set_channels(recHandle, hw_params, 1)) < 0) { + recChannels = 2U; + + if ((err = ::snd_pcm_hw_params_set_channels (recHandle, hw_params, 2)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot rec set channel count (%s)"), error.c_str()); + return false; + } + } + + if ((err = ::snd_pcm_hw_params(recHandle, hw_params)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot set parameters (%s)"), error.c_str()); + return false; + } + + ::snd_pcm_hw_params_free(hw_params); + + if ((err = ::snd_pcm_prepare(recHandle)) < 0) { + wxString error(::snd_strerror(err), wxConvLocal); + wxLogError(wxT("Cannot prepare audio interface for use (%s)"), error.c_str()); + return false; + } + + short samples[256]; + for (unsigned int i = 0U; i < 10U; ++i) + ::snd_pcm_readi(recHandle, samples, 128); + + wxLogMessage(wxT("Opened %s %s Rate %u"), writeDevice.c_str(), readDevice.c_str(), m_sampleRate); + + m_reader = new CSoundCardReader(recHandle, m_blockSize, recChannels, m_callback, m_id); + m_writer = new CSoundCardWriter(playHandle, m_blockSize, playChannels, m_callback, m_id); + + m_reader->Create(); + m_reader->Run(); + + m_writer->Create(); + m_writer->Run(); + + return true; +} + +void CSoundCardReaderWriter::close() +{ + m_reader->kill(); + m_writer->kill(); + + m_reader->Wait(); + m_writer->Wait(); +} + +bool CSoundCardReaderWriter::isWriterBusy() const +{ + return m_writer->isBusy(); +} + +CSoundCardReader::CSoundCardReader(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback, int id) : +wxThread(wxTHREAD_JOINABLE), +m_handle(handle), +m_blockSize(blockSize), +m_channels(channels), +m_callback(callback), +m_id(id), +m_killed(false), +m_buffer(NULL), +m_samples(NULL) +{ + wxASSERT(handle != NULL); + wxASSERT(blockSize > 0U); + wxASSERT(channels == 1U || channels == 2U); + wxASSERT(callback != NULL); + + m_buffer = new wxFloat32[blockSize]; + m_samples = new short[2U * blockSize]; +} + +CSoundCardReader::~CSoundCardReader() +{ + delete[] m_buffer; + delete[] m_samples; +} + +void* CSoundCardReader::Entry() +{ + wxLogMessage(wxT("Starting ALSA reader thread")); + + while (!m_killed) { + snd_pcm_sframes_t ret; + while ((ret = ::snd_pcm_readi(m_handle, m_samples, m_blockSize)) < 0) { + if (ret != -EPIPE) { + wxString error(::snd_strerror(ret), wxConvLocal); + wxLogWarning(wxT("snd_pcm_readi returned %d (%s)"), ret, error.c_str()); + } + + ::snd_pcm_recover(m_handle, ret, 1); + } + + if (m_channels == 1U) { + for (int n = 0; n < ret; n++) + m_buffer[n] = wxFloat32(m_samples[n]) / 32768.0F; + } else { + int i = 0; + for (int n = 0; n < (ret * 2); n += 2) + m_buffer[i++] = wxFloat32(m_samples[n + 1]) / 32768.0F; + } + + m_callback->readCallback(m_buffer, (unsigned int)ret, m_id); + } + + wxLogMessage(wxT("Stopping ALSA reader thread")); + + ::snd_pcm_close(m_handle); + + return NULL; +} + +void CSoundCardReader::kill() +{ + m_killed = true; +} + +CSoundCardWriter::CSoundCardWriter(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback, int id) : +wxThread(wxTHREAD_JOINABLE), +m_handle(handle), +m_blockSize(blockSize), +m_channels(channels), +m_callback(callback), +m_id(id), +m_killed(false), +m_buffer(NULL), +m_samples(NULL) +{ + wxASSERT(handle != NULL); + wxASSERT(blockSize > 0U); + wxASSERT(channels == 1U || channels == 2U); + wxASSERT(callback != NULL); + + m_buffer = new wxFloat32[2U * blockSize]; + m_samples = new short[4U * blockSize]; +} + +CSoundCardWriter::~CSoundCardWriter() +{ + delete[] m_buffer; + delete[] m_samples; +} + +void* CSoundCardWriter::Entry() +{ + wxLogMessage(wxT("Starting ALSA writer thread")); + + while (!m_killed) { + unsigned int nSamples = 2U * m_blockSize; + m_callback->writeCallback(m_buffer, nSamples, m_id); + + if (nSamples == 0U) { + Sleep(5UL); + } else { + if (m_channels == 1U) { + for (unsigned int n = 0U; n < nSamples; n++) + m_samples[n] = short(m_buffer[n] * 32767.0F); + } else { + unsigned int i = 0U; + for (unsigned int n = 0U; n < nSamples; n++) { + short sample = short(m_buffer[n] * 32767.0F); + m_samples[i++] = sample; + m_samples[i++] = sample; // Same value to both channels + } + } + + unsigned int offset = 0U; + snd_pcm_sframes_t ret; + while ((ret = ::snd_pcm_writei(m_handle, m_samples + offset, nSamples - offset)) != (nSamples - offset)) { + if (ret < 0) { + if (ret != -EPIPE) { + wxString error(::snd_strerror(ret), wxConvLocal); + wxLogWarning(wxT("snd_pcm_writei returned %d (%s)"), ret, error.c_str()); + } + + ::snd_pcm_recover(m_handle, ret, 1); + } else { + offset += (unsigned int)ret; + } + } + } + } + + wxLogMessage(wxT("Stopping ALSA writer thread")); + + ::snd_pcm_close(m_handle); + + return NULL; +} + +void CSoundCardWriter::kill() +{ + m_killed = true; +} + +bool CSoundCardWriter::isBusy() const +{ + snd_pcm_state_t state = ::snd_pcm_state(m_handle); + + return state == SND_PCM_STATE_RUNNING || state == SND_PCM_STATE_DRAINING; +} + +#endif diff --git a/Common/SoundCardReaderWriter.h b/Common/SoundCardReaderWriter.h new file mode 100644 index 0000000..0facbbc --- /dev/null +++ b/Common/SoundCardReaderWriter.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2009,2010,2015 by Jonathan Naylor, G4KLX + * Copyright (C) 2014 by John Wiseman, G8BPQ + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef SoundCardReaderWriter_H +#define SoundCardReaderWriter_H + +#include "AudioCallback.h" + +#include + +#if (defined(__APPLE__) && defined(__MACH__)) || defined(__WINDOWS__) + +#include "portaudio.h" + +class CSoundCardReaderWriter { +public: + CSoundCardReaderWriter(const wxString& readDevice, const wxString& writeDevice, unsigned int sampleRate, unsigned int blockSize); + ~CSoundCardReaderWriter(); + + void setCallback(IAudioCallback* callback, int id); + bool open(); + void close(); + + void callback(const wxFloat32* input, wxFloat32* output, unsigned int nSamples); + + static wxArrayString getReadDevices(); + static wxArrayString getWriteDevices(); + +private: + wxString m_readDevice; + wxString m_writeDevice; + unsigned int m_sampleRate; + unsigned int m_blockSize; + IAudioCallback* m_callback; + int m_id; + PaStream* m_stream; + + bool convertNameToDevices(PaDeviceIndex& inDev, PaDeviceIndex& outDev); +}; + +#else + +#include + +class CSoundCardReader : public wxThread { +public: + CSoundCardReader(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback, int id); + virtual ~CSoundCardReader(); + + virtual void* Entry(); + + virtual void kill(); + +private: + snd_pcm_t* m_handle; + unsigned int m_blockSize; + unsigned int m_channels; + IAudioCallback* m_callback; + int m_id; + bool m_killed; + wxFloat32* m_buffer; + short* m_samples; +}; + +class CSoundCardWriter : public wxThread { +public: + CSoundCardWriter(snd_pcm_t* handle, unsigned int blockSize, unsigned int channels, IAudioCallback* callback, int id); + virtual ~CSoundCardWriter(); + + virtual void* Entry(); + + virtual void kill(); + + virtual bool isBusy() const; + +private: + snd_pcm_t* m_handle; + unsigned int m_blockSize; + unsigned int m_channels; + IAudioCallback* m_callback; + int m_id; + bool m_killed; + wxFloat32* m_buffer; + short* m_samples; +}; + +class CSoundCardReaderWriter { +public: + CSoundCardReaderWriter(const wxString& readDevice, const wxString& writeDevice, unsigned int sampleRate, unsigned int blockSize); + ~CSoundCardReaderWriter(); + + void setCallback(IAudioCallback* callback, int id); + bool open(); + void close(); + + bool isWriterBusy() const; + + static wxArrayString getReadDevices(); + static wxArrayString getWriteDevices(); + +private: + wxString m_readDevice; + wxString m_writeDevice; + unsigned int m_sampleRate; + unsigned int m_blockSize; + IAudioCallback* m_callback; + int m_id; + CSoundCardReader* m_reader; + CSoundCardWriter* m_writer; + + static wxArrayString m_readDevices; + static wxArrayString m_writeDevices; +}; + +#endif + +#endif diff --git a/Common/SplitController.cpp b/Common/SplitController.cpp new file mode 100644 index 0000000..bc730e1 --- /dev/null +++ b/Common/SplitController.cpp @@ -0,0 +1,747 @@ +/* + * Copyright (C) 2012-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "SplitController.h" + +const unsigned int REGISTRATION_TIMEOUT = 200U; + +const unsigned int BUFFER_LENGTH = 200U; + +CAMBESlot::CAMBESlot(unsigned int rxCount) : +m_valid(NULL), +m_errors(999U), +m_best(99U), +m_ambe(NULL), +m_length(0U), +m_end(NULL), +m_timer(1000U), +m_rxCount(rxCount) +{ + wxASSERT(rxCount > 0U); + + m_ambe = new unsigned char[DV_FRAME_MAX_LENGTH_BYTES]; + m_valid = new bool[rxCount]; + m_end = new bool[rxCount]; + + reset(); +} + +CAMBESlot::~CAMBESlot() +{ + delete[] m_end; + delete[] m_valid; + delete[] m_ambe; +} + +void CAMBESlot::reset() +{ + m_errors = 999U; + m_best = 99U; + m_length = 0U; + + for (unsigned int i = 0U; i < m_rxCount; i++) { + m_valid[i] = false; + m_end[i] = false; + } + + m_timer.stop(); +} + +bool CAMBESlot::isFirst() const +{ + return m_length == 0U; +} + +CSplitController::CSplitController(const wxString& localAddress, unsigned int localPort, const wxArrayString& transmitterNames, const wxArrayString& receiverNames, unsigned int timeout) : +CModem(), +m_handler(localAddress, localPort), +m_transmitterNames(transmitterNames), +m_receiverNames(receiverNames), +m_timeout(timeout), +m_txCount(0U), +m_rxCount(0U), +m_txAddresses(NULL), +m_txPorts(NULL), +m_txTimers(NULL), +m_rxAddresses(NULL), +m_rxPorts(NULL), +m_rxTimers(NULL), +m_txData(1000U), +m_outId(0x00U), +m_outSeq(0U), +m_endTimer(1000U, 1U), +m_listening(true), +m_inSeqNo(0x00U), +m_outSeqNo(0x00U), +m_header(NULL), +m_id(NULL), +m_valid(NULL), +m_slots(NULL), +m_headerSent(false), +m_packets(NULL), +m_best(NULL), +m_missed(NULL), +m_silence(0U) +{ + m_txCount = transmitterNames.GetCount(); + m_rxCount = receiverNames.GetCount(); + + wxASSERT(m_txCount > 0U); + wxASSERT(m_rxCount > 0U); + + m_txAddresses = new in_addr[m_txCount]; + m_txPorts = new unsigned int[m_txCount]; + m_txTimers = new CTimer*[m_txCount]; + + m_rxAddresses = new in_addr[m_rxCount]; + m_rxPorts = new unsigned int[m_rxCount]; + m_rxTimers = new CTimer*[m_rxCount]; + + m_header = new unsigned char[RADIO_HEADER_LENGTH_BYTES]; + + m_id = new wxUint16[m_rxCount]; + m_valid = new bool[m_rxCount]; + m_packets = new unsigned int[m_rxCount]; + m_best = new unsigned int[m_rxCount]; + m_missed = new unsigned int[m_rxCount]; + + for (unsigned int i = 0U; i < m_rxCount; i++) { + m_rxTimers[i] = new CTimer(1000U, REGISTRATION_TIMEOUT); + m_rxPorts[i] = 0U; + m_valid[i] = false; + m_id[i] = 0x00U; + m_packets[i] = 0U; + m_best[i] = 0U; + m_missed[i] = 0U; + } + + for (unsigned int i = 0U; i < m_txCount; i++) { + m_txTimers[i] = new CTimer(1000U, REGISTRATION_TIMEOUT); + m_txPorts[i] = 0U; + } + + m_slots = new CAMBESlot*[21U]; + for (unsigned int i = 0U; i < 21U; i++) + m_slots[i] = new CAMBESlot(m_rxCount); +} + +CSplitController::~CSplitController() +{ + delete[] m_txAddresses; + delete[] m_txPorts; + delete[] m_rxAddresses; + delete[] m_rxPorts; + delete[] m_missed; + delete[] m_best; + delete[] m_packets; + delete[] m_header; + delete[] m_id; + delete[] m_valid; + + for (unsigned int i = 0U; i < 21U; i++) + delete m_slots[i]; + delete[] m_slots; + + for (unsigned int i = 0U; i < m_txCount; i++) + delete m_txTimers[i]; + for (unsigned int i = 0U; i < m_rxCount; i++) + delete m_rxTimers[i]; + + delete[] m_txTimers; + delete[] m_rxTimers; +} + +bool CSplitController::start() +{ + bool ret = m_handler.open(); + if (!ret) + return false; + + Create(); + SetPriority(100U); + Run(); + + return true; +} + +void* CSplitController::Entry() +{ + wxLogMessage(wxT("Starting Split Controller thread")); + + wxStopWatch stopWatch; + + while (!m_stopped) { + stopWatch.Start(); + + transmit(); + + receive(); + + Sleep(10UL); + + unsigned int ms = stopWatch.Time(); + timers(ms); + } + + wxLogMessage(wxT("Stopping Split Controller thread")); + + m_handler.close(); + + return NULL; +} + +bool CSplitController::writeHeader(const CHeaderData& header) +{ + bool ret = m_txData.hasSpace(RADIO_HEADER_LENGTH_BYTES); + if (!ret) { + wxLogWarning(wxT("No space to write the header")); + return false; + } + + unsigned char buffer[100U]; + + ::memset(buffer, ' ', RADIO_HEADER_LENGTH_BYTES - 2U); + + buffer[0U] = header.getFlag1(); + buffer[1U] = header.getFlag2(); + buffer[2U] = header.getFlag3(); + + wxString rpt2 = header.getRptCall2(); + for (unsigned int i = 0U; i < rpt2.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 3U] = rpt2.GetChar(i); + + wxString rpt1 = header.getRptCall1(); + for (unsigned int i = 0U; i < rpt1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 11U] = rpt1.GetChar(i); + + wxString your = header.getYourCall(); + for (unsigned int i = 0U; i < your.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 19U] = your.GetChar(i); + + wxString my1 = header.getMyCall1(); + for (unsigned int i = 0U; i < my1.Len() && i < LONG_CALLSIGN_LENGTH; i++) + buffer[i + 27U] = my1.GetChar(i); + + wxString my2 = header.getMyCall2(); + for (unsigned int i = 0U; i < my2.Len() && i < SHORT_CALLSIGN_LENGTH; i++) + buffer[i + 35U] = my2.GetChar(i); + + wxMutexLocker locker(m_mutex); + + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES - 2U; + m_txData.addData(data, 2U); + + m_txData.addData(buffer, RADIO_HEADER_LENGTH_BYTES - 2U); + + return true; +} + +bool CSplitController::writeData(const unsigned char* data, unsigned int length, bool end) +{ + bool ret = m_txData.hasSpace(DV_FRAME_LENGTH_BYTES + 2U); + if (!ret) { + wxLogWarning(wxT("No space to write data")); + return false; + } + + wxMutexLocker locker(m_mutex); + + unsigned char buffer[2U]; + buffer[0U] = end ? DSMTT_EOT : DSMTT_DATA; + buffer[1U] = DV_FRAME_LENGTH_BYTES; + m_txData.addData(buffer, 2U); + + m_txData.addData(data, DV_FRAME_LENGTH_BYTES); + + return true; +} + +unsigned int CSplitController::getSpace() +{ + return m_txData.freeSpace() / (DV_FRAME_LENGTH_BYTES + 2U); +} + +bool CSplitController::isTXReady() +{ + return m_txData.isEmpty(); +} + +void CSplitController::transmit() +{ + if (!m_txData.hasData()) + return; + + unsigned char type = 0U; + unsigned char length = 0U; + unsigned char buffer[RADIO_HEADER_LENGTH_BYTES]; + { + wxMutexLocker locker(m_mutex); + + m_txData.getData(&type, 1U); + m_txData.getData(&length, 1U); + m_txData.getData(buffer, length); + } + + if (type == DSMTT_HEADER) { + m_outId = (::rand() % 65535U) + 1U; + m_outSeq = 0U; + m_tx = true; + + for (unsigned int i = 0U; i < m_txCount; i++) { + if (m_txPorts[i] > 0U) + m_handler.writeHeader(buffer, m_outId, m_txAddresses[i], m_txPorts[i]); + } + } else { + // If this is a data sync, reset the sequence to zero + if (buffer[9] == 0x55U && buffer[10] == 0x2DU && buffer[11] == 0x16U) + m_outSeq = 0U; + + if (type == DSMTT_EOT) { + m_outSeq |= 0x40U; + m_tx = false; + } + + for (unsigned int i = 0U; i < m_txCount; i++) { + if (m_txPorts[i] > 0U) + m_handler.writeData(buffer, length, m_outId, m_outSeq, m_txAddresses[i], m_txPorts[i]); + } + + m_outSeq++; + if (m_outSeq > 0x14U) + m_outSeq = 0U; + } +} + +void CSplitController::receive() +{ + NETWORK_TYPE type = NETWORK_DATA; + + while (type != NETWORK_NONE) { + wxUint16 id; + in_addr address; + unsigned int port; + + type = m_handler.read(id, address, port); + + if (type == NETWORK_NONE) { + // Do nothing + } else if (type == NETWORK_HEADER) { + unsigned char header[RADIO_HEADER_LENGTH_BYTES]; + unsigned int length = m_handler.readHeader(header, RADIO_HEADER_LENGTH_BYTES); + + // A length of zero is a bad checksum, ignore it + if (length > 0U) { + bool found = false; + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (address.s_addr == m_rxAddresses[i].s_addr && port == m_rxPorts[i]) { + processHeader(i, id, header, length); + m_rxTimers[i]->start(); + found = true; + break; + } + } + + if (!found) { + wxString addr(::inet_ntoa(address), wxConvLocal); + wxLogError(wxT("Header received from unknown repeater - %s:%u"), addr.c_str(), port); + } + } + } else if (type == NETWORK_DATA) { + unsigned char ambe[DV_FRAME_MAX_LENGTH_BYTES]; + wxUint8 seqNo; + unsigned int errors; + unsigned int length = m_handler.readData(ambe, DV_FRAME_MAX_LENGTH_BYTES, seqNo, errors); + + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (address.s_addr == m_rxAddresses[i].s_addr && port == m_rxPorts[i]) { + processAMBE(i, id, ambe, length, seqNo, errors); + m_rxTimers[i]->start(); + break; + } + } + } else if (type == NETWORK_REGISTER) { + // These can be from transmitters and receivers + wxString name; + m_handler.readRegister(name); + + int n1 = m_receiverNames.Index(name); + int n2 = m_transmitterNames.Index(name); + + if (n1 != wxNOT_FOUND) { + wxASSERT(n1 < int(m_rxCount)); + m_rxTimers[n1]->start(); + + if (m_rxAddresses[n1].s_addr != address.s_addr || m_rxPorts[n1] != port) { + wxString addr1(::inet_ntoa(m_rxAddresses[n1]), wxConvLocal); + wxString addr2(::inet_ntoa(address), wxConvLocal); + + if (m_rxPorts[n1] == 0U) + wxLogMessage(wxT("Registration of RX %d \"%s\" set to %s:%u"), n1 + 1, name.c_str(), addr2.c_str(), port); + else + wxLogMessage(wxT("Registration of RX %d \"%s\" changed from %s:%u to %s:%u"), n1 + 1, name.c_str(), addr1.c_str(), m_rxPorts[n1], addr2.c_str(), port); + + m_rxAddresses[n1].s_addr = address.s_addr; + m_rxPorts[n1] = port; + } + } + + if (n2 != wxNOT_FOUND) { + wxASSERT(n2 < int(m_txCount)); + m_txTimers[n2]->start(); + + if (m_txAddresses[n2].s_addr != address.s_addr || m_txPorts[n2] != port) { + wxString addr1(::inet_ntoa(m_txAddresses[n2]), wxConvLocal); + wxString addr2(::inet_ntoa(address), wxConvLocal); + + if (m_txPorts[n2] == 0U) + wxLogMessage(wxT("Registration of TX %d \"%s\" set to %s:%u"), n2 + 1, name.c_str(), addr2.c_str(), port); + else + wxLogMessage(wxT("Registration of TX %d \"%s\" changed from %s:%u to %s:%u"), n2 + 1, name.c_str(), addr1.c_str(), m_txPorts[n2], addr2.c_str(), port); + + m_txAddresses[n2].s_addr = address.s_addr; + m_txPorts[n2] = port; + } + } + + if (n1 == wxNOT_FOUND && n2 == wxNOT_FOUND) { + wxString addr(::inet_ntoa(address), wxConvLocal); + wxLogError(wxT("Registration of \"%s\" received from unknown repeater - %s:%u"), name.c_str(), addr.c_str(), port); + } + } else { + wxString addr(::inet_ntoa(address), wxConvLocal); + wxLogError(wxT("Received invalid frame type %d from %s:%u"), int(type), addr.c_str(), port); + } + } +} + +void CSplitController::timers(unsigned int ms) +{ + m_endTimer.clock(ms); + if (m_endTimer.isRunning() && m_endTimer.hasExpired()) { + printStats(); + + wxMutexLocker locker(m_mutex); + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_listening = true; + m_endTimer.stop(); + + return; + } + + // Run the registration timers + for (unsigned int i = 0U; i < m_txCount; i++) { + m_txTimers[i]->clock(ms); + if (m_txTimers[i]->isRunning() && m_txTimers[i]->hasExpired()) { + wxLogWarning(wxT("TX %u registration has expired"), i + 1U); + m_txTimers[i]->stop(); + m_txPorts[i] = 0U; + } + } + + for (unsigned int i = 0U; i < m_rxCount; i++) { + m_rxTimers[i]->clock(ms); + if (m_rxTimers[i]->isRunning() && m_rxTimers[i]->hasExpired()) { + wxLogWarning(wxT("RX %u registration has expired"), i + 1U); + m_rxTimers[i]->stop(); + m_rxPorts[i] = 0U; + } + } + + // If we are listening then no need to run the slot timers + if (m_listening) + return; + + // Run the slot timers + for (unsigned int i = 0U; i < 21U; i++) + m_slots[i]->m_timer.clock(ms); + + // Check for expired timers + for (unsigned int i = 0U; i < 21U; i++) { + CAMBESlot* slot = m_slots[m_outSeqNo]; + + // Got to an unexpired timer + if (!slot->m_timer.hasExpired()) + return; + + // The last frame? + if (isEnd(*slot)) { + printStats(); + + wxMutexLocker locker(m_mutex); + + if (!m_headerSent) + sendHeader(); + + unsigned char data[2U]; + data[0U] = DSMTT_EOT; + data[1U] = 0U; + m_rxData.addData(data, 2U); + + m_listening = true; + m_endTimer.stop(); + + return; + } + + // Is there any data? + if (slot->m_length > 0U) { + m_best[slot->m_best]++; + + wxMutexLocker locker(m_mutex); + + if (!m_headerSent) + sendHeader(); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(slot->m_ambe, DV_FRAME_LENGTH_BYTES); + } else { + m_silence++; + + // Send a silence frame to the repeater + wxMutexLocker locker(m_mutex); + + if (!m_headerSent) + sendHeader(); + + unsigned char data[2U]; + data[0U] = DSMTT_DATA; + data[1U] = DV_FRAME_LENGTH_BYTES; + m_rxData.addData(data, 2U); + + m_rxData.addData(NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + } + + slot->reset(); + slot->m_timer.start(0U, 21U * DSTAR_FRAME_TIME_MS); + + m_outSeqNo++; + if (m_outSeqNo >= 21U) + m_outSeqNo = 0U; + } +} + +void CSplitController::processHeader(unsigned int n, wxUint16 id, const unsigned char* header, unsigned int length) +{ + if (m_listening) { + ::memcpy(m_header, header, RADIO_HEADER_LENGTH_BYTES - 2U); + + // Reset the slots and start the timers + for (unsigned int i = 0U; i < 21U; i++) { + m_slots[i]->reset(); + m_slots[i]->m_timer.start(0U, DSTAR_FRAME_TIME_MS + DSTAR_FRAME_TIME_MS * i + m_timeout); + } + + for (unsigned int i = 0U; i < m_rxCount; i++) { + m_valid[i] = false; + m_id[i] = 0x00U; + m_packets[i] = 0U; + m_best[i] = 0U; + m_missed[i] = 0U; + } + m_silence = 0U; + + m_valid[n] = true; + m_id[n] = id; + + m_inSeqNo = 0U; + m_outSeqNo = 0U; + m_listening = false; + m_headerSent = false; + m_endTimer.start(); + } else { + m_valid[n] = ::memcmp(m_header, header, RADIO_HEADER_LENGTH_BYTES - 2U) == 0; + m_id[n] = id; // Makes no difference as the valid flag will ensure we ignore bad ids + } +} + +void CSplitController::processAMBE(unsigned int n, wxUint16 id, const unsigned char* ambe, unsigned int length, wxUint8 seqNo, unsigned char errors) +{ + if (m_listening) + return; + + if (!m_valid[n] || id != m_id[n]) + return; + + // Mask out the control bits of the sequence number + unsigned char seqNo1 = seqNo & 0x1FU; + + // Check for an out of order frame + unsigned char seqNo2 = m_inSeqNo; + unsigned char count = 0U; + while (seqNo1 != seqNo2) { + count++; + + seqNo2++; + if (seqNo2 >= 21U) + seqNo2 = 0U; + } + + if (count > 18U) { + m_missed[n]++; + return; + } + + m_inSeqNo = (seqNo & 0x3FU) + 1U; + if (m_inSeqNo >= 21U) + m_inSeqNo = 0U; + + CAMBESlot* slot = m_slots[seqNo & 0x3FU]; + + m_endTimer.start(); + + bool end = (seqNo & 0x40U) == 0x40U; + slot->m_valid[n] = true; + m_packets[n]++; + + // An EOF removes the receiver from all future data + if (end) { + slot->m_end[n] = true; + m_valid[n] = false; + } + + // If the existing data is of a better quality and not an end packet + if (slot->m_errors < errors || end) + return; + + ::memcpy(slot->m_ambe, ambe, length); + slot->m_length = length; + + slot->m_errors = errors; + slot->m_best = n; +} + +bool CSplitController::isEnd(const CAMBESlot& slot) const +{ + bool hasNoEnd = false; + bool hasEnd = false; + + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (slot.m_valid[i] && slot.m_end[i]) + hasEnd = true; + + if (slot.m_valid[i] && !slot.m_end[i]) + hasNoEnd = true; + } + + return hasEnd && !hasNoEnd; +} + +void CSplitController::sendHeader() +{ + // Assume that the caller has the mutex lock + unsigned char data[2U]; + data[0U] = DSMTT_HEADER; + data[1U] = RADIO_HEADER_LENGTH_BYTES - 2U; + m_rxData.addData(data, 2U); + + m_rxData.addData(m_header, RADIO_HEADER_LENGTH_BYTES - 2U); + + m_headerSent = true; +} + +void CSplitController::printStats() const +{ + // Count the total number of packets in this transmission + unsigned int total = m_silence + 1U; + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (m_rxPorts[i] > 0U) + total += m_best[i]; + } + + wxString temp; + wxString text = wxT("Packets: total/"); + + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (m_rxPorts[i] > 0U) { + temp.Printf(wxT("rx%u/"), i + 1U); + text.Append(temp); + } + } + + temp.Printf(wxT("silence %u/"), total); + text.Append(temp); + + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (m_rxPorts[i] > 0U) { + temp.Printf(wxT("%u/"), m_packets[i]); + text.Append(temp); + } + } + + temp.Printf(wxT("%u, Proportion: "), m_silence); + text.Append(temp); + + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (m_rxPorts[i] > 0U) { + temp.Printf(wxT("rx%u/"), i + 1U); + text.Append(temp); + } + } + + text.Append(wxT("silence ")); + + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (m_rxPorts[i] > 0U) { + temp.Printf(wxT("%u%%%%/"), (100U * m_best[i]) / total); + text.Append(temp); + } + } + + temp.Printf(wxT("%u%%%%, Missed: "), (100U * m_silence) / total); + text.Append(temp); + + unsigned int n = 0U; + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (m_rxPorts[i] > 0U) { + if (n > 0U) + temp.Printf(wxT("/rx%u"), i + 1U); + else + temp.Printf(wxT("rx%u"), i + 1U); + + n++; + text.Append(temp); + } + } + + text.Append(wxT(" ")); + + n = 0U; + for (unsigned int i = 0U; i < m_rxCount; i++) { + if (m_rxPorts[i] > 0U) { + if (n > 0U) + temp.Printf(wxT("/%u%%%%"), (100U * m_missed[i]) / total); + else + temp.Printf(wxT("%u%%%%"), (100U * m_missed[i]) / total); + + n++; + text.Append(temp); + } + } + + wxLogMessage(text); +} diff --git a/Common/SplitController.h b/Common/SplitController.h new file mode 100644 index 0000000..e53b2ce --- /dev/null +++ b/Common/SplitController.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2012-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef SplitController_H +#define SplitController_H + +#include "GatewayProtocolHandler.h" +#include "DStarDefines.h" +#include "RingBuffer.h" +#include "Timer.h" +#include "Modem.h" +#include "Utils.h" + +#include + +class CAMBESlot { +public: + CAMBESlot(unsigned int rxCount); + ~CAMBESlot(); + + void reset(); + + bool isFirst() const; + + bool* m_valid; + unsigned int m_errors; + unsigned int m_best; + unsigned char* m_ambe; + unsigned int m_length; + bool* m_end; + CTimer m_timer; + +private: + unsigned int m_rxCount; +}; + +class CSplitController : public CModem { +public: + CSplitController(const wxString& localAddress, unsigned int localPort, const wxArrayString& transmitterNames, const wxArrayString& receiverNames, unsigned int timeout); + virtual ~CSplitController(); + + virtual void* Entry(); + + virtual bool start(); + + virtual unsigned int getSpace(); + virtual bool isTXReady(); + + virtual bool writeHeader(const CHeaderData& header); + virtual bool writeData(const unsigned char* data, unsigned int length, bool end); + +private: + CGatewayProtocolHandler m_handler; + wxArrayString m_transmitterNames; + wxArrayString m_receiverNames; + unsigned int m_timeout; + unsigned int m_txCount; + unsigned int m_rxCount; + in_addr* m_txAddresses; + unsigned int* m_txPorts; + CTimer** m_txTimers; + in_addr* m_rxAddresses; + unsigned int* m_rxPorts; + CTimer** m_rxTimers; + CRingBuffer m_txData; + wxUint16 m_outId; + wxUint8 m_outSeq; + CTimer m_endTimer; + bool m_listening; + wxUint8 m_inSeqNo; + wxUint8 m_outSeqNo; + unsigned char* m_header; + wxUint16* m_id; + bool* m_valid; + CAMBESlot** m_slots; + bool m_headerSent; + unsigned int* m_packets; + unsigned int* m_best; + unsigned int* m_missed; + unsigned int m_silence; + + void transmit(); + void receive(); + void timers(unsigned int ms); + void processHeader(unsigned int n, wxUint16 id, const unsigned char* header, unsigned int length); + void processAMBE(unsigned int n, wxUint16 id, const unsigned char* ambe, unsigned int length, wxUint8 seqNo, unsigned char errors); + bool isEnd(const CAMBESlot& slot) const; + void sendHeader(); + void printStats() const; +}; + +#endif diff --git a/Common/TCPReaderWriter.cpp b/Common/TCPReaderWriter.cpp new file mode 100644 index 0000000..7effc2c --- /dev/null +++ b/Common/TCPReaderWriter.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2010,2011,2012 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "TCPReaderWriter.h" +#include "UDPReaderWriter.h" + +#if !defined(__WINDOWS__) +#include +#endif + + +CTCPReaderWriter::CTCPReaderWriter(const wxString& address, unsigned int port, const wxString& localAddress) : +m_address(address), +m_port(port), +m_localAddress(localAddress), +m_fd(-1) +{ + wxASSERT(!address.IsEmpty()); + wxASSERT(port > 0U); + +#if defined(__WINDOWS__) + WSAData data; + int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); + if (wsaRet != 0) + wxLogError(wxT("Error from WSAStartup")); +#endif +} + +CTCPReaderWriter::CTCPReaderWriter() : +m_address(), +m_port(0U), +m_localAddress(), +m_fd(-1) +{ +#if defined(__WINDOWS__) + WSAData data; + int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); + if (wsaRet != 0) + wxLogError(wxT("Error from WSAStartup")); +#endif +} + +CTCPReaderWriter::~CTCPReaderWriter() +{ +#if defined(__WINDOWS__) + ::WSACleanup(); +#endif +} + +bool CTCPReaderWriter::open(const wxString& address, unsigned int port, const wxString& localAddress) +{ + m_address = address; + m_port = port; + m_localAddress = localAddress; + + return open(); +} + +bool CTCPReaderWriter::open() +{ + if (m_fd != -1) + return true; + + if (m_address.IsEmpty() || m_port == 0U) + return false; + + m_fd = ::socket(PF_INET, SOCK_STREAM, 0); + if (m_fd < 0) { +#if defined(__WINDOWS__) + wxLogError(wxT("Cannot create the TCP socket, err=%d"), ::GetLastError()); +#else + wxLogError(wxT("Cannot create the TCP socket, err=%d"), errno); +#endif + return false; + } + + if (!m_localAddress.IsEmpty()) { + sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = 0U; +#if defined(__WINDOWS__) + addr.sin_addr.s_addr = ::inet_addr(m_localAddress.mb_str()); +#else + addr.sin_addr.s_addr = ::inet_addr(m_localAddress.mb_str()); +#endif + if (addr.sin_addr.s_addr == INADDR_NONE) { + wxLogError(wxT("The address is invalid - %s"), m_localAddress.c_str()); + close(); + return false; + } + + if (::bind(m_fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { +#if defined(__WINDOWS__) + wxLogError(wxT("Cannot bind the TCP address, err=%d"), ::GetLastError()); +#else + wxLogError(wxT("Cannot bind the TCP address, err=%d"), errno); +#endif + close(); + return false; + } + } + + struct sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(struct sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(m_port); + addr.sin_addr = CUDPReaderWriter::lookup(m_address); + + if (addr.sin_addr.s_addr == INADDR_NONE) { + close(); + return false; + } + + if (::connect(m_fd, (sockaddr*)&addr, sizeof(struct sockaddr_in)) == -1) { +#if defined(__WINDOWS__) + wxLogError(wxT("Cannot connect the TCP socket, err=%d"), ::GetLastError()); +#else + wxLogError(wxT("Cannot connect the TCP socket, err=%d"), errno); +#endif + close(); + return false; + } + + int noDelay = 1; + if (::setsockopt(m_fd, IPPROTO_TCP, TCP_NODELAY, (char *)&noDelay, sizeof(noDelay)) == -1) { +#if defined(__WINDOWS__) + wxLogError(wxT("Cannot set the TCP socket option, err=%d"), ::GetLastError()); +#else + wxLogError(wxT("Cannot set the TCP socket option, err=%d"), errno); +#endif + close(); + return false; + } + + return true; +} + +int CTCPReaderWriter::read(unsigned char* buffer, unsigned int length, unsigned int secs, unsigned int msecs) +{ + wxASSERT(buffer != NULL); + wxASSERT(length > 0U); + wxASSERT(m_fd != -1); + + // Check that the recv() won't block + fd_set readFds; + FD_ZERO(&readFds); +#if defined(__WINDOWS__) + FD_SET((unsigned int)m_fd, &readFds); +#else + FD_SET(m_fd, &readFds); +#endif + + // Return after timeout + timeval tv; + tv.tv_sec = secs; + tv.tv_usec = msecs * 1000; + + int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv); + if (ret < 0) { +#if defined(__WINDOWS__) + wxLogError(wxT("Error returned from TCP select, err=%d"), ::GetLastError()); +#else + wxLogError(wxT("Error returned from TCP select, err=%d"), errno); +#endif + return -1; + } + +#if defined(__WINDOWS__) + if (!FD_ISSET((unsigned int)m_fd, &readFds)) + return 0; +#else + if (!FD_ISSET(m_fd, &readFds)) + return 0; +#endif + + ssize_t len = ::recv(m_fd, (char*)buffer, length, 0); + if (len < 0) { +#if defined(__WINDOWS__) + wxLogError(wxT("Error returned from recv, err=%d"), ::GetLastError()); +#else + wxLogError(wxT("Error returned from recv, err=%d"), errno); +#endif + return -1; + } + + return len; +} + +bool CTCPReaderWriter::write(const unsigned char* buffer, unsigned int length) +{ + wxASSERT(buffer != NULL); + wxASSERT(length > 0U); + wxASSERT(m_fd != -1); + + ssize_t ret = ::send(m_fd, (char *)buffer, length, 0); + if (ret != ssize_t(length)) { +#if defined(__WINDOWS__) + wxLogError(wxT("Error returned from send, err=%d"), ::GetLastError()); +#else + wxLogError(wxT("Error returned from send, err=%d"), errno); +#endif + return false; + } + + return true; +} + +void CTCPReaderWriter::close() +{ + if (m_fd != -1) { +#if defined(__WINDOWS__) + ::closesocket(m_fd); +#else + ::close(m_fd); +#endif + m_fd = -1; + } +} diff --git a/Common/TCPReaderWriter.h b/Common/TCPReaderWriter.h new file mode 100644 index 0000000..b67d53f --- /dev/null +++ b/Common/TCPReaderWriter.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010,2011,2012 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef TCPReaderWriter_H +#define TCPReaderWriter_H + +#include + +#if !defined(__WINDOWS__) +#include +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +class CTCPReaderWriter { +public: + CTCPReaderWriter(const wxString& address, unsigned int port, const wxString& localAddress = wxEmptyString); + CTCPReaderWriter(); + ~CTCPReaderWriter(); + + bool open(const wxString& address, unsigned int port, const wxString& localAddress = wxEmptyString); + bool open(); + + int read(unsigned char* buffer, unsigned int length, unsigned int secs, unsigned int msecs = 0U); + bool write(const unsigned char* buffer, unsigned int length); + + void close(); + +private: + wxString m_address; + unsigned short m_port; + wxString m_localAddress; + int m_fd; +}; + +#endif diff --git a/Common/Timer.cpp b/Common/Timer.cpp new file mode 100644 index 0000000..39e2ff1 --- /dev/null +++ b/Common/Timer.cpp @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2009,2010 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "Timer.h" + +#include + +CTimer::CTimer(unsigned int ticksPerSec, unsigned int secs, unsigned int msecs) : +m_ticksPerSec(ticksPerSec), +m_timeout(0U), +m_timer(0U) +{ + wxASSERT(ticksPerSec > 0U); + + if (secs > 0U || msecs > 0U) { + // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; + unsigned wxLongLong_t temp = (secs * wxULL(1000) + msecs) * m_ticksPerSec; + m_timeout = (unsigned int)(temp / wxULL(1000) + wxULL(1)); + } +} + +CTimer::~CTimer() +{ +} + +void CTimer::setTimeout(unsigned int secs, unsigned int msecs) +{ + if (secs > 0U || msecs > 0U) { + // m_timeout = ((secs * 1000U + msecs) * m_ticksPerSec) / 1000U + 1U; + unsigned wxLongLong_t temp = (secs * wxULL(1000) + msecs) * m_ticksPerSec; + m_timeout = (unsigned int)(temp / wxULL(1000) + wxULL(1)); + } else { + m_timeout = 0U; + m_timer = 0U; + } +} + +unsigned int CTimer::getTimeout() const +{ + if (m_timeout == 0U) + return 0U; + + return (m_timeout - 1U) / m_ticksPerSec; +} + +unsigned int CTimer::getTimer() const +{ + if (m_timer == 0U) + return 0U; + + return (m_timer - 1U) / m_ticksPerSec; +} diff --git a/Common/Timer.h b/Common/Timer.h new file mode 100644 index 0000000..711242d --- /dev/null +++ b/Common/Timer.h @@ -0,0 +1,89 @@ +/* + * Copyright (C) 2009,2010,2011,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Timer_H +#define Timer_H + +class CTimer { +public: + CTimer(unsigned int ticksPerSec = 1000, unsigned int secs = 0U, unsigned int msecs = 0U); + ~CTimer(); + + void setTimeout(unsigned int secs, unsigned int msecs = 0U); + + unsigned int getTimeout() const; + unsigned int getTimer() const; + + unsigned int getRemaining() + { + if (m_timeout == 0U || m_timer == 0U) + return 0U; + + if (m_timer >= m_timeout) + return 0U; + + return (m_timeout - m_timer) / m_ticksPerSec; + } + + bool isRunning() + { + return m_timer > 0U; + } + + void start(unsigned int secs, unsigned int msecs = 0U) + { + setTimeout(secs, msecs); + + start(); + } + + void start() + { + if (m_timeout > 0U) + m_timer = 1U; + } + + void stop() + { + m_timer = 0U; + } + + bool hasExpired() + { + if (m_timeout == 0U || m_timer == 0U) + return false; + + if (m_timer >= m_timeout) + return true; + + return false; + } + + void clock(unsigned int ticks = 1U) + { + if (m_timer > 0U && m_timeout > 0U) + m_timer += ticks; + } + +private: + unsigned int m_ticksPerSec; + unsigned int m_timeout; + unsigned int m_timer; +}; + +#endif diff --git a/Common/UDPReaderWriter.cpp b/Common/UDPReaderWriter.cpp new file mode 100644 index 0000000..5a18a8e --- /dev/null +++ b/Common/UDPReaderWriter.cpp @@ -0,0 +1,230 @@ +/* + * Copyright (C) 2006-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "UDPReaderWriter.h" + +#if !defined(__WINDOWS__) +#include +#endif + + +CUDPReaderWriter::CUDPReaderWriter(const wxString& address, unsigned int port) : +m_address(address), +m_port(port), +m_addr(), +m_fd(-1) +{ +#if defined(__WINDOWS__) + WSAData data; + int wsaRet = ::WSAStartup(MAKEWORD(2, 2), &data); + if (wsaRet != 0) + wxLogError(wxT("Error from WSAStartup")); +#endif +} + +CUDPReaderWriter::~CUDPReaderWriter() +{ +#if defined(__WINDOWS__) + ::WSACleanup(); +#endif +} + +in_addr CUDPReaderWriter::lookup(const wxString& hostname) +{ + in_addr addr; +#if defined(WIN32) + unsigned long address = ::inet_addr(hostname.mb_str()); + if (address != INADDR_NONE && address != INADDR_ANY) { + addr.s_addr = address; + return addr; + } + + struct hostent* hp = ::gethostbyname(hostname.mb_str()); + if (hp != NULL) { + ::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr)); + return addr; + } + + wxLogError(wxT("Cannot find address for host %s"), hostname.c_str()); + + addr.s_addr = INADDR_NONE; + return addr; +#else + in_addr_t address = ::inet_addr(hostname.mb_str()); + if (address != in_addr_t(-1)) { + addr.s_addr = address; + return addr; + } + + struct hostent* hp = ::gethostbyname(hostname.mb_str()); + if (hp != NULL) { + ::memcpy(&addr, hp->h_addr_list[0], sizeof(struct in_addr)); + return addr; + } + + wxLogError(wxT("Cannot find address for host %s"), hostname.c_str()); + + addr.s_addr = INADDR_NONE; + return addr; +#endif +} + +bool CUDPReaderWriter::open() +{ + m_fd = ::socket(PF_INET, SOCK_DGRAM, 0); + if (m_fd < 0) { +#if defined(__WINDOWS__) + wxLogError(wxT("Cannot create the UDP socket, err: %lu"), ::GetLastError()); +#else + wxLogError(wxT("Cannot create the UDP socket, err: %d"), errno); +#endif + return false; + } + + if (m_port > 0U) { + sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(sockaddr_in)); + addr.sin_family = AF_INET; + addr.sin_port = htons(m_port); + addr.sin_addr.s_addr = htonl(INADDR_ANY); + + if (!m_address.IsEmpty()) { +#if defined(__WINDOWS__) + addr.sin_addr.s_addr = ::inet_addr(m_address.mb_str()); +#else + addr.sin_addr.s_addr = ::inet_addr(m_address.mb_str()); +#endif + if (addr.sin_addr.s_addr == INADDR_NONE) { + wxLogError(wxT("The address is invalid - %s"), m_address.c_str()); + return false; + } + } + + int reuse = 1; + if (::setsockopt(m_fd, SOL_SOCKET, SO_REUSEADDR, (char *)&reuse, sizeof(reuse)) == -1) { +#if defined(__WINDOWS__) + wxLogError(wxT("Cannot set the UDP socket option (port: %u), err: %lu"), m_port, ::GetLastError()); +#else + wxLogError(wxT("Cannot set the UDP socket option (port: %u), err: %d"), m_port, errno); +#endif + return false; + } + + if (::bind(m_fd, (sockaddr*)&addr, sizeof(sockaddr_in)) == -1) { +#if defined(__WINDOWS__) + wxLogError(wxT("Cannot bind the UDP address (port: %u), err: %lu"), m_port, ::GetLastError()); +#else + wxLogError(wxT("Cannot bind the UDP address (port: %u), err: %d"), m_port, errno); +#endif + return false; + } + } + + return true; +} + +int CUDPReaderWriter::read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port) +{ + // Check that the readfrom() won't block + fd_set readFds; + FD_ZERO(&readFds); +#if defined(__WINDOWS__) + FD_SET((unsigned int)m_fd, &readFds); +#else + FD_SET(m_fd, &readFds); +#endif + + // Return immediately + timeval tv; + tv.tv_sec = 0L; + tv.tv_usec = 0L; + + int ret = ::select(m_fd + 1, &readFds, NULL, NULL, &tv); + if (ret < 0) { +#if defined(__WINDOWS__) + wxLogError(wxT("Error returned from UDP select (port: %u), err: %lu"), m_port, ::GetLastError()); +#else + wxLogError(wxT("Error returned from UDP select (port: %u), err: %d"), m_port, errno); +#endif + return -1; + } + + if (ret == 0) + return 0; + + sockaddr_in addr; +#if defined(__WINDOWS__) + int size = sizeof(sockaddr_in); +#else + socklen_t size = sizeof(sockaddr_in); +#endif + + ssize_t len = ::recvfrom(m_fd, (char*)buffer, length, 0, (sockaddr *)&addr, &size); + if (len <= 0) { +#if defined(__WINDOWS__) + wxLogError(wxT("Error returned from recvfrom (port: %u), err: %lu"), m_port, ::GetLastError()); +#else + wxLogError(wxT("Error returned from recvfrom (port: %u), err: %d"), m_port, errno); +#endif + return -1; + } + + address = addr.sin_addr; + port = ntohs(addr.sin_port); + + return len; +} + +bool CUDPReaderWriter::write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port) +{ + sockaddr_in addr; + ::memset(&addr, 0x00, sizeof(sockaddr_in)); + + addr.sin_family = AF_INET; + addr.sin_addr = address; + addr.sin_port = htons(port); + + ssize_t ret = ::sendto(m_fd, (char *)buffer, length, 0, (sockaddr *)&addr, sizeof(sockaddr_in)); + if (ret < 0) { +#if defined(__WINDOWS__) + wxLogError(wxT("Error returned from sendto (port: %u), err: %lu"), m_port, ::GetLastError()); +#else + wxLogError(wxT("Error returned from sendto (port: %u), err: %d"), m_port, errno); +#endif + return false; + } + + if (ret != ssize_t(length)) + return false; + + return true; +} + +void CUDPReaderWriter::close() +{ +#if defined(__WINDOWS__) + ::closesocket(m_fd); +#else + ::close(m_fd); +#endif +} + +unsigned int CUDPReaderWriter::getPort() const +{ + return m_port; +} diff --git a/Common/UDPReaderWriter.h b/Common/UDPReaderWriter.h new file mode 100644 index 0000000..4167662 --- /dev/null +++ b/Common/UDPReaderWriter.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2009-2011,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef UDPReaderWriter_H +#define UDPReaderWriter_H + +#include + +#if !defined(__WINDOWS__) +#include +#include +#include +#include +#include +#include +#include +#include +#endif + +class CUDPReaderWriter { +public: + CUDPReaderWriter(const wxString& address, unsigned int port); + ~CUDPReaderWriter(); + + static in_addr lookup(const wxString& hostName); + + bool open(); + + int read(unsigned char* buffer, unsigned int length, in_addr& address, unsigned int& port); + bool write(const unsigned char* buffer, unsigned int length, const in_addr& address, unsigned int port); + + void close(); + + unsigned int getPort() const; + +private: + wxString m_address; + unsigned short m_port; + in_addr m_addr; + int m_fd; +}; + +#endif diff --git a/Common/UDRCController.cpp b/Common/UDRCController.cpp new file mode 100644 index 0000000..31808fd --- /dev/null +++ b/Common/UDRCController.cpp @@ -0,0 +1,215 @@ +/* + * Header here + * + */ + +#include +#include +#include + +#include "UDRCController.h" + +#define ARRAY_SIZE(a) (sizeof((a)) / sizeof((a)[0])) + +// Pin definitions +// These use the BCM numbering system, *NOT* physical pin numbers or WiringPi +// "we need to be special" pin numbering. +static const unsigned int BASE_PIN = 6; +static const unsigned int PTT_PIN = 12; +static const unsigned int PKSQL_PIN = 5; +static const unsigned int SQL_PIN = 25; +static const unsigned int EXT1_PIN = 24; +static const unsigned int EXT2_PIN = 22; +static const unsigned int EXT3_PIN = 23; +static const unsigned int EXT4_PIN = 27; + +static const unsigned int input_pins[] = { + SQL_PIN, + PKSQL_PIN, +}; + +static const unsigned int output_pins[] = { + BASE_PIN, + EXT1_PIN, + EXT2_PIN, + EXT3_PIN, + EXT4_PIN, +}; + +static const char vendor_string_path[] = "/proc/device-tree/hat/vendor"; +static const char product_id_string_path[] = "/proc/device-tree/hat/product_id"; +static const char nwdr_vendor_string[] = "NW Digital Radio"; + +#define UDRC_PRODUCT_ID 2 +#define UDRC_II_PRODUCT_ID 3 + +static int getNWDRProductID() { + int proc_file = -1; + ssize_t read_size; + char buffer[255]; + long product_id = 0; + + if((proc_file = open(vendor_string_path, O_RDONLY|O_NONBLOCK)) == -1) { + wxLogError("Open %s: %s\n", vendor_string_path, strerror(errno)); + } + + read_size = read(proc_file, buffer, sizeof(buffer)); + if(read_size < 1) { + wxLogError("Read %s: %s\n", vendor_string_path, strerror(errno)); + close(proc_file); + return -1; + } + + close(proc_file); + + if(strncmp(nwdr_vendor_string, buffer, sizeof(nwdr_vendor_string))) { + wxLogError("HAT is not a NW Digital Radio Product: %.255s\n", buffer); + return -1; + } + + if((proc_file = open(product_id_string_path, O_RDONLY|O_NONBLOCK)) == -1) { + wxLogError("Open %s: %s\n", product_id_string_path, strerror(errno)); + } + + read_size = read(proc_file, buffer, sizeof(buffer)); + if(read_size < 1) { + wxLogError("Read %s: %s\n", product_id_string_path, strerror(errno)); + close(proc_file); + return -1; + } + + close(proc_file); + + product_id = strtol(buffer, NULL, 16); + if(product_id == EINVAL || product_id == ERANGE) { + wxLogError("Invalid value for product id: %.255s\n", buffer); + return -1; + } + + return product_id; +} + +// XXX This is ugly, we should have a better parent class +CUDRCController::CUDRCController(enum repeater_modes mode) : +CExternalController(NULL, false), +m_mode(mode), +m_pttPin(PTT_PIN) +{ +} + +void CUDRCController::switchMode(enum repeater_modes mode) { + + if(mode == HOTSPOT) + return; + + switch(mode) { + case AUTO_FM: // Auto/FM + ::digitalWrite(EXT1_PIN, HIGH); + ::digitalWrite(EXT2_PIN, HIGH); + break; + case DIGITAL_DIGITAL: // Digital/Digital + ::digitalWrite(EXT1_PIN, HIGH); + ::digitalWrite(EXT2_PIN, LOW); + break; + case FM_FM: // FM/FM Fixed + ::digitalWrite(EXT1_PIN, LOW); + ::digitalWrite(EXT2_PIN, HIGH); + break; + case AUTO_AUTO: // Auto/Auto + ::digitalWrite(EXT1_PIN, LOW); + ::digitalWrite(EXT2_PIN, LOW); + break; + } +} + +bool CUDRCController::open() +{ + if(::wiringPiSetupGpio() != 0) { + wxLogError("Unable to initialize the wiringPi library"); + return false; + } + + if(m_mode != HOTSPOT) { + ::pinMode(m_pttPin, OUTPUT); + + for(int i = 0; i < ARRAY_SIZE(input_pins); ++i) { + ::pinMode(input_pins[i], INPUT); + ::pullUpDnControl(input_pins[i], PUD_UP); + } + + for(int i = 0; i < ARRAY_SIZE(output_pins); ++i) { + ::pinMode(output_pins[i], OUTPUT); + ::digitalWrite(output_pins[i], HIGH); + } + + ::digitalWrite(m_pttPin, HIGH); + + ::digitalWrite(BASE_PIN, LOW); + switchMode(m_mode); + ::digitalWrite(BASE_PIN, HIGH); + } else { + // On a UDRC II in Hotspot mode, the PTT is on EXT3_PIN instead + switch(getNWDRProductID()) { + case UDRC_PRODUCT_ID: + m_pttPin = PTT_PIN; + break; + case UDRC_II_PRODUCT_ID: + m_pttPin = EXT3_PIN; + break; + case -1: + return false; + default: + wxLogError("Unknown NW Digital Radio Product"); + return false; + } + + ::pinMode(m_pttPin, OUTPUT); + ::digitalWrite(m_pttPin, LOW); + } + + return true; +} + +bool CUDRCController::getDisable() const +{ + if(m_mode == HOTSPOT) + return false; + + return ::digitalRead(PKSQL_PIN) == LOW; +} + +void CUDRCController::setRadioTransmit(bool value) +{ + // If we're in hotspot mode, we're using the miniDIN connector that is + // reversed from the normal sense of PTT. + if(m_mode == HOTSPOT) + value = !value; + + ::digitalWrite(m_pttPin, value ? LOW : HIGH); +} + +void CUDRCController::setActive(bool value) +{ + if(m_mode == HOTSPOT) + return; + + if(value == HIGH) { + ::digitalWrite(BASE_PIN, LOW); + switchMode(FM_FM); + } else { + switchMode(m_mode); + ::digitalWrite(BASE_PIN, HIGH); + } +} + +// This needs to be overridden for now because the base class, ExternalController +// will try to dork around with threads that we're not using anymore. +void CUDRCController::close() +{ + if(m_mode == HOTSPOT) { + ::digitalWrite(m_pttPin, LOW); + } else { + ::digitalWrite(m_pttPin, HIGH); + } +} + diff --git a/Common/UDRCController.h b/Common/UDRCController.h new file mode 100644 index 0000000..d7fc6a8 --- /dev/null +++ b/Common/UDRCController.h @@ -0,0 +1,33 @@ +#ifndef UDRCController_H +#define UDRCController_H + +#include "ExternalController.h" + +enum repeater_modes { + HOTSPOT = 0, + AUTO_FM = 1, + DIGITAL_DIGITAL = 3, + FM_FM = 4, + AUTO_AUTO = 2, +}; + +class CUDRCController: public CExternalController { +public: + CUDRCController(enum repeater_modes mode); + + bool getDisable() const; + + void setRadioTransmit(bool value); + void setActive(bool value); + + bool open(); + void close(); + +private: + const enum repeater_modes m_mode; + int m_pttPin; + + void switchMode(enum repeater_modes mode); +}; + +#endif // UDRCController_H diff --git a/Common/URIUSBController.cpp b/Common/URIUSBController.cpp new file mode 100644 index 0000000..893c545 --- /dev/null +++ b/Common/URIUSBController.cpp @@ -0,0 +1,411 @@ +/* + * Copyright (C) 2009-2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "URIUSBController.h" + +const unsigned int C108_VENDOR_ID = 0x0D8CU; +const unsigned int C108_PRODUCT_ID = 0x000CU; +const unsigned int C108AH_PRODUCT_ID = 0x013CU; +const unsigned int C119_PRODUCT_ID = 0x0008U; +const unsigned int C119A_PRODUCT_ID = 0x013AU; + +const char REPORT_ID = 0x00; + +const char HID_OR0 = 0x00; +const char HID_OR1 = 0x00; +const char HID_OR2 = 0x0F; // Set GPIO1 to GPIO4 to output +const char HID_OR3 = 0x00; +const char HID_OR1_GPIO4 = 0x08; // GPIO4, Switched +const char HID_OR1_GPIO3 = 0x04; // GPIO3, PTT +const char HID_OR1_GPIO2 = 0x02; // GPIO2, Switched +const char HID_OR1_GPIO1 = 0x01; // GPIO1, Heartbeat +const char HID_IR0_RM = 0x08; // Record-Mute, Disable +const char HID_IR0_PM = 0x04; // Playback-Mute, Battery +const char HID_IR0_VD = 0x02; // Volume-Down, COR_DET +const char HID_IR0_VU = 0x01; // Volume-Up, CTCSS_DET + +#if defined(__WINDOWS__) + +#include +#include + +const ULONG USB_BUFSIZE = 5UL; + +CURIUSBController::CURIUSBController(unsigned int address, bool checkInput) : +m_address(address), +m_checkInput(checkInput), +m_outp1(false), +m_outp3(false), +m_outp5(false), +m_outp6(false), +m_handle(INVALID_HANDLE_VALUE) +{ +} + +CURIUSBController::~CURIUSBController() +{ +} + +bool CURIUSBController::open() +{ + wxASSERT(m_handle == INVALID_HANDLE_VALUE); + + GUID guid; + ::HidD_GetHidGuid(&guid); + + HDEVINFO devInfo = ::SetupDiGetClassDevs(&guid, NULL, NULL, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + if (devInfo == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from SetupDiGetClassDevs: err=%u"), ::GetLastError()); + return false; + } + + SP_DEVICE_INTERFACE_DATA devInfoData; + devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + + unsigned int count = 0U; + for (DWORD index = 0U; ::SetupDiEnumDeviceInterfaces(devInfo, NULL, &guid, index, &devInfoData); index++) { + // Find the required length of the device structure + DWORD length; + ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, NULL, 0U, &length, NULL); + + PSP_DEVICE_INTERFACE_DETAIL_DATA detailData = PSP_DEVICE_INTERFACE_DETAIL_DATA(::malloc(length)); + detailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + // Get the detailed data into the newly allocated device structure + DWORD required; + BOOL res = ::SetupDiGetDeviceInterfaceDetail(devInfo, &devInfoData, detailData, length, &required, NULL); + if (!res) { + wxLogError(wxT("Error from SetupDiGetDeviceInterfaceDetail: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + // Get the handle for getting the attributes + HANDLE handle = ::CreateFile(detailData->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (handle == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from CreateFile: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + HIDD_ATTRIBUTES attributes; + attributes.Size = sizeof(HIDD_ATTRIBUTES); + res = ::HidD_GetAttributes(handle, &attributes); + if (!res) { + wxLogError(wxT("Error from HidD_GetAttributes: err=%u"), ::GetLastError()); + ::CloseHandle(handle); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::CloseHandle(handle); + + // Is this a CM108 or equivalent? + if (attributes.VendorID == C108_VENDOR_ID && + (attributes.ProductID == C108_PRODUCT_ID || + attributes.ProductID == C108AH_PRODUCT_ID || + attributes.ProductID == C119_PRODUCT_ID || + attributes.ProductID == C119A_PRODUCT_ID)) { + count++; + + // Is this the right device? + if (count == m_address) { + m_handle = ::CreateFile(detailData->DevicePath, GENERIC_WRITE | GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); + if (m_handle == INVALID_HANDLE_VALUE) { + wxLogError(wxT("Error from CreateFile: err=%u"), ::GetLastError()); + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + return false; + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + ::free(detailData); + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + return true; + } + } + + ::free(detailData); + } + + ::SetupDiDestroyDeviceInfoList(devInfo); + + return false; +} + +void CURIUSBController::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + if (!m_checkInput) { + inp1 = inp2 = inp3 = inp4 = inp5 = false; + return; + } + + char buffer[USB_BUFSIZE]; + buffer[0] = REPORT_ID; + buffer[1] = 0x00; + buffer[2] = 0x00; + buffer[3] = 0x00; + buffer[4] = 0x00; + BOOL res = ::HidD_GetInputReport(m_handle, buffer, USB_BUFSIZE); + if (!res) { + wxLogError(wxT("Error from HidD_GetInputReport: err=%u"), ::GetLastError()); + return; + } + + inp3 = false; + + inp5 = (buffer[1] & HID_IR0_RM) == HID_IR0_RM; // Disable + inp4 = (buffer[1] & HID_IR0_PM) == HID_IR0_PM; // Battery + inp2 = (buffer[1] & HID_IR0_VU) == HID_IR0_VU; // Squelch 2 + inp1 = (buffer[1] & HID_IR0_VD) == HID_IR0_VD; // Squelch 1 +} + +void CURIUSBController::setDigitalOutputs(bool outp1, bool, bool outp3, bool outp4, bool outp5, bool outp6, bool, bool) +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + if (outp1 == m_outp1 && outp3 == m_outp3 && outp5 == m_outp5 && outp6 == m_outp6) + return; + + char buffer[USB_BUFSIZE]; + buffer[0] = REPORT_ID; + buffer[1] = HID_OR0; + buffer[2] = HID_OR1; + buffer[3] = HID_OR2; + buffer[4] = HID_OR3; + + if (outp1) // Transmit + buffer[2] |= HID_OR1_GPIO3; + if (outp3) // Heartbeat + buffer[2] |= HID_OR1_GPIO1; + if (outp4) // Active + buffer[2] |= HID_OR1_GPIO2; + if (outp5) // Output 1 + buffer[2] |= HID_OR1_GPIO4; + + BOOL res = ::HidD_SetOutputReport(m_handle, buffer, USB_BUFSIZE); + if (!res) + wxLogError(wxT("Error from HidD_SetOutputReport: err=%u"), ::GetLastError()); + + m_outp1 = outp1; + m_outp3 = outp3; + m_outp5 = outp5; + m_outp6 = outp6; +} + +void CURIUSBController::close() +{ + wxASSERT(m_handle != INVALID_HANDLE_VALUE); + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + ::CloseHandle(m_handle); + m_handle = INVALID_HANDLE_VALUE; +} + +#else + +const unsigned int C108_HID_INTERFACE = 3U; + +const unsigned int HID_REPORT_GET = 0x01U; +const unsigned int HID_REPORT_SET = 0x09U; + +const unsigned int HID_RT_INPUT = 0x01U; +const unsigned int HID_RT_OUTPUT = 0x02U; + +const int USB_BUFSIZE = 4; + +const int USB_TIMEOUT = 5000; + +CURIUSBController::CURIUSBController(unsigned int address, bool checkInput) : +m_address(address), +m_checkInput(checkInput), +m_outp1(false), +m_outp3(false), +m_outp5(false), +m_outp6(false), +m_context(NULL), +m_handle(NULL) +{ + ::libusb_init(&m_context); +} + +CURIUSBController::~CURIUSBController() +{ + wxASSERT(m_context != NULL); + + ::libusb_exit(m_context); +} + +bool CURIUSBController::open() +{ + wxASSERT(m_context != NULL); + wxASSERT(m_handle == NULL); + + libusb_device** list; + ssize_t cnt = ::libusb_get_device_list(NULL, &list); + if (cnt <= 0) { + wxLogError(wxT("Cannot find any USB devices!")); + return false; + } + + int err = 0; + unsigned int count = 0U; + for (ssize_t i = 0; i < cnt; i++) { + libusb_device_descriptor desc; + err = ::libusb_get_device_descriptor(list[i], &desc); + if (err < 0) + continue; + + if (desc.idVendor == C108_VENDOR_ID && + (desc.idProduct == C108_PRODUCT_ID || + desc.idProduct == C108AH_PRODUCT_ID || + desc.idProduct == C119_PRODUCT_ID || + desc.idProduct == C119A_PRODUCT_ID)) { + count++; + + // Is this the correct device? + if (count == m_address) { + err = ::libusb_open(list[i], &m_handle); + break; + } + } + } + + if (err != 0) { + wxLogError(wxT("libusb_open failed, err=%d"), err); + ::libusb_free_device_list(list, 1); + return false; + } + + if (m_handle == NULL) { + wxLogError(wxT("Could not find a suitable CM108 based device")); + ::libusb_free_device_list(list, 1); + return false; + } + + ::libusb_free_device_list(list, 1); + + int res = ::libusb_claim_interface(m_handle, C108_HID_INTERFACE); + if (res != 0) { + res = ::libusb_detach_kernel_driver(m_handle, C108_HID_INTERFACE); + if (res != 0) { + wxLogError(wxT("libusb_detach_kernel_driver failed, err=%d"), res); + ::libusb_close(m_handle); + m_handle = NULL; + return false; + } + + res = ::libusb_claim_interface(m_handle, C108_HID_INTERFACE); + if (res != 0) { + wxLogError(wxT("libusb_claim_interface failed, err=%d"), res); + ::libusb_close(m_handle); + m_handle = NULL; + return false; + } + } + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + return true; +} + +void CURIUSBController::getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5) +{ + wxASSERT(m_handle != NULL); + + if (!m_checkInput) { + inp1 = inp2 = inp3 = inp4 = inp5 = false; + return; + } + + unsigned char buffer[USB_BUFSIZE]; + buffer[0] = 0x00; + buffer[1] = 0x00; + buffer[2] = 0x00; + buffer[3] = 0x00; + int res = ::libusb_control_transfer(m_handle, LIBUSB_ENDPOINT_IN + LIBUSB_REQUEST_TYPE_CLASS + LIBUSB_RECIPIENT_INTERFACE, HID_REPORT_GET, REPORT_ID + (HID_RT_INPUT << 8), C108_HID_INTERFACE, buffer, USB_BUFSIZE, USB_TIMEOUT); + if (res < 0) { + wxLogError(wxT("Error from libusb_control_transfer: err=%d"), res); + return; + } + + if (res < USB_BUFSIZE) + return; + + inp3 = false; + + inp5 = (buffer[0] & HID_IR0_RM) == HID_IR0_RM; // Disable + inp4 = (buffer[0] & HID_IR0_PM) == HID_IR0_PM; // Battery + inp2 = (buffer[0] & HID_IR0_VU) == HID_IR0_VU; // Squelch 2 + inp1 = (buffer[0] & HID_IR0_VD) == HID_IR0_VD; // Squelch 1 +} + +void CURIUSBController::setDigitalOutputs(bool outp1, bool, bool outp3, bool outp4, bool outp5, bool outp6, bool, bool) +{ + wxASSERT(m_handle != NULL); + + if (outp1 == m_outp1 && outp3 == m_outp3 && outp5 == m_outp5 && outp6 == m_outp6) + return; + + unsigned char buffer[USB_BUFSIZE]; + buffer[0] = HID_OR0; + buffer[1] = HID_OR1; + buffer[2] = HID_OR2; + buffer[3] = HID_OR3; + + if (outp1) // Transmit + buffer[1] |= HID_OR1_GPIO3; + if (outp3) // Heartbeat + buffer[1] |= HID_OR1_GPIO1; + if (outp4) // Active + buffer[1] |= HID_OR1_GPIO2; + if (outp5) // Output 1 + buffer[1] |= HID_OR1_GPIO4; + + int res = ::libusb_control_transfer(m_handle, LIBUSB_ENDPOINT_OUT + LIBUSB_REQUEST_TYPE_CLASS + LIBUSB_RECIPIENT_INTERFACE, HID_REPORT_SET, REPORT_ID + (HID_RT_OUTPUT << 8), C108_HID_INTERFACE, buffer, USB_BUFSIZE, USB_TIMEOUT); + if (res < 0) { + wxLogError(wxT("Error from libusb_control_transfer: err=%d"), res); + return; + } + + if (res < USB_BUFSIZE) + return; + + m_outp1 = outp1; + m_outp3 = outp3; + m_outp5 = outp5; + m_outp6 = outp6; +} + +void CURIUSBController::close() +{ + wxASSERT(m_handle != NULL); + + setDigitalOutputs(false, false, false, false, false, false, false, false); + + ::libusb_release_interface(m_handle, C108_HID_INTERFACE); + + ::libusb_close(m_handle); + m_handle = NULL; +} + +#endif + diff --git a/Common/URIUSBController.h b/Common/URIUSBController.h new file mode 100644 index 0000000..52fb2d1 --- /dev/null +++ b/Common/URIUSBController.h @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2009,2011 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef URIUSBController_H +#define URIUSBController_H + +#include "HardwareController.h" + +#include + +#if defined(__WINDOWS__) +#include +#else +#include +#endif + +class CURIUSBController : public IHardwareController { +public: + CURIUSBController(unsigned int address, bool checkInput); + virtual ~CURIUSBController(); + + virtual bool open(); + + virtual void getDigitalInputs(bool& inp1, bool& inp2, bool& inp3, bool& inp4, bool& inp5); + + virtual void setDigitalOutputs(bool outp1, bool outp2, bool outp3, bool outp4, bool outp5, bool outp6, bool outp7, bool outp8); + + virtual void close(); + +private: + unsigned int m_address; + bool m_checkInput; + bool m_outp1; + bool m_outp3; + bool m_outp5; + bool m_outp6; +#if defined(__WINDOWS__) + HANDLE m_handle; +#else + libusb_context* m_context; + libusb_device_handle* m_handle; +#endif +}; + +#endif diff --git a/Common/Utils.cpp b/Common/Utils.cpp new file mode 100644 index 0000000..b89758d --- /dev/null +++ b/Common/Utils.cpp @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2009,2014 Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "Utils.h" + +void CUtils::dump(const wxChar* title, const unsigned char* data, unsigned int length) +{ + wxASSERT(title != NULL); + wxASSERT(data != NULL); + + wxLogMessage(wxT("%s"), title); + + unsigned int offset = 0U; + + while (length > 0U) { + wxString output; + + unsigned int bytes = (length > 16U) ? 16U : length; + + for (unsigned i = 0U; i < bytes; i++) { + wxString temp; + temp.Printf(wxT("%02X "), data[offset + i]); + output += temp; + } + + for (unsigned int i = bytes; i < 16U; i++) + output += wxT(" "); + + output += wxT(" *"); + + for (unsigned i = 0U; i < bytes; i++) { + unsigned char c = data[offset + i]; + + if (::isprint(c)) + output += wxChar(c); + else + output += wxT('.'); + } + + output += wxT('*'); + + wxLogMessage(wxT("%04X: %s"), offset, output.c_str()); + + offset += 16U; + + if (length >= 16U) + length -= 16U; + else + length = 0U; + } +} diff --git a/Common/Utils.h b/Common/Utils.h new file mode 100644 index 0000000..21d6b17 --- /dev/null +++ b/Common/Utils.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2009,2014 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#ifndef Utils_H +#define Utils_H + +#include + +enum TRISTATE { + STATE_FALSE, + STATE_TRUE, + STATE_UNKNOWN +}; + +class CUtils { +public: + static void dump(const wxChar* title, const unsigned char* data, unsigned int length); + +private: +}; + +#endif diff --git a/Common/Version.h b/Common/Version.h new file mode 100644 index 0000000..22b1544 --- /dev/null +++ b/Common/Version.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2010-2015,2018 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef Version_H +#define Version_H + +#include + +const wxString VENDOR_NAME = wxT("G4KLX"); + +#if defined(__WXDEBUG__) +const wxString VERSION = wxT("20180510 - DEBUG"); +#else +const wxString VERSION = wxT("20180510"); +#endif + +#endif diff --git a/DStarRepeater.sln b/DStarRepeater.sln new file mode 100644 index 0000000..5e1ac62 --- /dev/null +++ b/DStarRepeater.sln @@ -0,0 +1,61 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 15 +VisualStudioVersion = 15.0.27703.2000 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DStarRepeater", "DStarRepeater\DStarRepeater.vcxproj", "{E3458953-FC3D-47AF-A245-A1A87DC2BC7E}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "DStarRepeaterConfig", "DStarRepeaterConfig\DStarRepeaterConfig.vcxproj", "{FBAE8201-11CC-4256-BF39-A860B9DF963C}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "Common", "Common\Common.vcxproj", "{3753EF20-2330-415E-B933-2560A474498B}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "GUICommon", "GUICommon\GUICommon.vcxproj", "{2C548AAB-A9F7-496F-BCF5-6185BDA304D2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Win32 = Debug|Win32 + Debug|x64 = Debug|x64 + Release|Win32 = Release|Win32 + Release|x64 = Release|x64 + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E}.Debug|Win32.ActiveCfg = Debug|Win32 + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E}.Debug|Win32.Build.0 = Debug|Win32 + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E}.Debug|x64.ActiveCfg = Debug|x64 + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E}.Debug|x64.Build.0 = Debug|x64 + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E}.Release|Win32.ActiveCfg = Release|Win32 + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E}.Release|Win32.Build.0 = Release|Win32 + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E}.Release|x64.ActiveCfg = Release|x64 + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E}.Release|x64.Build.0 = Release|x64 + {FBAE8201-11CC-4256-BF39-A860B9DF963C}.Debug|Win32.ActiveCfg = Debug|Win32 + {FBAE8201-11CC-4256-BF39-A860B9DF963C}.Debug|Win32.Build.0 = Debug|Win32 + {FBAE8201-11CC-4256-BF39-A860B9DF963C}.Debug|x64.ActiveCfg = Debug|x64 + {FBAE8201-11CC-4256-BF39-A860B9DF963C}.Debug|x64.Build.0 = Debug|x64 + {FBAE8201-11CC-4256-BF39-A860B9DF963C}.Release|Win32.ActiveCfg = Release|Win32 + {FBAE8201-11CC-4256-BF39-A860B9DF963C}.Release|Win32.Build.0 = Release|Win32 + {FBAE8201-11CC-4256-BF39-A860B9DF963C}.Release|x64.ActiveCfg = Release|x64 + {FBAE8201-11CC-4256-BF39-A860B9DF963C}.Release|x64.Build.0 = Release|x64 + {3753EF20-2330-415E-B933-2560A474498B}.Debug|Win32.ActiveCfg = Debug|Win32 + {3753EF20-2330-415E-B933-2560A474498B}.Debug|Win32.Build.0 = Debug|Win32 + {3753EF20-2330-415E-B933-2560A474498B}.Debug|x64.ActiveCfg = Debug|x64 + {3753EF20-2330-415E-B933-2560A474498B}.Debug|x64.Build.0 = Debug|x64 + {3753EF20-2330-415E-B933-2560A474498B}.Release|Win32.ActiveCfg = Release|Win32 + {3753EF20-2330-415E-B933-2560A474498B}.Release|Win32.Build.0 = Release|Win32 + {3753EF20-2330-415E-B933-2560A474498B}.Release|x64.ActiveCfg = Release|x64 + {3753EF20-2330-415E-B933-2560A474498B}.Release|x64.Build.0 = Release|x64 + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2}.Debug|Win32.ActiveCfg = Debug|Win32 + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2}.Debug|Win32.Build.0 = Debug|Win32 + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2}.Debug|x64.ActiveCfg = Debug|x64 + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2}.Debug|x64.Build.0 = Debug|x64 + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2}.Release|Win32.ActiveCfg = Release|Win32 + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2}.Release|Win32.Build.0 = Release|Win32 + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2}.Release|x64.ActiveCfg = Release|x64 + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {F4EB6A2D-4233-46ED-9DEC-6A55E5765F30} + EndGlobalSection +EndGlobal diff --git a/DStarRepeater/DStarRepeater.vcxproj b/DStarRepeater/DStarRepeater.vcxproj new file mode 100644 index 0000000..9045988 --- /dev/null +++ b/DStarRepeater/DStarRepeater.vcxproj @@ -0,0 +1,204 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {E3458953-FC3D-47AF-A245-A1A87DC2BC7E} + DStarRepeater + Win32Proj + 10.0.17134.0 + + + + Application + v141 + Unicode + true + + + Application + v141 + Unicode + true + + + Application + v141 + Unicode + + + Application + v141 + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.24720.0 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_dll\mswud + + + true + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_dll\mswud + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_dll\mswu + + + false + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_dll\mswu + + + + Disabled + $(SolutionDir)..\WinUSB\include;$(SolutionDir)..\portaudio\include;$(WXWIN)\include\msvc;$(SolutionDir)Common;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;__WXDEBUG__;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level4 + EditAndContinue + + + winusb.lib;wsock32.lib;setupapi.lib;hid.lib;portaudio_x86.lib;%(AdditionalDependencies) + $(SolutionDir)..\portaudio\build\msvc\Win32\Debug;$(WXWIN)\lib\vc_dll;%(AdditionalLibraryDirectories) + true + Windows + MachineX86 + + + + + Disabled + $(SolutionDir)..\WinUSB\include;$(SolutionDir)..\portaudio\include;$(WXWIN)\include\msvc;$(SolutionDir)Common;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;__WXDEBUG__;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + ProgramDatabase + + + winusb.lib;wsock32.lib;setupapi.lib;hid.lib;portaudio_x64.lib;%(AdditionalDependencies) + $(SolutionDir)..\portaudio\build\msvc\x64\Debug;$(WXWIN)\lib\vc_x64_dll;%(AdditionalLibraryDirectories) + true + Windows + + + + + MaxSpeed + true + $(SolutionDir)..\WinUSB\include;$(SolutionDir)..\portaudio\include;$(WXWIN)\include\msvc;$(SolutionDir)Common;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + Level3 + ProgramDatabase + + + winusb.lib;wsock32.lib;setupapi.lib;hid.lib;portaudio_x86.lib;%(AdditionalDependencies) + $(SolutionDir)..\portaudio\build\msvc\Win32\Release;$(WXWIN)\lib\vc_dll;%(AdditionalLibraryDirectories) + true + Windows + true + true + MachineX86 + + + + + MaxSpeed + true + $(SolutionDir)..\WinUSB\include;$(SolutionDir)..\portaudio\include;$(WXWIN)\include\msvc;$(SolutionDir)Common;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + winusb.lib;wsock32.lib;setupapi.lib;hid.lib;portaudio_x64.lib;%(AdditionalDependencies) + $(SolutionDir)..\portaudio\build\msvc\x64\Release;$(WXWIN)\lib\vc_x64_dll;%(AdditionalLibraryDirectories) + true + Windows + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3753ef20-2330-415e-b933-2560a474498b} + false + + + + + + \ No newline at end of file diff --git a/DStarRepeater/DStarRepeater.vcxproj.filters b/DStarRepeater/DStarRepeater.vcxproj.filters new file mode 100644 index 0000000..078751f --- /dev/null +++ b/DStarRepeater/DStarRepeater.vcxproj.filters @@ -0,0 +1,74 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/DStarRepeater/DStarRepeaterApp.cpp b/DStarRepeater/DStarRepeaterApp.cpp new file mode 100644 index 0000000..ff80d73 --- /dev/null +++ b/DStarRepeater/DStarRepeaterApp.cpp @@ -0,0 +1,694 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include + +#include +#include +#include + +#include "DStarRepeaterTXRXThread.h" +#include "RepeaterProtocolHandler.h" +#include "DStarRepeaterTRXThread.h" +#include "DStarRepeaterTXThread.h" +#include "DStarRepeaterRXThread.h" +#include "SerialLineController.h" +#include "DStarRepeaterLogger.h" +#include "DStarRepeaterThread.h" +#include "SoundCardController.h" +#include "DVRPTRV1Controller.h" +#include "DVRPTRV2Controller.h" +#include "DVRPTRV3Controller.h" +#include "ArduinoController.h" +#include "DVMegaController.h" +#include "DStarRepeaterApp.h" +#include "MMDVMController.h" +#include "URIUSBController.h" +#include "K8055Controller.h" +#include "DummyController.h" +#include "SplitController.h" +#if defined(GPIO) +#include "GPIOController.h" +#include "UDRCController.h" +#endif +#include "DVAPController.h" +#include "GMSKController.h" +#include "DStarDefines.h" +#include "Version.h" +#include "Logger.h" + +IMPLEMENT_APP(CDStarRepeaterApp) + +wxDEFINE_EVENT(wxEVT_THREAD_COMMAND, wxThreadEvent); + +wxBEGIN_EVENT_TABLE(CDStarRepeaterApp, wxApp) + EVT_THREAD(wxEVT_THREAD_COMMAND, CDStarRepeaterApp::OnRemoteCmd) +wxEND_EVENT_TABLE() + +const wxString NAME_PARAM = "Repeater Name"; +const wxString NOLOGGING_SWITCH = "nolog"; +const wxString GUI_SWITCH = "gui"; +const wxString LOGDIR_OPTION = "logdir"; +const wxString CONFDIR_OPTION = "confdir"; +const wxString AUDIODIR_OPTION = "audiodir"; +#if (wxUSE_GUI == 1) +const wxString LOG_BASE_NAME = "dstarrepeater"; +#else +const wxString LOG_BASE_NAME = "dstarrepeaterd"; +#endif + +CDStarRepeaterApp::CDStarRepeaterApp() : +#if (wxUSE_GUI == 1) +m_frame(NULL), +#endif +wxApp(), +m_name(), +m_nolog(false), +m_gui(false), +m_logDir(), +m_confDir(), +m_audioDir(), +m_thread(NULL), +m_config(NULL), +m_checker(NULL), +m_logChain(NULL) +{ +} + +CDStarRepeaterApp::~CDStarRepeaterApp() +{ +} + +bool CDStarRepeaterApp::OnInit() +{ + SetVendorName(VENDOR_NAME); + + if (!wxApp::OnInit()) + return false; + + if (!m_nolog) { + wxString logBaseName = LOG_BASE_NAME; + if (!m_name.IsEmpty()) { + logBaseName.Append("_"); + logBaseName.Append(m_name); + } + +#if defined(__WINDOWS__) + if (m_logDir.IsEmpty()) + m_logDir = ::wxGetHomeDir(); +#else + if (m_logDir.IsEmpty()) + m_logDir = LOG_DIR; +#endif + + try { + CLogger* log = new CLogger(m_logDir, logBaseName); + wxLog::SetActiveTarget(log); + } catch ( const std::runtime_error& e ) { + wxLog::SetActiveTarget(new wxLogStderr()); + wxLogError("Could not open log file, logging to stderr"); + } + } else { + new wxLogNull; + } + + m_logChain = new wxLogChain(new CDStarRepeaterLogger); + + wxString appName; + if (!m_name.IsEmpty()) + appName = APPLICATION_NAME + " " + m_name; + else + appName = APPLICATION_NAME; + +#if !defined(__WINDOWS__) + appName.Replace(" ", "_"); + m_checker = new wxSingleInstanceChecker(appName, "/tmp"); +#else + m_checker = new wxSingleInstanceChecker(appName); +#endif + + bool ret = m_checker->IsAnotherRunning(); + if (ret) { + wxLogError("Another copy of the D-Star Repeater is running, exiting"); + return false; + } + +#if defined(__WINDOWS__) + if (m_confDir.IsEmpty()) + m_confDir = ::wxGetHomeDir(); + + m_config = new CDStarRepeaterConfig(new wxConfig(APPLICATION_NAME), m_confDir, CONFIG_FILE_NAME, m_name); +#else + if (m_confDir.IsEmpty()) + m_confDir = CONF_DIR; + + try { + m_config = new CDStarRepeaterConfig(m_confDir, CONFIG_FILE_NAME, m_name, true); + } catch( std::runtime_error& e ) { + wxLogError("Could not open configuration file"); + return false; + } +#endif + + wxString type; + m_config->getModem(type); + +#if (wxUSE_GUI == 1) + wxString frameName = APPLICATION_NAME + " (" + type + ") - "; + if (!m_name.IsEmpty()) { + frameName.Append(m_name); + frameName.Append(" - "); + } + frameName.Append(VERSION); + + wxPoint position = wxDefaultPosition; + + int x, y; + m_config->getPosition(x, y); + if (x >= 0 && y >= 0) + position = wxPoint(x, y); + + m_frame = new CDStarRepeaterFrame(frameName, type, position, m_gui); + m_frame->Show(); + + SetTopWindow(m_frame); +#endif + + wxLogInfo("Starting " + APPLICATION_NAME + " - " + VERSION); + + // Log the version of wxWidgets and the Operating System + wxLogInfo("Using wxWidgets %d.%d.%d on %s", wxMAJOR_VERSION, wxMINOR_VERSION, wxRELEASE_NUMBER, ::wxGetOsDescription().c_str()); + + createThread(); + + return true; +} + +int CDStarRepeaterApp::OnExit() +{ + wxLogInfo(APPLICATION_NAME + " is exiting"); + + m_logChain->SetLog(NULL); + + m_thread->kill(); + + delete m_config; + + delete m_checker; + + return 0; +} + +void CDStarRepeaterApp::OnInitCmdLine(wxCmdLineParser& parser) +{ + parser.AddSwitch(NOLOGGING_SWITCH, wxEmptyString, wxEmptyString, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddSwitch(GUI_SWITCH, wxEmptyString, wxEmptyString, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddOption(LOGDIR_OPTION, wxEmptyString, wxEmptyString, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddOption(CONFDIR_OPTION, wxEmptyString, wxEmptyString, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddOption(AUDIODIR_OPTION, wxEmptyString, wxEmptyString, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddParam(NAME_PARAM, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + + wxApp::OnInitCmdLine(parser); +} + +bool CDStarRepeaterApp::OnCmdLineParsed(wxCmdLineParser& parser) +{ + if (!wxApp::OnCmdLineParsed(parser)) + return false; + + m_nolog = parser.Found(NOLOGGING_SWITCH); + m_gui = parser.Found(GUI_SWITCH); + + wxString logDir; + bool found = parser.Found(LOGDIR_OPTION, &logDir); + if (found) + m_logDir = logDir; + + wxString confDir; + found = parser.Found(CONFDIR_OPTION, &confDir); + if (found) + m_confDir = confDir; + + wxString audioDir; + found = parser.Found(AUDIODIR_OPTION, &audioDir); + if (found) + m_audioDir = audioDir; + else +// XXX Homedir here isn't appropriate. I don't know if logDir is either. +// XXX Need to look at more code +#if defined(__WINDOWS__) + m_audioDir = ::wxGetHomeDir(); +#else + m_audioDir = logDir; +#endif + + if (parser.GetParamCount() > 0U) + m_name = parser.GetParam(0U); + + return true; +} + +#if defined(__WXDEBUG__) +void CDStarRepeaterApp::OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg) +{ + wxLogFatalError("Assertion failed on line %d in file %s and function %s: %s %s", line, file, func, cond, msg); +} +#endif + +CDStarRepeaterStatusData* CDStarRepeaterApp::getStatus() const +{ + return m_thread->getStatus(); +} + +void CDStarRepeaterApp::showLog(const wxString& text) +{ +#if (wxUSE_GUI == 1) + if(m_frame) + m_frame->showLog(text); +#endif +} + +void CDStarRepeaterApp::setOutputs(bool out1, bool out2, bool out3, bool out4) +{ + wxLogInfo("Output 1 = %d, output 2 = %d, output 3 = %d, output 4 = %d", int(out1), int(out2), int(out3), int(out4)); + + m_thread->setOutputs(out1, out2, out3, out4); +} + +void CDStarRepeaterApp::setLogging(bool logging) +{ + wxLogInfo("Frame logging set to %d, in %s", int(logging), m_audioDir.c_str()); + + m_thread->setLogging(logging, m_audioDir); +} + +void CDStarRepeaterApp::setPosition(int x, int y) +{ + m_config->setPosition(x, y); + m_config->write(); +} + +void CDStarRepeaterApp::OnRemoteCmd(wxThreadEvent& event) +{ + wxLogMessage("Request to execute command %s (%d)", + m_commandLine[event.GetInt()], event.GetInt()); + // XXX sanity check the command line here. + wxShell(m_commandLine[event.GetInt()]); +} + +void CDStarRepeaterApp::startup() +{ + m_thread->startup(); +} + +void CDStarRepeaterApp::shutdown() +{ + m_thread->shutdown(); +} + +void CDStarRepeaterApp::createThread() +{ + wxASSERT(m_config != NULL); + + wxString callsign, gateway; + DSTAR_MODE mode; + ACK_TYPE ack; + bool restriction, rpt1Validation, dtmfBlanking, errorReply; + m_config->getCallsign(callsign, gateway, mode, ack, restriction, rpt1Validation, dtmfBlanking, errorReply); + + wxString modemType; + m_config->getModem(modemType); + + // DVAP can only do simplex, force the mode accordingly + if (modemType.IsSameAs("DVAP")) { + if (mode == MODE_DUPLEX) { + wxLogInfo("DVAP: changing mode from DUPLEX to SIMPLEX"); + mode = MODE_SIMPLEX; + } else if (mode == MODE_TXANDRX) { + wxLogInfo("DVAP: changing mode from TX_AND_RX to RX_ONLY"); + mode = MODE_RXONLY; + } + } + + // XXX This should be m_thread eventually. + switch (mode) { + case MODE_RXONLY: + m_thread = new CDStarRepeaterRXThread(modemType); + break; + case MODE_TXONLY: + m_thread = new CDStarRepeaterTXThread(modemType); + break; + case MODE_TXANDRX: + m_thread = new CDStarRepeaterTXRXThread(modemType); + break; + default: + m_thread = new CDStarRepeaterTRXThread(modemType); + break; + } + + m_thread->setCallsign(callsign, gateway, mode, ack, restriction, rpt1Validation, dtmfBlanking, errorReply); + wxLogInfo("Callsign set to \"%s\", gateway set to \"%s\", mode: %d, ack: %d, restriction: %d, RPT1 validation: %d, DTMF blanking: %d, Error reply: %d", callsign.c_str(), gateway.c_str(), int(mode), int(ack), int(restriction), int(rpt1Validation), int(dtmfBlanking), int(errorReply)); + + wxString gatewayAddress, localAddress, name; + unsigned int gatewayPort, localPort; + m_config->getNetwork(gatewayAddress, gatewayPort, localAddress, localPort, name); + wxLogInfo("Gateway set to %s:%u, local set to %s:%u, name set to \"%s\"", gatewayAddress.c_str(), gatewayPort, localAddress.c_str(), localPort, name.c_str()); + + if (!gatewayAddress.IsEmpty()) { + bool local = gatewayAddress.IsSameAs("127.0.0.1"); + + CRepeaterProtocolHandler* handler = new CRepeaterProtocolHandler(gatewayAddress, gatewayPort, localAddress, localPort, name); + + bool res = handler->open(); + if (!res) + wxLogError("Cannot open the protocol handler"); + else + m_thread->setProtocolHandler(handler, local); + } + + unsigned int timeout, ackTime; + m_config->getTimes(timeout, ackTime); + m_thread->setTimes(timeout, ackTime); + wxLogInfo("Timeout set to %u secs, ack time set to %u ms", timeout, ackTime); + + unsigned int beaconTime; + wxString beaconText; + bool beaconVoice; + TEXT_LANG language; + m_config->getBeacon(beaconTime, beaconText, beaconVoice, language); + if (mode == MODE_GATEWAY) + beaconTime = 0U; + m_thread->setBeacon(beaconTime, beaconText, beaconVoice, language); + wxLogInfo("Beacon set to %u mins, text set to \"%s\", voice set to %d, language set to %d", beaconTime / 60U, beaconText.c_str(), int(beaconVoice), int(language)); + + bool announcementEnabled; + unsigned int announcementTime; + wxString announcementRecordRPT1, announcementRecordRPT2; + wxString announcementDeleteRPT1, announcementDeleteRPT2; + m_config->getAnnouncement(announcementEnabled, announcementTime, announcementRecordRPT1, announcementRecordRPT2, announcementDeleteRPT1, announcementDeleteRPT2); + if (mode == MODE_GATEWAY) + announcementEnabled = false; + m_thread->setAnnouncement(announcementEnabled, announcementTime, announcementRecordRPT1, announcementRecordRPT2, announcementDeleteRPT1, announcementDeleteRPT2); + wxLogInfo("Announcement enabled: %d, time: %u mins, record RPT1: \"%s\", record RPT2: \"%s\", delete RPT1: \"%s\", delete RPT2: \"%s\"", int(announcementEnabled), announcementTime / 60U, announcementRecordRPT1.c_str(), announcementRecordRPT2.c_str(), announcementDeleteRPT1.c_str(), announcementDeleteRPT2.c_str()); + + wxLogInfo("Modem type set to \"%s\"", modemType.c_str()); + + CModem* modem = NULL; + if (modemType.IsSameAs("DVAP")) { + wxString port; + unsigned int frequency; + int power, squelch; + m_config->getDVAP(port, frequency, power, squelch); + wxLogInfo("DVAP: port: %s, frequency: %u Hz, power: %d dBm, squelch: %d dBm", port.c_str(), frequency, power, squelch); + modem = new CDVAPController(port, frequency, power, squelch); + } else if (modemType.IsSameAs("DV-RPTR V1")) { + wxString port; + bool rxInvert, txInvert, channel; + unsigned int modLevel, txDelay; + m_config->getDVRPTR1(port, rxInvert, txInvert, channel, modLevel, txDelay); + wxLogInfo("DV-RPTR V1, port: %s, RX invert: %d, TX invert: %d, channel: %s, mod level: %u%%, TX delay: %u ms", port.c_str(), int(rxInvert), int(txInvert), channel ? "B" : "A", modLevel, txDelay); + modem = new CDVRPTRV1Controller(port, wxEmptyString, rxInvert, txInvert, channel, modLevel, txDelay); + } else if (modemType.IsSameAs("DV-RPTR V2")) { + CONNECTION_TYPE connType; + wxString usbPort, address; + bool txInvert; + unsigned int port, modLevel, txDelay; + m_config->getDVRPTR2(connType, usbPort, address, port, txInvert, modLevel, txDelay); + wxLogInfo("DV-RPTR V2, type: %d, address: %s:%u, TX invert: %d, mod level: %u%%, TX delay: %u ms", int(connType), address.c_str(), port, int(txInvert), modLevel, txDelay); + switch (connType) { + case CT_USB: + modem = new CDVRPTRV2Controller(usbPort, wxEmptyString, txInvert, modLevel, mode == MODE_DUPLEX || mode == MODE_TXANDRX, callsign, txDelay); + break; + case CT_NETWORK: + modem = new CDVRPTRV2Controller(address, port, txInvert, modLevel, mode == MODE_DUPLEX || mode == MODE_TXANDRX, callsign, txDelay); + break; + } + } else if (modemType.IsSameAs("DV-RPTR V3")) { + CONNECTION_TYPE connType; + wxString usbPort, address; + bool txInvert; + unsigned int port, modLevel, txDelay; + m_config->getDVRPTR3(connType, usbPort, address, port, txInvert, modLevel, txDelay); + wxLogInfo("DV-RPTR V3, type: %d, address: %s:%u, TX invert: %d, mod level: %u%%, TX delay: %u ms", int(connType), address.c_str(), port, int(txInvert), modLevel, txDelay); + switch (connType) { + case CT_USB: + modem = new CDVRPTRV3Controller(usbPort, wxEmptyString, txInvert, modLevel, mode == MODE_DUPLEX || mode == MODE_TXANDRX, callsign, txDelay); + break; + case CT_NETWORK: + modem = new CDVRPTRV3Controller(address, port, txInvert, modLevel, mode == MODE_DUPLEX || mode == MODE_TXANDRX, callsign, txDelay); + break; + } + } else if (modemType.IsSameAs("DVMEGA")) { + wxString port; + DVMEGA_VARIANT variant; + bool rxInvert, txInvert; + unsigned int txDelay, rxFrequency, txFrequency, power; + m_config->getDVMEGA(port, variant, rxInvert, txInvert, txDelay, rxFrequency, txFrequency, power); + wxLogInfo("DVMEGA, port: %s, variant: %d, RX invert: %d, TX invert: %d, TX delay: %u ms, rx frequency: %u Hz, tx frequency: %u Hz, power: %u %%", port.c_str(), int(variant), int(rxInvert), int(txInvert), txDelay, rxFrequency, txFrequency, power); + switch (variant) { + case DVMV_MODEM: + modem = new CDVMegaController(port, wxEmptyString, rxInvert, txInvert, txDelay); + break; + case DVMV_RADIO_2M: + case DVMV_RADIO_70CM: + case DVMV_RADIO_2M_70CM: + modem = new CDVMegaController(port, wxEmptyString, txDelay, rxFrequency, txFrequency, power); + break; + default: + wxLogError("Unknown DVMEGA variant - %d"), int(variant); + break; + } + } else if (modemType.IsSameAs("GMSK Modem")) { + USB_INTERFACE iface; + unsigned int address; + m_config->getGMSK(iface, address); + wxLogInfo("GMSK, interface: %d, address: %04X", int(iface), address); + modem = new CGMSKController(iface, address, mode == MODE_DUPLEX || mode == MODE_TXANDRX); + } else if (modemType.IsSameAs("Sound Card")) { + wxString rxDevice, txDevice; + bool rxInvert, txInvert; + wxFloat32 rxLevel, txLevel; + unsigned int txDelay, txTail; + m_config->getSoundCard(rxDevice, txDevice, rxInvert, txInvert, rxLevel, txLevel, txDelay, txTail); + wxLogInfo("Sound Card, devices: %s:%s, invert: %d:%d, levels: %.2f:%.2f, tx delay: %u ms, tx tail: %u ms", rxDevice.c_str(), txDevice.c_str(), int(rxInvert), int(txInvert), rxLevel, txLevel, txDelay, txTail); + modem = new CSoundCardController(rxDevice, txDevice, rxInvert, txInvert, rxLevel, txLevel, txDelay, txTail); + } else if (modemType.IsSameAs("MMDVM")) { + wxString port; + bool rxInvert, txInvert, pttInvert; + unsigned int txDelay, rxLevel, txLevel; + m_config->getMMDVM(port, rxInvert, txInvert, pttInvert, txDelay, rxLevel, txLevel); + wxLogInfo("MMDVM, port: %s, RX invert: %d, TX invert: %d, PTT invert: %d, TX delay: %u ms, RX level: %u%%, TX level: %u%%", port.c_str(), int(rxInvert), int(txInvert), int(pttInvert), txDelay, rxLevel, txLevel); + modem = new CMMDVMController(port, wxEmptyString, rxInvert, txInvert, pttInvert, txDelay, rxLevel, txLevel); + } else if (modemType.IsSameAs("Split")) { + wxString localAddress; + unsigned int localPort; + wxArrayString transmitterNames, receiverNames; + unsigned int timeout; + m_config->getSplit(localAddress, localPort, transmitterNames, receiverNames, timeout); + wxLogInfo("Split, local: %s:%u, timeout: %u ms", localAddress.c_str(), localPort, timeout); + for (unsigned int i = 0U; i < transmitterNames.GetCount(); i++) { + wxString name = transmitterNames.Item(i); + if (!name.IsEmpty()) + wxLogInfo("\tTX %u name: %s", i + 1U, name.c_str()); + } + for (unsigned int i = 0U; i < receiverNames.GetCount(); i++) { + wxString name = receiverNames.Item(i); + if (!name.IsEmpty()) + wxLogInfo("\tRX %u name: %s", i + 1U, name.c_str()); + } + modem = new CSplitController(localAddress, localPort, transmitterNames, receiverNames, timeout); + } else { + wxLogError("Unknown modem type: %s", modemType.c_str()); + } + + if (modem != NULL) { + bool res = modem->start(); + if (!res) + wxLogError("Cannot open the D-Star modem"); + else + m_thread->setModem(modem); + } + + wxString controllerType; + unsigned int portConfig, activeHangTime; + bool pttInvert; + m_config->getController(controllerType, portConfig, pttInvert, activeHangTime); + wxLogInfo("Controller set to %s, config: %u, PTT invert: %d, active hang time: %u ms", controllerType.c_str(), portConfig, int(pttInvert), activeHangTime); + + CExternalController* controller = NULL; + + wxString port; + if (controllerType.StartsWith("Velleman K8055 - ", &port)) { + unsigned long num; + port.ToULong(&num); + controller = new CExternalController(new CK8055Controller(num), pttInvert); + } else if (controllerType.StartsWith("URI USB - ", &port)) { + unsigned long num; + port.ToULong(&num); + controller = new CExternalController(new CURIUSBController(num, true), pttInvert); + } else if (controllerType.StartsWith("Serial - ", &port)) { + controller = new CExternalController(new CSerialLineController(port, portConfig), pttInvert); + } else if (controllerType.StartsWith("Arduino - ", &port)) { + controller = new CExternalController(new CArduinoController(port), pttInvert); +#if defined(GPIO) + } else if (controllerType.IsSameAs("GPIO")) { + controller = new CExternalController(new CGPIOController(portConfig), pttInvert); + } else if (controllerType.IsSameAs(wxT("UDRC"))) { + switch(portConfig) { + case 1: + controller = new CUDRCController(AUTO_FM); + break; + case 3: + controller = new CUDRCController(DIGITAL_DIGITAL); + break; + case 4: + controller = new CUDRCController(FM_FM); + break; + case 5: + controller = new CUDRCController(HOTSPOT); + break; + default: + case 2: + controller = new CUDRCController(AUTO_AUTO); + break; + + } +#endif + } else { + wxLogError("Unrecognized controller %s, using dummy controller", controllerType); + controller = new CExternalController(new CDummyController, pttInvert); + } + + bool res = controller->open(); + if (!res) + wxLogError("Cannot open the hardware interface - %s", controllerType.c_str()); + else + m_thread->setController(controller, activeHangTime); + + bool out1, out2, out3, out4; + m_config->getOutputs(out1, out2, out3, out4); + m_thread->setOutputs(out1, out2, out3, out4); +#if (wxUSE_GUI == 1) + m_frame->setOutputs(out1, out2, out3, out4); +#endif + wxLogInfo("Output 1 = %d, output 2 = %d, output 3 = %d, output 4 = %d", int(out1), int(out2), int(out3), int(out4)); + + bool enabled; + wxString rpt1Callsign, rpt2Callsign; + wxString shutdown, startup; + + // XXX Initialization should be temporary until we get them coming + // from m_config->getControl + wxArrayString status; + status.Add("", 5); + wxArrayString command; + command.Add("", 6); + wxArrayString output; + output.Add("", 4); + + m_config->getControl(enabled, rpt1Callsign, rpt2Callsign, shutdown, startup, status[0], status[1], status[2], status[3], status[4], command[0], m_commandLine[0], command[1], m_commandLine[1], command[2], m_commandLine[2], command[3], m_commandLine[3], command[4], m_commandLine[4], command[5], m_commandLine[5], output[0], output[1], output[2], output[3]); + + m_thread->setControl(enabled, rpt1Callsign, rpt2Callsign, shutdown, + startup, command, status, output); + + wxLogInfo(wxT("Control: enabled: %d, RPT1: %s, RPT2: %s, shutdown: %s, startup: %s, status1: %s, status2: %s, status3: %s, status4: %s, status5: %s, command1: %s = %s, command2: %s = %s, command3: %s = %s, command4: %s = %s, command5: %s = %s, command6: %s = %s, output1: %s, output2: %s, output3: %s, output4: %s"), enabled, rpt1Callsign.c_str(), rpt2Callsign.c_str(), shutdown.c_str(), startup.c_str(), status[0].c_str(), status[1].c_str(), status[2].c_str(), status[3].c_str(), status[4].c_str(), command[0].c_str(), m_commandLine[0].c_str(), command[1].c_str(), m_commandLine[1].c_str(), command[2].c_str(), m_commandLine[2].c_str(), command[3].c_str(), m_commandLine[3].c_str(), command[4].c_str(), m_commandLine[4].c_str(), command[5].c_str(), m_commandLine[5].c_str(), output[0].c_str(), output[1].c_str(), output[2].c_str(), output[3].c_str()); + + bool logging; + m_config->getLogging(logging); + m_thread->setLogging(logging, m_audioDir); +#if (wxUSE_GUI == 1) + m_frame->setLogging(logging); +#endif + wxLogInfo("Frame logging set to %d, in %s", int(logging), m_audioDir.c_str()); + +#if defined(__WINDOWS__) + wxFileName wlFilename(wxFileName::GetHomeDir(), PRIMARY_WHITELIST_FILE_NAME); +#else + wxFileName wlFilename(CONF_DIR, PRIMARY_WHITELIST_FILE_NAME); +#endif + bool exists = wlFilename.FileExists(); + + if (!exists) { +#if defined(__WINDOWS__) + wlFilename.Assign(wxFileName::GetHomeDir(), SECONDARY_WHITELIST_FILE_NAME); +#else + wlFilename.Assign(CONF_DIR, SECONDARY_WHITELIST_FILE_NAME); +#endif + exists = wlFilename.FileExists(); + } + + if (exists) { + CCallsignList* list = new CCallsignList(wlFilename.GetFullPath()); + bool res = list->load(); + if (!res) { + wxLogError("Unable to open white list file - %s", wlFilename.GetFullPath().c_str()); + delete list; + } else { + wxLogInfo("%u callsigns loaded into the white list", list->getCount()); + m_thread->setWhiteList(list); + } + } +#if defined(__WINDOWS__) + wxFileName blFilename(wxFileName::GetHomeDir(), PRIMARY_BLACKLIST_FILE_NAME); +#else + wxFileName blFilename(CONF_DIR, PRIMARY_BLACKLIST_FILE_NAME); +#endif + exists = blFilename.FileExists(); + + if (!exists) { +#if defined(__WINDOWS__) + blFilename.Assign(wxFileName::GetHomeDir(), SECONDARY_BLACKLIST_FILE_NAME); +#else + blFilename.Assign(CONF_DIR, SECONDARY_BLACKLIST_FILE_NAME); +#endif + exists = blFilename.FileExists(); + } + + if (exists) { + CCallsignList* list = new CCallsignList(blFilename.GetFullPath()); + bool res = list->load(); + if (!res) { + wxLogError("Unable to open black list file - %s", blFilename.GetFullPath().c_str()); + delete list; + } else { + wxLogInfo("%u callsigns loaded into the black list", list->getCount()); + m_thread->setBlackList(list); + } + } +#if defined(__WINDOWS__) + wxFileName glFilename(wxFileName::GetHomeDir(), GREYLIST_FILE_NAME); +#else + wxFileName glFilename(CONF_DIR, GREYLIST_FILE_NAME); +#endif + exists = glFilename.FileExists(); + if (exists) { + CCallsignList* list = new CCallsignList(glFilename.GetFullPath()); + bool res = list->load(); + if (!res) { + wxLogError("Unable to open grey list file - %s", glFilename.GetFullPath().c_str()); + delete list; + } else { + wxLogInfo("%u callsigns loaded into the grey list", list->getCount()); + m_thread->setGreyList(list); + } + } + + m_thread->Create(); + m_thread->SetPriority(wxPRIORITY_MAX); + m_thread->Run(); +} diff --git a/DStarRepeater/DStarRepeaterApp.h b/DStarRepeater/DStarRepeaterApp.h new file mode 100644 index 0000000..0e8e90e --- /dev/null +++ b/DStarRepeater/DStarRepeaterApp.h @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterApp_H +#define DStarRepeaterApp_H + +#include +#include + +#include "DStarRepeaterThread.h" +#include "DStarRepeaterStatusData.h" +#include "DStarRepeaterConfig.h" +#if (wxUSE_GUI == 1) +#include "DStarRepeaterFrame.h" +#endif +#include "DStarRepeaterDefs.h" + +wxDECLARE_EVENT(wxEVT_THREAD_COMMAND, wxThreadEvent); + +class CDStarRepeaterApp : public wxApp { +public: + CDStarRepeaterApp(); + virtual ~CDStarRepeaterApp(); + + virtual bool OnInit(); + virtual int OnExit(); + + virtual void OnInitCmdLine(wxCmdLineParser& parser); + virtual bool OnCmdLineParsed(wxCmdLineParser& parser); + + // This is overridden because dialog boxes from threads are bad news +#if defined(__WXDEBUG__) + virtual void OnAssertFailure(const wxChar* file, int line, const wxChar* func, const wxChar* cond, const wxChar* msg); +#endif + + virtual CDStarRepeaterStatusData* getStatus() const; + + virtual void showLog(const wxString& text); + + virtual void setOutputs(bool out1, bool out2, bool out3, bool out4); + + virtual void setLogging(bool logging); + + virtual void setPosition(int x, int y); + + void startup(); + void shutdown(); + +private: +#if (wxUSE_GUI == 1) + CDStarRepeaterFrame* m_frame; +#endif + + wxString m_name; + bool m_nolog; + bool m_gui; + wxString m_logDir; + wxString m_confDir; + wxString m_audioDir; + IDStarRepeaterThread* m_thread; + CDStarRepeaterConfig* m_config; + wxSingleInstanceChecker* m_checker; + wxLogChain* m_logChain; + wxString m_commandLine[6]; + + void createThread(); + + void OnRemoteCmd(wxThreadEvent& event); + + wxDECLARE_EVENT_TABLE(); +}; + +DECLARE_APP(CDStarRepeaterApp) + +#endif diff --git a/DStarRepeater/DStarRepeaterD.cpp b/DStarRepeater/DStarRepeaterD.cpp new file mode 100644 index 0000000..8c84863 --- /dev/null +++ b/DStarRepeater/DStarRepeaterD.cpp @@ -0,0 +1,565 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterTXRXThread.h" +#include "RepeaterProtocolHandler.h" +#include "DStarRepeaterTRXThread.h" +#include "DStarRepeaterRXThread.h" +#include "DStarRepeaterTXThread.h" +#include "SerialLineController.h" +#include "DStarRepeaterConfig.h" +#include "SoundCardController.h" +#include "DVRPTRV1Controller.h" +#include "DVRPTRV2Controller.h" +#include "DVRPTRV3Controller.h" +#include "ArduinoController.h" +#include "DVMegaController.h" +#include "MMDVMController.h" +#include "URIUSBController.h" +#include "K8055Controller.h" +#include "DummyController.h" +#include "SplitController.h" +#include "DStarRepeaterD.h" +#include "DVAPController.h" +#include "GMSKController.h" +#if defined(GPIO) +#include "GPIOController.h" +#endif +#include "CallsignList.h" +#include "DStarDefines.h" +#include "Version.h" +#include "Logger.h" + +#include +#include +#include +#include + +#include +#include +#include +#include + +const wxChar* NAME_PARAM = wxT("Repeater Name"); +const wxChar* NOLOGGING_SWITCH = wxT("nolog"); +const wxChar* LOGDIR_OPTION = wxT("logdir"); +const wxChar* CONFDIR_OPTION = wxT("confdir"); +const wxChar* AUDIODIR_OPTION = wxT("audiodir"); +const wxChar* DAEMON_SWITCH = wxT("daemon"); + +const wxString LOG_BASE_NAME = wxT("dstarrepeaterd"); + +static CDStarRepeaterD* m_repeater = NULL; + +static void handler(int signum) +{ + m_repeater->kill(); +} + +int main(int argc, char** argv) +{ + bool res = ::wxInitialize(); + if (!res) { + ::fprintf(stderr, "dstarrepeaterd: failed to initialise the wxWidgets library, exiting\n"); + return -1; + } + + wxCmdLineParser parser(argc, argv); + parser.AddSwitch(NOLOGGING_SWITCH, wxEmptyString, wxEmptyString, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddSwitch(DAEMON_SWITCH, wxEmptyString, wxEmptyString, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddOption(LOGDIR_OPTION, wxEmptyString, wxEmptyString, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddOption(CONFDIR_OPTION, wxEmptyString, wxEmptyString, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddOption(AUDIODIR_OPTION, wxEmptyString, wxEmptyString, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddParam(NAME_PARAM, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + + int cmd = parser.Parse(); + if (cmd != 0) { + ::wxUninitialize(); + return 0; + } + + bool nolog = parser.Found(NOLOGGING_SWITCH); + bool daemon = parser.Found(DAEMON_SWITCH); + + wxString logDir; + bool found = parser.Found(LOGDIR_OPTION, &logDir); + if (!found) + logDir.Clear(); + + wxString confDir; + found = parser.Found(CONFDIR_OPTION, &confDir); + if (!found) + confDir = wxT(CONF_DIR); + + wxString audioDir; + found = parser.Found(AUDIODIR_OPTION, &audioDir); + if (!found) + audioDir = ::wxGetHomeDir(); + + wxString name; + if (parser.GetParamCount() > 0U) + name = parser.GetParam(0U); + + if (daemon) { + pid_t pid = ::fork(); + + if (pid < 0) { + ::fprintf(stderr, "dstarrepeaterd: error in fork(), exiting\n"); + ::wxUninitialize(); + return 1; + } + + // If this is the parent, exit + if (pid > 0) + return 0; + + // We are the child from here onwards + ::setsid(); + + if(::chdir("/") == -1) + ::fprintf(stderr, "dstarrepeaterd: error changing directory: %s\n", strerror(errno)); + + ::umask(0); + } + + wxString pidFileName; + if (!name.IsEmpty()) + pidFileName.Printf(wxT("/var/run/dstarrepeater_%s.pid"), name.c_str()); + else + pidFileName = wxT("/var/run/dstarrepeater.pid"); + pidFileName.Replace(wxT(" "), wxT("_")); + + char fileName[128U]; + ::memset(fileName, 0x00U, 128U); + for (unsigned int i = 0U; i < pidFileName.Len(); i++) + fileName[i] = pidFileName.GetChar(i); + + FILE* fp = ::fopen(fileName, "wt"); + if (fp != NULL) { + ::fprintf(fp, "%u\n", ::getpid()); + ::fclose(fp); + } + + ::signal(SIGUSR1, handler); + + m_repeater = new CDStarRepeaterD(nolog, logDir, confDir, audioDir, name); + + if (!m_repeater->init()) { + ::wxUninitialize(); + return 1; + } + + m_repeater->run(); + + delete m_repeater; + + ::unlink(fileName); + + ::wxUninitialize(); + + return 0; +} + +CDStarRepeaterD::CDStarRepeaterD(bool nolog, const wxString& logDir, const wxString& confDir, const wxString& audioDir, const wxString& name) : +m_name(name), +m_nolog(nolog), +m_logDir(logDir), +m_confDir(confDir), +m_audioDir(audioDir), +m_thread(NULL), +m_checker(NULL) +{ +} + +CDStarRepeaterD::~CDStarRepeaterD() +{ +} + +bool CDStarRepeaterD::init() +{ + if (!m_nolog) { + wxString logBaseName = LOG_BASE_NAME; + if (!m_name.IsEmpty()) { + logBaseName.Append(wxT("_")); + logBaseName.Append(m_name); + } + + if (m_logDir.IsEmpty()) + m_logDir = wxT(LOG_DIR); + + wxLog* log = new CLogger(m_logDir, logBaseName); + wxLog::SetActiveTarget(log); + } else { + new wxLogNull; + } + + wxString appName; + if (!m_name.IsEmpty()) + appName = APPLICATION_NAME + wxT(" ") + m_name; + else + appName = APPLICATION_NAME; + appName.Replace(wxT(" "), wxT("_")); + + m_checker = new wxSingleInstanceChecker(appName, wxT("/tmp")); + bool ret = m_checker->IsAnotherRunning(); + if (ret) { + wxLogError(wxT("Another copy of the D-Star Repeater is running, exiting")); + return false; + } + + wxLogInfo(wxT("Starting ") + APPLICATION_NAME + wxT(" daemon - ") + VERSION); + + // Log the version of wxWidgets and the Operating System + wxLogInfo(wxT("Using wxWidgets %d.%d.%d on %s"), wxMAJOR_VERSION, wxMINOR_VERSION, wxRELEASE_NUMBER, ::wxGetOsDescription().c_str()); + + return createThread(); +} + +void CDStarRepeaterD::run() +{ + m_thread->run(); + + delete m_checker; + + wxLogInfo(APPLICATION_NAME + wxT(" is exiting")); +} + +bool CDStarRepeaterD::createThread() +{ + CDStarRepeaterConfig config(m_confDir, CONFIG_FILE_NAME, m_name); + + wxString callsign, gateway; + DSTAR_MODE mode; + ACK_TYPE ack; + bool restriction, rpt1Validation, dtmfBlanking, errorReply; + config.getCallsign(callsign, gateway, mode, ack, restriction, rpt1Validation, dtmfBlanking, errorReply); + + wxString modemType; + config.getModem(modemType); + + // DVAP can only do simplex, force the mode accordingly + if (modemType.IsSameAs(wxT("DVAP"))) { + if (mode == MODE_DUPLEX) { + wxLogInfo(wxT("DVAP: changing mode from DUPLEX to SIMPLEX")); + mode = MODE_SIMPLEX; + } else if (mode == MODE_TXANDRX) { + wxLogInfo(wxT("DVAP: changing mode from TX_AND_RX to RX_ONLY")); + mode = MODE_RXONLY; + } + } + + switch (mode) { + case MODE_RXONLY: + m_thread = new CDStarRepeaterRXThread(modemType); + break; + case MODE_TXONLY: + m_thread = new CDStarRepeaterTXThread(modemType); + break; + case MODE_TXANDRX: + m_thread = new CDStarRepeaterTXRXThread(modemType); + break; + default: + m_thread = new CDStarRepeaterTRXThread(modemType); + break; + } + + m_thread->setCallsign(callsign, gateway, mode, ack, restriction, rpt1Validation, dtmfBlanking, errorReply); + wxLogInfo(wxT("Callsign set to \"%s\", gateway set to \"%s\", mode: %d, ack: %d, restriction: %d, RPT1 validation: %d, DTMF blanking: %d, Error reply: %d"), callsign.c_str(), gateway.c_str(), int(mode), int(ack), int(restriction), int(rpt1Validation), int(dtmfBlanking), int(errorReply)); + + wxString gatewayAddress, localAddress, name; + unsigned int gatewayPort, localPort; + config.getNetwork(gatewayAddress, gatewayPort, localAddress, localPort, name); + wxLogInfo(wxT("Gateway set to %s:%u, local set to %s:%u, name set to \"%s\""), gatewayAddress.c_str(), gatewayPort, localAddress.c_str(), localPort, name.c_str()); + + if (!gatewayAddress.IsEmpty()) { + bool local = gatewayAddress.IsSameAs(wxT("127.0.0.1")); + + CRepeaterProtocolHandler* handler = new CRepeaterProtocolHandler(gatewayAddress, gatewayPort, localAddress, localPort, name); + + bool res = handler->open(); + if (!res) { + wxLogError(wxT("Cannot open the protocol handler")); + return false; + } + + m_thread->setProtocolHandler(handler, local); + } + + unsigned int timeout, ackTime; + config.getTimes(timeout, ackTime); + m_thread->setTimes(timeout, ackTime); + wxLogInfo(wxT("Timeout set to %u secs, ack time set to %u ms"), timeout, ackTime); + + unsigned int beaconTime; + wxString beaconText; + bool beaconVoice; + TEXT_LANG language; + config.getBeacon(beaconTime, beaconText, beaconVoice, language); + if (mode == MODE_GATEWAY) + beaconTime = 0U; + m_thread->setBeacon(beaconTime, beaconText, beaconVoice, language); + wxLogInfo(wxT("Beacon set to %u mins, text set to \"%s\", voice set to %d, language set to %d"), beaconTime / 60U, beaconText.c_str(), int(beaconVoice), int(language)); + + bool announcementEnabled; + unsigned int announcementTime; + wxString announcementRecordRPT1, announcementRecordRPT2; + wxString announcementDeleteRPT1, announcementDeleteRPT2; + config.getAnnouncement(announcementEnabled, announcementTime, announcementRecordRPT1, announcementRecordRPT2, announcementDeleteRPT1, announcementDeleteRPT2); + if (mode == MODE_GATEWAY) + announcementEnabled = false; + m_thread->setAnnouncement(announcementEnabled, announcementTime, announcementRecordRPT1, announcementRecordRPT2, announcementDeleteRPT1, announcementDeleteRPT2); + wxLogInfo(wxT("Announcement enabled: %d, time: %u mins, record RPT1: \"%s\", record RPT2: \"%s\", delete RPT1: \"%s\", delete RPT2: \"%s\""), int(announcementEnabled), announcementTime / 60U, announcementRecordRPT1.c_str(), announcementRecordRPT2.c_str(), announcementDeleteRPT1.c_str(), announcementDeleteRPT2.c_str()); + + wxLogInfo(wxT("Modem type set to \"%s\""), modemType.c_str()); + + CModem* modem = NULL; + if (modemType.IsSameAs(wxT("DVAP"))) { + wxString port; + unsigned int frequency; + int power, squelch; + config.getDVAP(port, frequency, power, squelch); + wxLogInfo(wxT("DVAP: port: %s, frequency: %u Hz, power: %d dBm, squelch: %d dBm"), port.c_str(), frequency, power, squelch); + modem = new CDVAPController(port, frequency, power, squelch); + } else if (modemType.IsSameAs(wxT("DV-RPTR V1"))) { + wxString port; + bool rxInvert, txInvert, channel; + unsigned int modLevel, txDelay; + config.getDVRPTR1(port, rxInvert, txInvert, channel, modLevel, txDelay); + wxLogInfo(wxT("DV-RPTR V1, port: %s, RX invert: %d, TX invert: %d, channel: %s, mod level: %u%%, TX delay: %u ms"), port.c_str(), int(rxInvert), int(txInvert), channel ? wxT("B") : wxT("A"), modLevel, txDelay); + modem = new CDVRPTRV1Controller(port, wxEmptyString, rxInvert, txInvert, channel, modLevel, txDelay); + } else if (modemType.IsSameAs(wxT("DV-RPTR V2"))) { + CONNECTION_TYPE connType; + wxString usbPort, address; + bool txInvert; + unsigned int port, modLevel, txDelay; + config.getDVRPTR2(connType, usbPort, address, port, txInvert, modLevel, txDelay); + wxLogInfo(wxT("DV-RPTR V2, type: %d, address: %s:%u, TX invert: %d, mod level: %u%%, TX delay: %u ms"), int(connType), address.c_str(), port, int(txInvert), modLevel, txDelay); + switch (connType) { + case CT_USB: + modem = new CDVRPTRV2Controller(usbPort, wxEmptyString, txInvert, modLevel, mode == MODE_DUPLEX || mode == MODE_TXANDRX, callsign, txDelay); + break; + case CT_NETWORK: + modem = new CDVRPTRV2Controller(address, port, txInvert, modLevel, mode == MODE_DUPLEX || mode == MODE_TXANDRX, callsign, txDelay); + break; + } + } else if (modemType.IsSameAs(wxT("DV-RPTR V3"))) { + CONNECTION_TYPE connType; + wxString usbPort, address; + bool txInvert; + unsigned int port, modLevel, txDelay; + config.getDVRPTR3(connType, usbPort, address, port, txInvert, modLevel, txDelay); + wxLogInfo(wxT("DV-RPTR V3, type: %d, address: %s:%u, TX invert: %d, mod level: %u%%, TX delay: %u ms"), int(connType), address.c_str(), port, int(txInvert), modLevel, txDelay); + switch (connType) { + case CT_USB: + modem = new CDVRPTRV3Controller(usbPort, wxEmptyString, txInvert, modLevel, mode == MODE_DUPLEX || mode == MODE_TXANDRX, callsign, txDelay); + break; + case CT_NETWORK: + modem = new CDVRPTRV3Controller(address, port, txInvert, modLevel, mode == MODE_DUPLEX || mode == MODE_TXANDRX, callsign, txDelay); + break; + } + } else if (modemType.IsSameAs(wxT("DVMEGA"))) { + wxString port; + DVMEGA_VARIANT variant; + bool rxInvert, txInvert; + unsigned int txDelay, rxFrequency, txFrequency, power; + config.getDVMEGA(port, variant, rxInvert, txInvert, txDelay, rxFrequency, txFrequency, power); + wxLogInfo(wxT("DVMEGA, port: %s, variant: %d, RX invert: %d, TX invert: %d, TX delay: %u ms, rx frequency: %u Hz, tx frequency: %u Hz, power: %u %%"), port.c_str(), int(variant), int(rxInvert), int(txInvert), txDelay, rxFrequency, txFrequency, power); + switch (variant) { + case DVMV_MODEM: + modem = new CDVMegaController(port, wxEmptyString, rxInvert, txInvert, txDelay); + break; + case DVMV_RADIO_2M: + case DVMV_RADIO_70CM: + case DVMV_RADIO_2M_70CM: + modem = new CDVMegaController(port, wxEmptyString, txDelay, rxFrequency, txFrequency, power); + break; + default: + wxLogError(wxT("Unknown DVMEGA variant - %d"), int(variant)); + break; + } + } else if (modemType.IsSameAs(wxT("GMSK Modem"))) { + USB_INTERFACE iface; + unsigned int address; + config.getGMSK(iface, address); + wxLogInfo(wxT("GMSK, interface: %d, address: %04X"), int(iface), address); + modem = new CGMSKController(iface, address, mode == MODE_DUPLEX || mode == MODE_TXANDRX); + } else if (modemType.IsSameAs(wxT("Sound Card"))) { + wxString rxDevice, txDevice; + bool rxInvert, txInvert; + wxFloat32 rxLevel, txLevel; + unsigned int txDelay, txTail; + config.getSoundCard(rxDevice, txDevice, rxInvert, txInvert, rxLevel, txLevel, txDelay, txTail); + wxLogInfo(wxT("Sound Card, devices: %s:%s, invert: %d:%d, levels: %.2f:%.2f, tx delay: %u ms, tx tail: %u ms"), rxDevice.c_str(), txDevice.c_str(), int(rxInvert), int(txInvert), rxLevel, txLevel, txDelay, txTail); + modem = new CSoundCardController(rxDevice, txDevice, rxInvert, txInvert, rxLevel, txLevel, txDelay, txTail); + } else if (modemType.IsSameAs(wxT("MMDVM"))) { + wxString port; + bool rxInvert, txInvert, pttInvert; + unsigned int txDelay, rxLevel, txLevel; + config.getMMDVM(port, rxInvert, txInvert, pttInvert, txDelay, rxLevel, txLevel); + wxLogInfo(wxT("MMDVM, port: %s, RX invert: %d, TX invert: %d, PTT invert: %d, TX delay: %u ms, RX level: %u%%, TX level: %u%%"), port.c_str(), int(rxInvert), int(txInvert), int(pttInvert), txDelay, rxLevel, txLevel); + modem = new CMMDVMController(port, wxEmptyString, rxInvert, txInvert, pttInvert, txDelay, rxLevel, txLevel); + } else if (modemType.IsSameAs(wxT("Split"))) { + wxString localAddress; + unsigned int localPort; + wxArrayString transmitterNames, receiverNames; + unsigned int timeout; + config.getSplit(localAddress, localPort, transmitterNames, receiverNames, timeout); + wxLogInfo(wxT("Split, local: %s:%u, timeout: %u ms"), localAddress.c_str(), localPort, timeout); + for (unsigned int i = 0U; i < transmitterNames.GetCount(); i++) { + wxString name = transmitterNames.Item(i); + if (!name.IsEmpty()) + wxLogInfo(wxT("\tTX %u name: %s"), i + 1U, name.c_str()); + } + for (unsigned int i = 0U; i < receiverNames.GetCount(); i++) { + wxString name = receiverNames.Item(i); + if (!name.IsEmpty()) + wxLogInfo(wxT("\tRX %u name: %s"), i + 1U, name.c_str()); + } + modem = new CSplitController(localAddress, localPort, transmitterNames, receiverNames, timeout); + } else { + wxLogError(wxT("Unknown modem type: %s"), modemType.c_str()); + } + + if (modem != NULL) { + bool res = modem->start(); + if (!res) + wxLogError(wxT("Cannot open the D-Star modem")); + else + m_thread->setModem(modem); + } + + wxString controllerType; + unsigned int portConfig, activeHangTime; + bool pttInvert; + config.getController(controllerType, portConfig, pttInvert, activeHangTime); + wxLogInfo(wxT("Controller set to %s, config: %u, PTT invert: %d, active hang time: %u ms"), controllerType.c_str(), portConfig, int(pttInvert), activeHangTime); + + CExternalController* controller = NULL; + + wxString port; + if (controllerType.StartsWith(wxT("Velleman K8055 - "), &port)) { + unsigned long num; + port.ToULong(&num); + controller = new CExternalController(new CK8055Controller(num), pttInvert); + } else if (controllerType.StartsWith(wxT("URI USB - "), &port)) { + unsigned long num; + port.ToULong(&num); + controller = new CExternalController(new CURIUSBController(num, true), pttInvert); + } else if (controllerType.StartsWith(wxT("Serial - "), &port)) { + controller = new CExternalController(new CSerialLineController(port, portConfig), pttInvert); + } else if (controllerType.StartsWith(wxT("Arduino - "), &port)) { + controller = new CExternalController(new CArduinoController(port), pttInvert); +#if defined(GPIO) + } else if (controllerType.IsSameAs(wxT("GPIO"))) { + controller = new CExternalController(new CGPIOController(portConfig), pttInvert); +#endif + } else { + controller = new CExternalController(new CDummyController, pttInvert); + } + + bool res = controller->open(); + if (!res) + wxLogError(wxT("Cannot open the hardware interface - %s"), controllerType.c_str()); + else + m_thread->setController(controller, activeHangTime); + + bool out1, out2, out3, out4; + config.getOutputs(out1, out2, out3, out4); + m_thread->setOutputs(out1, out2, out3, out4); + wxLogInfo(wxT("Output 1 = %d, output 2 = %d, output 3 = %d, output 4 = %d"), out1, out2, out3, out4); + + bool enabled; + wxString rpt1Callsign, rpt2Callsign; + wxString shutdown, startup; + wxString status1, status2, status3, status4, status5; + wxString command1, command1Line, command2, command2Line; + wxString command3, command3Line, command4, command4Line; + wxString command5, command5Line, command6, command6Line; + wxString output1, output2, output3, output4; + config.getControl(enabled, rpt1Callsign, rpt2Callsign, shutdown, startup, status1, status2, status3, status4, status5, command1, command1Line, command2, command2Line, command3, command3Line, command4, command4Line, command5, command5Line, command6, command6Line, output1, output2, output3, output4); + m_thread->setControl(enabled, rpt1Callsign, rpt2Callsign, shutdown, startup, status1, status2, status3, status4, status5, command1, command1Line, command2, command2Line, command3, command3Line, command4, command4Line, command5, command5Line, command6, command6Line, output1, output2, output3, output4); + wxLogInfo(wxT("Control: enabled: %d, RPT1: %s, RPT2: %s, shutdown: %s, startup: %s, status1: %s, status2: %s, status3: %s, status4: %s, status5: %s, command1: %s = %s, command2: %s = %s, command3: %s = %s, command4: %s = %s, command5: %s = %s, command6: %s = %s, output1: %s, output2: %s, output3: %s, output4: %s"), enabled, rpt1Callsign.c_str(), rpt2Callsign.c_str(), shutdown.c_str(), startup.c_str(), status1.c_str(), status2.c_str(), status3.c_str(), status4.c_str(), status5.c_str(), command1.c_str(), command1Line.c_str(), command2.c_str(), command2Line.c_str(), command3.c_str(), command3Line.c_str(), command4.c_str(), command4Line.c_str(), command5.c_str(), command5Line.c_str(), command6.c_str(), command6Line.c_str(), output1.c_str(), output2.c_str(), output3.c_str(), output4.c_str()); + + bool logging; + config.getLogging(logging); + m_thread->setLogging(logging, m_audioDir); + wxLogInfo(wxT("Frame logging set to %d, in %s"), int(logging), m_audioDir.c_str()); + + wxFileName wlFilename(wxFileName::GetHomeDir(), PRIMARY_WHITELIST_FILE_NAME); + bool exists = wlFilename.FileExists(); + + if (!exists) { + wlFilename.Assign(wxFileName::GetHomeDir(), SECONDARY_WHITELIST_FILE_NAME); + exists = wlFilename.FileExists(); + } + + if (exists) { + CCallsignList* list = new CCallsignList(wlFilename.GetFullPath()); + bool res = list->load(); + if (!res) { + wxLogError(wxT("Unable to open white list file - %s"), wlFilename.GetFullPath().c_str()); + delete list; + } else { + wxLogInfo(wxT("%u callsigns loaded into the white list"), list->getCount()); + m_thread->setWhiteList(list); + } + } + + wxFileName blFilename(wxFileName::GetHomeDir(), PRIMARY_BLACKLIST_FILE_NAME); + exists = blFilename.FileExists(); + + if (!exists) { + blFilename.Assign(wxFileName::GetHomeDir(), SECONDARY_BLACKLIST_FILE_NAME); + exists = blFilename.FileExists(); + } + + if (exists) { + CCallsignList* list = new CCallsignList(blFilename.GetFullPath()); + bool res = list->load(); + if (!res) { + wxLogError(wxT("Unable to open black list file - %s"), blFilename.GetFullPath().c_str()); + delete list; + } else { + wxLogInfo(wxT("%u callsigns loaded into the black list"), list->getCount()); + m_thread->setBlackList(list); + } + } + + wxFileName glFilename(wxFileName::GetHomeDir(), GREYLIST_FILE_NAME); + exists = glFilename.FileExists(); + if (exists) { + CCallsignList* list = new CCallsignList(glFilename.GetFullPath()); + bool res = list->load(); + if (!res) { + wxLogError(wxT("Unable to open grey list file - %s"), glFilename.GetFullPath().c_str()); + delete list; + } else { + wxLogInfo(wxT("%u callsigns loaded into the grey list"), list->getCount()); + m_thread->setGreyList(list); + } + } + + return true; +} + +void CDStarRepeaterD::kill() +{ + wxASSERT(m_thread != NULL); + + m_thread->kill(); +} diff --git a/DStarRepeater/DStarRepeaterD.h b/DStarRepeater/DStarRepeaterD.h new file mode 100644 index 0000000..61fef9f --- /dev/null +++ b/DStarRepeater/DStarRepeaterD.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterD_H +#define DStarRepeaterD_H + +#include "DStarRepeaterThread.h" +#include "DStarRepeaterDefs.h" + +#include +#include + +class CDStarRepeaterD { + +public: + CDStarRepeaterD(bool nolog, const wxString& logDir, const wxString& confDir, const wxString& audioDir, const wxString& name); + ~CDStarRepeaterD(); + + bool init(); + + void run(); + + void kill(); + +private: + wxString m_name; + bool m_nolog; + wxString m_logDir; + wxString m_confDir; + wxString m_audioDir; + IDStarRepeaterThread* m_thread; + wxSingleInstanceChecker* m_checker; + + bool createThread(); +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterDefs.h b/DStarRepeater/DStarRepeaterDefs.h new file mode 100644 index 0000000..0ca8e63 --- /dev/null +++ b/DStarRepeater/DStarRepeaterDefs.h @@ -0,0 +1,39 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterDefs_H +#define DStarRepeaterDefs_H + +#include + +const wxString APPLICATION_NAME = wxT("D-Star Repeater"); +const wxString CONFIG_FILE_NAME = wxT("dstarrepeater"); + +const wxString PRIMARY_WHITELIST_FILE_NAME = wxT("rptr_whitelist.dat"); +const wxString SECONDARY_WHITELIST_FILE_NAME = wxT("whitelist.dat"); +const wxString PRIMARY_BLACKLIST_FILE_NAME = wxT("rptr_blacklist.dat"); +const wxString SECONDARY_BLACKLIST_FILE_NAME = wxT("blacklist.dat"); +const wxString GREYLIST_FILE_NAME = wxT("greylist.dat"); + +enum DSTAR_RX_STATE { + DSRXS_LISTENING, + DSRXS_PROCESS_DATA, + DSRXS_PROCESS_SLOW_DATA +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterFrame.cpp b/DStarRepeater/DStarRepeaterFrame.cpp new file mode 100644 index 0000000..173a00e --- /dev/null +++ b/DStarRepeater/DStarRepeaterFrame.cpp @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterFrame.h" +#include "DStarRepeaterApp.h" +#include "DStarDefines.h" +#include "LogEvent.h" +#include "Version.h" + +#include + +DEFINE_EVENT_TYPE(LOG_EVENT) + +enum { + Menu_File_Logging = 6000, + + Menu_View_Updates, + + Menu_Action_Shutdown, + Menu_Action_Startup, + Menu_Action_Command1, + Menu_Action_Command2, + Menu_Action_Command3, + Menu_Action_Command4, + Menu_Action_Command5, + Menu_Action_Command6, + + Menu_Output_1, + Menu_Output_2, + Menu_Output_3, + Menu_Output_4 +}; + +BEGIN_EVENT_TABLE(CDStarRepeaterFrame, wxFrame) + EVT_MENU(Menu_File_Logging, CDStarRepeaterFrame::onLogging) + EVT_MENU(wxID_EXIT, CDStarRepeaterFrame::onQuit) + EVT_MENU(Menu_View_Updates, CDStarRepeaterFrame::onUpdates) + EVT_MENU(wxID_ABOUT, CDStarRepeaterFrame::onAbout) + + EVT_MENU(Menu_Action_Startup, CDStarRepeaterFrame::onStatus) + EVT_MENU(Menu_Action_Shutdown, CDStarRepeaterFrame::onStatus) + + EVT_MENU(Menu_Action_Command1, CDStarRepeaterFrame::onActions) + EVT_MENU(Menu_Action_Command2, CDStarRepeaterFrame::onActions) + EVT_MENU(Menu_Action_Command3, CDStarRepeaterFrame::onActions) + EVT_MENU(Menu_Action_Command4, CDStarRepeaterFrame::onActions) + EVT_MENU(Menu_Action_Command5, CDStarRepeaterFrame::onActions) + EVT_MENU(Menu_Action_Command6, CDStarRepeaterFrame::onActions) + + EVT_MENU(Menu_Output_1, CDStarRepeaterFrame::onOutputs) + EVT_MENU(Menu_Output_2, CDStarRepeaterFrame::onOutputs) + EVT_MENU(Menu_Output_3, CDStarRepeaterFrame::onOutputs) + EVT_MENU(Menu_Output_4, CDStarRepeaterFrame::onOutputs) + + EVT_CLOSE(CDStarRepeaterFrame::onClose) + + EVT_TIMER(-1, CDStarRepeaterFrame::onTimer) + + EVT_CUSTOM(LOG_EVENT, wxID_ANY, CDStarRepeaterFrame::onLog) +END_EVENT_TABLE() + +#if defined(__WINDOWS__) +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH = 100U; +const unsigned int LABEL_WIDTH = 70U; +const unsigned int LOGTEXT_WIDTH = 560U; +#else +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH = 150U; +const unsigned int LABEL_WIDTH = 70U; +const unsigned int LOGTEXT_WIDTH = 710U; +#endif + +CDStarRepeaterFrame::CDStarRepeaterFrame(const wxString& title, const wxString& type, const wxPoint& position, bool gui) : +wxFrame(NULL, -1, title, position), +m_logging(NULL), +m_rxState(NULL), +m_rptState(NULL), +m_tx(NULL), +m_squelch(NULL), +m_signal(NULL), +m_your(NULL), +m_my(NULL), +m_rpt1(NULL), +m_rpt2(NULL), +m_flags(NULL), +m_percent(NULL), +m_timeout(NULL), +m_beacon(NULL), +m_announce(NULL), +m_text(NULL), +m_status1(NULL), +m_status2(NULL), +m_status3(NULL), +m_status4(NULL), +m_status5(NULL), +m_logLine1(NULL), +m_logLine2(NULL), +m_logLine3(NULL), +m_timer(this), +m_outputMenu(NULL), +#if defined(__WXDEBUG__) +m_updates(true) +#else +m_updates(gui) +#endif +{ + SetMenuBar(createMenuBar()); + + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + + wxPanel* panel = new wxPanel(this); + + wxBoxSizer* panelSizer = new wxBoxSizer(wxVERTICAL); + + wxStaticBoxSizer* stat1Sizer = new wxStaticBoxSizer(new wxStaticBox(panel, -1, _("Status")), wxVERTICAL); + wxFlexGridSizer* stat2Sizer = new wxFlexGridSizer(6); + + wxStaticText* rxStateLabel = new wxStaticText(panel, -1, wxT("RX State:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + stat2Sizer->Add(rxStateLabel, 0, wxALL, BORDER_SIZE); + + m_rxState = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + stat2Sizer->Add(m_rxState, 0, wxALL, BORDER_SIZE); + + wxStaticText* rptStateLabel = new wxStaticText(panel, -1, wxT("Rpt State:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + stat2Sizer->Add(rptStateLabel, 0, wxALL, BORDER_SIZE); + + m_rptState = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + stat2Sizer->Add(m_rptState, 0, wxALL, BORDER_SIZE); + + wxStaticText* txLabel = new wxStaticText(panel, -1, wxT("TX:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + stat2Sizer->Add(txLabel, 0, wxALL, BORDER_SIZE); + + m_tx = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + stat2Sizer->Add(m_tx, 0, wxALL, BORDER_SIZE); + + stat1Sizer->Add(stat2Sizer); + panelSizer->Add(stat1Sizer, 0, wxALL, BORDER_SIZE); + + if (type.IsSameAs(wxT("DVAP"))) { + wxStaticBoxSizer* rx1Sizer = new wxStaticBoxSizer(new wxStaticBox(panel, -1, _("Receiver")), wxVERTICAL); + wxFlexGridSizer* rx2Sizer = new wxFlexGridSizer(6); + + wxStaticText* squelchLabel = new wxStaticText(panel, -1, wxT("Squelch:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + rx2Sizer->Add(squelchLabel, 0, wxALL, BORDER_SIZE); + + m_squelch = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + rx2Sizer->Add(m_squelch, 0, wxALL, BORDER_SIZE); + + wxStaticText* signalLabel = new wxStaticText(panel, -1, wxT("Signal:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + rx2Sizer->Add(signalLabel, 0, wxALL, BORDER_SIZE); + + m_signal = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + rx2Sizer->Add(m_signal, 0, wxALL, BORDER_SIZE); + + wxStaticText* dummy20Label = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(LABEL_WIDTH, -1)); + rx2Sizer->Add(dummy20Label, 0, wxALL, BORDER_SIZE); + + wxStaticText* dummy21Label = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + rx2Sizer->Add(dummy21Label, 0, wxALL, BORDER_SIZE); + + rx1Sizer->Add(rx2Sizer); + panelSizer->Add(rx1Sizer, 0, wxALL, BORDER_SIZE); + } + + wxStaticBoxSizer* info1Sizer = new wxStaticBoxSizer(new wxStaticBox(panel, -1, _("Header")), wxVERTICAL); + wxFlexGridSizer* info2Sizer = new wxFlexGridSizer(6); + + wxStaticText* yourLabel = new wxStaticText(panel, -1, wxT("UR:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + info2Sizer->Add(yourLabel, 0, wxALL, BORDER_SIZE); + + m_your = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + info2Sizer->Add(m_your, 0, wxALL, BORDER_SIZE); + + wxStaticText* rpt1Label = new wxStaticText(panel, -1, wxT("RPT1:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + info2Sizer->Add(rpt1Label, 0, wxALL, BORDER_SIZE); + + m_rpt1 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + info2Sizer->Add(m_rpt1, 0, wxALL, BORDER_SIZE); + + wxStaticText* rpt2Label = new wxStaticText(panel, -1, wxT("RPT2:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + info2Sizer->Add(rpt2Label, 0, wxALL, BORDER_SIZE); + + m_rpt2 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + info2Sizer->Add(m_rpt2, 0, wxALL, BORDER_SIZE); + + wxStaticText* myLabel = new wxStaticText(panel, -1, wxT("MY:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + info2Sizer->Add(myLabel, 0, wxALL, BORDER_SIZE); + + m_my = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + info2Sizer->Add(m_my, 0, wxALL, BORDER_SIZE); + + wxStaticText* flagsLabel = new wxStaticText(panel, -1, _("Flags:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + info2Sizer->Add(flagsLabel, 0, wxALL, BORDER_SIZE); + + m_flags = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(LABEL_WIDTH, -1)); + info2Sizer->Add(m_flags, 0, wxALL, BORDER_SIZE); + + wxStaticText* percentLabel = new wxStaticText(panel, -1, _("Loss/BER:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + info2Sizer->Add(percentLabel, 0, wxALL, BORDER_SIZE); + + m_percent = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(LABEL_WIDTH, -1)); + info2Sizer->Add(m_percent, 0, wxALL, BORDER_SIZE); + + info1Sizer->Add(info2Sizer); + panelSizer->Add(info1Sizer, 0, wxALL, BORDER_SIZE); + + wxStaticBoxSizer* timer1Sizer = new wxStaticBoxSizer(new wxStaticBox(panel, -1, _("Timers")), wxVERTICAL); + wxFlexGridSizer* timer2Sizer = new wxFlexGridSizer(6); + + wxStaticText* timeoutLabel = new wxStaticText(panel, -1, _("Timeout:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + timer2Sizer->Add(timeoutLabel, 0, wxALL, BORDER_SIZE); + + m_timeout = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + timer2Sizer->Add(m_timeout, 0, wxALL, BORDER_SIZE); + + wxStaticText* beaconLabel = new wxStaticText(panel, -1, _("Beacon:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + timer2Sizer->Add(beaconLabel, 0, wxALL, BORDER_SIZE); + + m_beacon = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + timer2Sizer->Add(m_beacon, 0, wxALL, BORDER_SIZE); + + wxStaticText* announceLabel = new wxStaticText(panel, -1, _("Announce:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + timer2Sizer->Add(announceLabel, 0, wxALL, BORDER_SIZE); + + m_announce = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + timer2Sizer->Add(m_announce, 0, wxALL, BORDER_SIZE); + + timer1Sizer->Add(timer2Sizer); + panelSizer->Add(timer1Sizer, 0, wxALL, BORDER_SIZE); + + wxStaticBoxSizer* text1Sizer = new wxStaticBoxSizer(new wxStaticBox(panel, -1, _("Gateway")), wxVERTICAL); + wxFlexGridSizer* text2Sizer = new wxFlexGridSizer(6); + + wxStaticText* textLabel = new wxStaticText(panel, -1, _("Ack Text:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + text2Sizer->Add(textLabel, 0, wxALL, BORDER_SIZE); + + m_text = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + text2Sizer->Add(m_text, 0, wxALL, BORDER_SIZE); + + wxStaticText* status1Label = new wxStaticText(panel, -1, _("Status 1:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + text2Sizer->Add(status1Label, 0, wxALL, BORDER_SIZE); + + m_status1 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + text2Sizer->Add(m_status1, 0, wxALL, BORDER_SIZE); + + wxStaticText* status2Label = new wxStaticText(panel, -1, _("Status 2:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + text2Sizer->Add(status2Label, 0, wxALL, BORDER_SIZE); + + m_status2 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + text2Sizer->Add(m_status2, 0, wxALL, BORDER_SIZE); + + wxStaticText* status3Label = new wxStaticText(panel, -1, _("Status 3:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + text2Sizer->Add(status3Label, 0, wxALL, BORDER_SIZE); + + m_status3 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + text2Sizer->Add(m_status3, 0, wxALL, BORDER_SIZE); + + wxStaticText* status4Label = new wxStaticText(panel, -1, _("Status 4:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + text2Sizer->Add(status4Label, 0, wxALL, BORDER_SIZE); + + m_status4 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + text2Sizer->Add(m_status4, 0, wxALL, BORDER_SIZE); + + wxStaticText* status5Label = new wxStaticText(panel, -1, _("Status 5:"), wxDefaultPosition, wxSize(LABEL_WIDTH, -1), wxALIGN_RIGHT); + text2Sizer->Add(status5Label, 0, wxALL, BORDER_SIZE); + + m_status5 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + text2Sizer->Add(m_status5, 0, wxALL, BORDER_SIZE); + + text1Sizer->Add(text2Sizer); + panelSizer->Add(text1Sizer, 0, wxALL, BORDER_SIZE); + + wxStaticBoxSizer* log1Sizer = new wxStaticBoxSizer(new wxStaticBox(panel, -1, _("Log")), wxVERTICAL); + wxBoxSizer* log2Sizer = new wxBoxSizer(wxVERTICAL); + + m_logLine1 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(LOGTEXT_WIDTH, -1)); + m_logLine1->Wrap(LOGTEXT_WIDTH); + log2Sizer->Add(m_logLine1, 0, wxTOP | wxLEFT | wxRIGHT, BORDER_SIZE); + + m_logLine2 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(LOGTEXT_WIDTH, -1)); + m_logLine2->Wrap(LOGTEXT_WIDTH); + log2Sizer->Add(m_logLine2, 0, wxLEFT | wxRIGHT, BORDER_SIZE); + + m_logLine3 = new wxStaticText(panel, -1, wxEmptyString, wxDefaultPosition, wxSize(LOGTEXT_WIDTH, -1)); + m_logLine3->Wrap(LOGTEXT_WIDTH); + log2Sizer->Add(m_logLine3, 0, wxLEFT | wxRIGHT | wxBOTTOM, BORDER_SIZE); + + log1Sizer->Add(log2Sizer); + panelSizer->Add(log1Sizer, 0, wxALL, BORDER_SIZE); + + panel->SetSizer(panelSizer); + panelSizer->SetSizeHints(panel); + + mainSizer->Add(panel); + + SetSizer(mainSizer); + mainSizer->SetSizeHints(this); + + m_timer.Start(1000); +} + +CDStarRepeaterFrame::~CDStarRepeaterFrame() +{ +} + +wxMenuBar* CDStarRepeaterFrame::createMenuBar() +{ + wxMenu* fileMenu = new wxMenu(); + m_logging = fileMenu->AppendCheckItem(Menu_File_Logging, _("Logging")); + fileMenu->AppendSeparator(); + fileMenu->Append(wxID_EXIT, _("Exit")); + + wxMenu* viewMenu = new wxMenu(); + viewMenu->AppendCheckItem(Menu_View_Updates, _("GUI Updates")); + viewMenu->Check(Menu_View_Updates, m_updates); + + wxMenu* actionMenu = new wxMenu(); + actionMenu->Append(Menu_Action_Shutdown, _("Shutdown")); + actionMenu->Append(Menu_Action_Startup, _("Startup")); + actionMenu->Append(Menu_Action_Command1, _("Command 1")); + actionMenu->Append(Menu_Action_Command2, _("Command 2")); + actionMenu->Append(Menu_Action_Command3, _("Command 3")); + actionMenu->Append(Menu_Action_Command4, _("Command 4")); + actionMenu->Append(Menu_Action_Command5, _("Command 5")); + actionMenu->Append(Menu_Action_Command6, _("Command 6")); + + m_outputMenu = new wxMenu(); + m_outputMenu->AppendCheckItem(Menu_Output_1, _("Output 1")); + m_outputMenu->AppendCheckItem(Menu_Output_2, _("Output 2")); + m_outputMenu->AppendCheckItem(Menu_Output_3, _("Output 3")); + m_outputMenu->AppendCheckItem(Menu_Output_4, _("Output 4")); + + wxMenu* helpMenu = new wxMenu(); + helpMenu->Append(wxID_ABOUT, _("About D-Star Repeater")); + + wxMenuBar* menuBar = new wxMenuBar(); + menuBar->Append(fileMenu, _("File")); + menuBar->Append(viewMenu, _("View")); + menuBar->Append(actionMenu, _("Action")); + menuBar->Append(m_outputMenu, _("Outputs")); + menuBar->Append(helpMenu, _("Help")); + + return menuBar; +} + +void CDStarRepeaterFrame::setLogging(bool logging) +{ + m_logging->Check(logging); +} + +void CDStarRepeaterFrame::onLogging(wxCommandEvent& event) +{ + ::wxGetApp().setLogging(event.IsChecked()); +} + +void CDStarRepeaterFrame::onQuit(wxCommandEvent&) +{ + Close(false); +} + +void CDStarRepeaterFrame::onClose(wxCloseEvent&) +{ + int x, y; + GetPosition(&x, &y); + if (x >= 0 && y >= 0) + ::wxGetApp().setPosition(x, y); + + Destroy(); +} + +void CDStarRepeaterFrame::onStatus(wxCommandEvent& event) +{ + switch (event.GetId()) { + case Menu_Action_Shutdown: + ((CDStarRepeaterApp *) wxTheApp)->shutdown(); + break; + case Menu_Action_Startup: + ((CDStarRepeaterApp *) wxTheApp)->startup(); + break; + } +} + +void CDStarRepeaterFrame::onActions(wxCommandEvent& event) +{ + wxThreadEvent evt(wxEVT_THREAD, wxEVT_THREAD_COMMAND); + + switch (event.GetId()) { + case Menu_Action_Command1: + evt.SetInt(0); + break; + case Menu_Action_Command2: + evt.SetInt(1); + break; + case Menu_Action_Command3: + evt.SetInt(2); + break; + case Menu_Action_Command4: + evt.SetInt(3); + break; + case Menu_Action_Command5: + evt.SetInt(4); + break; + case Menu_Action_Command6: + evt.SetInt(5); + break; + } + + wxTheApp->QueueEvent(evt.Clone()); +} + +void CDStarRepeaterFrame::onOutputs(wxCommandEvent&) +{ + bool out1 = m_outputMenu->IsChecked(Menu_Output_1); + bool out2 = m_outputMenu->IsChecked(Menu_Output_2); + bool out3 = m_outputMenu->IsChecked(Menu_Output_3); + bool out4 = m_outputMenu->IsChecked(Menu_Output_4); + + ::wxGetApp().setOutputs(out1, out2, out3, out4); +} + +void CDStarRepeaterFrame::onUpdates(wxCommandEvent& event) +{ + m_updates = event.IsChecked(); +} + +void CDStarRepeaterFrame::onAbout(wxCommandEvent&) +{ + wxAboutDialogInfo info; + info.AddDeveloper(wxT("Jonathan Naylor, G4KLX")); + info.SetCopyright(wxT("(C) 2013-2015 using GPL v2 or later")); + info.SetName(APPLICATION_NAME); + info.SetVersion(VERSION); + info.SetDescription(_("This program allows a computer with a suitable\nmodem to act as a D-Star repeater.")); + + ::wxAboutBox(info); +} + +void CDStarRepeaterFrame::onTimer(wxTimerEvent&) +{ + if (!m_updates) + return; + + CDStarRepeaterStatusData* status = ::wxGetApp().getStatus(); + if (status == NULL) + return; + + switch (status->getRxState()) { + case DSRXS_LISTENING: + m_rxState->SetLabel(_("Listening")); + break; + case DSRXS_PROCESS_DATA: + m_rxState->SetLabel(_("Process Data")); + break; + case DSRXS_PROCESS_SLOW_DATA: + m_rxState->SetLabel(_("Slow Data")); + break; + } + + switch (status->getRptState()) { + case DSRS_SHUTDOWN: + m_rptState->SetLabel(_("Shutdown")); + break; + case DSRS_LISTENING: + m_rptState->SetLabel(_("Listening")); + break; + case DSRS_VALID: + m_rptState->SetLabel(_("Valid")); + break; + case DSRS_VALID_WAIT: + case DSRS_INVALID_WAIT: + case DSRS_TIMEOUT_WAIT: + m_rptState->SetLabel(_("Wait")); + break; + case DSRS_INVALID: + m_rptState->SetLabel(_("Invalid")); + break; + case DSRS_TIMEOUT: + m_rptState->SetLabel(_("Timeout")); + break; + case DSRS_NETWORK: + m_rptState->SetLabel(_("Network")); + break; + } + + m_your->SetLabel(status->getYourCall()); + + wxString myCall = status->getMyCall1(); + wxString myCall2 = status->getMyCall2(); + if (!myCall2.IsEmpty() && !myCall2.IsSameAs(wxT(" "))) { + myCall.Append(wxT("/")); + myCall.Append(myCall2); + } + m_my->SetLabel(myCall); + + m_rpt1->SetLabel(status->getRptCall1()); + + m_rpt2->SetLabel(status->getRptCall2()); + + wxString text; + + text.Printf(wxT("%02X %02X %02X"), status->getFlag1(), status->getFlag2(), status->getFlag3()); + m_flags->SetLabel(text); + + text.Printf(wxT("%.1f%%"), status->getPercent()); + m_percent->SetLabel(text); + + bool tx = status->getTX(); + m_tx->SetLabel(tx ? _("On") : _("Off")); + + text.Printf(wxT("%u/%u"), status->getTimeoutTimer(), status->getTimeoutExpiry()); + m_timeout->SetLabel(text); + + text.Printf(wxT("%u/%u"), status->getBeaconTimer(), status->getBeaconExpiry()); + m_beacon->SetLabel(text); + + text.Printf(wxT("%u/%u"), status->getAnnounceTimer(), status->getAnnounceExpiry()); + m_announce->SetLabel(text); + + m_text->SetLabel(status->getText()); + m_status1->SetLabel(status->getStatus1()); + m_status2->SetLabel(status->getStatus2()); + m_status3->SetLabel(status->getStatus3()); + m_status4->SetLabel(status->getStatus4()); + m_status5->SetLabel(status->getStatus5()); + + // DVAP + if (m_squelch != NULL) { + if (tx) { + m_squelch->SetLabel(wxEmptyString); + m_signal->SetLabel(wxEmptyString); + } else { + bool squelch = status->getSquelch(); + m_squelch->SetLabel(squelch ? _("Open") : _("Closed")); + + text.Printf(wxT("%d dBm"), status->getSignal()); + m_signal->SetLabel(text); + } + } + + delete status; +} + +void CDStarRepeaterFrame::onLog(wxEvent& event) +{ + CLogEvent& logEvent = dynamic_cast(event); + + wxString text = logEvent.getText(); + + m_logLine1->SetLabel(m_logLine2->GetLabel()); + m_logLine2->SetLabel(m_logLine3->GetLabel()); + m_logLine3->SetLabel(text); +} + +void CDStarRepeaterFrame::showLog(const wxString& text) +{ + if (!m_updates) + return; + + CLogEvent event(text, LOG_EVENT); + + AddPendingEvent(event); +} + +void CDStarRepeaterFrame::setOutputs(bool out1, bool out2, bool out3, bool out4) +{ + m_outputMenu->Check(Menu_Output_1, out1); + m_outputMenu->Check(Menu_Output_2, out2); + m_outputMenu->Check(Menu_Output_3, out3); + m_outputMenu->Check(Menu_Output_4, out4); +} diff --git a/DStarRepeater/DStarRepeaterFrame.h b/DStarRepeater/DStarRepeaterFrame.h new file mode 100644 index 0000000..1cb25ca --- /dev/null +++ b/DStarRepeater/DStarRepeaterFrame.h @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterFrame_H +#define DStarRepeaterFrame_H + +#include "DStarRepeaterDefs.h" + +#include + +class CDStarRepeaterFrame : public wxFrame { +public: + CDStarRepeaterFrame(const wxString& title, const wxString& type, const wxPoint& position, bool gui); + virtual ~CDStarRepeaterFrame(); + + virtual void setLogging(bool logging); + + virtual void onLogging(wxCommandEvent& event); + virtual void onQuit(wxCommandEvent& event); + virtual void onUpdates(wxCommandEvent& event); + virtual void onAbout(wxCommandEvent& event); + virtual void onClose(wxCloseEvent& event); + virtual void onStatus(wxCommandEvent& event); + virtual void onActions(wxCommandEvent& event); + virtual void onOutputs(wxCommandEvent& event); + virtual void onLog(wxEvent& event); + + virtual void onTimer(wxTimerEvent& event); + + virtual void showLog(const wxString& text); + + virtual void setOutputs(bool out1, bool out2, bool out3, bool out4); + +private: + wxMenuItem* m_logging; + wxStaticText* m_rxState; + wxStaticText* m_rptState; + wxStaticText* m_tx; + wxStaticText* m_squelch; + wxStaticText* m_signal; + wxStaticText* m_your; + wxStaticText* m_my; + wxStaticText* m_rpt1; + wxStaticText* m_rpt2; + wxStaticText* m_flags; + wxStaticText* m_percent; + wxStaticText* m_timeout; + wxStaticText* m_beacon; + wxStaticText* m_announce; + wxStaticText* m_text; + wxStaticText* m_status1; + wxStaticText* m_status2; + wxStaticText* m_status3; + wxStaticText* m_status4; + wxStaticText* m_status5; + wxStaticText* m_logLine1; + wxStaticText* m_logLine2; + wxStaticText* m_logLine3; + wxTimer m_timer; + wxMenu* m_outputMenu; + bool m_updates; + + DECLARE_EVENT_TABLE() + + wxMenuBar* createMenuBar(); +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterLogger.cpp b/DStarRepeater/DStarRepeaterLogger.cpp new file mode 100644 index 0000000..5536e13 --- /dev/null +++ b/DStarRepeater/DStarRepeaterLogger.cpp @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2002,2003,2009-2012 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterLogger.h" +#include "DStarRepeaterApp.h" + +CDStarRepeaterLogger::CDStarRepeaterLogger() : +wxLog() +{ +} + +CDStarRepeaterLogger::~CDStarRepeaterLogger() +{ +} + +void CDStarRepeaterLogger::DoLog(wxLogLevel level, const wxChar* msg, time_t timestamp) +{ + wxASSERT(msg != NULL); + + wxString letter; + + switch (level) { + case wxLOG_FatalError: letter = wxT("F"); break; + case wxLOG_Error: letter = wxT("E"); break; + case wxLOG_Warning: letter = wxT("W"); break; + case wxLOG_Info: letter = wxT("I"); break; + case wxLOG_Message: letter = wxT("M"); break; + case wxLOG_Status: letter = wxT("M"); break; + case wxLOG_Trace: letter = wxT("T"); break; + case wxLOG_Debug: letter = wxT("D"); break; + default: letter = wxT("U"); break; + } + + struct tm* tm = ::gmtime(×tamp); + + wxString message; + message.Printf(wxT("%s: %04d-%02d-%02d %02d:%02d:%02d: %s\n"), letter.c_str(), tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min, tm->tm_sec, msg); + + DoLogString(message.c_str(), timestamp); + + if (level == wxLOG_FatalError) + ::abort(); +} + +void CDStarRepeaterLogger::DoLogString(const wxChar* msg, time_t timestamp) +{ + wxASSERT(msg != NULL); + + ::wxGetApp().showLog(msg); +} diff --git a/DStarRepeater/DStarRepeaterLogger.h b/DStarRepeater/DStarRepeaterLogger.h new file mode 100644 index 0000000..c9ec940 --- /dev/null +++ b/DStarRepeater/DStarRepeaterLogger.h @@ -0,0 +1,35 @@ +/* + * Copyright (C) 2002,2003,2009-2011 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterLogger_H +#define DStarRepeaterLogger_H + +#include + +class CDStarRepeaterLogger : public wxLog { +public: + CDStarRepeaterLogger(); + virtual ~CDStarRepeaterLogger(); + + virtual void DoLog(wxLogLevel level, const wxChar* msg, time_t timestamp); + virtual void DoLogString(const wxChar* msg, time_t timestamp); + +private: +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterRXThread.cpp b/DStarRepeater/DStarRepeaterRXThread.cpp new file mode 100644 index 0000000..28cf810 --- /dev/null +++ b/DStarRepeater/DStarRepeaterRXThread.cpp @@ -0,0 +1,503 @@ +/* + * Copyright (C) 2011-2016 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterStatusData.h" +#include "DStarRepeaterRXThread.h" +#include "DVAPController.h" +#include "DStarDefines.h" +#include "HeaderData.h" +#include "Version.h" +#include "Utils.h" + +const unsigned char DTMF_MASK[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x82U, 0x00U, 0x00U}; +const unsigned char DTMF_SIG[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; + +const unsigned int MAX_DATA_SYNC_BIT_ERRS = 2U; + +const unsigned int NETWORK_QUEUE_COUNT = 2U; + +const unsigned int CYCLE_TIME = 9U; + +CDStarRepeaterRXThread::CDStarRepeaterRXThread(const wxString& type) : +m_type(type), +m_modem(NULL), +m_protocolHandler(NULL), +m_rxHeader(NULL), +m_radioSeqNo(0U), +m_registerTimer(1000U), +m_rptState(DSRS_LISTENING), +m_rxState(DSRXS_LISTENING), +m_slowDataDecoder(), +m_killed(false), +m_ambe(), +m_ambeFrames(0U), +m_ambeSilence(0U), +m_ambeBits(1U), +m_ambeErrors(0U), +m_lastAMBEBits(0U), +m_lastAMBEErrors(0U) +{ + setRadioState(DSRXS_LISTENING); +} + +CDStarRepeaterRXThread::~CDStarRepeaterRXThread() +{ + delete m_rxHeader; +} + +void *CDStarRepeaterRXThread::Entry() +{ + // Wait here until we have the essentials to run + while (!m_killed && (m_modem == NULL || m_protocolHandler == NULL)) + ::wxMilliSleep(500UL); // 1/2 sec + + if (m_killed) + return NULL; + + m_registerTimer.start(10U); + + wxString hardware = m_type; + int n = hardware.Find(wxT(' ')); + if (n != wxNOT_FOUND) + hardware = m_type.Left(n); + + wxLogMessage(wxT("Starting the D-Star receiver thread")); + + wxStopWatch stopWatch; + + try { + while (!m_killed) { + stopWatch.Start(); + + receiveModem(); + + receiveNetwork(); + + // Send the register packet if needed and restart the timer + if (m_registerTimer.hasExpired()) { + m_protocolHandler->writeRegister(); + m_registerTimer.start(30U); + } + + unsigned long ms = stopWatch.Time(); + if (ms < CYCLE_TIME) { + ::wxMilliSleep(CYCLE_TIME - ms); + clock(CYCLE_TIME); + } else { + clock(ms); + } + } + } + catch (std::exception& e) { + wxString message(e.what(), wxConvLocal); + wxLogError(wxT("Exception raised - \"%s\""), message.c_str()); + } + catch (...) { + wxLogError(wxT("Unknown exception raised")); + } + + wxLogMessage(wxT("Stopping the D-Star receiver thread")); + + m_modem->stop(); + + m_protocolHandler->close(); + delete m_protocolHandler; + + return NULL; +} + +void CDStarRepeaterRXThread::kill() +{ + m_killed = true; +} + +void CDStarRepeaterRXThread::setCallsign(const wxString&, const wxString&, DSTAR_MODE, ACK_TYPE, bool, bool, bool, bool) +{ +} + +void CDStarRepeaterRXThread::setProtocolHandler(CRepeaterProtocolHandler* handler, bool local) +{ + wxASSERT(handler != NULL); + + m_protocolHandler = handler; +} + +void CDStarRepeaterRXThread::setModem(CModem* modem) +{ + wxASSERT(modem != NULL); + + m_modem = modem; +} + +void CDStarRepeaterRXThread::setTimes(unsigned int, unsigned int) +{ +} + +void CDStarRepeaterRXThread::setBeacon(unsigned int, const wxString&, bool, TEXT_LANG) +{ +} + +void CDStarRepeaterRXThread::setAnnouncement(bool, unsigned int, const wxString&, const wxString&, const wxString&, const wxString&) +{ +} + +void CDStarRepeaterRXThread::setController(CExternalController*, unsigned int) +{ +} + +void CDStarRepeaterRXThread::setOutputs(bool, bool, bool, bool) +{ +} + +void CDStarRepeaterRXThread::setLogging(bool, const wxString&) +{ +} + +void CDStarRepeaterRXThread::setWhiteList(CCallsignList*) +{ +} + +void CDStarRepeaterRXThread::setBlackList(CCallsignList*) +{ +} + +void CDStarRepeaterRXThread::setGreyList(CCallsignList*) +{ +} + +void CDStarRepeaterRXThread::receiveModem() +{ + for (;;) { + DSMT_TYPE type = m_modem->read(); + if (type == DSMTT_NONE) + return; + + switch (m_rxState) { + case DSRXS_LISTENING: + if (type == DSMTT_HEADER) { + CHeaderData* header = m_modem->readHeader(); + receiveHeader(header); + } else if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + setRadioState(DSRXS_PROCESS_SLOW_DATA); + receiveSlowData(data, length); + } + break; + + case DSRXS_PROCESS_SLOW_DATA: + if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + receiveSlowData(data, length); + } else if (type == DSMTT_EOT || type == DSMTT_LOST) { + setRadioState(DSRXS_LISTENING); + } + break; + + case DSRXS_PROCESS_DATA: + if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + receiveRadioData(data, length); + } else if (type == DSMTT_EOT || type == DSMTT_LOST) { + unsigned char data[20U]; + ::memcpy(data, END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + processRadioFrame(data, FRAME_END); + setRadioState(DSRXS_LISTENING); + endOfRadioData(); + } + break; + } + } +} + +void CDStarRepeaterRXThread::receiveHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + wxLogMessage(wxT("Radio header decoded - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + bool res = processRadioHeader(header); + if (res) { + // A valid header and is a DV packet + m_radioSeqNo = 20U; + setRadioState(DSRXS_PROCESS_DATA); + } else { + // This is a DD packet or some other problem + // wxLogMessage(wxT("Invalid header")); + } +} + +void CDStarRepeaterRXThread::receiveSlowData(unsigned char* data, unsigned int) +{ + unsigned int errs; + errs = countBits(data[VOICE_FRAME_LENGTH_BYTES + 0U] ^ DATA_SYNC_BYTES[0U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 1U] ^ DATA_SYNC_BYTES[1U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 2U] ^ DATA_SYNC_BYTES[2U]); + + // The data sync has been seen, a fuzzy match is used, two bit errors or less + if (errs <= MAX_DATA_SYNC_BIT_ERRS) { + // wxLogMessage(wxT("Found data sync at frame %u, errs: %u"), m_radioSeqNo, errs); + m_radioSeqNo = 0U; + m_slowDataDecoder.sync(); + } else if (m_radioSeqNo == 20U) { + // wxLogMessage(wxT("Assuming data sync")); + m_radioSeqNo = 0U; + m_slowDataDecoder.sync(); + } else { + m_radioSeqNo++; + m_slowDataDecoder.addData(data + VOICE_FRAME_LENGTH_BYTES); + + CHeaderData* header = m_slowDataDecoder.getHeaderData(); + if (header == NULL) + return; + + wxLogMessage(wxT("Radio header from slow data - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X BER: 0%%"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + if (header != NULL) { + bool res = processRadioHeader(header); + if (res) { + // A valid header and is a DV packet, go to normal data relaying + setRadioState(DSRXS_PROCESS_DATA); + } else { + // This is a DD packet or some other problem + // wxLogMessage(wxT("Invalid header")); + } + } + } +} + +void CDStarRepeaterRXThread::receiveRadioData(unsigned char* data, unsigned int) +{ + unsigned int errs; + errs = countBits(data[VOICE_FRAME_LENGTH_BYTES + 0U] ^ DATA_SYNC_BYTES[0U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 1U] ^ DATA_SYNC_BYTES[1U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 2U] ^ DATA_SYNC_BYTES[2U]); + + // The data sync has been seen, a fuzzy match is used, two bit errors or less + if (errs <= MAX_DATA_SYNC_BIT_ERRS) { + // wxLogMessage(wxT("Found data sync at frame %u, errs: %u"), m_radioSeqNo, errs); + m_radioSeqNo = 0U; + processRadioFrame(data, FRAME_SYNC); + } else if (m_radioSeqNo == 20U) { + // wxLogMessage(wxT("Regenerating data sync")); + m_radioSeqNo = 0U; + processRadioFrame(data, FRAME_SYNC); + } else { + m_radioSeqNo++; + processRadioFrame(data, FRAME_NORMAL); + } +} + +void CDStarRepeaterRXThread::receiveNetwork() +{ + NETWORK_TYPE type; + + do { + type = m_protocolHandler->read(); + } while (type != NETWORK_NONE); +} + +void CDStarRepeaterRXThread::setRadioState(DSTAR_RX_STATE state) +{ + // This is the too state + switch (state) { + case DSRXS_LISTENING: + m_rxState = DSRXS_LISTENING; + break; + + case DSRXS_PROCESS_DATA: + m_ambeFrames = 0U; + m_ambeSilence = 0U; + m_ambeBits = 1U; + m_ambeErrors = 0U; + m_lastAMBEBits = 0U; + m_lastAMBEErrors = 0U; + m_rxState = DSRXS_PROCESS_DATA; + break; + + case DSRXS_PROCESS_SLOW_DATA: + m_radioSeqNo = 0U; + m_slowDataDecoder.reset(); + m_rxState = DSRXS_PROCESS_SLOW_DATA; + break; + } +} + +bool CDStarRepeaterRXThread::processRadioHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + // We don't handle DD data packets + if (header->isDataPacket()) { + wxLogMessage(wxT("Received a DD packet, ignoring")); + delete header; + return false; + } + + m_rptState = DSRS_VALID; + + // Send the valid header to the gateway if we are accepted + delete m_rxHeader; + m_rxHeader = header; + + CHeaderData netHeader(*m_rxHeader); + netHeader.setRptCall1(m_rxHeader->getRptCall2()); + netHeader.setRptCall2(m_rxHeader->getRptCall1()); + + m_protocolHandler->writeHeader(netHeader); + + return true; +} + +void CDStarRepeaterRXThread::processRadioFrame(unsigned char* data, FRAME_TYPE type) +{ + m_ambeFrames++; + + // If a sync frame, regenerate the sync bytes + if (type == FRAME_SYNC) + ::memcpy(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + // Only regenerate the AMBE on received radio data + unsigned int errors = 0U; + if (type != FRAME_END) { + // Data packets have no AMBE FEC + if (!m_rxHeader->isDataPacket()) + errors = m_ambe.count(data); + + m_ambeErrors += errors; + m_ambeBits += 48U; // Only count the bits with FEC added + } + + if (::memcmp(data, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES) == 0) + m_ambeSilence++; + + if (type == FRAME_END) { + // Send null data and the end marker over the network, and the statistics + unsigned char bytes[DV_FRAME_MAX_LENGTH_BYTES]; + ::memcpy(bytes, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(bytes + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES); + m_protocolHandler->writeData(bytes, DV_FRAME_MAX_LENGTH_BYTES, 0U, true); + } else { + // Send the data to the network + m_protocolHandler->writeData(data, DV_FRAME_LENGTH_BYTES, errors, false); + } +} + +void CDStarRepeaterRXThread::endOfRadioData() +{ + wxLogMessage(wxT("AMBE for %s Frames: %.1fs, Silence: %.1f%%, BER: %.1f%%"), m_rxHeader->getMyCall1().c_str(), float(m_ambeFrames) / 50.0F, float(m_ambeSilence * 100U) / float(m_ambeFrames), float(m_ambeErrors * 100U) / float(m_ambeBits)); + + m_rptState = DSRS_LISTENING; + + m_protocolHandler->reset(); +} + +CDStarRepeaterStatusData* CDStarRepeaterRXThread::getStatus() +{ + float bits = float(m_ambeBits - m_lastAMBEBits); + float errors = float(m_ambeErrors - m_lastAMBEErrors); + if (bits == 0.0F) + bits = 1.0F; + + m_lastAMBEBits = m_ambeBits; + m_lastAMBEErrors = m_ambeErrors; + + CDStarRepeaterStatusData* status; + if (m_rptState == DSRS_SHUTDOWN || m_rptState == DSRS_LISTENING) + status = new CDStarRepeaterStatusData(wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, + wxEmptyString, 0x00, 0x00, 0x00, false, m_rxState, m_rptState, 0U, 0U, 0U, 0U, 0U, 0U, 0.0F, + wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString); + else + status = new CDStarRepeaterStatusData(m_rxHeader->getMyCall1(), m_rxHeader->getMyCall2(), + m_rxHeader->getYourCall(), m_rxHeader->getRptCall1(), m_rxHeader->getRptCall2(), + m_rxHeader->getFlag1(), m_rxHeader->getFlag2(), m_rxHeader->getFlag3(), false, m_rxState, + m_rptState, 0U, 0U, 0U, 0U, 0U, 0U, (errors * 100.0F) / bits, wxEmptyString, wxEmptyString, + wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString); + + if (m_type.IsSameAs(wxT("DVAP")) && m_modem != NULL) { + CDVAPController* dvap = static_cast(m_modem); + bool squelch = dvap->getSquelch(); + int signal = dvap->getSignal(); + status->setDVAP(squelch, signal); + } + + return status; +} + +void CDStarRepeaterRXThread::clock(unsigned int ms) +{ + m_registerTimer.clock(ms); +} + +void CDStarRepeaterRXThread::shutdown() +{ +} + +void CDStarRepeaterRXThread::startup() +{ +} + +void CDStarRepeaterRXThread::command1() +{ +} + +void CDStarRepeaterRXThread::command2() +{ +} + +void CDStarRepeaterRXThread::command3() +{ +} + +void CDStarRepeaterRXThread::command4() +{ +} + +void CDStarRepeaterRXThread::command5() +{ +} + +void CDStarRepeaterRXThread::command6() +{ +} + +unsigned int CDStarRepeaterRXThread::countBits(unsigned char byte) +{ + unsigned int bits = 0U; + + if ((byte & 0x01U) == 0x01U) + bits++; + if ((byte & 0x02U) == 0x02U) + bits++; + if ((byte & 0x04U) == 0x04U) + bits++; + if ((byte & 0x08U) == 0x08U) + bits++; + if ((byte & 0x10U) == 0x10U) + bits++; + if ((byte & 0x20U) == 0x20U) + bits++; + if ((byte & 0x40U) == 0x40U) + bits++; + if ((byte & 0x80U) == 0x80U) + bits++; + + return bits; +} diff --git a/DStarRepeater/DStarRepeaterRXThread.h b/DStarRepeater/DStarRepeaterRXThread.h new file mode 100644 index 0000000..ba656f7 --- /dev/null +++ b/DStarRepeater/DStarRepeaterRXThread.h @@ -0,0 +1,98 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterRXThread_H +#define DStarRepeaterRXThread_H + +#include "DStarRepeaterThread.h" +#include "SlowDataDecoder.h" +#include "HeaderData.h" +#include "AMBEFEC.h" +#include "Timer.h" + +#include + +class CDStarRepeaterRXThread : public IDStarRepeaterThread { +public: + CDStarRepeaterRXThread(const wxString& type); + virtual ~CDStarRepeaterRXThread(); + + virtual void setCallsign(const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply); + virtual void setProtocolHandler(CRepeaterProtocolHandler* handler, bool local); + virtual void setModem(CModem* modem); + virtual void setController(CExternalController* controller, unsigned int activeHangTime); + virtual void setTimes(unsigned int timeout, unsigned int ackTime); + virtual void setBeacon(unsigned int time, const wxString& text, bool voice, TEXT_LANG language); + virtual void setAnnouncement(bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2); + + virtual void setOutputs(bool out1, bool out2, bool out3, bool out4); + virtual void setLogging(bool logging, const wxString& dir); + virtual void setWhiteList(CCallsignList* list); + virtual void setBlackList(CCallsignList* list); + virtual void setGreyList(CCallsignList* list); + + virtual void shutdown(); + virtual void startup(); + + virtual void command1(); + virtual void command2(); + virtual void command3(); + virtual void command4(); + virtual void command5(); + virtual void command6(); + + virtual CDStarRepeaterStatusData* getStatus(); + + virtual void kill(); + + virtual void *Entry(); + +private: + wxString m_type; + CModem* m_modem; + CRepeaterProtocolHandler* m_protocolHandler; + CHeaderData* m_rxHeader; + unsigned char m_radioSeqNo; + CTimer m_registerTimer; + DSTAR_RPT_STATE m_rptState; + DSTAR_RX_STATE m_rxState; + CSlowDataDecoder m_slowDataDecoder; + bool m_killed; + CAMBEFEC m_ambe; + unsigned int m_ambeFrames; + unsigned int m_ambeSilence; + unsigned int m_ambeBits; + unsigned int m_ambeErrors; + unsigned int m_lastAMBEBits; + unsigned int m_lastAMBEErrors; + + void receiveHeader(CHeaderData* header); + void receiveRadioData(unsigned char* data, unsigned int length); + void receiveSlowData(unsigned char* data, unsigned int length); + + void receiveModem(); + void receiveNetwork(); + bool processRadioHeader(CHeaderData* header); + void processRadioFrame(unsigned char* data, FRAME_TYPE type); + void endOfRadioData(); + void setRadioState(DSTAR_RX_STATE state); + unsigned int countBits(unsigned char byte); + void clock(unsigned int ms); +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterStatusData.cpp b/DStarRepeater/DStarRepeaterStatusData.cpp new file mode 100644 index 0000000..9d4dacc --- /dev/null +++ b/DStarRepeater/DStarRepeaterStatusData.cpp @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterStatusData.h" + +CDStarRepeaterStatusData::CDStarRepeaterStatusData(const wxString& myCall1, const wxString& myCall2, + const wxString& yourCall, const wxString& rptCall1, + const wxString& rptCall2, unsigned char flag1, + unsigned char flag2, unsigned char flag3, bool tx, + DSTAR_RX_STATE rxState, DSTAR_RPT_STATE rptState, + unsigned int timeoutTimer, unsigned int timeoutExpiry, + unsigned int beaconTimer, unsigned int beaconExpiry, + unsigned int announceTimer, unsigned int announceExpiry, + float percent, const wxString& text, const wxString& status1, + const wxString& status2, const wxString& status3, + const wxString& status4, const wxString& status5) : +m_myCall1(myCall1), +m_myCall2(myCall2), +m_yourCall(yourCall), +m_rptCall1(rptCall1), +m_rptCall2(rptCall2), +m_flag1(flag1), +m_flag2(flag2), +m_flag3(flag3), +m_tx(tx), +m_rxState(rxState), +m_rptState(rptState), +m_timeoutTimer(timeoutTimer), +m_timeoutExpiry(timeoutExpiry), +m_beaconTimer(beaconTimer), +m_beaconExpiry(beaconExpiry), +m_announceTimer(announceTimer), +m_announceExpiry(announceExpiry), +m_percent(percent), +m_text(text), +m_status1(status1), +m_status2(status2), +m_status3(status3), +m_status4(status4), +m_status5(status5), +m_squelch(false), +m_signal(0) +{ +} + +CDStarRepeaterStatusData::~CDStarRepeaterStatusData() +{ +} + +void CDStarRepeaterStatusData::setDVAP(bool squelch, int signal) +{ + m_squelch = squelch; + m_signal = signal; +} + +wxString CDStarRepeaterStatusData::getMyCall1() const +{ + return m_myCall1; +} + +wxString CDStarRepeaterStatusData::getMyCall2() const +{ + return m_myCall2; +} + +wxString CDStarRepeaterStatusData::getYourCall() const +{ + return m_yourCall; +} + +wxString CDStarRepeaterStatusData::getRptCall1() const +{ + return m_rptCall1; +} + +wxString CDStarRepeaterStatusData::getRptCall2() const +{ + return m_rptCall2; +} + +unsigned char CDStarRepeaterStatusData::getFlag1() const +{ + return m_flag1; +} + +unsigned char CDStarRepeaterStatusData::getFlag2() const +{ + return m_flag2; +} + +unsigned char CDStarRepeaterStatusData::getFlag3() const +{ + return m_flag3; +} + +bool CDStarRepeaterStatusData::getTX() const +{ + return m_tx; +} + +bool CDStarRepeaterStatusData::getSquelch() const +{ + return m_squelch; +} + +int CDStarRepeaterStatusData::getSignal() const +{ + return m_signal; +} + +DSTAR_RX_STATE CDStarRepeaterStatusData::getRxState() const +{ + return m_rxState; +} + +DSTAR_RPT_STATE CDStarRepeaterStatusData::getRptState() const +{ + return m_rptState; +} + +unsigned int CDStarRepeaterStatusData::getTimeoutTimer() const +{ + return m_timeoutTimer; +} + +unsigned int CDStarRepeaterStatusData::getTimeoutExpiry() const +{ + return m_timeoutExpiry; +} + +unsigned int CDStarRepeaterStatusData::getBeaconTimer() const +{ + return m_beaconTimer; +} + +unsigned int CDStarRepeaterStatusData::getBeaconExpiry() const +{ + return m_beaconExpiry; +} + +unsigned int CDStarRepeaterStatusData::getAnnounceTimer() const +{ + return m_announceTimer; +} + +unsigned int CDStarRepeaterStatusData::getAnnounceExpiry() const +{ + return m_announceExpiry; +} + +float CDStarRepeaterStatusData::getPercent() const +{ + return m_percent; +} + +wxString CDStarRepeaterStatusData::getText() const +{ + return m_text; +} + +wxString CDStarRepeaterStatusData::getStatus1() const +{ + return m_status1; +} + +wxString CDStarRepeaterStatusData::getStatus2() const +{ + return m_status2; +} + +wxString CDStarRepeaterStatusData::getStatus3() const +{ + return m_status3; +} + +wxString CDStarRepeaterStatusData::getStatus4() const +{ + return m_status4; +} + +wxString CDStarRepeaterStatusData::getStatus5() const +{ + return m_status5; +} diff --git a/DStarRepeater/DStarRepeaterStatusData.h b/DStarRepeater/DStarRepeaterStatusData.h new file mode 100644 index 0000000..f0ae787 --- /dev/null +++ b/DStarRepeater/DStarRepeaterStatusData.h @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterStatusData_H +#define DStarRepeaterStatusData_H + +#include "DStarRepeaterDefs.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterStatusData { +public: + CDStarRepeaterStatusData(const wxString& myCall1, const wxString& myCall2, const wxString& yourCall, + const wxString& rptCall1, const wxString& rptCall2, unsigned char flag1, + unsigned char flag2, unsigned char flag3, bool tx, DSTAR_RX_STATE rxState, + DSTAR_RPT_STATE rptState, unsigned int timeoutTimer, unsigned int timeoutExpiry, + unsigned int beaconTimer, unsigned int beaconExpiry, unsigned int announceTimer, + unsigned int announceExpiry, float percent, const wxString& text, + const wxString& status1, const wxString& status2, const wxString& status3, + const wxString& status4, const wxString& status5); + ~CDStarRepeaterStatusData(); + + void setDVAP(bool squelch, int signal); + + wxString getMyCall1() const; + wxString getMyCall2() const; + wxString getYourCall() const; + wxString getRptCall1() const; + wxString getRptCall2() const; + unsigned char getFlag1() const; + unsigned char getFlag2() const; + unsigned char getFlag3() const; + + bool getTX() const; + bool getSquelch() const; + int getSignal() const; + + DSTAR_RPT_STATE getRptState() const; + DSTAR_RX_STATE getRxState() const; + + unsigned int getTimeoutTimer() const; + unsigned int getTimeoutExpiry() const; + + unsigned int getBeaconTimer() const; + unsigned int getBeaconExpiry() const; + + unsigned int getAnnounceTimer() const; + unsigned int getAnnounceExpiry() const; + + float getPercent() const; + + wxString getText() const; + wxString getStatus1() const; + wxString getStatus2() const; + wxString getStatus3() const; + wxString getStatus4() const; + wxString getStatus5() const; + +private: + wxString m_myCall1; + wxString m_myCall2; + wxString m_yourCall; + wxString m_rptCall1; + wxString m_rptCall2; + unsigned char m_flag1; + unsigned char m_flag2; + unsigned char m_flag3; + bool m_tx; + DSTAR_RX_STATE m_rxState; + DSTAR_RPT_STATE m_rptState; + unsigned int m_timeoutTimer; + unsigned int m_timeoutExpiry; + unsigned int m_beaconTimer; + unsigned int m_beaconExpiry; + unsigned int m_announceTimer; + unsigned int m_announceExpiry; + float m_percent; + wxString m_text; + wxString m_status1; + wxString m_status2; + wxString m_status3; + wxString m_status4; + wxString m_status5; + + // DVAP + bool m_squelch; + int m_signal; +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterTRXThread.cpp b/DStarRepeater/DStarRepeaterTRXThread.cpp new file mode 100644 index 0000000..0292133 --- /dev/null +++ b/DStarRepeater/DStarRepeaterTRXThread.cpp @@ -0,0 +1,1979 @@ +/* + * Copyright (C) 2011-2016 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterStatusData.h" +#include "DStarRepeaterTRXThread.h" +#include "DStarRepeaterApp.h" +#include "DVAPController.h" +#include "DStarDefines.h" +#include "HeaderData.h" +#include "Version.h" + +const unsigned char DTMF_MASK[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x82U, 0x00U, 0x00U}; +const unsigned char DTMF_SIG[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; + +const unsigned int MAX_DATA_SYNC_BIT_ERRS = 2U; + +const unsigned int NETWORK_QUEUE_COUNT = 2U; + +const unsigned int SILENCE_THRESHOLD = 2U; + +const unsigned int CYCLE_TIME = 9U; + +CDStarRepeaterTRXThread::CDStarRepeaterTRXThread(const wxString& type) : +m_type(type), +m_modem(NULL), +m_protocolHandler(NULL), +m_controller(NULL), +m_stopped(true), +m_rptCallsign(), +m_gwyCallsign(), +m_beacon(NULL), +m_announcement(NULL), +m_recordRPT1(), +m_recordRPT2(), +m_deleteRPT1(), +m_deleteRPT2(), +m_rxHeader(NULL), +m_localQueue((DV_FRAME_LENGTH_BYTES + 2U) * 50U, LOCAL_RUN_FRAME_COUNT), // 1s worth of data +m_radioQueue((DV_FRAME_LENGTH_BYTES + 2U) * 50U, RADIO_RUN_FRAME_COUNT), // 1s worth of data +m_networkQueue(NULL), +m_writeNum(0U), +m_readNum(0U), +m_radioSeqNo(0U), +m_networkSeqNo(0U), +m_lastSlowDataType(0x00U), +m_timeoutTimer(1000U, 180U), // 180s +m_watchdogTimer(1000U, NETWORK_TIMEOUT), +m_pollTimer(1000U, 60U), // 60s +m_ackTimer(1000U, 0U, 500U), // 0.5s +m_beaconTimer(1000U, 600U), // 10 mins +m_announcementTimer(1000U, 0U), // not running +m_statusTimer(1000U, 0U, 100U), // 100ms +m_heartbeatTimer(1000U, 1U), // 1s +m_rptState(DSRS_LISTENING), +m_rxState(DSRXS_LISTENING), +m_slowDataDecoder(), +m_ackEncoder(), +m_linkEncoder(), +m_headerEncoder(), +m_status1Encoder(), +m_status2Encoder(), +m_status3Encoder(), +m_status4Encoder(), +m_status5Encoder(), +m_tx(false), +m_space(0U), +m_killed(false), +m_mode(MODE_DUPLEX), +m_ack(AT_BER), +m_restriction(false), +m_rpt1Validation(true), +m_errorReply(true), +m_controlEnabled(false), +m_controlRPT1(), +m_controlRPT2(), +m_controlShutdown(), +m_controlStartup(), +m_activeHangTimer(1000U), +m_shutdown(false), +m_disable(false), +m_logging(NULL), +m_lastData(NULL), +m_ambe(), +m_ambeFrames(0U), +m_ambeSilence(0U), +m_ambeBits(1U), +m_ambeErrors(0U), +m_lastAMBEBits(0U), +m_lastAMBEErrors(0U), +m_ackText(), +m_tempAckText(), +m_linkStatus(LS_NONE), +m_reflector(), +m_regEx(wxT("^[A-Z0-9]{1}[A-Z0-9]{0,1}[0-9]{1,2}[A-Z]{1,4} {0,4}[ A-Z]{1}$")), +m_headerTime(), +m_packetTime(), +m_packetCount(0U), +m_packetSilence(0U), +m_whiteList(NULL), +m_blackList(NULL), +m_greyList(NULL), +m_blocked(false), +m_busyData(false), +m_blanking(true), +m_recording(false), +m_deleting(false) +{ + for(int i = 0; i < 5; ++i) + m_statusAnnounceTimer[i] = CTimer(1000U, 3U); + + m_statusText.Add("", 5); + + m_networkQueue = new COutputQueue*[NETWORK_QUEUE_COUNT]; + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + m_networkQueue[i] = new COutputQueue((DV_FRAME_LENGTH_BYTES + 2U) * 200U, NETWORK_RUN_FRAME_COUNT); // 4s worth of data); + + m_lastData = new unsigned char[DV_FRAME_MAX_LENGTH_BYTES]; + + setRepeaterState(DSRS_LISTENING); + setRadioState(DSRXS_LISTENING); +} + +CDStarRepeaterTRXThread::~CDStarRepeaterTRXThread() +{ + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + delete m_networkQueue[i]; + delete[] m_networkQueue; + delete[] m_lastData; + delete m_rxHeader; +} + +void *CDStarRepeaterTRXThread::Entry() +{ + // Wait here until we have the essentials to run + while (!m_killed && (m_modem == NULL || m_controller == NULL || m_rptCallsign.IsEmpty() || m_rptCallsign.IsSameAs(wxT(" ")))) + ::wxMilliSleep(500UL); // 1/2 sec + + if (m_killed) + return NULL; + + m_stopped = false; + + m_beaconTimer.start(); + m_announcementTimer.start(); + m_controller->setActive(false); + m_controller->setRadioTransmit(false); + m_statusTimer.start(); + m_heartbeatTimer.start(); + + if (m_protocolHandler != NULL) + m_pollTimer.start(); + + wxString hardware = m_type; + int n = hardware.Find(wxT(' ')); + if (n != wxNOT_FOUND) + hardware = m_type.Left(n); + + wxString pollText; +#if defined(__WINDOWS__) + pollText.Printf(wxT("win_%s-%s"), hardware.c_str(), VERSION.c_str()); +#else + pollText.Printf(wxT("linux_%s-%s"), hardware.c_str(), VERSION.c_str()); +#endif + pollText.Replace(wxT(" "), wxT("-")); + pollText.MakeLower(); + wxLogMessage(wxT("Poll text set to \"%s\""), pollText.c_str()); + + wxLogMessage(wxT("Starting the D-Star repeater thread")); + + wxStopWatch stopWatch; + + try { + while (!m_killed) { + stopWatch.Start(); + + if (m_statusTimer.hasExpired() || m_space == 0U) { + m_space = m_modem->getSpace(); + m_tx = m_modem->isTX(); + m_statusTimer.start(); + } + + receiveModem(); + + receiveNetwork(); + + repeaterStateMachine(); + + // Send the network poll if needed and restart the timer + if (m_pollTimer.hasExpired()) { + m_protocolHandler->writePoll(pollText); + m_pollTimer.start(); + } + + // Send the beacon and restart the timer + if (m_beaconTimer.isRunning() && m_beaconTimer.hasExpired()) { + m_beacon->sendBeacon(); + m_beaconTimer.start(); + } + + // Send the announcement and restart the timer + if (m_announcementTimer.isRunning() && m_announcementTimer.hasExpired()) { + m_announcement->startAnnouncement(); + m_announcementTimer.start(); + } + + for(int i = 0; i < 5; ++ i) { + if(m_statusAnnounceTimer[i].isRunning() && + m_statusAnnounceTimer[i].hasExpired()) { + m_statusAnnounceTimer[i].stop(); + if(m_rptState == DSRS_LISTENING) + transmitUserStatus(i); + } + } + + // Clock the heartbeat output every one second + if (m_heartbeatTimer.hasExpired()) { + m_controller->setHeartbeat(); + m_heartbeatTimer.start(); + } + + // Set the output state + if (m_rptState == DSRS_VALID || m_rptState == DSRS_INVALID || m_rptState == DSRS_TIMEOUT || + m_rptState == DSRS_VALID_WAIT || m_rptState == DSRS_INVALID_WAIT || m_rptState == DSRS_TIMEOUT_WAIT || + m_rptState == DSRS_NETWORK || (m_activeHangTimer.isRunning() && !m_activeHangTimer.hasExpired())) { + m_controller->setActive(true); + } else { + m_controller->setActive(false); + m_activeHangTimer.stop(); + } + + // Check the shutdown state, state changes are done here to bypass the state machine which is + // frozen when m_disable or m_shutdown are asserted + m_disable = m_controller->getDisable(); + if (m_disable || m_shutdown) { + if (m_rptState != DSRS_SHUTDOWN) { + m_timeoutTimer.stop(); + m_watchdogTimer.stop(); + m_activeHangTimer.stop(); + m_ackTimer.stop(); + m_beaconTimer.stop(); + m_announcementTimer.stop(); + m_localQueue.reset(); + m_radioQueue.reset(); + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + m_networkQueue[i]->reset(); + m_controller->setActive(false); + m_controller->setRadioTransmit(false); + m_rptState = DSRS_SHUTDOWN; + } + } else { + if (m_rptState == DSRS_SHUTDOWN) { + m_timeoutTimer.stop(); + m_watchdogTimer.stop(); + m_ackTimer.stop(); + m_beaconTimer.start(); + m_announcementTimer.start(); + m_rptState = DSRS_LISTENING; + if (m_protocolHandler != NULL) // Tell the protocol handler + m_protocolHandler->reset(); + } + } + + if (m_radioQueue.dataReady()) + transmitRadioData(); + else if (m_localQueue.dataReady()) + transmitLocalData(); + else if (m_networkQueue[m_readNum]->dataReady()) + transmitNetworkData(); + else if (m_radioQueue.headerReady()) + transmitRadioHeader(); + else if (m_localQueue.headerReady()) + transmitLocalHeader(); + else if (m_networkQueue[m_readNum]->headerReady()) + transmitNetworkHeader(); + + m_controller->setRadioTransmit(m_tx); + + unsigned long ms = stopWatch.Time(); + if (ms < CYCLE_TIME) { + ::wxMilliSleep(CYCLE_TIME - ms); + clock(CYCLE_TIME); + } else { + clock(ms); + } + } + } + catch (std::exception& e) { + wxString message(e.what(), wxConvLocal); + wxLogError(wxT("Exception raised - \"%s\""), message.c_str()); + } + catch (...) { + wxLogError(wxT("Unknown exception raised")); + } + + wxLogMessage(wxT("Stopping the D-Star repeater thread")); + + m_modem->stop(); + + if (m_logging != NULL) { + m_logging->close(); + delete m_logging; + } + + delete m_beacon; + delete m_announcement; + + delete m_whiteList; + delete m_blackList; + delete m_greyList; + + m_controller->setActive(false); + m_controller->setRadioTransmit(false); + m_controller->close(); + delete m_controller; + + if (m_protocolHandler != NULL) { + m_protocolHandler->close(); + delete m_protocolHandler; + } + + return NULL; +} + +void CDStarRepeaterTRXThread::kill() +{ + m_killed = true; +} + +void CDStarRepeaterTRXThread::setCallsign(const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply) +{ + // Pad the callsign up to eight characters + m_rptCallsign = callsign; + m_rptCallsign.resize(LONG_CALLSIGN_LENGTH, wxT(' ')); + + if (gateway.IsEmpty()) { + m_gwyCallsign = callsign; + m_gwyCallsign.resize(LONG_CALLSIGN_LENGTH - 1U, wxT(' ')); + m_gwyCallsign.Append(wxT("G")); + } else { + m_gwyCallsign = gateway; + m_gwyCallsign.resize(LONG_CALLSIGN_LENGTH, wxT(' ')); + } + + m_mode = mode; + m_ack = ack; + m_restriction = restriction; + m_rpt1Validation = rpt1Validation; + m_blanking = dtmfBlanking; + m_errorReply = errorReply; +} + +void CDStarRepeaterTRXThread::setProtocolHandler(CRepeaterProtocolHandler* handler, bool local) +{ + wxASSERT(handler != NULL); + + m_protocolHandler = handler; +} + +void CDStarRepeaterTRXThread::setModem(CModem* modem) +{ + wxASSERT(modem != NULL); + + m_modem = modem; +} + +void CDStarRepeaterTRXThread::setTimes(unsigned int timeout, unsigned int ackTime) +{ + m_timeoutTimer.setTimeout(timeout); + m_ackTimer.setTimeout(0U, ackTime); +} + +void CDStarRepeaterTRXThread::setBeacon(unsigned int time, const wxString& text, bool voice, TEXT_LANG language) +{ + m_beaconTimer.setTimeout(time); + + if (time > 0U) + m_beacon = new CBeaconUnit(this, m_rptCallsign, text, voice, language); +} + +void CDStarRepeaterTRXThread::setAnnouncement(bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2) +{ + if (enabled && time > 0U) { + m_announcement = new CAnnouncementUnit(this, m_rptCallsign); + + m_announcementTimer.setTimeout(time); + + m_recordRPT1 = recordRPT1; + m_recordRPT2 = recordRPT2; + m_deleteRPT1 = deleteRPT1; + m_deleteRPT2 = deleteRPT2; + + m_recordRPT1.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_recordRPT2.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_deleteRPT1.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_deleteRPT2.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + + m_recordRPT1.Truncate(LONG_CALLSIGN_LENGTH); + m_recordRPT2.Truncate(LONG_CALLSIGN_LENGTH); + m_deleteRPT1.Truncate(LONG_CALLSIGN_LENGTH); + m_deleteRPT2.Truncate(LONG_CALLSIGN_LENGTH); + } +} + +void CDStarRepeaterTRXThread::setController(CExternalController* controller, unsigned int activeHangTime) +{ + wxASSERT(controller != NULL); + + m_controller = controller; + m_activeHangTimer.setTimeout(activeHangTime); +} + +void CDStarRepeaterTRXThread::setControl(bool enabled, + const wxString& rpt1Callsign, const wxString& rpt2Callsign, + const wxString& shutdown,const wxString& startup, + const wxArrayString& command, const wxArrayString& status, + const wxArrayString& outputs) +{ + m_controlEnabled = enabled; + + m_controlRPT1 = rpt1Callsign; + m_controlRPT2 = rpt2Callsign; + + m_controlShutdown = shutdown; + m_controlStartup = startup; + + m_controlStatus = status; + m_controlCommand = command; + m_controlOutput = outputs; + + for(int i = 0; i < m_controlCommand.GetCount(); ++i) { + m_controlCommand[i].Append(' ', LONG_CALLSIGN_LENGTH); + m_controlCommand[i].Truncate(LONG_CALLSIGN_LENGTH); + } + + for(int i = 0; i < m_controlStatus.GetCount(); ++i) { + m_controlStatus[i].Append(' ', LONG_CALLSIGN_LENGTH); + m_controlStatus[i].Truncate(LONG_CALLSIGN_LENGTH); + } + + for(int i = 0; i < m_controlOutput.GetCount(); ++i) { + m_controlOutput[i].Append(' ', LONG_CALLSIGN_LENGTH); + m_controlOutput[i].Truncate(LONG_CALLSIGN_LENGTH); + } + + m_controlRPT1.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_controlRPT2.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_controlShutdown.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + m_controlStartup.Append(wxT(' '), LONG_CALLSIGN_LENGTH); + + m_controlRPT1.Truncate(LONG_CALLSIGN_LENGTH); + m_controlRPT2.Truncate(LONG_CALLSIGN_LENGTH); + m_controlShutdown.Truncate(LONG_CALLSIGN_LENGTH); + m_controlStartup.Truncate(LONG_CALLSIGN_LENGTH); +} + +void CDStarRepeaterTRXThread::setOutputs(bool out1, bool out2, bool out3, bool out4) +{ + if (m_controller == NULL) + return; + + m_output[0] = out1; + m_output[1] = out2; + m_output[2] = out3; + m_output[3] = out4; + + m_controller->setOutput1(m_output[0]); + m_controller->setOutput2(m_output[1]); + m_controller->setOutput3(m_output[2]); + m_controller->setOutput4(m_output[3]); +} + +void CDStarRepeaterTRXThread::setLogging(bool logging, const wxString& dir) +{ + if (logging && m_logging == NULL) { + m_logging = new CDVTOOLFileWriter; + m_logging->setDirectory(dir); + return; + } + + if (!logging && m_logging != NULL) { + delete m_logging; + m_logging = NULL; + return; + } +} + +void CDStarRepeaterTRXThread::setWhiteList(CCallsignList* list) +{ + wxASSERT(list != NULL); + + m_whiteList = list; +} + +void CDStarRepeaterTRXThread::setBlackList(CCallsignList* list) +{ + wxASSERT(list != NULL); + + m_blackList = list; +} + +void CDStarRepeaterTRXThread::setGreyList(CCallsignList* list) +{ + wxASSERT(list != NULL); + + m_greyList = list; +} + +void CDStarRepeaterTRXThread::receiveModem() +{ + for (;;) { + DSMT_TYPE type = m_modem->read(); + if (type == DSMTT_NONE) + return; + + switch (m_rxState) { + case DSRXS_LISTENING: + if (type == DSMTT_HEADER) { + CHeaderData* header = m_modem->readHeader(); + receiveHeader(header); + } else if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + setRadioState(DSRXS_PROCESS_SLOW_DATA); + receiveSlowData(data, length); + } + break; + + case DSRXS_PROCESS_SLOW_DATA: + if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + receiveSlowData(data, length); + } else if (type == DSMTT_EOT || type == DSMTT_LOST) { + setRadioState(DSRXS_LISTENING); + } + break; + + case DSRXS_PROCESS_DATA: + if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + receiveRadioData(data, length); + } else if (type == DSMTT_EOT || type == DSMTT_LOST) { + unsigned char data[20U]; + ::memcpy(data, END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + processRadioFrame(data, FRAME_END); + setRadioState(DSRXS_LISTENING); + endOfRadioData(); + } + break; + } + } +} + +void CDStarRepeaterTRXThread::receiveHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + wxLogMessage(wxT("Radio header decoded - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + bool res = processRadioHeader(header); + if (res) { + // A valid header and is a DV packet + m_radioSeqNo = 20U; + setRadioState(DSRXS_PROCESS_DATA); + } else { + // This is a DD packet or some other problem + // wxLogMessage(wxT("Invalid header")); + } +} + +void CDStarRepeaterTRXThread::receiveSlowData(unsigned char* data, unsigned int) +{ + unsigned int errs; + errs = countBits(data[VOICE_FRAME_LENGTH_BYTES + 0U] ^ DATA_SYNC_BYTES[0U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 1U] ^ DATA_SYNC_BYTES[1U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 2U] ^ DATA_SYNC_BYTES[2U]); + + // The data sync has been seen, a fuzzy match is used, two bit errors or less + if (errs <= MAX_DATA_SYNC_BIT_ERRS) { + // wxLogMessage(wxT("Found data sync at frame %u, errs: %u"), m_radioSeqNo, errs); + m_radioSeqNo = 0U; + m_slowDataDecoder.sync(); + } else if (m_radioSeqNo == 20U) { + // wxLogMessage(wxT("Assuming data sync")); + m_radioSeqNo = 0U; + m_slowDataDecoder.sync(); + } else { + m_radioSeqNo++; + m_slowDataDecoder.addData(data + VOICE_FRAME_LENGTH_BYTES); + + CHeaderData* header = m_slowDataDecoder.getHeaderData(); + if (header == NULL) + return; + + wxLogMessage(wxT("Radio header from slow data - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X BER: 0%%"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + if (header != NULL) { + bool res = processRadioHeader(header); + if (res) { + // A valid header and is a DV packet, go to normal data relaying + setRadioState(DSRXS_PROCESS_DATA); + } else { + // This is a DD packet or some other problem + // wxLogMessage(wxT("Invalid header")); + } + } + } +} + +void CDStarRepeaterTRXThread::receiveRadioData(unsigned char* data, unsigned int) +{ + unsigned int errs; + errs = countBits(data[VOICE_FRAME_LENGTH_BYTES + 0U] ^ DATA_SYNC_BYTES[0U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 1U] ^ DATA_SYNC_BYTES[1U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 2U] ^ DATA_SYNC_BYTES[2U]); + + // The data sync has been seen, a fuzzy match is used, two bit errors or less + if (errs <= MAX_DATA_SYNC_BIT_ERRS) { + // wxLogMessage(wxT("Found data sync at frame %u, errs: %u"), m_radioSeqNo, errs); + m_radioSeqNo = 0U; + processRadioFrame(data, FRAME_SYNC); + } else if (m_radioSeqNo == 20U) { + // wxLogMessage(wxT("Regenerating data sync")); + m_radioSeqNo = 0U; + processRadioFrame(data, FRAME_SYNC); + } else { + m_radioSeqNo++; + processRadioFrame(data, FRAME_NORMAL); + } +} + +void CDStarRepeaterTRXThread::receiveNetwork() +{ + if (m_protocolHandler == NULL) + return; + + NETWORK_TYPE type; + + for (;;) { + type = m_protocolHandler->read(); + + // Get the data from the network + if (type == NETWORK_NONE) { // Nothing received + break; + } else if (type == NETWORK_HEADER) { // A header + CHeaderData* header = m_protocolHandler->readHeader(); + if (header != NULL) { + ::memcpy(m_lastData, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + + processNetworkHeader(header); + + m_headerTime.Start(); + m_packetTime.Start(); + m_packetCount = 0U; + m_packetSilence = 0U; + } + } else if (type == NETWORK_DATA) { // AMBE data and slow data + unsigned char data[2U * DV_FRAME_MAX_LENGTH_BYTES]; + ::memset(data, 0x00U, 2U * DV_FRAME_MAX_LENGTH_BYTES); + + unsigned char seqNo; + unsigned int length = m_protocolHandler->readData(data, DV_FRAME_MAX_LENGTH_BYTES, seqNo); + if (length != 0U) { + ::memcpy(m_lastData, data, length); + m_watchdogTimer.start(); + m_packetCount += processNetworkFrame(data, length, seqNo); + } + } else if (type == NETWORK_TEXT) { // Slow data text for the Ack + m_protocolHandler->readText(m_ackText, m_linkStatus, m_reflector); + m_linkEncoder.setTextData(m_ackText); + wxLogMessage(wxT("Slow data set to \"%s\""), m_ackText.c_str()); + } else if (type == NETWORK_TEMPTEXT) { // Temporary slow data text for the Ack + m_protocolHandler->readTempText(m_tempAckText); + wxLogMessage(wxT("Temporary slow data set to \"%s\""), m_tempAckText.c_str()); + } else if (type == NETWORK_STATUS1) { // Status 1 data text + m_statusText[0] = m_protocolHandler->readStatus1(); + m_status1Encoder.setTextData(m_statusText[0]); + wxLogMessage(wxT("Status 1 data set to \"%s\""), m_statusText[0].c_str()); + } else if (type == NETWORK_STATUS2) { // Status 2 data text + m_statusText[1] = m_protocolHandler->readStatus2(); + m_status2Encoder.setTextData(m_statusText[1]); + wxLogMessage(wxT("Status 2 data set to \"%s\""), m_statusText[1].c_str()); + } else if (type == NETWORK_STATUS3) { // Status 3 data text + m_statusText[2] = m_protocolHandler->readStatus3(); + m_status3Encoder.setTextData(m_statusText[2]); + wxLogMessage(wxT("Status 3 data set to \"%s\""), m_statusText[2].c_str()); + } else if (type == NETWORK_STATUS4) { // Status 4 data text + m_statusText[3] = m_protocolHandler->readStatus4(); + m_status4Encoder.setTextData(m_statusText[3]); + wxLogMessage(wxT("Status 4 data set to \"%s\""), m_statusText[3].c_str()); + } else if (type == NETWORK_STATUS5) { // Status 5 data text + m_statusText[4] = m_protocolHandler->readStatus5(); + m_status5Encoder.setTextData(m_statusText[4]); + wxLogMessage(wxT("Status 5 data set to \"%s\""), m_statusText[4].c_str()); + } + } + + // Have we missed any data frames? + if (m_rptState == DSRS_NETWORK && m_packetTime.Time() > 200L) { + unsigned int packetsNeeded = m_headerTime.Time() / DSTAR_FRAME_TIME_MS; + + // wxLogMessage(wxT("Time: %u ms, need %u packets and received %u packets"), ms - m_headerMS, packetsNeeded, m_packetCount); + + if (packetsNeeded > m_packetCount) { + unsigned int count = packetsNeeded - m_packetCount; + + if (count > 5U) { + count -= 2U; + + // wxLogMessage(wxT("Inserting %u silence frames into the network data stream"), count); + + // Create silence frames + for (unsigned int i = 0U; i < count; i++) { + unsigned char data[DV_FRAME_LENGTH_BYTES]; + ::memcpy(data, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + m_packetCount += processNetworkFrame(data, DV_FRAME_LENGTH_BYTES, m_networkSeqNo); + m_packetSilence++; + } + } + } + + m_packetTime.Start(); + } +} + +void CDStarRepeaterTRXThread::transmitLocalHeader(CHeaderData* header) +{ + wxLogMessage(wxT("Transmitting to - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + m_headerEncoder.setHeaderData(*header); + + m_localQueue.reset(); + m_localQueue.setHeader(header); +} + +void CDStarRepeaterTRXThread::transmitBeaconHeader() +{ + CHeaderData* header = new CHeaderData(m_rptCallsign, wxT("RPTR"), wxT("CQCQCQ "), m_gwyCallsign, m_rptCallsign); + transmitLocalHeader(header); +} + +void CDStarRepeaterTRXThread::transmitBeaconData(const unsigned char* data, unsigned int length, bool end) +{ + m_localQueue.addData(data, length, end); +} + +void CDStarRepeaterTRXThread::transmitAnnouncementHeader(CHeaderData* header) +{ + header->setRptCall1(m_gwyCallsign); + header->setRptCall2(m_rptCallsign); + + transmitLocalHeader(header); +} + +void CDStarRepeaterTRXThread::transmitAnnouncementData(const unsigned char* data, unsigned int length, bool end) +{ + m_localQueue.addData(data, length, end); +} + +void CDStarRepeaterTRXThread::transmitRadioHeader(CHeaderData* header) +{ + wxLogMessage(wxT("Transmitting to - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + m_headerEncoder.setHeaderData(*header); + + m_radioQueue.reset(); + m_radioQueue.setHeader(header); +} + +void CDStarRepeaterTRXThread::transmitNetworkHeader(CHeaderData* header) +{ + wxLogMessage(wxT("Transmitting to - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + m_headerEncoder.setHeaderData(*header); + + bool empty = m_networkQueue[m_readNum]->isEmpty(); + if (!empty) { + bool headerReady = m_networkQueue[m_readNum]->headerReady(); + if (headerReady) { + // Transmission has never started, so just purge the queue + m_networkQueue[m_readNum]->reset(); + + m_readNum++; + if (m_readNum >= NETWORK_QUEUE_COUNT) + m_readNum = 0U; + } else { + // Append an end of stream + m_networkQueue[m_readNum]->reset(); + m_networkQueue[m_readNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + } + } + + m_networkQueue[m_writeNum]->reset(); + m_networkQueue[m_writeNum]->setHeader(header); +} + +void CDStarRepeaterTRXThread::transmitStatus() +{ + CHeaderData* header = new CHeaderData(m_rptCallsign, wxT(" "), m_rxHeader->getMyCall1(), m_gwyCallsign, m_rptCallsign, RELAY_UNAVAILABLE); + transmitLocalHeader(header); + + // Filler data + for (unsigned int i = 0U; i < 21U; i++) { + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + + if (i == 0U) { + m_ackEncoder.sync(); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + } else { + unsigned char text[DATA_FRAME_LENGTH_BYTES]; + m_ackEncoder.getTextData(text); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, text, DATA_FRAME_LENGTH_BYTES); + } + + m_localQueue.addData(buffer, DV_FRAME_LENGTH_BYTES, false); + } + + m_localQueue.addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); +} + +void CDStarRepeaterTRXThread::transmitErrorStatus() +{ + CHeaderData* header = new CHeaderData(m_rptCallsign, wxT(" "), m_rxHeader->getMyCall1(), m_rptCallsign, m_rptCallsign, RELAY_UNAVAILABLE); + transmitLocalHeader(header); + + // Filler data + for (unsigned int i = 0U; i < 21U; i++) { + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + + if (i == 0U) { + m_linkEncoder.sync(); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + } else { + unsigned char text[DATA_FRAME_LENGTH_BYTES]; + m_linkEncoder.getTextData(text); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, text, DATA_FRAME_LENGTH_BYTES); + } + + m_localQueue.addData(buffer, DV_FRAME_LENGTH_BYTES, false); + } + + m_localQueue.addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); +} + +void CDStarRepeaterTRXThread::transmitUserStatus(unsigned int n) +{ + CSlowDataEncoder* encoder = NULL; + CHeaderData* header = NULL; + switch (n) { + case 0U: + header = new CHeaderData(m_rptCallsign, wxT(" "), wxT("STATUS 1"), m_gwyCallsign, m_rptCallsign); + encoder = &m_status1Encoder; + break; + case 1U: + header = new CHeaderData(m_rptCallsign, wxT(" "), wxT("STATUS 2"), m_gwyCallsign, m_rptCallsign); + encoder = &m_status2Encoder; + break; + case 2U: + header = new CHeaderData(m_rptCallsign, wxT(" "), wxT("STATUS 3"), m_gwyCallsign, m_rptCallsign); + encoder = &m_status3Encoder; + break; + case 3U: + header = new CHeaderData(m_rptCallsign, wxT(" "), wxT("STATUS 4"), m_gwyCallsign, m_rptCallsign); + encoder = &m_status4Encoder; + break; + case 4U: + header = new CHeaderData(m_rptCallsign, wxT(" "), wxT("STATUS 5"), m_gwyCallsign, m_rptCallsign); + encoder = &m_status5Encoder; + break; + default: + wxLogWarning(wxT("Invalid status number - %u"), n); + return; + } + + transmitLocalHeader(header); + + // Filler data + for (unsigned int i = 0U; i < 21U; i++) { + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + + if (i == 0U) { + encoder->sync(); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + } else { + unsigned char text[DATA_FRAME_LENGTH_BYTES]; + encoder->getTextData(text); + + ::memcpy(buffer + 0U, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, text, DATA_FRAME_LENGTH_BYTES); + } + + m_localQueue.addData(buffer, DV_FRAME_LENGTH_BYTES, false); + } + + m_localQueue.addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); +} + +void CDStarRepeaterTRXThread::transmitLocalHeader() +{ + // Don't send a header until the modem is ready + bool ready = m_modem->isTXReady(); + if (!ready) + return; + + CHeaderData* header = m_localQueue.getHeader(); + if (header == NULL) + return; + + m_modem->writeHeader(*header); + delete header; +} + +void CDStarRepeaterTRXThread::transmitLocalData() +{ + if (m_space == 0U) + return; + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + bool end; + unsigned int length = m_localQueue.getData(buffer, DV_FRAME_LENGTH_BYTES, end); + + if (length == 0U) + return; + + m_modem->writeData(buffer, length, end); + m_space--; + + if (end) + m_localQueue.reset(); +} + +void CDStarRepeaterTRXThread::transmitRadioHeader() +{ + // Don't send a header until the modem is ready + bool ready = m_modem->isTXReady(); + if (!ready) + return; + + CHeaderData* header = m_radioQueue.getHeader(); + if (header == NULL) + return; + + m_modem->writeHeader(*header); + delete header; +} + +void CDStarRepeaterTRXThread::transmitRadioData() +{ + if (m_space == 0U) + return; + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + bool end; + unsigned int length = m_radioQueue.getData(buffer, DV_FRAME_LENGTH_BYTES, end); + + if (length == 0U) + return; + + m_modem->writeData(buffer, length, end); + m_space--; + + if (end) + m_radioQueue.reset(); +} + +void CDStarRepeaterTRXThread::transmitNetworkHeader() +{ + // Don't send a header until the modem is ready + bool ready = m_modem->isTXReady(); + if (!ready) + return; + + CHeaderData* header = m_networkQueue[m_readNum]->getHeader(); + if (header == NULL) + return; + + m_modem->writeHeader(*header); + delete header; +} + +void CDStarRepeaterTRXThread::transmitNetworkData() +{ + if (m_space == 0U) + return; + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + bool end; + unsigned int length = m_networkQueue[m_readNum]->getData(buffer, DV_FRAME_LENGTH_BYTES, end); + + if (length == 0U) + return; + + m_modem->writeData(buffer, length, end); + m_space--; + + if (end) { + m_networkQueue[m_readNum]->reset(); + + m_readNum++; + if (m_readNum >= NETWORK_QUEUE_COUNT) + m_readNum = 0U; + } +} + +void CDStarRepeaterTRXThread::repeaterStateMachine() +{ + switch (m_rptState) { + case DSRS_VALID: + if (m_timeoutTimer.isRunning() && m_timeoutTimer.hasExpired()) { + wxLogMessage(wxT("User has timed out")); + setRepeaterState(DSRS_TIMEOUT); + } + break; + + case DSRS_VALID_WAIT: + if (m_ackTimer.hasExpired()) { + if (m_mode != MODE_GATEWAY) + transmitStatus(); + setRepeaterState(DSRS_LISTENING); + m_activeHangTimer.start(); + } + break; + + case DSRS_INVALID_WAIT: + if (m_ackTimer.hasExpired()) { + if (m_mode != MODE_GATEWAY && m_errorReply) + transmitErrorStatus(); + setRepeaterState(DSRS_LISTENING); + m_activeHangTimer.start(); + } + break; + + case DSRS_TIMEOUT_WAIT: + if (m_ackTimer.hasExpired()) { + if (m_mode != MODE_GATEWAY) + transmitStatus(); + setRepeaterState(DSRS_LISTENING); + m_activeHangTimer.start(); + } + break; + + case DSRS_NETWORK: + if (m_watchdogTimer.hasExpired()) { + wxLogMessage(wxT("Network watchdog has expired")); + // Send end of transmission data to the radio + m_networkQueue[m_writeNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + endOfNetworkData(); + } + break; + + default: + break; + } +} + +void CDStarRepeaterTRXThread::setRadioState(DSTAR_RX_STATE state) +{ + // This is the too state + switch (state) { + case DSRXS_LISTENING: + m_rxState = DSRXS_LISTENING; + break; + + case DSRXS_PROCESS_DATA: + m_ambeFrames = 0U; + m_ambeSilence = 0U; + m_ambeBits = 1U; + m_ambeErrors = 0U; + m_lastAMBEBits = 0U; + m_lastAMBEErrors = 0U; + m_rxState = DSRXS_PROCESS_DATA; + break; + + case DSRXS_PROCESS_SLOW_DATA: + m_radioSeqNo = 0U; + m_slowDataDecoder.reset(); + m_rxState = DSRXS_PROCESS_SLOW_DATA; + break; + } +} + +bool CDStarRepeaterTRXThread::setRepeaterState(DSTAR_RPT_STATE state) +{ + // Can't change state when shutdown + if (m_disable || m_shutdown) + return false; + + // The "from" state + switch (m_rptState) { + case DSRS_SHUTDOWN: + m_beaconTimer.start(); + m_announcementTimer.start(); + break; + + case DSRS_LISTENING: + m_beaconTimer.stop(); + break; + + default: + break; + } + + // The "to" state + switch (state) { + case DSRS_SHUTDOWN: + m_timeoutTimer.stop(); + m_watchdogTimer.stop(); + m_activeHangTimer.stop(); + m_ackTimer.stop(); + m_beaconTimer.stop(); + m_announcementTimer.stop(); + m_controller->setActive(false); + m_controller->setRadioTransmit(false); + m_rptState = DSRS_SHUTDOWN; + break; + + case DSRS_LISTENING: + m_timeoutTimer.stop(); + m_watchdogTimer.stop(); + m_ackTimer.stop(); + m_beaconTimer.start(); + m_rptState = DSRS_LISTENING; + if (m_protocolHandler != NULL) // Tell the protocol handler + m_protocolHandler->reset(); + break; + + case DSRS_VALID: + if (m_rptState != DSRS_LISTENING && m_rptState != DSRS_VALID_WAIT) + return false; + + if (m_rptState == DSRS_LISTENING) + m_timeoutTimer.start(); + else + m_ackTimer.stop(); + + m_activeHangTimer.stop(); + m_rptState = DSRS_VALID; + break; + + case DSRS_VALID_WAIT: + m_ackTimer.start(); + m_rptState = DSRS_VALID_WAIT; + break; + + case DSRS_TIMEOUT: + // Send end of transmission data to the radio and the network + if (m_mode == MODE_DUPLEX) + m_radioQueue.addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + + if (!m_blocked && m_protocolHandler != NULL) { + unsigned char bytes[DV_FRAME_MAX_LENGTH_BYTES]; + ::memcpy(bytes, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(bytes + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES); + m_protocolHandler->writeData(bytes, DV_FRAME_MAX_LENGTH_BYTES, 0U, true); + } + + m_timeoutTimer.stop(); + m_rptState = DSRS_TIMEOUT; + break; + + case DSRS_TIMEOUT_WAIT: + m_ackTimer.start(); + m_timeoutTimer.stop(); + m_rptState = DSRS_TIMEOUT_WAIT; + break; + + case DSRS_INVALID: + if (m_rptState != DSRS_LISTENING) + return false; + + m_activeHangTimer.stop(); + m_timeoutTimer.stop(); + m_rptState = DSRS_INVALID; + break; + + case DSRS_INVALID_WAIT: + m_ackTimer.start(); + m_timeoutTimer.stop(); + m_rptState = DSRS_INVALID_WAIT; + break; + + case DSRS_NETWORK: + if (m_rptState != DSRS_LISTENING && m_rptState != DSRS_VALID_WAIT && m_rptState != DSRS_INVALID_WAIT && m_rptState != DSRS_TIMEOUT_WAIT) + return false; + + m_rptState = DSRS_NETWORK; + m_networkSeqNo = 0U; + m_timeoutTimer.stop(); + m_watchdogTimer.start(); + m_activeHangTimer.stop(); + m_ackTimer.stop(); + break; + } + + return true; +} + +bool CDStarRepeaterTRXThread::processRadioHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + // Check control messages + bool res = checkControl(*header); + if (res) { + delete header; + return true; + } + + // Check announcement messages + res = checkAnnouncements(*header); + if (res) { + bool res = setRepeaterState(DSRS_INVALID); + if (res) { + delete m_rxHeader; + m_rxHeader = header; + } else { + delete header; + } + return true; + } + + // If shutdown we ignore incoming headers + if (m_rptState == DSRS_SHUTDOWN) { + delete header; + return true; + } + + if (m_whiteList != NULL) { + bool res = m_whiteList->isInList(header->getMyCall1()); + if (!res) { + wxLogMessage(wxT("%s rejected due to not being in the white list"), header->getMyCall1().c_str()); + delete header; + return true; + } + } + + if (m_blackList != NULL) { + bool res = m_blackList->isInList(header->getMyCall1()); + if (res) { + wxLogMessage(wxT("%s rejected due to being in the black list"), header->getMyCall1().c_str()); + delete header; + return true; + } + } + + m_blocked = false; + if (m_greyList != NULL) { + bool res = m_greyList->isInList(header->getMyCall1()); + if (res) { + wxLogMessage(wxT("%s blocked from the network due to being in the grey list"), header->getMyCall1().c_str()); + m_blocked = true; + } + } + + // Check for receiving our own gateway data, and ignore it + if (m_mode == MODE_GATEWAY) { + if (header->getFlag2() == 0x01U) { + wxLogMessage(wxT("Receiving a gateway header, ignoring")); + delete header; + return true; + } + + header->setFlag2(0x00U); + } + + // We don't handle DD data packets + if (header->isDataPacket()) { + wxLogMessage(wxT("Received a DD packet, ignoring")); + delete header; + return false; + } + + TRISTATE valid = checkHeader(*header); + switch (valid) { + case STATE_FALSE: { + bool res = setRepeaterState(DSRS_INVALID); + if (res) { + delete m_rxHeader; + m_rxHeader = header; + } else { + delete header; + } + } + return true; + + case STATE_UNKNOWN: + delete header; + return true; + + case STATE_TRUE: + break; + } + + // If we're in network mode, send the header as a busy header to the gateway in case it's an unlink + // command + if (m_rptState == DSRS_NETWORK) { + // Only send on the network if the user isn't blocked we have one and RPT2 is not blank or the repeater callsign + if (!header->getRptCall2().IsSameAs(wxT(" ")) && !header->getRptCall2().IsSameAs(m_rptCallsign)) { + if (!m_blocked && m_protocolHandler != NULL) { + CHeaderData netHeader(*header); + netHeader.setRptCall1(header->getRptCall2()); + netHeader.setRptCall2(header->getRptCall1()); + netHeader.setFlag1(header->getFlag1() & ~REPEATER_MASK); + m_protocolHandler->writeBusyHeader(netHeader); + } + + m_busyData = true; + } + + delete header; + + return true; + } + + // Send the valid header to the gateway if we are accepted + res = setRepeaterState(DSRS_VALID); + if (res) { + delete m_rxHeader; + m_rxHeader = header; + + if (m_logging != NULL) + m_logging->open(*m_rxHeader); + + // Only send on the network if the user isn't blocked and we have one and RPT2 is not blank or the repeater callsign + if (!m_blocked && m_protocolHandler != NULL && !m_rxHeader->getRptCall2().IsSameAs(wxT(" ")) && !m_rxHeader->getRptCall2().IsSameAs(m_rptCallsign)) { + CHeaderData netHeader(*m_rxHeader); + netHeader.setRptCall1(m_rxHeader->getRptCall2()); + netHeader.setRptCall2(m_rxHeader->getRptCall1()); + netHeader.setFlag1(m_rxHeader->getFlag1() & ~REPEATER_MASK); + m_protocolHandler->writeHeader(netHeader); + } + + // Create the new radio header but only in duplex mode + if (m_mode == MODE_DUPLEX) { + CHeaderData* rfHeader = new CHeaderData(*m_rxHeader); + rfHeader->setRptCall1(m_rptCallsign); + rfHeader->setRptCall2(m_rptCallsign); + rfHeader->setFlag1(m_rxHeader->getFlag1() & ~REPEATER_MASK); + transmitRadioHeader(rfHeader); + } + } + + return true; +} + +void CDStarRepeaterTRXThread::processNetworkHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + // If shutdown we ignore incoming headers + if (m_rptState == DSRS_SHUTDOWN) { + delete header; + return; + } + + wxLogMessage(wxT("Network header received - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + // Is it for us? + if (!header->getRptCall2().IsSameAs(m_rptCallsign)) { + wxLogMessage(wxT("Invalid network RPT2 value, ignoring")); + delete header; + return; + } + + bool res = setRepeaterState(DSRS_NETWORK); + if (!res) { + delete header; + return; + } + + delete m_rxHeader; + m_rxHeader = header; + + if (m_mode == MODE_GATEWAY) { + // If in gateway mode, set the repeater bit, set flag 2 to 0x01, + // and change RPT1 & RPT2 just for transmission + CHeaderData* header = new CHeaderData(*m_rxHeader); + header->setRepeaterMode(true); + header->setFlag2(0x01U); + header->setRptCall1(m_rptCallsign); + header->setRptCall2(m_gwyCallsign); + transmitNetworkHeader(header); + } else { + CHeaderData* header = new CHeaderData(*m_rxHeader); + transmitNetworkHeader(header); + } +} + +void CDStarRepeaterTRXThread::processRadioFrame(unsigned char* data, FRAME_TYPE type) +{ + m_ambeFrames++; + + // If a sync frame, regenerate the sync bytes + if (type == FRAME_SYNC) + ::memcpy(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + // Only regenerate the AMBE on received radio data + unsigned int errors = 0U; + if (type != FRAME_END) { + // Data packets have no AMBE FEC + if (!m_rxHeader->isDataPacket()) + errors = m_ambe.regenerate(data); + + m_ambeErrors += errors; + m_ambeBits += 48U; // Only count the bits with FEC added + } + + if (::memcmp(data, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES) == 0) + m_ambeSilence++; + + // If this is deleting an announcement, ignore the audio + if (m_deleting) { + if (type == FRAME_END) { + m_deleting = false; + m_recording = false; + } + return; + } + + // If this is recording an announcement, send the audio to the announcement unit and then stop + if (m_recording) { + m_announcement->writeData(data, DV_FRAME_LENGTH_BYTES, type == FRAME_END); + if (type == FRAME_END) { + m_deleting = false; + m_recording = false; + } + return; + } + + // Pass background AMBE data to the network + if (m_busyData) { + if (type == FRAME_END) { + if (!m_blocked && m_protocolHandler != NULL) { + unsigned char bytes[DV_FRAME_MAX_LENGTH_BYTES]; + ::memcpy(bytes, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(bytes + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES); + m_protocolHandler->writeBusyData(bytes, DV_FRAME_MAX_LENGTH_BYTES, 0U, true); + } + + m_busyData = false; + } else { + if (!m_blocked && m_protocolHandler != NULL) + m_protocolHandler->writeBusyData(data, DV_FRAME_LENGTH_BYTES, errors, false); + } + + return; + } + + // Don't pass through the frame of an invalid transmission + if (m_rptState != DSRS_VALID) + return; + + if (type == FRAME_END) { + if (m_logging != NULL) + m_logging->close(); + + // Transmit the end sync on the radio, but only in duplex mode + if (m_mode == MODE_DUPLEX) + m_radioQueue.addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + + // Send null data and the end marker over the network, and the statistics + if (!m_blocked && m_protocolHandler != NULL) { + unsigned char bytes[DV_FRAME_MAX_LENGTH_BYTES]; + ::memcpy(bytes, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(bytes + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES); + m_protocolHandler->writeData(bytes, DV_FRAME_MAX_LENGTH_BYTES, 0U, true); + } + } else { + if (m_logging != NULL) + m_logging->write(data, DV_FRAME_LENGTH_BYTES); + + // Send the data to the network + if (!m_blocked && m_protocolHandler != NULL) + m_protocolHandler->writeData(data, DV_FRAME_LENGTH_BYTES, errors, false); + + // Send the data for transmission, but only in duplex mode + if (m_mode == MODE_DUPLEX) { + if (m_blanking) + blankDTMF(data); + m_radioQueue.addData(data, DV_FRAME_LENGTH_BYTES, false); + } + } +} + +unsigned int CDStarRepeaterTRXThread::processNetworkFrame(unsigned char* data, unsigned int length, unsigned char seqNo) +{ + wxASSERT(data != NULL); + wxASSERT(length > 0U); + + if (m_rptState != DSRS_NETWORK) + return 0U; + + bool end = (seqNo & 0x40U) == 0x40U; + if (end) { + m_networkQueue[m_writeNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + endOfNetworkData(); + return 1U; + } + + // Mask out the control bits of the sequence number + seqNo &= 0x1FU; + + // Count the number of silence frames to insert + unsigned int tempSeqNo = m_networkSeqNo; + unsigned int count = 0U; + while (seqNo != tempSeqNo) { + count++; + + tempSeqNo++; + if (tempSeqNo >= 21U) + tempSeqNo = 0U; + } + + // If the number is too high, then it probably means an old out-of-order frame, ignore it + if (count > 18U) + return 0U; + + unsigned int packetCount = 0U; + + // Insert missing frames + while (seqNo != m_networkSeqNo) { + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + if (count > SILENCE_THRESHOLD) { + ::memcpy(buffer, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + } else { + ::memcpy(buffer, m_lastData, DV_FRAME_LENGTH_BYTES); + // Data packets have no AMBE FEC + if (!m_rxHeader->isDataPacket()) + m_ambe.regenerate(buffer); + blankDTMF(buffer); + } + + if (m_networkSeqNo == 0U) + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + m_networkQueue[m_writeNum]->addData(buffer, DV_FRAME_LENGTH_BYTES, false); + + packetCount++; + m_networkSeqNo++; + m_packetSilence++; + if (m_networkSeqNo >= 21U) + m_networkSeqNo = 0U; + } + + // Regenerate the sync bytes + if (m_networkSeqNo == 0U) { + ::memcpy(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + m_headerEncoder.sync(); + } else { + // If in Gateway mode, replace the slow data with our modified header when the slow data contains the + // original header + if (m_mode == MODE_GATEWAY) { + unsigned char dataType = 0U; + if ((m_networkSeqNo & 0x01U) == 0x01U) + dataType = (data[VOICE_FRAME_LENGTH_BYTES] ^ SCRAMBLER_BYTE1) & SLOW_DATA_TYPE_MASK; + + if (dataType == SLOW_DATA_TYPE_HEADER || m_lastSlowDataType == SLOW_DATA_TYPE_HEADER) { + unsigned char slowData[DATA_FRAME_LENGTH_BYTES]; + m_headerEncoder.getHeaderData(slowData); + ::memcpy(data + VOICE_FRAME_LENGTH_BYTES, slowData, DATA_FRAME_LENGTH_BYTES); + } + + if ((m_networkSeqNo & 0x01U) == 0x01U) + m_lastSlowDataType = dataType; + } + } + + packetCount++; + m_networkSeqNo++; + if (m_networkSeqNo >= 21U) + m_networkSeqNo = 0U; + + // Data packets have no AMBE FEC + if (!m_rxHeader->isDataPacket()) + m_ambe.regenerate(data); + + blankDTMF(data); + + m_networkQueue[m_writeNum]->addData(data, DV_FRAME_LENGTH_BYTES, false); + + return packetCount; +} + +void CDStarRepeaterTRXThread::endOfRadioData() +{ + switch (m_rptState) { + case DSRS_VALID: + wxLogMessage(wxT("AMBE for %s Frames: %.1fs, Silence: %.1f%%, BER: %.1f%%"), m_rxHeader->getMyCall1().c_str(), float(m_ambeFrames) / 50.0F, float(m_ambeSilence * 100U) / float(m_ambeFrames), float(m_ambeErrors * 100U) / float(m_ambeBits)); + + if (m_tempAckText.IsEmpty()) { + if (m_ack == AT_BER) { + // Create the ack text with the linked reflector and BER + wxString ackText; + if (m_linkStatus == LS_LINKED_DEXTRA || m_linkStatus == LS_LINKED_DPLUS || m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKED_CCS || m_linkStatus == LS_LINKED_LOOPBACK) + ackText.Printf(wxT("%-8s BER: %.1f%% "), m_reflector.c_str(), float(m_ambeErrors * 100U) / float(m_ambeBits)); + else + ackText.Printf(wxT("BER: %.1f%% "), float(m_ambeErrors * 100U) / float(m_ambeBits)); + m_ackEncoder.setTextData(ackText); + } else { + m_ackEncoder.setTextData(m_ackText); + } + } else { + m_ackEncoder.setTextData(m_tempAckText); + m_tempAckText.Clear(); + } + + if (m_ack != AT_NONE || m_mode == MODE_GATEWAY) { + setRepeaterState(DSRS_VALID_WAIT); + } else { + setRepeaterState(DSRS_LISTENING); + m_activeHangTimer.start(); + } + break; + + case DSRS_INVALID: + wxLogMessage(wxT("AMBE for %s Frames: %.1fs, Silence: %.1f%%, BER: %.1f%%"), m_rxHeader->getMyCall1().c_str(), float(m_ambeFrames) / 50.0F, float(m_ambeSilence * 100U) / float(m_ambeFrames), float(m_ambeErrors * 100U) / float(m_ambeBits)); + + if (m_ack != AT_NONE || m_mode == MODE_GATEWAY) { + setRepeaterState(DSRS_INVALID_WAIT); + } else { + setRepeaterState(DSRS_LISTENING); + m_activeHangTimer.start(); + } + break; + + case DSRS_TIMEOUT: + wxLogMessage(wxT("AMBE for %s Frames: %.1fs, Silence: %.1f%%, BER: %.1f%%"), m_rxHeader->getMyCall1().c_str(), float(m_ambeFrames) / 50.0F, float(m_ambeSilence * 100U) / float(m_ambeFrames), float(m_ambeErrors * 100U) / float(m_ambeBits)); + + if (m_tempAckText.IsEmpty()) { + if (m_ack == AT_BER) { + // Create the ack text with the linked reflector and BER + wxString ackText; + if (m_linkStatus == LS_LINKED_DEXTRA || m_linkStatus == LS_LINKED_DPLUS || m_linkStatus == LS_LINKED_DCS || m_linkStatus == LS_LINKED_CCS || m_linkStatus == LS_LINKED_LOOPBACK) + ackText.Printf(wxT("%-8s BER: %.1f%% "), m_reflector.c_str(), float(m_ambeErrors * 100U) / float(m_ambeBits)); + else + ackText.Printf(wxT("BER: %.1f%% "), float(m_ambeErrors * 100U) / float(m_ambeBits)); + m_ackEncoder.setTextData(ackText); + } else { + m_ackEncoder.setTextData(m_ackText); + } + } else { + m_ackEncoder.setTextData(m_tempAckText); + m_tempAckText.Clear(); + } + + if (m_ack != AT_NONE || m_mode == MODE_GATEWAY) { + setRepeaterState(DSRS_TIMEOUT_WAIT); + } else { + setRepeaterState(DSRS_LISTENING); + m_activeHangTimer.start(); + } + break; + + default: + break; + } +} + +void CDStarRepeaterTRXThread::endOfNetworkData() +{ + float loss = 0.0F; + if (m_packetCount != 0U) + loss = float(m_packetSilence) / float(m_packetCount); + + wxLogMessage(wxT("Stats for %s Frames: %.1fs, Loss: %.1f%%, Packets: %u/%u"), m_rxHeader->getMyCall1().c_str(), float(m_packetCount) / 50.0F, loss * 100.0F, m_packetSilence, m_packetCount); + + setRepeaterState(DSRS_LISTENING); + m_activeHangTimer.start(); + + m_writeNum++; + if (m_writeNum >= NETWORK_QUEUE_COUNT) + m_writeNum = 0U; +} + +CDStarRepeaterStatusData* CDStarRepeaterTRXThread::getStatus() +{ + CDStarRepeaterStatusData* status; + if (m_rptState == DSRS_SHUTDOWN || m_rptState == DSRS_LISTENING) { + status = new CDStarRepeaterStatusData(wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, + wxEmptyString, 0x00, 0x00, 0x00, m_tx, m_rxState, m_rptState, m_timeoutTimer.getTimer(), + m_timeoutTimer.getTimeout(), m_beaconTimer.getTimer(), m_beaconTimer.getTimeout(), 0.0F, + m_announcementTimer.getTimer(), m_announcementTimer.getTimeout(), m_ackText, m_statusText[0], + m_statusText[1], m_statusText[2], m_statusText[3], m_statusText[4]); + } else if (m_rptState == DSRS_NETWORK) { + float loss = 0.0F; + if (m_packetCount != 0U) + loss = float(m_packetSilence) / float(m_packetCount); + + status = new CDStarRepeaterStatusData(m_rxHeader->getMyCall1(), m_rxHeader->getMyCall2(), + m_rxHeader->getYourCall(), m_rxHeader->getRptCall1(), m_rxHeader->getRptCall2(), + m_rxHeader->getFlag1(), m_rxHeader->getFlag2(), m_rxHeader->getFlag3(), m_tx, m_rxState, + m_rptState, m_timeoutTimer.getTimer(), m_timeoutTimer.getTimeout(), m_beaconTimer.getTimer(), + m_beaconTimer.getTimeout(), m_announcementTimer.getTimer(), m_announcementTimer.getTimeout(), + loss * 100.0F, m_ackText, m_statusText[0], m_statusText[1], m_statusText[2], m_statusText[3], m_statusText[4]); + } else { + float bits = float(m_ambeBits - m_lastAMBEBits); + float errors = float(m_ambeErrors - m_lastAMBEErrors); + if (bits == 0.0F) + bits = 1.0F; + + m_lastAMBEBits = m_ambeBits; + m_lastAMBEErrors = m_ambeErrors; + + status = new CDStarRepeaterStatusData(m_rxHeader->getMyCall1(), m_rxHeader->getMyCall2(), + m_rxHeader->getYourCall(), m_rxHeader->getRptCall1(), m_rxHeader->getRptCall2(), + m_rxHeader->getFlag1(), m_rxHeader->getFlag2(), m_rxHeader->getFlag3(), m_tx, m_rxState, + m_rptState, m_timeoutTimer.getTimer(), m_timeoutTimer.getTimeout(), m_beaconTimer.getTimer(), + m_beaconTimer.getTimeout(), m_announcementTimer.getTimer(), m_announcementTimer.getTimeout(), + (errors * 100.0F) / bits, m_ackText, m_statusText[0], m_statusText[1], m_statusText[2], m_statusText[3], + m_statusText[4]); + } + + if (m_type.IsSameAs(wxT("DVAP")) && m_modem != NULL) { + CDVAPController* dvap = static_cast(m_modem); + bool squelch = dvap->getSquelch(); + int signal = dvap->getSignal(); + status->setDVAP(squelch, signal); + } + + return status; +} + +void CDStarRepeaterTRXThread::clock(unsigned int ms) +{ + m_pollTimer.clock(ms); + m_timeoutTimer.clock(ms); + m_watchdogTimer.clock(ms); + m_activeHangTimer.clock(ms); + m_ackTimer.clock(ms); + + for(int i = 0; i < 5; ++i) + m_statusAnnounceTimer[i].clock(ms); + + m_beaconTimer.clock(ms); + m_announcementTimer.clock(ms); + m_statusTimer.clock(ms); + m_heartbeatTimer.clock(ms); + if (m_beacon != NULL) + m_beacon->clock(); + if (m_announcement != NULL) + m_announcement->clock(); +} + +void CDStarRepeaterTRXThread::shutdown() +{ + m_shutdown = true; +} + +void CDStarRepeaterTRXThread::startup() +{ + m_shutdown = false; +} + +bool CDStarRepeaterTRXThread::checkControl(const CHeaderData& header) +{ + if (!m_controlEnabled) + return false; + + if (!m_controlRPT1.IsSameAs(header.getRptCall1()) || !m_controlRPT2.IsSameAs(header.getRptCall2())) + return false; + + for(int i = 0; i < m_controlCommand.GetCount(); ++i) { + if(m_controlCommand[i].IsSameAs(header.getYourCall())) { + wxThreadEvent evt(wxEVT_THREAD, wxEVT_THREAD_COMMAND); + evt.SetInt(i); + wxTheApp->QueueEvent(evt.Clone()); + wxLogMessage("Command %d requested by %s/%s", i, header.getMyCall1().c_str(), header.getMyCall2().c_str()); + return true; + } + } + + for(int i = 0; i < m_controlStatus.GetCount(); ++i) { + if(m_controlStatus[i].IsSameAs(header.getYourCall())) { + wxLogMessage(wxT("Status %d requested by %s/%s"), + i, header.getMyCall1().c_str(), + header.getMyCall2().c_str()); + m_statusAnnounceTimer[i].start(); + return true; + } + } + + for(int i = 0; i < m_controlOutput.GetCount(); ++i) { + if(m_controlOutput[i].IsSameAs(header.getYourCall())) { + wxLogMessage(wxT("Output %d requested by %s/%s"), i, + header.getMyCall1().c_str(), + header.getMyCall2().c_str()); + m_output[i] = !m_output[i]; + + // XXX These should be fixed in the controller code! + m_controller->setOutput1(m_output[0]); + m_controller->setOutput2(m_output[1]); + m_controller->setOutput3(m_output[2]); + m_controller->setOutput4(m_output[3]); + return true; + } + } + + if (m_controlShutdown.IsSameAs(header.getYourCall())) { + wxLogMessage(wxT("Shutdown requested by %s/%s"), header.getMyCall1().c_str(), header.getMyCall2().c_str()); + shutdown(); + } else if (m_controlStartup.IsSameAs(header.getYourCall())) { + wxLogMessage(wxT("Startup requested by %s/%s"), header.getMyCall1().c_str(), header.getMyCall2().c_str()); + startup(); + } else { + wxLogMessage(wxT("Invalid command of %s sent by %s/%s"), header.getYourCall().c_str(), header.getMyCall1().c_str(), header.getMyCall2().c_str()); + } + + return true; +} + +bool CDStarRepeaterTRXThread::checkAnnouncements(const CHeaderData& header) +{ + if (m_announcement == NULL) + return false; + + if (m_recordRPT1.IsSameAs(header.getRptCall1()) && m_recordRPT2.IsSameAs(header.getRptCall2())) { + wxLogMessage(wxT("Announcement creation requested by %s/%s"), header.getMyCall1().c_str(), header.getMyCall2().c_str()); + m_announcement->writeHeader(header); + m_recording = true; + return true; + } + + if (m_deleteRPT1.IsSameAs(header.getRptCall1()) && m_deleteRPT2.IsSameAs(header.getRptCall2())) { + wxLogMessage(wxT("Announcement deletion requested by %s/%s"), header.getMyCall1().c_str(), header.getMyCall2().c_str()); + m_announcement->deleteAnnouncement(); + m_deleting = true; + return true; + } + + return false; +} + +TRISTATE CDStarRepeaterTRXThread::checkHeader(CHeaderData& header) +{ + // If not in RPT1 validation mode, then a simplex header is converted to a proper repeater header + if (!m_rpt1Validation) { + if (!header.isRepeaterMode()) { + // Convert to a properly addressed repeater packet + header.setRepeaterMode(true); + header.setRptCall1(m_rptCallsign); + header.setRptCall2(m_gwyCallsign); + } + } + + // The repeater bit must be set when not in gateway mode + if (m_mode != MODE_GATEWAY) { + if (!header.isRepeaterMode()) { + wxLogMessage(wxT("Received a non-repeater packet, ignoring")); + return STATE_FALSE; + } + } else { + // Quietly reject acks when in gateway mode + if (header.isAck() || header.isNoResponse() || header.isRelayUnavailable()) + return STATE_UNKNOWN; + + // As a second check, reject on own UR call + wxString ur = header.getYourCall(); + if (ur.IsSameAs(m_rptCallsign) || ur.IsSameAs(m_gwyCallsign)) + return STATE_UNKNOWN; + + // Change RPT2 to be the gateway callsign in gateway mode + header.setRptCall2(m_gwyCallsign); + } + + wxString my = header.getMyCall1(); + + // Make sure MyCall is not empty, a silly value, or the repeater or gateway callsigns, STN* is a special case + if (!my.Left(3U).IsSameAs(wxT("STN"))) { + if (my.IsSameAs(m_rptCallsign) || + my.IsSameAs(m_gwyCallsign) || + my.IsSameAs(wxT(" ")) || + my.Left(6U).IsSameAs(wxT("NOCALL")) || + my.Left(6U).IsSameAs(wxT("N0CALL")) || + my.Left(6U).IsSameAs(wxT("MYCALL"))) { + wxLogMessage(wxT("Invalid MYCALL value of %s, ignoring"), my.c_str()); + return STATE_UNKNOWN; + } + } + + // Check for a French class 3 novice callsign, and reject + // Of the form F0xxx + if (my.Left(2U).IsSameAs(wxT("F0"))) { + wxLogMessage(wxT("French novice class licence callsign found, %s, ignoring"), my.c_str()); + return STATE_UNKNOWN; + } + + // Check for an Australian foundation class licence callsign, and reject + // Of the form VKnFxxx + if (my.Left(2U).IsSameAs(wxT("VK")) && my.GetChar(3U) == wxT('F') && my.GetChar(6U) != wxT(' ')) { + wxLogMessage(wxT("Australian foundation class licence callsign found, %s, ignoring"), my.c_str()); + return STATE_UNKNOWN; + } + + // Check the MyCall value against the regular expression + bool ok = m_regEx.Matches(my); + if (!ok) { + wxLogMessage(wxT("Invalid MYCALL value of %s, ignoring"), my.c_str()); + return STATE_UNKNOWN; + } + + // Is it for us? + if (!header.getRptCall1().IsSameAs(m_rptCallsign)) { + wxLogMessage(wxT("Invalid RPT1 value %s, ignoring"), header.getRptCall1().c_str()); + return STATE_FALSE; + } + + // If using callsign restriction, validate the my callsign + if (m_restriction) { + if (!my.Left(LONG_CALLSIGN_LENGTH - 1U).IsSameAs(m_rptCallsign.Left(LONG_CALLSIGN_LENGTH - 1U))) { + wxLogMessage(wxT("Unauthorised user %s tried to access the repeater"), my.c_str()); + return STATE_UNKNOWN; + } + } + + return STATE_TRUE; +} + +unsigned int CDStarRepeaterTRXThread::countBits(unsigned char byte) +{ + unsigned int bits = 0U; + + if ((byte & 0x01U) == 0x01U) + bits++; + if ((byte & 0x02U) == 0x02U) + bits++; + if ((byte & 0x04U) == 0x04U) + bits++; + if ((byte & 0x08U) == 0x08U) + bits++; + if ((byte & 0x10U) == 0x10U) + bits++; + if ((byte & 0x20U) == 0x20U) + bits++; + if ((byte & 0x40U) == 0x40U) + bits++; + if ((byte & 0x80U) == 0x80U) + bits++; + + return bits; +} + +void CDStarRepeaterTRXThread::blankDTMF(unsigned char* data) +{ + wxASSERT(data != NULL); + + // DTMF begins with these byte values + if ((data[0] & DTMF_MASK[0]) == DTMF_SIG[0] && (data[1] & DTMF_MASK[1]) == DTMF_SIG[1] && + (data[2] & DTMF_MASK[2]) == DTMF_SIG[2] && (data[3] & DTMF_MASK[3]) == DTMF_SIG[3] && + (data[4] & DTMF_MASK[4]) == DTMF_SIG[4] && (data[5] & DTMF_MASK[5]) == DTMF_SIG[5] && + (data[6] & DTMF_MASK[6]) == DTMF_SIG[6] && (data[7] & DTMF_MASK[7]) == DTMF_SIG[7] && + (data[8] & DTMF_MASK[8]) == DTMF_SIG[8]) + ::memcpy(data, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); +} diff --git a/DStarRepeater/DStarRepeaterTRXThread.h b/DStarRepeater/DStarRepeaterTRXThread.h new file mode 100644 index 0000000..4f7384a --- /dev/null +++ b/DStarRepeater/DStarRepeaterTRXThread.h @@ -0,0 +1,223 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterTRXThread_H +#define DStarRepeaterTRXThread_H + +#include "DStarRepeaterStatusData.h" +#include "RepeaterProtocolHandler.h" +#include "AnnouncementCallback.h" +#include "DStarRepeaterThread.h" +#include "DStarRepeaterDefs.h" +#include "DVTOOLFileWriter.h" +#include "AnnouncementUnit.h" +#include "SlowDataDecoder.h" +#include "SlowDataEncoder.h" +#include "BeaconCallback.h" +#include "CallsignList.h" +#include "OutputQueue.h" +#include "BeaconUnit.h" +#include "HeaderData.h" +#include "AMBEFEC.h" +#include "Timer.h" +#include "Utils.h" + +#include +#include + +class CDStarRepeaterTRXThread : public IDStarRepeaterThread, public IBeaconCallback, public IAnnouncementCallback { +public: + CDStarRepeaterTRXThread(const wxString& type); + virtual ~CDStarRepeaterTRXThread(); + + virtual void setCallsign(const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply); + virtual void setProtocolHandler(CRepeaterProtocolHandler* handler, bool local); + virtual void setModem(CModem* modem); + virtual void setController(CExternalController* controller, unsigned int activeHangTime); + virtual void setTimes(unsigned int timeout, unsigned int ackTime); + virtual void setBeacon(unsigned int time, const wxString& text, bool voice, TEXT_LANG language); + virtual void setAnnouncement(bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2); + + virtual void setControl(bool enabled, const wxString& rpt1Callsign, + const wxString& rpt2Callsign, const wxString& shutdown, + const wxString& startup, const wxArrayString& command, + const wxArrayString& status, const wxArrayString& outputs + ); + + virtual void setOutputs(bool out1, bool out2, bool out3, bool out4); + virtual void setLogging(bool logging, const wxString& dir); + virtual void setWhiteList(CCallsignList* list); + virtual void setBlackList(CCallsignList* list); + virtual void setGreyList(CCallsignList* list); + + virtual void shutdown(); + virtual void startup(); + + virtual CDStarRepeaterStatusData* getStatus(); + + virtual void *Entry(); + + virtual void kill(); + + virtual void transmitBeaconHeader(); + virtual void transmitBeaconData(const unsigned char* data, unsigned int length, bool end); + + virtual void transmitAnnouncementHeader(CHeaderData* header); + virtual void transmitAnnouncementData(const unsigned char* data, unsigned int length, bool end); + +private: + wxString m_type; + CModem* m_modem; + CRepeaterProtocolHandler* m_protocolHandler; + CExternalController* m_controller; + bool m_stopped; + wxString m_rptCallsign; + wxString m_gwyCallsign; + CBeaconUnit* m_beacon; + CAnnouncementUnit* m_announcement; + wxString m_recordRPT1; + wxString m_recordRPT2; + wxString m_deleteRPT1; + wxString m_deleteRPT2; + CHeaderData* m_rxHeader; + COutputQueue m_localQueue; + COutputQueue m_radioQueue; + COutputQueue** m_networkQueue; + unsigned int m_writeNum; + unsigned int m_readNum; + unsigned char m_radioSeqNo; + unsigned char m_networkSeqNo; + unsigned char m_lastSlowDataType; + CTimer m_timeoutTimer; + CTimer m_watchdogTimer; + CTimer m_pollTimer; + CTimer m_ackTimer; + + CTimer m_statusAnnounceTimer[5]; + + CTimer m_beaconTimer; + CTimer m_announcementTimer; + + CTimer m_statusTimer; + + CTimer m_heartbeatTimer; + DSTAR_RPT_STATE m_rptState; + DSTAR_RX_STATE m_rxState; + CSlowDataDecoder m_slowDataDecoder; + CSlowDataEncoder m_ackEncoder; + CSlowDataEncoder m_linkEncoder; + CSlowDataEncoder m_headerEncoder; + + // XXX ARRAY! + CSlowDataEncoder m_status1Encoder; + CSlowDataEncoder m_status2Encoder; + CSlowDataEncoder m_status3Encoder; + CSlowDataEncoder m_status4Encoder; + CSlowDataEncoder m_status5Encoder; + + // XXX ARRAY! + wxArrayString m_statusText; + + bool m_tx; + unsigned int m_space; + bool m_killed; + DSTAR_MODE m_mode; + ACK_TYPE m_ack; + bool m_restriction; + bool m_rpt1Validation; + bool m_errorReply; + bool m_controlEnabled; + wxString m_controlRPT1; + wxString m_controlRPT2; + wxString m_controlShutdown; + wxString m_controlStartup; + + wxArrayString m_controlStatus; + wxArrayString m_controlCommand; + + wxArrayString m_controlOutput; + + bool m_output[4]; + + CTimer m_activeHangTimer; + bool m_shutdown; + bool m_disable; + CDVTOOLFileWriter* m_logging; + unsigned char* m_lastData; + CAMBEFEC m_ambe; + unsigned int m_ambeFrames; + unsigned int m_ambeSilence; + unsigned int m_ambeBits; + unsigned int m_ambeErrors; + unsigned int m_lastAMBEBits; + unsigned int m_lastAMBEErrors; + wxString m_ackText; + wxString m_tempAckText; + LINK_STATUS m_linkStatus; + wxString m_reflector; + + wxRegEx m_regEx; + wxStopWatch m_headerTime; + wxStopWatch m_packetTime; + unsigned int m_packetCount; + unsigned int m_packetSilence; + CCallsignList* m_whiteList; + CCallsignList* m_blackList; + CCallsignList* m_greyList; + bool m_blocked; + bool m_busyData; + bool m_blanking; + bool m_recording; + bool m_deleting; + + void receiveHeader(CHeaderData* header); + void receiveRadioData(unsigned char* data, unsigned int length); + void receiveSlowData(unsigned char* data, unsigned int length); + void transmitRadioHeader(CHeaderData* header); + void transmitLocalHeader(CHeaderData* header); + void transmitNetworkHeader(CHeaderData* header); + void transmitStatus(); + void transmitErrorStatus(); + void transmitUserStatus(unsigned int n); + void transmitLocalData(); + void transmitRadioData(); + void transmitNetworkData(); + void transmitLocalHeader(); + void transmitRadioHeader(); + void transmitNetworkHeader(); + + void repeaterStateMachine(); + void receiveModem(); + void receiveNetwork(); + bool processRadioHeader(CHeaderData* header); + void processNetworkHeader(CHeaderData* header); + void processRadioFrame(unsigned char* data, FRAME_TYPE type); + unsigned int processNetworkFrame(unsigned char* data, unsigned int length, unsigned char seqNo); + void endOfRadioData(); + void endOfNetworkData(); + void setRadioState(DSTAR_RX_STATE state); + bool setRepeaterState(DSTAR_RPT_STATE state); + bool checkControl(const CHeaderData& header); + bool checkAnnouncements(const CHeaderData& header); + TRISTATE checkHeader(CHeaderData& header); + unsigned int countBits(unsigned char byte); + void clock(unsigned int ms); + void blankDTMF(unsigned char* data); +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterTXRXThread.cpp b/DStarRepeater/DStarRepeaterTXRXThread.cpp new file mode 100644 index 0000000..2465ea0 --- /dev/null +++ b/DStarRepeater/DStarRepeaterTXRXThread.cpp @@ -0,0 +1,913 @@ +/* + * Copyright (C) 2011-2016 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterStatusData.h" +#include "DStarRepeaterTXRXThread.h" +#include "DVAPController.h" +#include "DStarDefines.h" +#include "HeaderData.h" +#include "Version.h" +#include "Utils.h" + +const unsigned char DTMF_MASK[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x82U, 0x00U, 0x00U}; +const unsigned char DTMF_SIG[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; + +const unsigned int MAX_DATA_SYNC_BIT_ERRS = 2U; + +const unsigned int NETWORK_QUEUE_COUNT = 2U; + +const unsigned int SILENCE_THRESHOLD = 2U; + +const unsigned int CYCLE_TIME = 9U; + +CDStarRepeaterTXRXThread::CDStarRepeaterTXRXThread(const wxString& type) : +m_type(type), +m_modem(NULL), +m_protocolHandler(NULL), +m_controller(NULL), +m_rptCallsign(), +m_rxHeader(NULL), +m_txHeader(NULL), +m_networkQueue(NULL), +m_writeNum(0U), +m_readNum(0U), +m_radioSeqNo(0U), +m_networkSeqNo(0U), +m_watchdogTimer(1000U, NETWORK_TIMEOUT), +m_registerTimer(1000U), +m_statusTimer(1000U, 0U, 100U), // 100ms +m_heartbeatTimer(1000U, 1U), // 1s +m_rptState(DSRS_LISTENING), +m_rxState(DSRXS_LISTENING), +m_slowDataDecoder(), +m_tx(false), +m_transmitting(false), +m_space(0U), +m_killed(false), +m_activeHangTimer(1000U), +m_disable(false), +m_lastData(NULL), +m_ambe(), +m_ambeFrames(0U), +m_ambeSilence(0U), +m_ambeBits(1U), +m_ambeErrors(0U), +m_lastAMBEBits(0U), +m_lastAMBEErrors(0U), +m_headerTime(), +m_packetTime(), +m_packetCount(0U), +m_packetSilence(0U) +{ + m_networkQueue = new COutputQueue*[NETWORK_QUEUE_COUNT]; + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + m_networkQueue[i] = new COutputQueue((DV_FRAME_LENGTH_BYTES + 2U) * 200U, NETWORK_RUN_FRAME_COUNT); // 4s worth of data); + + m_lastData = new unsigned char[DV_FRAME_MAX_LENGTH_BYTES]; + + setRepeaterState(DSRS_LISTENING); + setRadioState(DSRXS_LISTENING); +} + +CDStarRepeaterTXRXThread::~CDStarRepeaterTXRXThread() +{ + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + delete m_networkQueue[i]; + delete[] m_networkQueue; + delete[] m_lastData; + delete m_rxHeader; + delete m_txHeader; +} + +void *CDStarRepeaterTXRXThread::Entry() +{ + // Wait here until we have the essentials to run + while (!m_killed && (m_modem == NULL || m_controller == NULL || m_protocolHandler == NULL || m_rptCallsign.IsEmpty() || m_rptCallsign.IsSameAs(wxT(" ")))) + ::wxMilliSleep(500UL); // 1/2 sec + + if (m_killed) + return NULL; + + m_controller->setActive(false); + m_controller->setRadioTransmit(false); + + m_heartbeatTimer.start(); + m_statusTimer.start(); + m_registerTimer.start(10U); + + wxString hardware = m_type; + int n = hardware.Find(wxT(' ')); + if (n != wxNOT_FOUND) + hardware = m_type.Left(n); + + wxLogMessage(wxT("Starting the D-Star transmitter and receiver thread")); + + wxStopWatch stopWatch; + + try { + while (!m_killed) { + stopWatch.Start(); + + if (m_statusTimer.hasExpired() || m_space == 0U) { + m_space = m_modem->getSpace(); + m_tx = m_modem->isTX(); + m_statusTimer.start(); + } + + receiveModem(); + + receiveNetwork(); + + repeaterStateMachine(); + + // Send the register packet if needed and restart the timer + if (m_registerTimer.hasExpired()) { + m_protocolHandler->writeRegister(); + m_registerTimer.start(30U); + } + + // Clock the heartbeat output every one second + if (m_heartbeatTimer.hasExpired()) { + m_controller->setHeartbeat(); + m_heartbeatTimer.start(); + } + + // Set the output state + if (m_tx || (m_activeHangTimer.isRunning() && !m_activeHangTimer.hasExpired())) { + m_controller->setActive(true); + } else { + m_controller->setActive(false); + m_activeHangTimer.stop(); + } + + // Check the shutdown state, state changes are done here to bypass the state machine which is + // frozen when m_disable is asserted + m_disable = m_controller->getDisable(); + if (m_disable) { + if (m_rptState != DSRS_SHUTDOWN) { + m_watchdogTimer.stop(); + m_activeHangTimer.stop(); + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + m_networkQueue[i]->reset(); + m_controller->setActive(false); + m_controller->setRadioTransmit(false); + m_rptState = DSRS_SHUTDOWN; + m_transmitting = false; + } + } else { + if (m_rptState == DSRS_SHUTDOWN) { + m_watchdogTimer.stop(); + m_rptState = DSRS_LISTENING; + m_protocolHandler->reset(); + m_transmitting = false; + } + } + + if (m_networkQueue[m_readNum]->dataReady()) + transmitNetworkData(); + else if (m_networkQueue[m_readNum]->headerReady()) + transmitNetworkHeader(); + + m_controller->setRadioTransmit(m_tx); + + unsigned long ms = stopWatch.Time(); + if (ms < CYCLE_TIME) { + ::wxMilliSleep(CYCLE_TIME - ms); + clock(CYCLE_TIME); + } else { + clock(ms); + } + } + } + catch (std::exception& e) { + wxString message(e.what(), wxConvLocal); + wxLogError(wxT("Exception raised - \"%s\""), message.c_str()); + } + catch (...) { + wxLogError(wxT("Unknown exception raised")); + } + + wxLogMessage(wxT("Stopping the D-Star transmitter and receiver thread")); + + m_modem->stop(); + + m_controller->setActive(false); + m_controller->setRadioTransmit(false); + m_controller->close(); + delete m_controller; + + m_protocolHandler->close(); + delete m_protocolHandler; + + return NULL; +} + +void CDStarRepeaterTXRXThread::kill() +{ + m_killed = true; +} + +void CDStarRepeaterTXRXThread::setCallsign(const wxString& callsign, const wxString&, DSTAR_MODE, ACK_TYPE, bool, bool, bool, bool) +{ + // Pad the callsign up to eight characters + m_rptCallsign = callsign; + m_rptCallsign.resize(LONG_CALLSIGN_LENGTH, wxT(' ')); +} + +void CDStarRepeaterTXRXThread::setProtocolHandler(CRepeaterProtocolHandler* handler, bool local) +{ + wxASSERT(handler != NULL); + + m_protocolHandler = handler; + + if (local) { + wxLogInfo(wxT("Reducing transmit buffering because of local connection")); + + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + m_networkQueue[i]->setThreshold(LOCAL_RUN_FRAME_COUNT); + } +} + +void CDStarRepeaterTXRXThread::setModem(CModem* modem) +{ + wxASSERT(modem != NULL); + + m_modem = modem; +} + +void CDStarRepeaterTXRXThread::setTimes(unsigned int, unsigned int) +{ +} + +void CDStarRepeaterTXRXThread::setBeacon(unsigned int, const wxString&, bool, TEXT_LANG) +{ +} + +void CDStarRepeaterTXRXThread::setAnnouncement(bool, unsigned int, const wxString&, const wxString&, const wxString&, const wxString&) +{ +} + +void CDStarRepeaterTXRXThread::setController(CExternalController* controller, unsigned int activeHangTime) +{ + wxASSERT(controller != NULL); + + m_controller = controller; + m_activeHangTimer.setTimeout(activeHangTime); +} + +void CDStarRepeaterTXRXThread::setOutputs(bool, bool, bool, bool) +{ +} + +void CDStarRepeaterTXRXThread::setLogging(bool, const wxString&) +{ +} + +void CDStarRepeaterTXRXThread::setWhiteList(CCallsignList*) +{ +} + +void CDStarRepeaterTXRXThread::setBlackList(CCallsignList*) +{ +} + +void CDStarRepeaterTXRXThread::setGreyList(CCallsignList*) +{ +} + +void CDStarRepeaterTXRXThread::receiveModem() +{ + for (;;) { + DSMT_TYPE type = m_modem->read(); + if (type == DSMTT_NONE) + return; + + switch (m_rxState) { + case DSRXS_LISTENING: + if (type == DSMTT_HEADER) { + CHeaderData* header = m_modem->readHeader(); + receiveHeader(header); + } else if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + setRadioState(DSRXS_PROCESS_SLOW_DATA); + receiveSlowData(data, length); + } + break; + + case DSRXS_PROCESS_SLOW_DATA: + if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + receiveSlowData(data, length); + } else if (type == DSMTT_EOT || type == DSMTT_LOST) { + setRadioState(DSRXS_LISTENING); + } + break; + + case DSRXS_PROCESS_DATA: + if (type == DSMTT_DATA) { + unsigned char data[20U]; + unsigned int length = m_modem->readData(data, 20U); + receiveRadioData(data, length); + } else if (type == DSMTT_EOT || type == DSMTT_LOST) { + unsigned char data[20U]; + ::memcpy(data, END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES); + processRadioFrame(data, FRAME_END); + setRadioState(DSRXS_LISTENING); + endOfRadioData(); + } + break; + } + } +} + +void CDStarRepeaterTXRXThread::receiveHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + wxLogMessage(wxT("Radio header decoded - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + bool res = processRadioHeader(header); + if (res) { + // A valid header and is a DV packet + m_radioSeqNo = 20U; + setRadioState(DSRXS_PROCESS_DATA); + } else { + // This is a DD packet or some other problem + // wxLogMessage(wxT("Invalid header")); + } +} + +void CDStarRepeaterTXRXThread::receiveSlowData(unsigned char* data, unsigned int) +{ + unsigned int errs; + errs = countBits(data[VOICE_FRAME_LENGTH_BYTES + 0U] ^ DATA_SYNC_BYTES[0U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 1U] ^ DATA_SYNC_BYTES[1U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 2U] ^ DATA_SYNC_BYTES[2U]); + + // The data sync has been seen, a fuzzy match is used, two bit errors or less + if (errs <= MAX_DATA_SYNC_BIT_ERRS) { + // wxLogMessage(wxT("Found data sync at frame %u, errs: %u"), m_radioSeqNo, errs); + m_radioSeqNo = 0U; + m_slowDataDecoder.sync(); + } else if (m_radioSeqNo == 20U) { + // wxLogMessage(wxT("Assuming data sync")); + m_radioSeqNo = 0U; + m_slowDataDecoder.sync(); + } else { + m_radioSeqNo++; + m_slowDataDecoder.addData(data + VOICE_FRAME_LENGTH_BYTES); + + CHeaderData* header = m_slowDataDecoder.getHeaderData(); + if (header == NULL) + return; + + wxLogMessage(wxT("Radio header from slow data - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X BER: 0%%"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + if (header != NULL) { + bool res = processRadioHeader(header); + if (res) { + // A valid header and is a DV packet, go to normal data relaying + setRadioState(DSRXS_PROCESS_DATA); + } else { + // This is a DD packet or some other problem + // wxLogMessage(wxT("Invalid header")); + } + } + } +} + +void CDStarRepeaterTXRXThread::receiveRadioData(unsigned char* data, unsigned int) +{ + unsigned int errs; + errs = countBits(data[VOICE_FRAME_LENGTH_BYTES + 0U] ^ DATA_SYNC_BYTES[0U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 1U] ^ DATA_SYNC_BYTES[1U]); + errs += countBits(data[VOICE_FRAME_LENGTH_BYTES + 2U] ^ DATA_SYNC_BYTES[2U]); + + // The data sync has been seen, a fuzzy match is used, two bit errors or less + if (errs <= MAX_DATA_SYNC_BIT_ERRS) { + // wxLogMessage(wxT("Found data sync at frame %u, errs: %u"), m_radioSeqNo, errs); + m_radioSeqNo = 0U; + processRadioFrame(data, FRAME_SYNC); + } else if (m_radioSeqNo == 20U) { + // wxLogMessage(wxT("Regenerating data sync")); + m_radioSeqNo = 0U; + processRadioFrame(data, FRAME_SYNC); + } else { + m_radioSeqNo++; + processRadioFrame(data, FRAME_NORMAL); + } +} + +void CDStarRepeaterTXRXThread::receiveNetwork() +{ + NETWORK_TYPE type; + + for (;;) { + type = m_protocolHandler->read(); + + // Get the data from the network + if (type == NETWORK_NONE) { // Nothing received + break; + } else if (type == NETWORK_HEADER) { // A header + CHeaderData* header = m_protocolHandler->readHeader(); + if (header != NULL) { + ::memcpy(m_lastData, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + + processNetworkHeader(header); + + m_headerTime.Start(); + m_packetTime.Start(); + m_packetCount = 0U; + m_packetSilence = 0U; + } + } else if (type == NETWORK_DATA) { // AMBE data and slow data + unsigned char data[2U * DV_FRAME_MAX_LENGTH_BYTES]; + ::memset(data, 0x00U, 2U * DV_FRAME_MAX_LENGTH_BYTES); + + unsigned char seqNo; + unsigned int length = m_protocolHandler->readData(data, DV_FRAME_MAX_LENGTH_BYTES, seqNo); + if (length != 0U && m_transmitting) { + ::memcpy(m_lastData, data, length); + m_watchdogTimer.start(); + m_packetCount += processNetworkFrame(data, length, seqNo); + } + } + } + + // Have we missed any data frames? + if (m_transmitting && m_packetTime.Time() > 200L) { + unsigned int packetsNeeded = m_headerTime.Time() / DSTAR_FRAME_TIME_MS; + + // wxLogMessage(wxT("Time: %u ms, need %u packets and received %u packets"), ms - m_headerMS, packetsNeeded, m_packetCount); + + if (packetsNeeded > m_packetCount) { + unsigned int count = packetsNeeded - m_packetCount; + + if (count > 5U) { + count -= 2U; + + // wxLogMessage(wxT("Inserting %u silence frames into the network data stream"), count); + + // Create silence frames + for (unsigned int i = 0U; i < count; i++) { + unsigned char data[DV_FRAME_LENGTH_BYTES]; + ::memcpy(data, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + m_packetCount += processNetworkFrame(data, DV_FRAME_LENGTH_BYTES, m_networkSeqNo); + m_packetSilence++; + } + } + } + + m_packetTime.Start(); + } +} + +void CDStarRepeaterTXRXThread::transmitNetworkHeader(const CHeaderData& header) +{ + wxLogMessage(wxT("Transmitting to - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header.getMyCall1().c_str(), header.getMyCall2().c_str(), header.getYourCall().c_str(), header.getRptCall1().c_str(), header.getRptCall2().c_str(), header.getFlag1(), header.getFlag2(), header.getFlag3()); + + bool empty = m_networkQueue[m_readNum]->isEmpty(); + if (!empty) { + bool headerReady = m_networkQueue[m_readNum]->headerReady(); + if (headerReady) { + // Transmission has never started, so just purge the queue + m_networkQueue[m_readNum]->reset(); + + m_readNum++; + if (m_readNum >= NETWORK_QUEUE_COUNT) + m_readNum = 0U; + } else { + // Append an end of stream + m_networkQueue[m_readNum]->reset(); + m_networkQueue[m_readNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + } + } + + m_networkQueue[m_writeNum]->reset(); + m_networkQueue[m_writeNum]->setHeader(new CHeaderData(header)); +} + +void CDStarRepeaterTXRXThread::transmitNetworkHeader() +{ + // Don't send a header until the modem is ready + bool ready = m_modem->isTXReady(); + if (!ready) + return; + + CHeaderData* header = m_networkQueue[m_readNum]->getHeader(); + if (header == NULL) + return; + + m_modem->writeHeader(*header); + delete header; +} + +void CDStarRepeaterTXRXThread::transmitNetworkData() +{ + if (m_space == 0U) + return; + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + bool end; + unsigned int length = m_networkQueue[m_readNum]->getData(buffer, DV_FRAME_LENGTH_BYTES, end); + + if (length == 0U) + return; + + m_modem->writeData(buffer, length, end); + m_space--; + + if (end) { + m_networkQueue[m_readNum]->reset(); + + m_readNum++; + if (m_readNum >= NETWORK_QUEUE_COUNT) + m_readNum = 0U; + } +} + +void CDStarRepeaterTXRXThread::repeaterStateMachine() +{ + if (m_watchdogTimer.isRunning() && m_watchdogTimer.hasExpired()) { + wxLogMessage(wxT("Network watchdog has expired")); + // Send end of transmission data to the radio + m_networkQueue[m_writeNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + endOfNetworkData(); + } +} + +void CDStarRepeaterTXRXThread::setRadioState(DSTAR_RX_STATE state) +{ + // This is the too state + switch (state) { + case DSRXS_LISTENING: + m_rxState = DSRXS_LISTENING; + break; + + case DSRXS_PROCESS_DATA: + m_ambeFrames = 0U; + m_ambeSilence = 0U; + m_ambeBits = 1U; + m_ambeErrors = 0U; + m_lastAMBEBits = 0U; + m_lastAMBEErrors = 0U; + m_rxState = DSRXS_PROCESS_DATA; + break; + + case DSRXS_PROCESS_SLOW_DATA: + m_radioSeqNo = 0U; + m_slowDataDecoder.reset(); + m_rxState = DSRXS_PROCESS_SLOW_DATA; + break; + } +} + +bool CDStarRepeaterTXRXThread::setRepeaterState(DSTAR_RPT_STATE state) +{ + // Can't change state when shutdown + if (m_disable) + return false; + + // The "to" state + switch (state) { + case DSRS_SHUTDOWN: + m_watchdogTimer.stop(); + m_activeHangTimer.stop(); + m_controller->setActive(false); + m_controller->setRadioTransmit(false); + m_rptState = DSRS_SHUTDOWN; + m_transmitting = false; + break; + + case DSRS_LISTENING: + m_rptState = DSRS_LISTENING; + break; + + case DSRS_VALID: + if (m_rptState != DSRS_LISTENING) + return false; + + m_rptState = DSRS_VALID; + break; + + default: + break; + } + + return true; +} + +bool CDStarRepeaterTXRXThread::processRadioHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + // We don't handle DD data packets + if (header->isDataPacket()) { + wxLogMessage(wxT("Received a DD packet, ignoring")); + delete header; + return false; + } + + setRepeaterState(DSRS_VALID); + + if (m_rptState == DSRS_VALID) { + // Send the valid header to the gateway if we are accepted + delete m_rxHeader; + m_rxHeader = header; + + CHeaderData netHeader(*m_rxHeader); + netHeader.setRptCall1(m_rxHeader->getRptCall2()); + netHeader.setRptCall2(m_rxHeader->getRptCall1()); + + m_protocolHandler->writeHeader(netHeader); + } else { + delete header; + } + + return true; +} + +void CDStarRepeaterTXRXThread::processNetworkHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + // If shutdown we ignore incoming headers + if (m_rptState == DSRS_SHUTDOWN) { + delete header; + return; + } + + wxLogMessage(wxT("Network header received - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + // Is it for us? + if (!header->getRptCall2().IsSameAs(m_rptCallsign)) { + wxLogMessage(wxT("Invalid network RPT2 value, ignoring")); + delete header; + return; + } + + delete m_txHeader; + m_txHeader = header; + + m_networkSeqNo = 0U; + m_transmitting = true; + m_watchdogTimer.start(); + m_activeHangTimer.stop(); + + transmitNetworkHeader(*header); +} + +void CDStarRepeaterTXRXThread::processRadioFrame(unsigned char* data, FRAME_TYPE type) +{ + m_ambeFrames++; + + // If a sync frame, regenerate the sync bytes + if (type == FRAME_SYNC) + ::memcpy(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + // Only regenerate the AMBE on received radio data + unsigned int errors = 0U; + if (type != FRAME_END) { + // Data packets have no AMBE FEC + if (!m_rxHeader->isDataPacket()) + errors = m_ambe.count(data); + + m_ambeErrors += errors; + m_ambeBits += 48U; // Only count the bits with FEC added + } + + if (::memcmp(data, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES) == 0) + m_ambeSilence++; + + // Don't pass through the frame of an invalid transmission + if (m_rptState != DSRS_VALID) + return; + + if (type == FRAME_END) { + // Send null data and the end marker over the network, and the statistics + unsigned char bytes[DV_FRAME_MAX_LENGTH_BYTES]; + ::memcpy(bytes, NULL_AMBE_DATA_BYTES, VOICE_FRAME_LENGTH_BYTES); + ::memcpy(bytes + VOICE_FRAME_LENGTH_BYTES, END_PATTERN_BYTES, END_PATTERN_LENGTH_BYTES); + m_protocolHandler->writeData(bytes, DV_FRAME_MAX_LENGTH_BYTES, 0U, true); + } else { + // Send the data to the network + m_protocolHandler->writeData(data, DV_FRAME_LENGTH_BYTES, errors, false); + } +} + +unsigned int CDStarRepeaterTXRXThread::processNetworkFrame(unsigned char* data, unsigned int length, unsigned char seqNo) +{ + wxASSERT(data != NULL); + wxASSERT(length > 0U); + + // If shutdown we ignore incoming data + if (m_rptState == DSRS_SHUTDOWN) + return 1U; + + bool end = (seqNo & 0x40U) == 0x40U; + if (end) { + m_networkQueue[m_writeNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + endOfNetworkData(); + return 1U; + } + + // Mask out the control bits of the sequence number + seqNo &= 0x1FU; + + // Count the number of silence frames to insert + unsigned int tempSeqNo = m_networkSeqNo; + unsigned int count = 0U; + while (seqNo != tempSeqNo) { + count++; + + tempSeqNo++; + if (tempSeqNo >= 21U) + tempSeqNo = 0U; + } + + // If the number is too high, then it probably means an old out-of-order frame, ignore it + if (count > 18U) + return 0U; + + unsigned int packetCount = 0U; + + // Insert missing frames + while (seqNo != m_networkSeqNo) { + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + if (count > SILENCE_THRESHOLD) { + ::memcpy(buffer, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + } else { + ::memcpy(buffer, m_lastData, DV_FRAME_LENGTH_BYTES); + // Data packets have no AMBE FEC + if (!m_txHeader->isDataPacket()) + m_ambe.regenerate(buffer); + } + + if (m_networkSeqNo == 0U) + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + m_networkQueue[m_writeNum]->addData(buffer, DV_FRAME_LENGTH_BYTES, false); + + packetCount++; + m_networkSeqNo++; + m_packetSilence++; + if (m_networkSeqNo >= 21U) + m_networkSeqNo = 0U; + } + + // Regenerate the sync bytes + if (m_networkSeqNo == 0U) + ::memcpy(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + packetCount++; + m_networkSeqNo++; + if (m_networkSeqNo >= 21U) + m_networkSeqNo = 0U; + + // Data packets have no AMBE FEC + if (!m_txHeader->isDataPacket()) + m_ambe.regenerate(data); + + m_networkQueue[m_writeNum]->addData(data, DV_FRAME_LENGTH_BYTES, false); + + return packetCount; +} + +void CDStarRepeaterTXRXThread::endOfRadioData() +{ + wxLogMessage(wxT("AMBE for %s Frames: %.1fs, Silence: %.1f%%, BER: %.1f%%"), m_rxHeader->getMyCall1().c_str(), float(m_ambeFrames) / 50.0F, float(m_ambeSilence * 100U) / float(m_ambeFrames), float(m_ambeErrors * 100U) / float(m_ambeBits)); + + setRepeaterState(DSRS_LISTENING); +} + +void CDStarRepeaterTXRXThread::endOfNetworkData() +{ + float loss = 0.0F; + if (m_packetCount != 0U) + loss = float(m_packetSilence) / float(m_packetCount); + + wxLogMessage(wxT("Stats for %s Frames: %.1fs, Loss: %.1f%%, Packets: %u/%u"), m_txHeader->getMyCall1().c_str(), float(m_packetCount) / 50.0F, loss * 100.0F, m_packetSilence, m_packetCount); + + m_watchdogTimer.stop(); + m_activeHangTimer.start(); + m_protocolHandler->reset(); + m_transmitting = false; + + m_writeNum++; + if (m_writeNum >= NETWORK_QUEUE_COUNT) + m_writeNum = 0U; +} + +CDStarRepeaterStatusData* CDStarRepeaterTXRXThread::getStatus() +{ + float bits = float(m_ambeBits - m_lastAMBEBits); + float errors = float(m_ambeErrors - m_lastAMBEErrors); + if (bits == 0.0F) + bits = 1.0F; + + m_lastAMBEBits = m_ambeBits; + m_lastAMBEErrors = m_ambeErrors; + + CDStarRepeaterStatusData* status; + if (m_rptState == DSRS_SHUTDOWN || m_rptState == DSRS_LISTENING) + status = new CDStarRepeaterStatusData(wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, + wxEmptyString, 0x00, 0x00, 0x00, m_tx, m_rxState, m_rptState, 0U, 0U, 0U, 0U, 0U, 0U, 0.0F, + wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString); + else + status = new CDStarRepeaterStatusData(m_rxHeader->getMyCall1(), m_rxHeader->getMyCall2(), + m_rxHeader->getYourCall(), m_rxHeader->getRptCall1(), m_rxHeader->getRptCall2(), + m_rxHeader->getFlag1(), m_rxHeader->getFlag2(), m_rxHeader->getFlag3(), m_tx, m_rxState, + m_rptState, 0U, 0U, 0U, 0U, 0U, 0U, (errors * 100.0F) / bits, wxEmptyString, wxEmptyString, + wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString); + + if (m_type.IsSameAs(wxT("DVAP")) && m_modem != NULL) { + CDVAPController* dvap = static_cast(m_modem); + bool squelch = dvap->getSquelch(); + int signal = dvap->getSignal(); + status->setDVAP(squelch, signal); + } + + return status; +} + +void CDStarRepeaterTXRXThread::clock(unsigned int ms) +{ + m_registerTimer.clock(ms); + m_watchdogTimer.clock(ms); + m_activeHangTimer.clock(ms); + m_statusTimer.clock(ms); + m_heartbeatTimer.clock(ms); +} + +void CDStarRepeaterTXRXThread::shutdown() +{ +} + +void CDStarRepeaterTXRXThread::startup() +{ +} + +void CDStarRepeaterTXRXThread::command1() +{ +} + +void CDStarRepeaterTXRXThread::command2() +{ +} + +void CDStarRepeaterTXRXThread::command3() +{ +} + +void CDStarRepeaterTXRXThread::command4() +{ +} + +void CDStarRepeaterTXRXThread::command5() +{ +} + +void CDStarRepeaterTXRXThread::command6() +{ +} + +unsigned int CDStarRepeaterTXRXThread::countBits(unsigned char byte) +{ + unsigned int bits = 0U; + + if ((byte & 0x01U) == 0x01U) + bits++; + if ((byte & 0x02U) == 0x02U) + bits++; + if ((byte & 0x04U) == 0x04U) + bits++; + if ((byte & 0x08U) == 0x08U) + bits++; + if ((byte & 0x10U) == 0x10U) + bits++; + if ((byte & 0x20U) == 0x20U) + bits++; + if ((byte & 0x40U) == 0x40U) + bits++; + if ((byte & 0x80U) == 0x80U) + bits++; + + return bits; +} diff --git a/DStarRepeater/DStarRepeaterTXRXThread.h b/DStarRepeater/DStarRepeaterTXRXThread.h new file mode 100644 index 0000000..b0d35c5 --- /dev/null +++ b/DStarRepeater/DStarRepeaterTXRXThread.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterTXRXThread_H +#define DStarRepeaterTXRXThread_H + +#include "DStarRepeaterStatusData.h" +#include "RepeaterProtocolHandler.h" +#include "DStarRepeaterThread.h" +#include "DStarRepeaterDefs.h" +#include "DVTOOLFileWriter.h" +#include "SlowDataDecoder.h" +#include "CallsignList.h" +#include "OutputQueue.h" +#include "HeaderData.h" +#include "AMBEFEC.h" +#include "Timer.h" + +#include + +class CDStarRepeaterTXRXThread : public IDStarRepeaterThread { +public: + CDStarRepeaterTXRXThread(const wxString& type); + virtual ~CDStarRepeaterTXRXThread(); + + virtual void setCallsign(const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply); + virtual void setProtocolHandler(CRepeaterProtocolHandler* handler, bool local); + virtual void setModem(CModem* modem); + virtual void setController(CExternalController* controller, unsigned int activeHangTime); + virtual void setTimes(unsigned int timeout, unsigned int ackTime); + virtual void setBeacon(unsigned int time, const wxString& text, bool voice, TEXT_LANG language); + virtual void setAnnouncement(bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2); + + virtual void setOutputs(bool out1, bool out2, bool out3, bool out4); + virtual void setLogging(bool logging, const wxString& dir); + virtual void setWhiteList(CCallsignList* list); + virtual void setBlackList(CCallsignList* list); + virtual void setGreyList(CCallsignList* list); + + virtual void shutdown(); + virtual void startup(); + + virtual void command1(); + virtual void command2(); + virtual void command3(); + virtual void command4(); + virtual void command5(); + virtual void command6(); + + virtual CDStarRepeaterStatusData* getStatus(); + + virtual void kill(); + + virtual void *Entry(); + +private: + wxString m_type; + CModem* m_modem; + CRepeaterProtocolHandler* m_protocolHandler; + CExternalController* m_controller; + wxString m_rptCallsign; + CHeaderData* m_rxHeader; + CHeaderData* m_txHeader; + COutputQueue** m_networkQueue; + unsigned int m_writeNum; + unsigned int m_readNum; + unsigned char m_radioSeqNo; + unsigned char m_networkSeqNo; + CTimer m_watchdogTimer; + CTimer m_registerTimer; + CTimer m_statusTimer; + CTimer m_heartbeatTimer; + DSTAR_RPT_STATE m_rptState; + DSTAR_RX_STATE m_rxState; + CSlowDataDecoder m_slowDataDecoder; + bool m_tx; + bool m_transmitting; + unsigned int m_space; + bool m_killed; + CTimer m_activeHangTimer; + bool m_disable; + unsigned char* m_lastData; + CAMBEFEC m_ambe; + unsigned int m_ambeFrames; + unsigned int m_ambeSilence; + unsigned int m_ambeBits; + unsigned int m_ambeErrors; + unsigned int m_lastAMBEBits; + unsigned int m_lastAMBEErrors; + wxStopWatch m_headerTime; + wxStopWatch m_packetTime; + unsigned int m_packetCount; + unsigned int m_packetSilence; + + void receiveHeader(CHeaderData* header); + void receiveRadioData(unsigned char* data, unsigned int length); + void receiveSlowData(unsigned char* data, unsigned int length); + void transmitNetworkHeader(const CHeaderData& header); + void transmitNetworkData(); + void transmitNetworkHeader(); + + void repeaterStateMachine(); + void receiveModem(); + void receiveNetwork(); + bool processRadioHeader(CHeaderData* header); + void processNetworkHeader(CHeaderData* header); + void processRadioFrame(unsigned char* data, FRAME_TYPE type); + unsigned int processNetworkFrame(unsigned char* data, unsigned int length, unsigned char seqNo); + void endOfRadioData(); + void endOfNetworkData(); + void setRadioState(DSTAR_RX_STATE state); + bool setRepeaterState(DSTAR_RPT_STATE state); + unsigned int countBits(unsigned char byte); + void clock(unsigned int ms); +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterTXThread.cpp b/DStarRepeater/DStarRepeaterTXThread.cpp new file mode 100644 index 0000000..062b991 --- /dev/null +++ b/DStarRepeater/DStarRepeaterTXThread.cpp @@ -0,0 +1,536 @@ +/* + * Copyright (C) 2011-2016 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterStatusData.h" +#include "DStarRepeaterTXThread.h" +#include "DStarDefines.h" +#include "HeaderData.h" +#include "Version.h" + +const unsigned char DTMF_MASK[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x82U, 0x00U, 0x00U}; +const unsigned char DTMF_SIG[] = {0x82U, 0x08U, 0x20U, 0x82U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U}; + +const unsigned int MAX_DATA_SYNC_BIT_ERRS = 2U; + +const unsigned int NETWORK_QUEUE_COUNT = 2U; + +const unsigned int SILENCE_THRESHOLD = 2U; + +const unsigned int CYCLE_TIME = 9U; + +CDStarRepeaterTXThread::CDStarRepeaterTXThread(const wxString& type) : +m_type(type), +m_modem(NULL), +m_protocolHandler(NULL), +m_stopped(true), +m_rptCallsign(), +m_txHeader(NULL), +m_networkQueue(NULL), +m_writeNum(0U), +m_readNum(0U), +m_networkSeqNo(0U), +m_watchdogTimer(1000U, NETWORK_TIMEOUT), +m_registerTimer(1000U), +m_statusTimer(1000U, 0U, 100U), // 100ms +m_state(DSRS_LISTENING), +m_tx(false), +m_space(0U), +m_killed(false), +m_lastData(NULL), +m_ambe(), +m_headerTime(), +m_packetTime(), +m_packetCount(0U), +m_packetSilence(0U) +{ + m_networkQueue = new COutputQueue*[NETWORK_QUEUE_COUNT]; + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + m_networkQueue[i] = new COutputQueue((DV_FRAME_LENGTH_BYTES + 2U) * 200U, NETWORK_RUN_FRAME_COUNT); // 4s worth of data); + + m_lastData = new unsigned char[DV_FRAME_MAX_LENGTH_BYTES]; +} + +CDStarRepeaterTXThread::~CDStarRepeaterTXThread() +{ + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + delete m_networkQueue[i]; + delete[] m_networkQueue; + delete[] m_lastData; + delete m_txHeader; +} + +void *CDStarRepeaterTXThread::Entry() +{ + // Wait here until we have the essentials to run + while (!m_killed && (m_modem == NULL || m_protocolHandler == NULL || m_rptCallsign.IsEmpty() || m_rptCallsign.IsSameAs(wxT(" ")))) + ::wxMilliSleep(500UL); // 1/2 sec + + if (m_killed) + return NULL; + + m_stopped = false; + + m_registerTimer.start(10U); + m_statusTimer.start(); + + wxString hardware = m_type; + int n = hardware.Find(wxT(' ')); + if (n != wxNOT_FOUND) + hardware = m_type.Left(n); + + wxLogMessage(wxT("Starting the D-Star transmitter thread")); + + wxStopWatch stopWatch; + + try { + while (!m_killed) { + stopWatch.Start(); + + if (m_statusTimer.hasExpired() || m_space == 0U) { + m_space = m_modem->getSpace(); + m_tx = m_modem->isTX(); + m_statusTimer.start(); + } + + receiveNetwork(); + + receiveModem(); + + if (m_state == DSRS_NETWORK) { + if (m_watchdogTimer.hasExpired()) { + wxLogMessage(wxT("Network watchdog has expired")); + // Send end of transmission data to the radio + m_networkQueue[m_writeNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + endOfNetworkData(); + } + } + + // Send the register packet if needed and restart the timer + if (m_registerTimer.hasExpired()) { + m_protocolHandler->writeRegister(); + m_registerTimer.start(30U); + } + + if (m_networkQueue[m_readNum]->dataReady()) + transmitNetworkData(); + else if (m_networkQueue[m_readNum]->headerReady()) + transmitNetworkHeader(); + + unsigned long ms = stopWatch.Time(); + if (ms < CYCLE_TIME) { + ::wxMilliSleep(CYCLE_TIME - ms); + clock(CYCLE_TIME); + } else { + clock(ms); + } + } + } + catch (std::exception& e) { + wxString message(e.what(), wxConvLocal); + wxLogError(wxT("Exception raised - \"%s\""), message.c_str()); + } + catch (...) { + wxLogError(wxT("Unknown exception raised")); + } + + wxLogMessage(wxT("Stopping the D-Star transmitter thread")); + + m_modem->stop(); + + m_protocolHandler->close(); + delete m_protocolHandler; + + return NULL; +} + +void CDStarRepeaterTXThread::kill() +{ + m_killed = true; +} + +void CDStarRepeaterTXThread::setCallsign(const wxString& callsign, const wxString&, DSTAR_MODE, ACK_TYPE, bool, bool, bool, bool) +{ + // Pad the callsign up to eight characters + m_rptCallsign = callsign; + m_rptCallsign.resize(LONG_CALLSIGN_LENGTH, wxT(' ')); +} + +void CDStarRepeaterTXThread::setProtocolHandler(CRepeaterProtocolHandler* handler, bool local) +{ + wxASSERT(handler != NULL); + + m_protocolHandler = handler; + + if (local) { + wxLogInfo(wxT("Reducing transmit buffering because of local connection")); + + for (unsigned int i = 0U; i < NETWORK_QUEUE_COUNT; i++) + m_networkQueue[i]->setThreshold(LOCAL_RUN_FRAME_COUNT); + } +} + +void CDStarRepeaterTXThread::setModem(CModem* modem) +{ + wxASSERT(modem != NULL); + + m_modem = modem; +} + +void CDStarRepeaterTXThread::setTimes(unsigned int, unsigned int) +{ +} + +void CDStarRepeaterTXThread::setBeacon(unsigned int, const wxString&, bool, TEXT_LANG) +{ +} + +void CDStarRepeaterTXThread::setAnnouncement(bool, unsigned int, const wxString&, const wxString&, const wxString&, const wxString&) +{ +} + +void CDStarRepeaterTXThread::setController(CExternalController*, unsigned int) +{ +} + +void CDStarRepeaterTXThread::setOutputs(bool, bool, bool, bool) +{ +} + +void CDStarRepeaterTXThread::setLogging(bool, const wxString&) +{ +} + +void CDStarRepeaterTXThread::setWhiteList(CCallsignList*) +{ +} + +void CDStarRepeaterTXThread::setBlackList(CCallsignList*) +{ +} + +void CDStarRepeaterTXThread::setGreyList(CCallsignList*) +{ +} + +void CDStarRepeaterTXThread::receiveModem() +{ + for (;;) { + DSMT_TYPE type = m_modem->read(); + if (type == DSMTT_NONE) + return; + } +} + +void CDStarRepeaterTXThread::receiveNetwork() +{ + NETWORK_TYPE type; + + for (;;) { + type = m_protocolHandler->read(); + + // Get the data from the network + if (type == NETWORK_NONE) { // Nothing received + break; + } else if (type == NETWORK_HEADER) { // A header + CHeaderData* header = m_protocolHandler->readHeader(); + if (header != NULL) { + ::memcpy(m_lastData, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + + processNetworkHeader(header); + + m_headerTime.Start(); + m_packetTime.Start(); + m_packetCount = 0U; + m_packetSilence = 0U; + } + } else if (type == NETWORK_DATA) { // AMBE data and slow data + unsigned char data[2U * DV_FRAME_MAX_LENGTH_BYTES]; + ::memset(data, 0x00U, 2U * DV_FRAME_MAX_LENGTH_BYTES); + + unsigned char seqNo; + unsigned int length = m_protocolHandler->readData(data, DV_FRAME_MAX_LENGTH_BYTES, seqNo); + if (length != 0U) { + ::memcpy(m_lastData, data, length); + m_watchdogTimer.start(); + m_packetCount += processNetworkFrame(data, length, seqNo); + } + } + } + + // Have we missed any data frames? + if (m_state == DSRS_NETWORK && m_packetTime.Time() > 200L) { + unsigned int packetsNeeded = m_headerTime.Time() / DSTAR_FRAME_TIME_MS; + + // wxLogMessage(wxT("Time: %u ms, need %u packets and received %u packets"), ms - m_headerMS, packetsNeeded, m_packetCount); + + if (packetsNeeded > m_packetCount) { + unsigned int count = packetsNeeded - m_packetCount; + + if (count > 5U) { + count -= 2U; + + // wxLogMessage(wxT("Inserting %u silence frames into the network data stream"), count); + + // Create silence frames + for (unsigned int i = 0U; i < count; i++) { + unsigned char data[DV_FRAME_LENGTH_BYTES]; + ::memcpy(data, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + m_packetCount += processNetworkFrame(data, DV_FRAME_LENGTH_BYTES, m_networkSeqNo); + m_packetSilence++; + } + } + } + + m_packetTime.Start(); + } +} + +void CDStarRepeaterTXThread::transmitNetworkHeader(CHeaderData* header) +{ + wxLogMessage(wxT("Transmitting to - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + bool empty = m_networkQueue[m_readNum]->isEmpty(); + if (!empty) { + bool headerReady = m_networkQueue[m_readNum]->headerReady(); + if (headerReady) { + // Transmission has never started, so just purge the queue + m_networkQueue[m_readNum]->reset(); + + m_readNum++; + if (m_readNum >= NETWORK_QUEUE_COUNT) + m_readNum = 0U; + } else { + // Append an end of stream + m_networkQueue[m_readNum]->reset(); + m_networkQueue[m_readNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + } + } + + m_networkQueue[m_writeNum]->reset(); + m_networkQueue[m_writeNum]->setHeader(header); +} + +void CDStarRepeaterTXThread::transmitNetworkHeader() +{ + // Don't send a header until the modem is ready + bool ready = m_modem->isTXReady(); + if (!ready) + return; + + CHeaderData* header = m_networkQueue[m_readNum]->getHeader(); + if (header == NULL) + return; + + m_modem->writeHeader(*header); + delete header; +} + +void CDStarRepeaterTXThread::transmitNetworkData() +{ + if (m_space == 0U) + return; + + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + bool end; + unsigned int length = m_networkQueue[m_readNum]->getData(buffer, DV_FRAME_LENGTH_BYTES, end); + + if (length == 0U) + return; + + m_modem->writeData(buffer, length, end); + m_space--; + + if (end) { + m_networkQueue[m_readNum]->reset(); + + m_readNum++; + if (m_readNum >= NETWORK_QUEUE_COUNT) + m_readNum = 0U; + } +} + +void CDStarRepeaterTXThread::processNetworkHeader(CHeaderData* header) +{ + wxASSERT(header != NULL); + + wxLogMessage(wxT("Network header received - My: %s/%s Your: %s Rpt1: %s Rpt2: %s Flags: %02X %02X %02X"), header->getMyCall1().c_str(), header->getMyCall2().c_str(), header->getYourCall().c_str(), header->getRptCall1().c_str(), header->getRptCall2().c_str(), header->getFlag1(), header->getFlag2(), header->getFlag3()); + + // Is it for us? + if (!header->getRptCall2().IsSameAs(m_rptCallsign)) { + wxLogMessage(wxT("Invalid network RPT2 value, ignoring")); + delete header; + return; + } + + m_state = DSRS_NETWORK; + m_networkSeqNo = 0U; + m_watchdogTimer.start(); + + delete m_txHeader; + m_txHeader = header; + + transmitNetworkHeader(new CHeaderData(*header)); +} + +unsigned int CDStarRepeaterTXThread::processNetworkFrame(unsigned char* data, unsigned int length, unsigned char seqNo) +{ + wxASSERT(data != NULL); + wxASSERT(length > 0U); + + bool end = (seqNo & 0x40U) == 0x40U; + if (end) { + m_networkQueue[m_writeNum]->addData(END_PATTERN_BYTES, DV_FRAME_LENGTH_BYTES, true); + endOfNetworkData(); + return 1U; + } + + // Mask out the control bits of the sequence number + seqNo &= 0x1FU; + + // Count the number of silence frames to insert + unsigned int tempSeqNo = m_networkSeqNo; + unsigned int count = 0U; + while (seqNo != tempSeqNo) { + count++; + + tempSeqNo++; + if (tempSeqNo >= 21U) + tempSeqNo = 0U; + } + + // If the number is too high, then it probably means an old out-of-order frame, ignore it + if (count > 18U) + return 0U; + + unsigned int packetCount = 0U; + + // Insert missing frames + while (seqNo != m_networkSeqNo) { + unsigned char buffer[DV_FRAME_LENGTH_BYTES]; + if (count > SILENCE_THRESHOLD) { + ::memcpy(buffer, NULL_FRAME_DATA_BYTES, DV_FRAME_LENGTH_BYTES); + } else { + ::memcpy(buffer, m_lastData, DV_FRAME_LENGTH_BYTES); + // Data packets have no AMBE FEC + if (!m_txHeader->isDataPacket()) + m_ambe.regenerate(buffer); + } + + if (m_networkSeqNo == 0U) + ::memcpy(buffer + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + m_networkQueue[m_writeNum]->addData(buffer, DV_FRAME_LENGTH_BYTES, false); + + packetCount++; + m_networkSeqNo++; + m_packetSilence++; + if (m_networkSeqNo >= 21U) + m_networkSeqNo = 0U; + } + + // Regenerate the sync bytes + if (m_networkSeqNo == 0U) + ::memcpy(data + VOICE_FRAME_LENGTH_BYTES, DATA_SYNC_BYTES, DATA_FRAME_LENGTH_BYTES); + + packetCount++; + m_networkSeqNo++; + if (m_networkSeqNo >= 21U) + m_networkSeqNo = 0U; + + // Data packets have no AMBE FEC + if (!m_txHeader->isDataPacket()) + m_ambe.regenerate(data); + + m_networkQueue[m_writeNum]->addData(data, DV_FRAME_LENGTH_BYTES, false); + + return packetCount; +} + +void CDStarRepeaterTXThread::endOfNetworkData() +{ + float loss = 0.0F; + if (m_packetCount != 0U) + loss = float(m_packetSilence) / float(m_packetCount); + + wxLogMessage(wxT("Stats for %s Frames: %.1fs, Loss: %.1f%%, Packets: %u/%u"), m_txHeader->getMyCall1().c_str(), float(m_packetCount) / 50.0F, loss * 100.0F, m_packetSilence, m_packetCount); + + m_state = DSRS_LISTENING; + m_watchdogTimer.stop(); + m_protocolHandler->reset(); + + m_writeNum++; + if (m_writeNum >= NETWORK_QUEUE_COUNT) + m_writeNum = 0U; +} + +CDStarRepeaterStatusData* CDStarRepeaterTXThread::getStatus() +{ + float loss = 0.0F; + if (m_packetCount != 0U) + loss = float(m_packetSilence) / float(m_packetCount); + + if (m_state == DSRS_LISTENING) + return new CDStarRepeaterStatusData(wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, + wxEmptyString, 0x00, 0x00, 0x00, m_tx, DSRXS_LISTENING, m_state, 0U, 0U, 0U, 0U, 0U, 0U, 0.0F, + wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString); + else + return new CDStarRepeaterStatusData(m_txHeader->getMyCall1(), m_txHeader->getMyCall2(), + m_txHeader->getYourCall(), m_txHeader->getRptCall1(), m_txHeader->getRptCall2(), + m_txHeader->getFlag1(), m_txHeader->getFlag2(), m_txHeader->getFlag3(), m_tx, DSRXS_LISTENING, + m_state, 0U, 0U, 0U, 0U, 0U, 0U, loss * 100.0F, wxEmptyString, wxEmptyString, wxEmptyString, wxEmptyString, + wxEmptyString, wxEmptyString); +} + +void CDStarRepeaterTXThread::clock(unsigned int ms) +{ + m_registerTimer.clock(ms); + m_watchdogTimer.clock(ms); + m_statusTimer.clock(ms); +} + +void CDStarRepeaterTXThread::shutdown() +{ +} + +void CDStarRepeaterTXThread::startup() +{ +} + +void CDStarRepeaterTXThread::command1() +{ +} + +void CDStarRepeaterTXThread::command2() +{ +} + +void CDStarRepeaterTXThread::command3() +{ +} + +void CDStarRepeaterTXThread::command4() +{ +} + +void CDStarRepeaterTXThread::command5() +{ +} + +void CDStarRepeaterTXThread::command6() +{ +} diff --git a/DStarRepeater/DStarRepeaterTXThread.h b/DStarRepeater/DStarRepeaterTXThread.h new file mode 100644 index 0000000..a1d8f9f --- /dev/null +++ b/DStarRepeater/DStarRepeaterTXThread.h @@ -0,0 +1,101 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterTXThread_H +#define DStarRepeaterTXThread_H + +#include "DStarRepeaterThread.h" +#include "OutputQueue.h" +#include "HeaderData.h" +#include "AMBEFEC.h" +#include "Timer.h" + +#include + +class CDStarRepeaterTXThread : public IDStarRepeaterThread { +public: + CDStarRepeaterTXThread(const wxString& type); + virtual ~CDStarRepeaterTXThread(); + + virtual void setCallsign(const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply); + virtual void setProtocolHandler(CRepeaterProtocolHandler* handler, bool local); + virtual void setModem(CModem* modem); + virtual void setController(CExternalController* controller, unsigned int activeHangTime); + virtual void setTimes(unsigned int timeout, unsigned int ackTime); + virtual void setBeacon(unsigned int time, const wxString& text, bool voice, TEXT_LANG language); + virtual void setAnnouncement(bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2); + virtual void setOutputs(bool out1, bool out2, bool out3, bool out4); + virtual void setLogging(bool logging, const wxString& dir); + virtual void setWhiteList(CCallsignList* list); + virtual void setBlackList(CCallsignList* list); + virtual void setGreyList(CCallsignList* list); + + virtual void shutdown(); + virtual void startup(); + + virtual void command1(); + virtual void command2(); + virtual void command3(); + virtual void command4(); + virtual void command5(); + virtual void command6(); + + virtual CDStarRepeaterStatusData* getStatus(); + + virtual void kill(); + + virtual void *Entry(); + +private: + wxString m_type; + CModem* m_modem; + CRepeaterProtocolHandler* m_protocolHandler; + bool m_stopped; + wxString m_rptCallsign; + CHeaderData* m_txHeader; + COutputQueue** m_networkQueue; + unsigned int m_writeNum; + unsigned int m_readNum; + unsigned char m_networkSeqNo; + CTimer m_watchdogTimer; + CTimer m_registerTimer; + CTimer m_statusTimer; + DSTAR_RPT_STATE m_state; + bool m_tx; + unsigned int m_space; + bool m_killed; + unsigned char* m_lastData; + CAMBEFEC m_ambe; + wxStopWatch m_headerTime; + wxStopWatch m_packetTime; + unsigned int m_packetCount; + unsigned int m_packetSilence; + + void transmitNetworkHeader(CHeaderData* header); + void transmitNetworkData(); + void transmitNetworkHeader(); + + void receiveModem(); + void receiveNetwork(); + void processNetworkHeader(CHeaderData* header); + unsigned int processNetworkFrame(unsigned char* data, unsigned int length, unsigned char seqNo); + void endOfNetworkData(); + void clock(unsigned int ms); +}; + +#endif diff --git a/DStarRepeater/DStarRepeaterThread.cpp b/DStarRepeater/DStarRepeaterThread.cpp new file mode 100644 index 0000000..b698094 --- /dev/null +++ b/DStarRepeater/DStarRepeaterThread.cpp @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2013 by Jonathan Naylor, G4KLX + * + * 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; version 2 of the License. + * + * 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. + */ + +#include "DStarRepeaterThread.h" + +IDStarRepeaterThread::IDStarRepeaterThread(): +wxThread(wxTHREAD_JOINABLE) +{ +} + +IDStarRepeaterThread::~IDStarRepeaterThread() +{ +} diff --git a/DStarRepeater/DStarRepeaterThread.h b/DStarRepeater/DStarRepeaterThread.h new file mode 100644 index 0000000..5df4b93 --- /dev/null +++ b/DStarRepeater/DStarRepeaterThread.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterThread_H +#define DStarRepeaterThread_H + +#include "DStarRepeaterStatusData.h" +#include "RepeaterProtocolHandler.h" +#include "ExternalController.h" +#include "DStarRepeaterDefs.h" +#include "CallsignList.h" +#include "Modem.h" + +#include + +enum FRAME_TYPE { + FRAME_NORMAL, + FRAME_SYNC, + FRAME_END +}; + +class IDStarRepeaterThread : public wxThread { +public: + IDStarRepeaterThread(); + + virtual ~IDStarRepeaterThread() = 0; + + virtual void setCallsign(const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply) = 0; + + virtual void setProtocolHandler(CRepeaterProtocolHandler* handler, bool local) = 0; + virtual void setModem(CModem* modem) = 0; + virtual void setController(CExternalController* controller, unsigned int activeHangTime) = 0; + + virtual void setTimes(unsigned int timeout, unsigned int ackTime) = 0; + + virtual void setBeacon(unsigned int time, const wxString& text, bool voice, TEXT_LANG language) = 0; + virtual void setAnnouncement(bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2) = 0; + virtual void setControl(bool enabled, const wxString& rpt1Callsign, + const wxString& rpt2Callsign, const wxString& shutdown, + const wxString& startup, const wxArrayString &command, + const wxArrayString& status, const wxArrayString& outputs + ) { }; + virtual void setOutputs(bool out1, bool out2, bool out3, bool out4) = 0; + virtual void setLogging(bool logging, const wxString& dir) = 0; + + virtual void setWhiteList(CCallsignList* list) = 0; + virtual void setBlackList(CCallsignList* list) = 0; + virtual void setGreyList(CCallsignList* list) = 0; + + virtual void shutdown() = 0; + virtual void startup() = 0; + + virtual CDStarRepeaterStatusData* getStatus() = 0; + + virtual void kill() = 0; + +private: +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfig.vcxproj b/DStarRepeaterConfig/DStarRepeaterConfig.vcxproj new file mode 100644 index 0000000..e7aca24 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfig.vcxproj @@ -0,0 +1,228 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {FBAE8201-11CC-4256-BF39-A860B9DF963C} + DStarRepeaterConfig + Win32Proj + 10.0.17134.0 + + + + Application + v141 + Unicode + true + + + Application + v141 + Unicode + true + + + Application + v141 + Unicode + + + Application + v141 + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.24720.0 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + true + + + true + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + false + + + false + + + + Disabled + $(WXWIN)\include\msvc;$(SolutionDir)..\portaudio\include;$(SolutionDir)Common;$(SolutionDir)GUICommon;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;__WXDEBUG__;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level4 + EditAndContinue + + + portaudio_x86.lib;wsock32.lib;setupapi.lib;hid.lib;%(AdditionalDependencies) + $(SolutionDir)..\portaudio\build\msvc\Win32\Debug;$(WXWIN)\lib\vc_dll;%(AdditionalLibraryDirectories) + true + Windows + MachineX86 + + + + + Disabled + $(WXWIN)\include\msvc;$(SolutionDir)..\portaudio\include;$(SolutionDir)Common;$(SolutionDir)GUICommon;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;__WXDEBUG__;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level4 + ProgramDatabase + + + portaudio_x64.lib;wsock32.lib;setupapi.lib;hid.lib;%(AdditionalDependencies) + $(SolutionDir)..\portaudio\build\msvc\x64\Debug;$(WXWIN)\lib\vc_x64_dll;%(AdditionalLibraryDirectories) + true + Windows + + + + + MaxSpeed + true + $(WXWIN)\include\msvc;$(SolutionDir)..\portaudio\include;$(SolutionDir)Common;$(SolutionDir)GUICommon;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + Level3 + ProgramDatabase + + + portaudio_x86.lib;wsock32.lib;setupapi.lib;hid.lib;%(AdditionalDependencies) + $(SolutionDir)..\portaudio\build\msvc\Win32\Release;$(WXWIN)\lib\vc_dll;%(AdditionalLibraryDirectories) + true + Windows + true + true + MachineX86 + + + + + MaxSpeed + true + $(WXWIN)\include\msvc;$(SolutionDir)..\portaudio\include;$(SolutionDir)Common;$(SolutionDir)GUICommon;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + portaudio_x64.lib;wsock32.lib;setupapi.lib;hid.lib;%(AdditionalDependencies) + $(SolutionDir)..\portaudio\build\msvc\x64\Release;$(WXWIN)\lib\vc_x64_dll;%(AdditionalLibraryDirectories) + true + Windows + true + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {3753ef20-2330-415e-b933-2560a474498b} + false + + + {2c548aab-a9f7-496f-bcf5-6185bda304d2} + false + + + + + + \ No newline at end of file diff --git a/DStarRepeaterConfig/DStarRepeaterConfig.vcxproj.filters b/DStarRepeaterConfig/DStarRepeaterConfig.vcxproj.filters new file mode 100644 index 0000000..520c410 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfig.vcxproj.filters @@ -0,0 +1,146 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/DStarRepeaterConfig/DStarRepeaterConfigActiveHangSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigActiveHangSet.cpp new file mode 100644 index 0000000..34dff7d --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigActiveHangSet.cpp @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigActiveHangSet.h" + +const unsigned int TIMED_WIDTH = 300U; + +const unsigned int BORDER_SIZE = 5U; + +CDStarRepeaterConfigActiveHangSet::CDStarRepeaterConfigActiveHangSet(wxWindow* parent, int id, const wxString& title, unsigned int time) : +wxPanel(parent, id), +m_title(title), +m_time(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* timeLabel = new wxStaticText(this, -1, _("Time (secs)")); + sizer->Add(timeLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_time = new wxSlider(this, -1, time, 0, 300, wxDefaultPosition, wxSize(TIMED_WIDTH, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_time, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigActiveHangSet::~CDStarRepeaterConfigActiveHangSet() +{ +} + +bool CDStarRepeaterConfigActiveHangSet::Validate() +{ + return true; +} + +unsigned int CDStarRepeaterConfigActiveHangSet::getTime() const +{ + return m_time->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigActiveHangSet.h b/DStarRepeaterConfig/DStarRepeaterConfigActiveHangSet.h new file mode 100644 index 0000000..da7bda3 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigActiveHangSet.h @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2010,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigActiveHangSet_H +#define DStarRepeaterConfigActiveHangSet_H + +#include + +class CDStarRepeaterConfigActiveHangSet : public wxPanel { +public: + CDStarRepeaterConfigActiveHangSet(wxWindow* parent, int id, const wxString& title, unsigned int time); + virtual ~CDStarRepeaterConfigActiveHangSet(); + + virtual bool Validate(); + + virtual unsigned int getTime() const; + +private: + wxString m_title; + wxSlider* m_time; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigAnnouncementSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigAnnouncementSet.cpp new file mode 100644 index 0000000..a594ad2 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigAnnouncementSet.cpp @@ -0,0 +1,130 @@ +/* + * Copyright (C) 2013,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigAnnouncementSet.h" + +const unsigned int CONTROL_SIZE = 100U; +const unsigned int TIMES_WIDTH = 300U; +const unsigned int BORDER_SIZE = 5U; + +CDStarRepeaterConfigAnnouncementSet::CDStarRepeaterConfigAnnouncementSet(wxWindow* parent, int id, const wxString& title, bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2) : +wxPanel(parent, id), +m_title(title), +m_enabled(NULL), +m_time(NULL), +m_recordRPT1(NULL), +m_recordRPT2(NULL), +m_deleteRPT1(NULL), +m_deleteRPT2(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* enabledText = new wxStaticText(this, -1, _("Enabled")); + sizer->Add(enabledText, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_enabled = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_SIZE, -1)); + m_enabled->Append(_("Disabled")); + m_enabled->Append(_("Enabled")); + sizer->Add(m_enabled, 0, wxALL, BORDER_SIZE); + m_enabled->SetSelection(enabled ? 1 : 0); + + wxStaticText* timeLabel = new wxStaticText(this, -1, _("Time (mins)")); + sizer->Add(timeLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_time = new wxSlider(this, -1, time / 60U, 0, 30, wxDefaultPosition, wxSize(TIMES_WIDTH, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_time, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* recordRPT1Text = new wxStaticText(this, -1, _("Record RPT1")); + sizer->Add(recordRPT1Text, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_recordRPT1 = new CCallsignTextCtrl(this, -1, recordRPT1, wxDefaultPosition, wxSize(CONTROL_SIZE, -1)); + m_recordRPT1->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_recordRPT1, 0, wxALL, BORDER_SIZE); + + wxStaticText* recordRPT2Text = new wxStaticText(this, -1, _("Record RPT2")); + sizer->Add(recordRPT2Text, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_recordRPT2 = new CCallsignTextCtrl(this, -1, recordRPT2, wxDefaultPosition, wxSize(CONTROL_SIZE, -1)); + m_recordRPT2->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_recordRPT2, 0, wxALL, BORDER_SIZE); + + wxStaticText* deleteRPT1Text = new wxStaticText(this, -1, _("Delete RPT1")); + sizer->Add(deleteRPT1Text, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_deleteRPT1 = new CCallsignTextCtrl(this, -1, deleteRPT1, wxDefaultPosition, wxSize(CONTROL_SIZE, -1)); + m_deleteRPT1->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_deleteRPT1, 0, wxALL, BORDER_SIZE); + + wxStaticText* deleteRPT2Text = new wxStaticText(this, -1, _("Delete RPT2")); + sizer->Add(deleteRPT2Text, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_deleteRPT2 = new CCallsignTextCtrl(this, -1, deleteRPT2, wxDefaultPosition, wxSize(CONTROL_SIZE, -1)); + m_deleteRPT2->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_deleteRPT2, 0, wxALL, BORDER_SIZE); + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigAnnouncementSet::~CDStarRepeaterConfigAnnouncementSet() +{ +} + +bool CDStarRepeaterConfigAnnouncementSet::Validate() +{ + return m_enabled->GetCurrentSelection() != wxNOT_FOUND; +} + +bool CDStarRepeaterConfigAnnouncementSet::getEnabled() const +{ + int n = m_enabled->GetCurrentSelection(); + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +unsigned int CDStarRepeaterConfigAnnouncementSet::getTime() const +{ + return m_time->GetValue() * 60U; +} + +wxString CDStarRepeaterConfigAnnouncementSet::getRecordRPT1() const +{ + return m_recordRPT1->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigAnnouncementSet::getRecordRPT2() const +{ + return m_recordRPT2->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigAnnouncementSet::getDeleteRPT1() const +{ + return m_deleteRPT1->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigAnnouncementSet::getDeleteRPT2() const +{ + return m_deleteRPT2->GetValue().MakeUpper(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigAnnouncementSet.h b/DStarRepeaterConfig/DStarRepeaterConfigAnnouncementSet.h new file mode 100644 index 0000000..bc6bca8 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigAnnouncementSet.h @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2013,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigAnnouncementSet_H +#define DStarRepeaterConfigAnnouncementSet_H + +#include "CallsignTextCtrl.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigAnnouncementSet : public wxPanel { +public: + CDStarRepeaterConfigAnnouncementSet(wxWindow* parent, int id, const wxString& title, bool enabled, unsigned int time, const wxString& recordRPT1, const wxString& recordRPT2, const wxString& deleteRPT1, const wxString& deleteRPT2); + virtual ~CDStarRepeaterConfigAnnouncementSet(); + + virtual bool Validate(); + + virtual bool getEnabled() const; + virtual unsigned int getTime() const; + virtual wxString getRecordRPT1() const; + virtual wxString getRecordRPT2() const; + virtual wxString getDeleteRPT1() const; + virtual wxString getDeleteRPT2() const; + +private: + wxString m_title; + wxChoice* m_enabled; + wxSlider* m_time; + CCallsignTextCtrl* m_recordRPT1; + CCallsignTextCtrl* m_recordRPT2; + CCallsignTextCtrl* m_deleteRPT1; + CCallsignTextCtrl* m_deleteRPT2; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigApp.cpp b/DStarRepeaterConfig/DStarRepeaterConfigApp.cpp new file mode 100644 index 0000000..2567784 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigApp.cpp @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigApp.h" +#include "DStarDefines.h" +#include "Version.h" + +#include +#include + +IMPLEMENT_APP(CDStarRepeaterConfigApp) + +const wxChar* NAME_PARAM = wxT("Repeater Name"); +const wxChar* CONFDIR_OPTION = wxT("confdir"); + + +CDStarRepeaterConfigApp::CDStarRepeaterConfigApp() : +wxApp(), +m_name(), +m_confDir(), +m_frame(NULL) +{ +} + +CDStarRepeaterConfigApp::~CDStarRepeaterConfigApp() +{ +} + +bool CDStarRepeaterConfigApp::OnInit() +{ + SetVendorName(VENDOR_NAME); + + if (!wxApp::OnInit()) + return false; + + wxString frameName = APPLICATION_NAME + wxT(" - "); + if (!m_name.IsEmpty()) { + frameName.Append(m_name); + frameName.Append(wxT(" - ")); + } + frameName.Append(VERSION); + + m_frame = new CDStarRepeaterConfigFrame(frameName, m_confDir, m_name); + m_frame->Show(); + + SetTopWindow(m_frame); + + return true; +} + +int CDStarRepeaterConfigApp::OnExit() +{ + return 0; +} + +void CDStarRepeaterConfigApp::OnInitCmdLine(wxCmdLineParser& parser) +{ + parser.AddOption(CONFDIR_OPTION, wxEmptyString, wxEmptyString, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + parser.AddParam(NAME_PARAM, wxCMD_LINE_VAL_STRING, wxCMD_LINE_PARAM_OPTIONAL); + + wxApp::OnInitCmdLine(parser); +} + +bool CDStarRepeaterConfigApp::OnCmdLineParsed(wxCmdLineParser& parser) +{ + if (!wxApp::OnCmdLineParsed(parser)) + return false; + + wxString confDir; + bool found = parser.Found(CONFDIR_OPTION, &confDir); + if (found) + m_confDir = confDir; + + if (parser.GetParamCount() > 0U) + m_name = parser.GetParam(0U); + + return true; +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigApp.h b/DStarRepeaterConfig/DStarRepeaterConfigApp.h new file mode 100644 index 0000000..0013955 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigApp.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigApp_H +#define DStarRepeaterConfigApp_H + +#include "DStarRepeaterConfigFrame.h" +#include "DStarRepeaterConfigDefs.h" + +#include + +class CDStarRepeaterConfigApp : public wxApp { + +public: + CDStarRepeaterConfigApp(); + virtual ~CDStarRepeaterConfigApp(); + + virtual bool OnInit(); + virtual int OnExit(); + + virtual void OnInitCmdLine(wxCmdLineParser& parser); + virtual bool OnCmdLineParsed(wxCmdLineParser& parser); + +private: + wxString m_name; + wxString m_confDir; + CDStarRepeaterConfigFrame* m_frame; +}; + +DECLARE_APP(CDStarRepeaterConfigApp) + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigBeaconSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigBeaconSet.cpp new file mode 100644 index 0000000..053c72b --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigBeaconSet.cpp @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2012,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigBeaconSet.h" + +const unsigned int MAX_MESSAGE_LENGTH = 20U; + +const unsigned int CONTROL_SIZE1 = 200U; +const unsigned int CONTROL_SIZE2 = 100U; +const unsigned int TIMES_WIDTH = 300U; +const unsigned int BORDER_SIZE = 5U; + +CDStarRepeaterConfigBeaconSet::CDStarRepeaterConfigBeaconSet(wxWindow* parent, int id, const wxString& title, unsigned int time, const wxString& text, bool voice, TEXT_LANG language) : +wxPanel(parent, id), +m_title(title), +m_time(NULL), +m_text(NULL), +m_voice(NULL), +m_language(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* timeLabel = new wxStaticText(this, -1, _("Time (mins)")); + sizer->Add(timeLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_time = new wxSlider(this, -1, time / 60U, 0, 30, wxDefaultPosition, wxSize(TIMES_WIDTH, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_time, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* messageText = new wxStaticText(this, -1, _("Message")); + sizer->Add(messageText, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_text = new CMessageTextCtrl(this, -1, text, wxDefaultPosition, wxSize(CONTROL_SIZE1, -1)); + m_text->SetMaxLength(MAX_MESSAGE_LENGTH); + sizer->Add(m_text, 0, wxALL, BORDER_SIZE); + + wxStaticText* voiceText = new wxStaticText(this, -1, _("Voice")); + sizer->Add(voiceText, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_voice = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_SIZE2, -1)); + m_voice->Append(_("Disabled")); + m_voice->Append(_("Enabled")); + sizer->Add(m_voice, 0, wxALL, BORDER_SIZE); + m_voice->SetSelection(voice ? 1 : 0); + + wxStaticText* languageLabel = new wxStaticText(this, -1, _("Language")); + sizer->Add(languageLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_language = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_SIZE2, -1)); + m_language->Append(wxT("English (UK)")); + m_language->Append(wxT("Deutsch")); + m_language->Append(wxT("Dansk")); + m_language->Append(wxT("Francais")); + m_language->Append(wxT("Italiano")); + m_language->Append(wxT("Polski")); + m_language->Append(wxT("Espanol")); + m_language->Append(wxT("Svenska")); + m_language->Append(wxT("Nederlands")); + m_language->Append(wxT("English (US)")); + m_language->Append(wxT("Norsk")); + sizer->Add(m_language, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_language->SetSelection(int(language)); + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigBeaconSet::~CDStarRepeaterConfigBeaconSet() +{ +} + +bool CDStarRepeaterConfigBeaconSet::Validate() +{ + if (m_voice->GetCurrentSelection() == wxNOT_FOUND) + return false; + + return m_language->GetCurrentSelection() != wxNOT_FOUND; +} + +unsigned int CDStarRepeaterConfigBeaconSet::getTime() const +{ + return m_time->GetValue() * 60U; +} + +wxString CDStarRepeaterConfigBeaconSet::getText() const +{ + return m_text->GetValue(); +} + +bool CDStarRepeaterConfigBeaconSet::getVoice() const +{ + int n = m_voice->GetSelection(); + + return n == 1; +} + +TEXT_LANG CDStarRepeaterConfigBeaconSet::getLanguage() const +{ + int n = m_language->GetCurrentSelection(); + if (n == wxNOT_FOUND) + return TL_ENGLISH_UK; + + return TEXT_LANG(n); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigBeaconSet.h b/DStarRepeaterConfig/DStarRepeaterConfigBeaconSet.h new file mode 100644 index 0000000..b9a0256 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigBeaconSet.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigBeaconSet_H +#define DStarRepeaterConfigBeaconSet_H + +#include "MessageTextCtrl.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigBeaconSet : public wxPanel { +public: + CDStarRepeaterConfigBeaconSet(wxWindow* parent, int id, const wxString& title, unsigned int time, const wxString& text, bool voice, TEXT_LANG language); + virtual ~CDStarRepeaterConfigBeaconSet(); + + virtual bool Validate(); + + virtual unsigned int getTime() const; + virtual wxString getText() const; + virtual bool getVoice() const; + virtual TEXT_LANG getLanguage() const; + +private: + wxString m_title; + wxSlider* m_time; + CMessageTextCtrl* m_text; + wxChoice* m_voice; + wxChoice* m_language; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigCallsignSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigCallsignSet.cpp new file mode 100644 index 0000000..609277b --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigCallsignSet.cpp @@ -0,0 +1,334 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigCallsignSet.h" + +const unsigned int CALLSIGN_WIDTH = 80U; +const unsigned int SUFFIX_WIDTH = 50U; + +const unsigned int BORDER_SIZE = 5U; + +const int CHOICE_MODE = 8745; + +BEGIN_EVENT_TABLE(CDStarRepeaterConfigCallsignSet, wxPanel) + EVT_CHOICE(CHOICE_MODE, CDStarRepeaterConfigCallsignSet::onMode) +END_EVENT_TABLE() + + +CDStarRepeaterConfigCallsignSet::CDStarRepeaterConfigCallsignSet(wxWindow* parent, int id, const wxString& title, const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply) : +wxPanel(parent, id), +m_title(title), +m_callsign(NULL), +m_gateway(NULL), +m_suffix(NULL), +m_mode(NULL), +m_ack(NULL), +m_restriction(NULL), +m_rpt1Validation(NULL), +m_dtmfBlanking(NULL), +m_errorReply(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(3); + + wxStaticText* callsignLabel = new wxStaticText(this, -1, _("Callsign")); + sizer->Add(callsignLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + wxString call = callsign; + call.Append(wxT(" ")); + call.Truncate(LONG_CALLSIGN_LENGTH); + + m_callsign = new CCallsignTextCtrl(this, -1, call.Left(LONG_CALLSIGN_LENGTH - 1U).Trim(), wxDefaultPosition, wxSize(CALLSIGN_WIDTH, -1)); + m_callsign->SetMaxLength(LONG_CALLSIGN_LENGTH - 1U); + sizer->Add(m_callsign, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_suffix = new wxChoice(this, -1, wxDefaultPosition, wxSize(SUFFIX_WIDTH, -1)); +#if defined(__WXDEBUG__) + m_suffix->Append(wxT(" ")); +#endif + m_suffix->Append(wxT("A")); + m_suffix->Append(wxT("B")); + m_suffix->Append(wxT("C")); + m_suffix->Append(wxT("D")); + m_suffix->Append(wxT("E")); + sizer->Add(m_suffix, 0, wxALL, BORDER_SIZE); + + wxString suffix = callsign.Right(1U); + bool res = m_suffix->SetStringSelection(suffix); + if (!res) + m_suffix->SetStringSelection(wxT("B")); + + wxStaticText* gatewayLabel = new wxStaticText(this, -1, _("Gateway")); + sizer->Add(gatewayLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + call = gateway; + call.Append(wxT(" ")); + call.Truncate(LONG_CALLSIGN_LENGTH); + + m_gateway = new CCallsignTextCtrl(this, -1, call.Left(LONG_CALLSIGN_LENGTH - 1U).Trim(), wxDefaultPosition, wxSize(CALLSIGN_WIDTH, -1)); + m_gateway->SetMaxLength(LONG_CALLSIGN_LENGTH - 1U); + sizer->Add(m_gateway, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* gLabel = new wxStaticText(this, -1, wxT("G")); + sizer->Add(gLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* modeLabel = new wxStaticText(this, -1, _("Mode")); + sizer->Add(modeLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_mode = new wxChoice(this, CHOICE_MODE, wxDefaultPosition, wxSize(CALLSIGN_WIDTH, -1)); + m_mode->Append(_("Duplex")); + m_mode->Append(_("Simplex")); + m_mode->Append(_("Gateway")); + m_mode->Append(_("TX Only")); + m_mode->Append(_("RX Only")); + m_mode->Append(_("TX and RX")); + sizer->Add(m_mode, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_mode->SetSelection(int(mode)); + + wxStaticText* dummy1 = new wxStaticText(this, -1, wxEmptyString); + sizer->Add(dummy1, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* ackLabel = new wxStaticText(this, -1, _("Ack")); + sizer->Add(ackLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_ack = new wxChoice(this, -1, wxDefaultPosition, wxSize(CALLSIGN_WIDTH, -1)); + m_ack->Append(_("Off")); + m_ack->Append(wxT("BER")); + m_ack->Append(_("Status")); + sizer->Add(m_ack, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_ack->SetSelection(int(ack)); + + wxStaticText* dummy2 = new wxStaticText(this, -1, wxEmptyString); + sizer->Add(dummy2, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* restrictionLabel = new wxStaticText(this, -1, _("Restrict")); + sizer->Add(restrictionLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_restriction = new wxChoice(this, -1, wxDefaultPosition, wxSize(CALLSIGN_WIDTH, -1)); + m_restriction->Append(_("Off")); + m_restriction->Append(_("On")); + sizer->Add(m_restriction, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_restriction->SetSelection(restriction ? 1 : 0); + + wxStaticText* dummy3 = new wxStaticText(this, -1, wxEmptyString); + sizer->Add(dummy3, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* rpt1ValidationLabel = new wxStaticText(this, -1, _("RPT1 Validation")); + sizer->Add(rpt1ValidationLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_rpt1Validation = new wxChoice(this, -1, wxDefaultPosition, wxSize(CALLSIGN_WIDTH, -1)); + m_rpt1Validation->Append(_("Off")); + m_rpt1Validation->Append(_("On")); + sizer->Add(m_rpt1Validation, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_rpt1Validation->SetSelection(rpt1Validation ? 1 : 0); + + wxStaticText* dummy4 = new wxStaticText(this, -1, wxEmptyString); + sizer->Add(dummy4, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* dtmfBlankingLabel = new wxStaticText(this, -1, _("DTMF Blanking")); + sizer->Add(dtmfBlankingLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_dtmfBlanking = new wxChoice(this, -1, wxDefaultPosition, wxSize(CALLSIGN_WIDTH, -1)); + m_dtmfBlanking->Append(_("Off")); + m_dtmfBlanking->Append(_("On")); + sizer->Add(m_dtmfBlanking, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_dtmfBlanking->SetSelection(dtmfBlanking ? 1 : 0); + + wxStaticText* dummy5 = new wxStaticText(this, -1, wxEmptyString); + sizer->Add(dummy5, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* errorReplyLabel = new wxStaticText(this, -1, _("Error Reply")); + sizer->Add(errorReplyLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_errorReply = new wxChoice(this, -1, wxDefaultPosition, wxSize(CALLSIGN_WIDTH, -1)); + m_errorReply->Append(_("Off")); + m_errorReply->Append(_("On")); + sizer->Add(m_errorReply, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_errorReply->SetSelection(errorReply ? 1 : 0); + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigCallsignSet::~CDStarRepeaterConfigCallsignSet() +{ +} + +bool CDStarRepeaterConfigCallsignSet::Validate() +{ + bool res = getCallsign().IsEmpty(); + if (res) { + wxMessageDialog dialog(this, _("The Callsign may not be empty"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + if (getCallsign().Left(2U).IsSameAs(wxT("F0"))) { + wxMessageDialog dialog(this, _("The Callsign may not be for a French novice"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + res = getGateway().IsEmpty(); + if (res) { + wxMessageDialog dialog(this, _("The Gateway may not be empty"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + if (m_suffix->GetSelection() == wxNOT_FOUND) { + wxMessageDialog dialog(this, _("The Callsign suffix is not set"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + if (m_mode->GetSelection() == wxNOT_FOUND) { + wxMessageDialog dialog(this, _("Mode is not set"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + if (m_ack->GetSelection() == wxNOT_FOUND) { + wxMessageDialog dialog(this, _("Ack is not set"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + if (m_restriction->GetSelection() == wxNOT_FOUND) { + wxMessageDialog dialog(this, _("Restrict is not set"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + if (m_rpt1Validation->GetSelection() == wxNOT_FOUND) { + wxMessageDialog dialog(this, _("RPT1 Validation is not set"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + if (m_dtmfBlanking->GetSelection() == wxNOT_FOUND) { + wxMessageDialog dialog(this, _("DTMF Blanking is not set"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + if (m_errorReply->GetSelection() == wxNOT_FOUND) { + wxMessageDialog dialog(this, _("Error Reply is not set"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + return true; +} + +wxString CDStarRepeaterConfigCallsignSet::getCallsign() const +{ + wxString callsign = m_callsign->GetValue().MakeUpper(); + wxString suffix = m_suffix->GetStringSelection(); + + callsign.Append(wxT(" ")); + callsign.Truncate(LONG_CALLSIGN_LENGTH - 1U); + callsign.Append(suffix); + + return callsign; +} + +wxString CDStarRepeaterConfigCallsignSet::getGateway() const +{ + wxString gateway = m_gateway->GetValue().MakeUpper(); + + gateway.Append(wxT(" ")); + gateway.Truncate(LONG_CALLSIGN_LENGTH - 1U); + gateway.Append(wxT("G")); + + return gateway; +} + +DSTAR_MODE CDStarRepeaterConfigCallsignSet::getMode() const +{ + int n = m_mode->GetCurrentSelection(); + + return DSTAR_MODE(n); +} + +ACK_TYPE CDStarRepeaterConfigCallsignSet::getAck() const +{ + int n = m_ack->GetCurrentSelection(); + if (n == wxNOT_FOUND) + return AT_BER; + + return ACK_TYPE(n); +} + +bool CDStarRepeaterConfigCallsignSet::getRestriction() const +{ + int n = m_restriction->GetCurrentSelection(); + + return n == 1; +} + +void CDStarRepeaterConfigCallsignSet::onMode(wxCommandEvent &event) +{ + DSTAR_MODE mode = DSTAR_MODE(event.GetSelection()); + + switch (mode) { + case MODE_GATEWAY: + m_ack->SetSelection(0); // Ack off + m_rpt1Validation->SetSelection(1); // RPT1 Validation on + m_ack->Disable(); + m_rpt1Validation->Disable(); + break; + + case MODE_TXONLY: + case MODE_RXONLY: + case MODE_TXANDRX: + m_ack->SetSelection(0); // Ack off + m_ack->Disable(); + break; + + default: + m_ack->Enable(); + m_rpt1Validation->Enable(); + break; + } +} + +bool CDStarRepeaterConfigCallsignSet::getRPT1Validation() const +{ + int n = m_rpt1Validation->GetCurrentSelection(); + + return n == 1; +} + +bool CDStarRepeaterConfigCallsignSet::getDTMFBlanking() const +{ + int n = m_dtmfBlanking->GetCurrentSelection(); + + return n == 1; +} + +bool CDStarRepeaterConfigCallsignSet::getErrorReply() const +{ + int n = m_errorReply->GetCurrentSelection(); + + return n == 1; +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigCallsignSet.h b/DStarRepeaterConfig/DStarRepeaterConfigCallsignSet.h new file mode 100644 index 0000000..4190223 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigCallsignSet.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigCallsignSet_H +#define DStarRepeaterConfigCallsignSet_H + +#include "CallsignTextCtrl.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigCallsignSet : public wxPanel { +public: + CDStarRepeaterConfigCallsignSet(wxWindow* parent, int id, const wxString& title, const wxString& callsign, const wxString& gateway, DSTAR_MODE mode, ACK_TYPE ack, bool restriction, bool rpt1Validation, bool dtmfBlanking, bool errorReply); + virtual ~CDStarRepeaterConfigCallsignSet(); + + virtual bool Validate(); + + virtual wxString getCallsign() const; + virtual wxString getGateway() const; + virtual DSTAR_MODE getMode() const; + virtual ACK_TYPE getAck() const; + virtual bool getRestriction() const; + virtual bool getRPT1Validation() const; + virtual bool getDTMFBlanking() const; + virtual bool getErrorReply() const; + + virtual void onMode(wxCommandEvent& event); + +private: + wxString m_title; + CCallsignTextCtrl* m_callsign; + CCallsignTextCtrl* m_gateway; + wxChoice* m_suffix; + wxChoice* m_mode; + wxChoice* m_ack; + wxChoice* m_restriction; + wxChoice* m_rpt1Validation; + wxChoice* m_dtmfBlanking; + wxChoice* m_errorReply; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigControl1Set.cpp b/DStarRepeaterConfig/DStarRepeaterConfigControl1Set.cpp new file mode 100644 index 0000000..6b15ee5 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigControl1Set.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2009-2012,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigControl1Set.h" +#include "DStarDefines.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH1 = 120U; +const unsigned int CONTROL_WIDTH2 = 200U; + +CDStarRepeaterConfigControl1Set::CDStarRepeaterConfigControl1Set(wxWindow* parent, int id, const wxString& title, bool enabled, const wxString& rpt1Callsign, const wxString& rpt2Callsign, const wxString& shutdown, const wxString& startup, const wxString& status1, const wxString& status2, const wxString& status3, const wxString& status4, const wxString& status5, const wxString& output1, const wxString& output2, const wxString& output3, const wxString& output4) : +wxPanel(parent, id), +m_title(title), +m_enabled(NULL), +m_rpt1Callsign(NULL), +m_rpt2Callsign(NULL), +m_shutdown(NULL), +m_startup(NULL), +m_status1(NULL), +m_status2(NULL), +m_status3(NULL), +m_status4(NULL), +m_status5(NULL), +m_output1(NULL), +m_output2(NULL), +m_output3(NULL), +m_output4(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* controlLabel = new wxStaticText(this, -1, _("Control")); + sizer->Add(controlLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_enabled = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_enabled->Append(_("Disabled")); + m_enabled->Append(_("Enabled")); + sizer->Add(m_enabled, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_enabled->SetSelection(enabled ? 1 : 0); + + wxStaticText* rpt1CallsignLabel = new wxStaticText(this, -1, _("RPT1 Callsign")); + sizer->Add(rpt1CallsignLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_rpt1Callsign = new CCallsignTextCtrl(this, -1, rpt1Callsign, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_rpt1Callsign->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_rpt1Callsign, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* rpt2CallsignLabel = new wxStaticText(this, -1, _("RPT2 Callsign")); + sizer->Add(rpt2CallsignLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_rpt2Callsign = new CCallsignTextCtrl(this, -1, rpt2Callsign, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_rpt2Callsign->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_rpt2Callsign, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* shutdownLabel = new wxStaticText(this, -1, _("Shutdown")); + sizer->Add(shutdownLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_shutdown = new CCallsignTextCtrl(this, -1, shutdown, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_shutdown->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_shutdown, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* startupLabel = new wxStaticText(this, -1, _("Startup")); + sizer->Add(startupLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_startup = new CCallsignTextCtrl(this, -1, startup, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_startup->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_startup, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* status1Label = new wxStaticText(this, -1, _("Status 1")); + sizer->Add(status1Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_status1 = new CCallsignTextCtrl(this, -1, status1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_status1->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_status1, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* status2Label = new wxStaticText(this, -1, _("Status 2")); + sizer->Add(status2Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_status2 = new CCallsignTextCtrl(this, -1, status2, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_status2->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_status2, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* status3Label = new wxStaticText(this, -1, _("Status 3")); + sizer->Add(status3Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_status3 = new CCallsignTextCtrl(this, -1, status3, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_status3->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_status3, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* status4Label = new wxStaticText(this, -1, _("Status 4")); + sizer->Add(status4Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_status4 = new CCallsignTextCtrl(this, -1, status4, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_status4->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_status4, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* status5Label = new wxStaticText(this, -1, _("Status 5")); + sizer->Add(status5Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_status5 = new CCallsignTextCtrl(this, -1, status5, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_status5->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_status5, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* output1Label = new wxStaticText(this, -1, _("Output 1")); + sizer->Add(output1Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_output1 = new CCallsignTextCtrl(this, -1, output1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_output1->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_output1, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* output2Label = new wxStaticText(this, -1, _("Output 2")); + sizer->Add(output2Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_output2 = new CCallsignTextCtrl(this, -1, output2, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_output2->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_output2, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* output3Label = new wxStaticText(this, -1, _("Output 3")); + sizer->Add(output3Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_output3 = new CCallsignTextCtrl(this, -1, output3, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_output3->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_output3, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* output4Label = new wxStaticText(this, -1, _("Output 4")); + sizer->Add(output4Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_output4 = new CCallsignTextCtrl(this, -1, output4, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_output4->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_output4, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigControl1Set::~CDStarRepeaterConfigControl1Set() +{ +} + +bool CDStarRepeaterConfigControl1Set::Validate() +{ + return m_enabled->GetCurrentSelection() != wxNOT_FOUND; +} + +bool CDStarRepeaterConfigControl1Set::getEnabled() const +{ + int n = m_enabled->GetCurrentSelection(); + + return n == 1; +} + +wxString CDStarRepeaterConfigControl1Set::getRPT1Callsign() const +{ + return m_rpt1Callsign->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getRPT2Callsign() const +{ + return m_rpt2Callsign->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getShutdown() const +{ + return m_shutdown->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getStartup() const +{ + return m_startup->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getStatus1() const +{ + return m_status1->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getStatus2() const +{ + return m_status2->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getStatus3() const +{ + return m_status3->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getStatus4() const +{ + return m_status4->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getStatus5() const +{ + return m_status5->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getOutput1() const +{ + return m_output1->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getOutput2() const +{ + return m_output2->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getOutput3() const +{ + return m_output3->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl1Set::getOutput4() const +{ + return m_output4->GetValue().MakeUpper(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigControl1Set.h b/DStarRepeaterConfig/DStarRepeaterConfigControl1Set.h new file mode 100644 index 0000000..c290aa3 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigControl1Set.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2009-2012,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigControl1Set_H +#define DStarRepeaterConfigControl1Set_H + +#include "CallsignTextCtrl.h" + +#include + +class CDStarRepeaterConfigControl1Set : public wxPanel { +public: + CDStarRepeaterConfigControl1Set(wxWindow* parent, int id, const wxString& title, bool enabled, + const wxString& rpt1Callsign, const wxString& rpt2Callsign, const wxString& shutdown, + const wxString& startup, const wxString& status1, const wxString& status2, const wxString& status3, + const wxString& status4, const wxString& status5, const wxString& output1, const wxString& output2, + const wxString& output3, const wxString& output4); + virtual ~CDStarRepeaterConfigControl1Set(); + + virtual bool Validate(); + + virtual bool getEnabled() const; + + virtual wxString getRPT1Callsign() const; + virtual wxString getRPT2Callsign() const; + + virtual wxString getShutdown() const; + virtual wxString getStartup() const; + + virtual wxString getStatus1() const; + virtual wxString getStatus2() const; + virtual wxString getStatus3() const; + virtual wxString getStatus4() const; + virtual wxString getStatus5() const; + + virtual wxString getOutput1() const; + virtual wxString getOutput2() const; + virtual wxString getOutput3() const; + virtual wxString getOutput4() const; + +private: + wxString m_title; + wxChoice* m_enabled; + CCallsignTextCtrl* m_rpt1Callsign; + CCallsignTextCtrl* m_rpt2Callsign; + CCallsignTextCtrl* m_shutdown; + CCallsignTextCtrl* m_startup; + CCallsignTextCtrl* m_status1; + CCallsignTextCtrl* m_status2; + CCallsignTextCtrl* m_status3; + CCallsignTextCtrl* m_status4; + CCallsignTextCtrl* m_status5; + CCallsignTextCtrl* m_output1; + CCallsignTextCtrl* m_output2; + CCallsignTextCtrl* m_output3; + CCallsignTextCtrl* m_output4; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigControl2Set.cpp b/DStarRepeaterConfig/DStarRepeaterConfigControl2Set.cpp new file mode 100644 index 0000000..e6198ba --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigControl2Set.cpp @@ -0,0 +1,180 @@ +/* + * Copyright (C) 2009,2010,2012,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigControl2Set.h" +#include "DStarDefines.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH1 = 120U; +const unsigned int CONTROL_WIDTH2 = 200U; + +CDStarRepeaterConfigControl2Set::CDStarRepeaterConfigControl2Set(wxWindow* parent, int id, const wxString& title, const wxString& command1, const wxString& command1Line, const wxString& command2, const wxString& command2Line, const wxString& command3, const wxString& command3Line, const wxString& command4, const wxString& command4Line, const wxString& command5, const wxString& command5Line, const wxString& command6, const wxString& command6Line) : +wxPanel(parent, id), +m_title(title), +m_command1(NULL), +m_command1Line(NULL), +m_command2(NULL), +m_command2Line(NULL), +m_command3(NULL), +m_command3Line(NULL), +m_command4(NULL), +m_command4Line(NULL), +m_command5(NULL), +m_command5Line(NULL), +m_command6(NULL), +m_command6Line(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(3); + + wxStaticText* command1Label = new wxStaticText(this, -1, _("Command 1")); + sizer->Add(command1Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command1 = new CCallsignTextCtrl(this, -1, command1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_command1->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_command1, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command1Line = new wxTextCtrl(this, -1, command1Line, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1)); + sizer->Add(m_command1Line, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* command2Label = new wxStaticText(this, -1, _("Command 2")); + sizer->Add(command2Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command2 = new CCallsignTextCtrl(this, -1, command2, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_command2->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_command2, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command2Line = new wxTextCtrl(this, -1, command2Line, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1)); + sizer->Add(m_command2Line, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* command3Label = new wxStaticText(this, -1, _("Command 3")); + sizer->Add(command3Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command3 = new CCallsignTextCtrl(this, -1, command3, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_command3->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_command3, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command3Line = new wxTextCtrl(this, -1, command3Line, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1)); + sizer->Add(m_command3Line, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* command4Label = new wxStaticText(this, -1, _("Command 4")); + sizer->Add(command4Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command4 = new CCallsignTextCtrl(this, -1, command4, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_command4->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_command4, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command4Line = new wxTextCtrl(this, -1, command4Line, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1)); + sizer->Add(m_command4Line, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* command5Label = new wxStaticText(this, -1, _("Command 5")); + sizer->Add(command5Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command5 = new CCallsignTextCtrl(this, -1, command5, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_command5->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_command5, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command5Line = new wxTextCtrl(this, -1, command5Line, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1)); + sizer->Add(m_command5Line, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* command6Label = new wxStaticText(this, -1, _("Command 6")); + sizer->Add(command6Label, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command6 = new CCallsignTextCtrl(this, -1, command6, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_command6->SetMaxLength(LONG_CALLSIGN_LENGTH); + sizer->Add(m_command6, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_command6Line = new wxTextCtrl(this, -1, command6Line, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1)); + sizer->Add(m_command6Line, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigControl2Set::~CDStarRepeaterConfigControl2Set() +{ +} + +bool CDStarRepeaterConfigControl2Set::Validate() +{ + return true; +} + +wxString CDStarRepeaterConfigControl2Set::getCommand1() const +{ + return m_command1->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand1Line() const +{ + return m_command1Line->GetValue(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand2() const +{ + return m_command2->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand2Line() const +{ + return m_command2Line->GetValue(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand3() const +{ + return m_command3->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand3Line() const +{ + return m_command3Line->GetValue(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand4() const +{ + return m_command4->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand4Line() const +{ + return m_command4Line->GetValue(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand5() const +{ + return m_command5->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand5Line() const +{ + return m_command5Line->GetValue(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand6() const +{ + return m_command6->GetValue().MakeUpper(); +} + +wxString CDStarRepeaterConfigControl2Set::getCommand6Line() const +{ + return m_command6Line->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigControl2Set.h b/DStarRepeaterConfig/DStarRepeaterConfigControl2Set.h new file mode 100644 index 0000000..49bdc3a --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigControl2Set.h @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2009,2010,2012,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigControl2Set_H +#define DStarRepeaterConfigControl2Set_H + +#include "CallsignTextCtrl.h" + +#include + +class CDStarRepeaterConfigControl2Set : public wxPanel { +public: + CDStarRepeaterConfigControl2Set(wxWindow* parent, int id, const wxString& title, const wxString& command1, + const wxString& command1Line, const wxString& command2, const wxString& command2Line, + const wxString& command3, const wxString& command3Line, const wxString& command4, + const wxString& command4Line, const wxString& command5, const wxString& command5Line, + const wxString& command6, const wxString& command6Line); + virtual ~CDStarRepeaterConfigControl2Set(); + + virtual bool Validate(); + + virtual wxString getCommand1() const; + virtual wxString getCommand1Line() const; + + virtual wxString getCommand2() const; + virtual wxString getCommand2Line() const; + + virtual wxString getCommand3() const; + virtual wxString getCommand3Line() const; + + virtual wxString getCommand4() const; + virtual wxString getCommand4Line() const; + + virtual wxString getCommand5() const; + virtual wxString getCommand5Line() const; + + virtual wxString getCommand6() const; + virtual wxString getCommand6Line() const; + +private: + wxString m_title; + CCallsignTextCtrl* m_command1; + wxTextCtrl* m_command1Line; + CCallsignTextCtrl* m_command2; + wxTextCtrl* m_command2Line; + CCallsignTextCtrl* m_command3; + wxTextCtrl* m_command3Line; + CCallsignTextCtrl* m_command4; + wxTextCtrl* m_command4Line; + CCallsignTextCtrl* m_command5; + wxTextCtrl* m_command5Line; + CCallsignTextCtrl* m_command6; + wxTextCtrl* m_command6Line; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigControllerSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigControllerSet.cpp new file mode 100644 index 0000000..e22e012 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigControllerSet.cpp @@ -0,0 +1,164 @@ +/* + * Copyright (C) 2012-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigControllerSet.h" +#include "SerialPortSelector.h" + +const unsigned int CONTROL_WIDTH1 = 150U; +const unsigned int CONTROL_WIDTH2 = 300U; +const unsigned int CONTROL_WIDTH3 = 80U; + +const unsigned int BORDER_SIZE = 5U; + +CDStarRepeaterConfigControllerSet::CDStarRepeaterConfigControllerSet(wxWindow* parent, int id, const wxString& title, const wxString& type, unsigned int config, bool pttInvert, unsigned int time) : +wxPanel(parent, id), +m_title(title), +m_type(NULL), +m_config(NULL), +m_pttInvert(NULL), +m_time(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* typeLabel = new wxStaticText(this, -1, _("Type")); + sizer->Add(typeLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_type = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + + m_type->Append(_("None")); + +#if defined(GPIO) + m_type->Append(wxT("GPIO")); + m_type->Append(wxT("UDRC")); +#endif + + m_type->Append(wxT("Velleman K8055 - 0")); + m_type->Append(wxT("Velleman K8055 - 1")); + m_type->Append(wxT("Velleman K8055 - 2")); + m_type->Append(wxT("Velleman K8055 - 3")); + + // Add the URI USB + m_type->Append(wxT("URI USB - 1")); + m_type->Append(wxT("URI USB - 2")); + m_type->Append(wxT("URI USB - 3")); + m_type->Append(wxT("URI USB - 4")); + m_type->Append(wxT("URI USB - 5")); + m_type->Append(wxT("URI USB - 6")); + + // Add the Serial ports + wxArrayString serialDevs = CSerialPortSelector::getDevices(); + for (size_t i = 0U; i < serialDevs.GetCount(); i++) + m_type->Append(wxT("Serial - ") + serialDevs.Item(i)); + + // Add the Arduino ports + serialDevs = CSerialPortSelector::getDevices(); + for (size_t i = 0U; i < serialDevs.GetCount(); i++) + m_type->Append(wxT("Arduino - ") + serialDevs.Item(i)); + + sizer->Add(m_type, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + if (type.IsEmpty()) + m_type->SetSelection(0); + else + m_type->SetStringSelection(type); + + wxStaticText* configLabel = new wxStaticText(this, -1, _("Config")); + sizer->Add(configLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_config = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH3, -1)); + m_config->Append("1"); + m_config->Append("2"); + m_config->Append("3"); + m_config->Append("4"); + m_config->Append("5"); + sizer->Add(m_config, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_config->SetSelection(config - 1); + + wxStaticText* pttInvertLabel = new wxStaticText(this, -1, _("PTT Inversion")); + sizer->Add(pttInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_pttInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH3, -1)); + m_pttInvert->Append(_("Off")); + m_pttInvert->Append(_("On")); + sizer->Add(m_pttInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_pttInvert->SetSelection(pttInvert ? 1 : 0); + + wxStaticText* timeLabel = new wxStaticText(this, -1, _("Time (secs)")); + sizer->Add(timeLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_time = new wxSlider(this, -1, time, 0, 300, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_time, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigControllerSet::~CDStarRepeaterConfigControllerSet() +{ +} + +bool CDStarRepeaterConfigControllerSet::Validate() +{ + if (m_type->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_config->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_pttInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + return true; +} + +wxString CDStarRepeaterConfigControllerSet::getType() const +{ + wxString type = m_type->GetStringSelection(); + + if (type.IsSameAs(_("None"))) + return wxEmptyString; + else + return type; +} + +unsigned int CDStarRepeaterConfigControllerSet::getConfig() const +{ + int n = m_config->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return 1U; + else + return n + 1U; +} + +bool CDStarRepeaterConfigControllerSet::getPTTInvert() const +{ + int n = m_pttInvert->GetCurrentSelection(); + + return n == 1; +} + +unsigned int CDStarRepeaterConfigControllerSet::getTime() const +{ + return m_time->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigControllerSet.h b/DStarRepeaterConfig/DStarRepeaterConfigControllerSet.h new file mode 100644 index 0000000..5c45df1 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigControllerSet.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2012,2013,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigControllerSet_H +#define DStarRepeaterConfigControllerSet_H + +#include + +class CDStarRepeaterConfigControllerSet : public wxPanel { +public: + CDStarRepeaterConfigControllerSet(wxWindow* parent, int id, const wxString& title, const wxString& type, unsigned int config, bool pttInvert, unsigned int time); + virtual ~CDStarRepeaterConfigControllerSet(); + + virtual bool Validate(); + + virtual wxString getType() const; + + virtual unsigned int getConfig() const; + + virtual bool getPTTInvert() const; + + virtual unsigned int getTime() const; + +private: + wxString m_title; + wxChoice* m_type; + wxChoice* m_config; + wxChoice* m_pttInvert; + wxSlider* m_time; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVAPSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigDVAPSet.cpp new file mode 100644 index 0000000..c2515c7 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVAPSet.cpp @@ -0,0 +1,170 @@ +/* + * Copyright (C) 2011-2016 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigDVAPSet.h" +#include "SerialPortSelector.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH = 100U; +const unsigned int NAME_WIDTH = 200U; + +CDStarRepeaterConfigDVAPSet::CDStarRepeaterConfigDVAPSet(wxWindow* parent, int id, const wxString& port, unsigned int frequency, int power, int squelch) : +wxDialog(parent, id, wxString(_("DVAP Modem Settings"))), +m_port(NULL), +m_band(NULL), +m_frequency(NULL), +m_power(NULL), +m_squelch(NULL) +{ + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* portLabel = new wxStaticText(this, -1, _("Port")); + sizer->Add(portLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_port = new wxChoice(this, -1, wxDefaultPosition, wxSize(NAME_WIDTH, -1)); + sizer->Add(m_port, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxArrayString ports = CSerialPortSelector::getDevices(); + for (unsigned int i = 0U; i < ports.GetCount(); i++) + m_port->Append(ports.Item(i)); + bool res = m_port->SetStringSelection(port); + if (!res) + m_port->SetSelection(0); + + wxStaticText* bandLabel = new wxStaticText(this, -1, _("Band")); + sizer->Add(bandLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_band = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + m_band->Append(wxT("144 MHz")); + m_band->Append(wxT("220 MHz")); + m_band->Append(wxT("430 MHz")); + sizer->Add(m_band, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + if (frequency > 400000000U) + m_band->SetSelection(2); + else if (frequency > 200000000U) + m_band->SetSelection(1); + else + m_band->SetSelection(0); + + wxStaticText* freqLabel = new wxStaticText(this, -1, _("Frequency (Hz)")); + sizer->Add(freqLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxString text; + text.Printf(wxT("%u"), frequency); + + m_frequency = new wxTextCtrl(this, -1, text, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + sizer->Add(m_frequency, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* powerLabel = new wxStaticText(this, -1, _("Power (dBm)")); + sizer->Add(powerLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_power = new wxSpinCtrl(this, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1), wxSP_ARROW_KEYS, -12, 10, power); + sizer->Add(m_power, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* squelchLabel = new wxStaticText(this, -1, _("Squelch (dBm)")); + sizer->Add(squelchLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_squelch = new wxSpinCtrl(this, -1, wxEmptyString, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1), wxSP_ARROW_KEYS, -128, -45, squelch); + sizer->Add(m_squelch, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigDVAPSet::~CDStarRepeaterConfigDVAPSet() +{ +} + +bool CDStarRepeaterConfigDVAPSet::Validate() +{ + if (m_port->GetCurrentSelection() == wxNOT_FOUND) + return false; + + unsigned int freq = getFrequency(); + + int n = m_band->GetCurrentSelection(); + switch (n) { + case 0: + if (freq < 144000000U || freq > 148000000U) { + wxMessageDialog dialog(this, _("The Frequency is out of range"), _("DVAP Modem Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + break; + + case 1: + if (freq < 220000000U || freq > 225000000U) { + wxMessageDialog dialog(this, _("The Frequency is out of range"), _("DVAP Modem Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + break; + + case 2: + if (freq < 420000000U || freq > 450000000U) { + wxMessageDialog dialog(this, _("The Frequency is out of range"), _("DVAP Modem Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + break; + + default: + return false; + } + + return true; +} + +wxString CDStarRepeaterConfigDVAPSet::getPort() const +{ + return m_port->GetStringSelection(); +} + +unsigned int CDStarRepeaterConfigDVAPSet::getFrequency() const +{ + wxString hz = m_frequency->GetValue(); + + unsigned long frequency; + hz.ToULong(&frequency); + + return frequency; +} + +int CDStarRepeaterConfigDVAPSet::getPower() const +{ + int power = m_power->GetValue(); + + return power; +} + +int CDStarRepeaterConfigDVAPSet::getSquelch() const +{ + int power = m_squelch->GetValue(); + + return power; +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVAPSet.h b/DStarRepeaterConfig/DStarRepeaterConfigDVAPSet.h new file mode 100644 index 0000000..338a2ec --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVAPSet.h @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigDVAPSet_H +#define DStarRepeaterConfigDVAPSet_H + +#include "DStarRepeaterConfigDefs.h" +#include "DStarDefines.h" + +#include +#include + +class CDStarRepeaterConfigDVAPSet : public wxDialog { +public: + CDStarRepeaterConfigDVAPSet(wxWindow* parent, int id, const wxString& port, unsigned int frequency, int power, int squelch); + virtual ~CDStarRepeaterConfigDVAPSet(); + + virtual bool Validate(); + + virtual wxString getPort() const; + virtual unsigned int getFrequency() const; + virtual int getPower() const; + virtual int getSquelch() const; + +private: + wxChoice* m_port; + wxChoice* m_band; + wxTextCtrl* m_frequency; + wxSpinCtrl* m_power; + wxSpinCtrl* m_squelch; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVMegaSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigDVMegaSet.cpp new file mode 100644 index 0000000..9286693 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVMegaSet.cpp @@ -0,0 +1,311 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigDVMegaSet.h" +#include "SerialPortSelector.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH1 = 150U; +const unsigned int CONTROL_WIDTH2 = 300U; + +const unsigned int PORT_LENGTH = 5U; + +const int CHOICE_VARIANT = 8770; + +BEGIN_EVENT_TABLE(CDStarRepeaterConfigDVMegaSet, wxDialog) + EVT_CHOICE(CHOICE_VARIANT, CDStarRepeaterConfigDVMegaSet::onVariant) +END_EVENT_TABLE() + + +CDStarRepeaterConfigDVMegaSet::CDStarRepeaterConfigDVMegaSet(wxWindow* parent, int id, const wxString& port, DVMEGA_VARIANT variant, bool rxInvert, bool txInvert, unsigned int txDelay, unsigned int rxFrequency, unsigned int txFrequency, unsigned int power) : +wxDialog(parent, id, wxString(_("DVMEGA Settings"))), +m_port(NULL), +m_variant(NULL), +m_txInvert(NULL), +m_rxInvert(NULL), +m_txDelay(NULL), +m_frequency(NULL), +m_power(NULL) +{ + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* portLabel = new wxStaticText(this, -1, _("Port")); + sizer->Add(portLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_port = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + sizer->Add(m_port, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxArrayString ports = CSerialPortSelector::getDevices(); + for (unsigned int i = 0U; i < ports.GetCount(); i++) + m_port->Append(ports.Item(i)); + + bool found = m_port->SetStringSelection(port); + if (!found) + m_port->SetSelection(0); + + wxStaticText* variantLabel = new wxStaticText(this, -1, _("Variant")); + sizer->Add(variantLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_variant = new wxChoice(this, CHOICE_VARIANT, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_variant->Append(_("Modem")); + m_variant->Append(_("Radio - 2m")); + m_variant->Append(_("Radio - 70cm")); + m_variant->Append(_("Radio - 2m + 70cm")); + sizer->Add(m_variant, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_variant->SetSelection(int(variant)); + + wxStaticText* txInvertLabel = new wxStaticText(this, -1, _("TX Inversion")); + sizer->Add(txInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_txInvert->Append(_("Off")); + m_txInvert->Append(_("On")); + sizer->Add(m_txInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_txInvert->SetSelection(txInvert ? 1 : 0); + + wxStaticText* rxInvertLabel = new wxStaticText(this, -1, _("RX Inversion")); + sizer->Add(rxInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_rxInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_rxInvert->Append(_("Off")); + m_rxInvert->Append(_("On")); + sizer->Add(m_rxInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_rxInvert->SetSelection(rxInvert ? 1 : 0); + + wxStaticText* freqLabel = new wxStaticText(this, -1, _("Frequency (Hz)")); + sizer->Add(freqLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxString text; + if (rxFrequency == txFrequency) + text.Printf(wxT("%u"), rxFrequency); + else + text.Printf(wxT("%u/%u"), rxFrequency, txFrequency); + + m_frequency = new wxTextCtrl(this, -1, text, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + sizer->Add(m_frequency, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txDelayLabel = new wxStaticText(this, -1, _("TX Delay (ms)")); + sizer->Add(txDelayLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txDelay = new wxSlider(this, -1, txDelay, 0, 350, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txDelay, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* powerLabel = new wxStaticText(this, -1, _("Power (%)")); + sizer->Add(powerLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_power = new wxSlider(this, -1, power, 1, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_power, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + switch (variant) { + case DVMV_RADIO_2M: + case DVMV_RADIO_70CM: + case DVMV_RADIO_2M_70CM: + m_txInvert->Disable(); + m_rxInvert->Disable(); + break; + default: + m_frequency->Disable(); + m_power->Disable(); + break; + } + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigDVMegaSet::~CDStarRepeaterConfigDVMegaSet() +{ +} + +bool CDStarRepeaterConfigDVMegaSet::Validate() +{ + if (m_port->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_variant->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_txInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_rxInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_variant->GetSelection() == 1) { + unsigned int frequency = getRXFrequency(); + + if (frequency >= 144000000U && frequency < 148000000U) + return true; + else + return false; + } else if (m_variant->GetSelection() == 2) { + unsigned int frequency = getRXFrequency(); + + if (frequency >= 420000000U && frequency < 450000000U) + return true; + else + return false; + } else if (m_variant->GetSelection() == 3) { + unsigned int rxFrequency = getRXFrequency(); + unsigned int txFrequency = getTXFrequency(); + + if (rxFrequency == txFrequency) { + if ((rxFrequency >= 144000000U && rxFrequency < 148000000U) || + (rxFrequency >= 420000000U && rxFrequency < 450000000U)) + return true; + else + return false; + } else { + bool rx2m = (rxFrequency >= 144000000U && rxFrequency < 148000000U); + bool rx70cm = (rxFrequency >= 420000000U && rxFrequency < 450000000U); + + bool tx2m = (txFrequency >= 144000000U && txFrequency < 148000000U); + bool tx70cm = (txFrequency >= 420000000U && txFrequency < 450000000U); + + if (rx2m && tx70cm) + return true; + else if (rx70cm && tx2m) + return true; + else + return false; + } + } + + return true; +} + +wxString CDStarRepeaterConfigDVMegaSet::getPort() const +{ + int n = m_port->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return wxEmptyString; + + return m_port->GetStringSelection(); +} + +DVMEGA_VARIANT CDStarRepeaterConfigDVMegaSet::getVariant() const +{ + int n = m_variant->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return DVMV_MODEM; + + return DVMEGA_VARIANT(n); +} + +bool CDStarRepeaterConfigDVMegaSet::getRXInvert() const +{ + int n = m_rxInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +bool CDStarRepeaterConfigDVMegaSet::getTXInvert() const +{ + int n = m_txInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +unsigned int CDStarRepeaterConfigDVMegaSet::getTXDelay() const +{ + return (unsigned int)m_txDelay->GetValue(); +} + +unsigned int CDStarRepeaterConfigDVMegaSet::getRXFrequency() const +{ + wxString hz = m_frequency->GetValue(); + + int pos = hz.Find(wxT('/')); + if (pos != wxNOT_FOUND) { + unsigned long frequency; + hz.Left(pos).ToULong(&frequency); + return frequency; + } else { + unsigned long frequency; + hz.ToULong(&frequency); + return frequency; + } +} + +unsigned int CDStarRepeaterConfigDVMegaSet::getTXFrequency() const +{ + DVMEGA_VARIANT variant = getVariant(); + wxString hz = m_frequency->GetValue(); + + int pos = hz.Find(wxT('/')); + if (pos != wxNOT_FOUND && variant == DVMV_RADIO_2M_70CM) { + unsigned long frequency; + hz.Mid(pos + 1).ToULong(&frequency); + return frequency; + } else { + if (pos != wxNOT_FOUND) { + unsigned long frequency; + hz.Left(pos).ToULong(&frequency); + return frequency; + } else { + unsigned long frequency; + hz.ToULong(&frequency); + return frequency; + } + } +} + +unsigned int CDStarRepeaterConfigDVMegaSet::getPower() const +{ + return (unsigned int)m_power->GetValue(); +} + +void CDStarRepeaterConfigDVMegaSet::onVariant(wxCommandEvent &event) +{ + DVMEGA_VARIANT variant = getVariant(); + + switch (variant) { + case DVMV_RADIO_2M: + case DVMV_RADIO_70CM: + case DVMV_RADIO_2M_70CM: + m_txInvert->Disable(); + m_rxInvert->Disable(); + m_frequency->Enable(); + m_power->Enable(); + break; + default: // DVMV_MODEM + m_txInvert->Enable(); + m_rxInvert->Enable(); + m_frequency->Disable(); + m_power->Disable(); + break; + } +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVMegaSet.h b/DStarRepeaterConfig/DStarRepeaterConfigDVMegaSet.h new file mode 100644 index 0000000..fcea64e --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVMegaSet.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigDVMegaSet_H +#define DStarRepeaterConfigDVMegaSet_H + +#include "DStarRepeaterConfigDefs.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigDVMegaSet : public wxDialog { +public: + CDStarRepeaterConfigDVMegaSet(wxWindow* parent, int id, const wxString& port, DVMEGA_VARIANT variant, bool rxInvert, bool txInvert, unsigned int txDelay, unsigned int rxFrequency, unsigned int txFrequency, unsigned int power); + virtual ~CDStarRepeaterConfigDVMegaSet(); + + virtual bool Validate(); + + virtual wxString getPort() const; + virtual DVMEGA_VARIANT getVariant() const; + virtual bool getRXInvert() const; + virtual bool getTXInvert() const; + virtual unsigned int getTXDelay() const; + virtual unsigned int getRXFrequency() const; + virtual unsigned int getTXFrequency() const; + virtual unsigned int getPower() const; + + virtual void onVariant(wxCommandEvent& event); + +private: + wxChoice* m_port; + wxChoice* m_variant; + wxChoice* m_txInvert; + wxChoice* m_rxInvert; + wxSlider* m_txDelay; + wxTextCtrl* m_frequency; + wxSlider* m_power; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR1Set.cpp b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR1Set.cpp new file mode 100644 index 0000000..ae09700 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR1Set.cpp @@ -0,0 +1,174 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigDVRPTR1Set.h" +#include "SerialPortSelector.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH1 = 150U; +const unsigned int CONTROL_WIDTH2 = 300U; + +const unsigned int ADDRESS_LENGTH = 15U; +const unsigned int PORT_LENGTH = 5U; + + +CDStarRepeaterConfigDVRPTR1Set::CDStarRepeaterConfigDVRPTR1Set(wxWindow* parent, int id, const wxString& port, bool rxInvert, bool txInvert, bool channel, unsigned int modLevel, unsigned int txDelay) : +wxDialog(parent, id, wxString(_("DV-RPTR V1 Settings"))), +m_port(NULL), +m_txInvert(NULL), +m_rxInvert(NULL), +m_channel(NULL), +m_modLevel(NULL), +m_txDelay(NULL) +{ + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* portLabel = new wxStaticText(this, -1, _("Port")); + sizer->Add(portLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_port = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + sizer->Add(m_port, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxArrayString ports = CSerialPortSelector::getDevices(); + for (unsigned int i = 0U; i < ports.GetCount(); i++) + m_port->Append(ports.Item(i)); + + bool found = m_port->SetStringSelection(port); + if (!found) + m_port->SetSelection(0); + + wxStaticText* txInvertLabel = new wxStaticText(this, -1, _("TX Inversion")); + sizer->Add(txInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_txInvert->Append(_("Off")); + m_txInvert->Append(_("On")); + sizer->Add(m_txInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_txInvert->SetSelection(txInvert ? 1 : 0); + + wxStaticText* rxInvertLabel = new wxStaticText(this, -1, _("RX Inversion")); + sizer->Add(rxInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_rxInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_rxInvert->Append(_("Off")); + m_rxInvert->Append(_("On")); + sizer->Add(m_rxInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_rxInvert->SetSelection(rxInvert ? 1 : 0); + + wxStaticText* channelLabel = new wxStaticText(this, -1, _("Channel")); + sizer->Add(channelLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_channel = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_channel->Append(_("A / FSK Pin")); + m_channel->Append(_("B / AFSK Pin")); + sizer->Add(m_channel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_channel->SetSelection(channel ? 1 : 0); + + wxStaticText* modLevelLabel = new wxStaticText(this, -1, _("TX Level (%)")); + sizer->Add(modLevelLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_modLevel = new wxSlider(this, -1, modLevel, 0, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_modLevel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txDelayLabel = new wxStaticText(this, -1, _("TX Delay (ms)")); + sizer->Add(txDelayLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txDelay = new wxSlider(this, -1, txDelay, 0, 500, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txDelay, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigDVRPTR1Set::~CDStarRepeaterConfigDVRPTR1Set() +{ +} + +bool CDStarRepeaterConfigDVRPTR1Set::Validate() +{ + if (m_port->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_txInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_rxInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + return m_channel->GetCurrentSelection() != wxNOT_FOUND; +} + +wxString CDStarRepeaterConfigDVRPTR1Set::getPort() const +{ + int n = m_port->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return wxEmptyString; + + return m_port->GetStringSelection(); +} + +bool CDStarRepeaterConfigDVRPTR1Set::getRXInvert() const +{ + int n = m_rxInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +bool CDStarRepeaterConfigDVRPTR1Set::getTXInvert() const +{ + int n = m_txInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +bool CDStarRepeaterConfigDVRPTR1Set::getChannel() const +{ + int n = m_channel->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +unsigned int CDStarRepeaterConfigDVRPTR1Set::getModLevel() const +{ + return (unsigned int)m_modLevel->GetValue(); +} + +unsigned int CDStarRepeaterConfigDVRPTR1Set::getTXDelay() const +{ + return (unsigned int)m_txDelay->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR1Set.h b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR1Set.h new file mode 100644 index 0000000..66c8ed9 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR1Set.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigDVRPTR1Set_H +#define DStarRepeaterConfigDVRPTR1Set_H + +#include "DStarRepeaterConfigDefs.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigDVRPTR1Set : public wxDialog { +public: + CDStarRepeaterConfigDVRPTR1Set(wxWindow* parent, int id, const wxString& port, bool rxInvert, bool txInvert, bool channel, unsigned int modLevel, unsigned int txDelay); + virtual ~CDStarRepeaterConfigDVRPTR1Set(); + + virtual bool Validate(); + + virtual wxString getPort() const; + virtual bool getRXInvert() const; + virtual bool getTXInvert() const; + virtual bool getChannel() const; + virtual unsigned int getModLevel() const; + virtual unsigned int getTXDelay() const; + +private: + wxChoice* m_port; + wxChoice* m_txInvert; + wxChoice* m_rxInvert; + wxChoice* m_channel; + wxSlider* m_modLevel; + wxSlider* m_txDelay; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR2Set.cpp b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR2Set.cpp new file mode 100644 index 0000000..71eea73 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR2Set.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigDVRPTR2Set.h" +#include "SerialPortSelector.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH1 = 150U; +const unsigned int CONTROL_WIDTH2 = 300U; + +const unsigned int ADDRESS_LENGTH = 15U; +const unsigned int PORT_LENGTH = 5U; + +const int CHOICE_TYPE = 8750; + +BEGIN_EVENT_TABLE(CDStarRepeaterConfigDVRPTR2Set, wxDialog) + EVT_CHOICE(CHOICE_TYPE, CDStarRepeaterConfigDVRPTR2Set::onConnectionType) +END_EVENT_TABLE() + + +CDStarRepeaterConfigDVRPTR2Set::CDStarRepeaterConfigDVRPTR2Set(wxWindow* parent, int id, CONNECTION_TYPE connectionType, const wxString& usbPort, const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, unsigned int txDelay) : +wxDialog(parent, id, wxString(_("DV-RPTR V2 Settings"))), +m_connectionType(NULL), +m_usbPort(NULL), +m_address(NULL), +m_port(NULL), +m_txInvert(NULL), +m_modLevel(NULL), +m_txDelay(NULL) +{ + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* connectionLabel = new wxStaticText(this, -1, _("Connection")); + sizer->Add(connectionLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_connectionType = new wxChoice(this, CHOICE_TYPE, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_connectionType->Append(wxT("USB")); + m_connectionType->Append(_("Network")); + sizer->Add(m_connectionType, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_connectionType->SetSelection(connectionType == CT_NETWORK ? 1 : 0); + + wxStaticText* usbPortLabel = new wxStaticText(this, -1, _("USB Port")); + sizer->Add(usbPortLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_usbPort = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + sizer->Add(m_usbPort, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxArrayString ports = CSerialPortSelector::getDevices(); + for (unsigned int i = 0U; i < ports.GetCount(); i++) + m_usbPort->Append(ports.Item(i)); + + bool found = m_usbPort->SetStringSelection(usbPort); + if (!found) + m_usbPort->SetSelection(0); + + wxStaticText* addressLabel = new wxStaticText(this, -1, _("Address")); + sizer->Add(addressLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_address = new CAddressTextCtrl(this, -1, address, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_address->SetMaxLength(ADDRESS_LENGTH); + sizer->Add(m_address, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* portLabel = new wxStaticText(this, -1, _("Port")); + sizer->Add(portLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxString buffer; + buffer.Printf(wxT("%u"), port); + + m_port = new CPortTextCtrl(this, -1, buffer, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_port->SetMaxLength(PORT_LENGTH); + sizer->Add(m_port, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txInvertLabel = new wxStaticText(this, -1, _("TX Inversion")); + sizer->Add(txInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_txInvert->Append(_("Off")); + m_txInvert->Append(_("On")); + sizer->Add(m_txInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_txInvert->SetSelection(txInvert ? 1 : 0); + + wxStaticText* modLevelLabel = new wxStaticText(this, -1, _("TX Level (%)")); + sizer->Add(modLevelLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_modLevel = new wxSlider(this, -1, modLevel, 0, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_modLevel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txDelayLabel = new wxStaticText(this, -1, _("TX Delay (ms)")); + sizer->Add(txDelayLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txDelay = new wxSlider(this, -1, txDelay, 100, 850, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txDelay, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + switch (connectionType) { + case CT_NETWORK: + m_usbPort->Disable(); + m_address->Enable(); + m_port->Enable(); + break; + default: + m_usbPort->Enable(); + m_address->Disable(); + m_port->Disable(); + break; + } + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigDVRPTR2Set::~CDStarRepeaterConfigDVRPTR2Set() +{ +} + +bool CDStarRepeaterConfigDVRPTR2Set::Validate() +{ + if (m_connectionType->GetCurrentSelection() == wxNOT_FOUND) + return false; + + CONNECTION_TYPE type = getConnectionType(); + if (type == CT_USB) { + if (m_usbPort->GetCurrentSelection() == wxNOT_FOUND) + return false; + } else { + unsigned int port = getPort(); + + if (port == 0U || port > 65535U) { + wxMessageDialog dialog(this, _("The Port is not valid"), _("DV-RPTR V2 Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + } + + return m_txInvert->GetCurrentSelection() != wxNOT_FOUND; +} + +CONNECTION_TYPE CDStarRepeaterConfigDVRPTR2Set::getConnectionType() const +{ + int n = m_connectionType->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return CT_USB; + + return n == 1 ? CT_NETWORK : CT_USB; +} + +wxString CDStarRepeaterConfigDVRPTR2Set::getUSBPort() const +{ + int n = m_usbPort->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return wxEmptyString; + + return m_usbPort->GetStringSelection(); +} + +wxString CDStarRepeaterConfigDVRPTR2Set::getAddress() const +{ + return m_address->GetValue(); +} + +unsigned int CDStarRepeaterConfigDVRPTR2Set::getPort() const +{ + unsigned long n; + + m_port->GetValue().ToULong(&n); + + return n; +} + +bool CDStarRepeaterConfigDVRPTR2Set::getTXInvert() const +{ + int n = m_txInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +unsigned int CDStarRepeaterConfigDVRPTR2Set::getModLevel() const +{ + return (unsigned int)m_modLevel->GetValue(); +} + +unsigned int CDStarRepeaterConfigDVRPTR2Set::getTXDelay() const +{ + return (unsigned int)m_txDelay->GetValue(); +} + +void CDStarRepeaterConfigDVRPTR2Set::onConnectionType(wxCommandEvent &event) +{ + int n = event.GetSelection(); + + switch (n) { + case 1: // CT_NETWORK + m_usbPort->Disable(); + m_address->Enable(); + m_port->Enable(); + break; + default: // CT_USB + m_usbPort->Enable(); + m_address->Disable(); + m_port->Disable(); + break; + } +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR2Set.h b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR2Set.h new file mode 100644 index 0000000..1198e61 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR2Set.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigDVRPTR2Set_H +#define DStarRepeaterConfigDVRPTR2Set_H + +#include "DStarRepeaterConfigDefs.h" +#include "AddressTextCtrl.h" +#include "DStarDefines.h" +#include "PortTextCtrl.h" + +#include + +class CDStarRepeaterConfigDVRPTR2Set : public wxDialog { +public: + CDStarRepeaterConfigDVRPTR2Set(wxWindow* parent, int id, CONNECTION_TYPE connectionType, const wxString& usbPort, const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, unsigned int txDelay); + virtual ~CDStarRepeaterConfigDVRPTR2Set(); + + virtual bool Validate(); + + virtual CONNECTION_TYPE getConnectionType() const; + virtual wxString getUSBPort() const; + virtual wxString getAddress() const; + virtual unsigned int getPort() const; + virtual bool getTXInvert() const; + virtual unsigned int getModLevel() const; + virtual unsigned int getTXDelay() const; + + virtual void onConnectionType(wxCommandEvent& event); + +private: + wxChoice* m_connectionType; + wxChoice* m_usbPort; + CAddressTextCtrl* m_address; + CPortTextCtrl* m_port; + wxChoice* m_txInvert; + wxSlider* m_modLevel; + wxSlider* m_txDelay; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR3Set.cpp b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR3Set.cpp new file mode 100644 index 0000000..568eed5 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR3Set.cpp @@ -0,0 +1,232 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigDVRPTR3Set.h" +#include "SerialPortSelector.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH1 = 150U; +const unsigned int CONTROL_WIDTH2 = 300U; + +const unsigned int ADDRESS_LENGTH = 15U; +const unsigned int PORT_LENGTH = 5U; + +const int CHOICE_TYPE = 8750; + +BEGIN_EVENT_TABLE(CDStarRepeaterConfigDVRPTR3Set, wxDialog) + EVT_CHOICE(CHOICE_TYPE, CDStarRepeaterConfigDVRPTR3Set::onConnectionType) +END_EVENT_TABLE() + + +CDStarRepeaterConfigDVRPTR3Set::CDStarRepeaterConfigDVRPTR3Set(wxWindow* parent, int id, CONNECTION_TYPE connectionType, const wxString& usbPort, const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, unsigned int txDelay) : +wxDialog(parent, id, wxString(_("DV-RPTR V3 Settings"))), +m_connectionType(NULL), +m_usbPort(NULL), +m_address(NULL), +m_port(NULL), +m_txInvert(NULL), +m_modLevel(NULL), +m_txDelay(NULL) +{ + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* connectionLabel = new wxStaticText(this, -1, _("Connection")); + sizer->Add(connectionLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_connectionType = new wxChoice(this, CHOICE_TYPE, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_connectionType->Append(wxT("USB")); + m_connectionType->Append(_("Network")); + sizer->Add(m_connectionType, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_connectionType->SetSelection(connectionType == CT_NETWORK ? 1 : 0); + + wxStaticText* usbPortLabel = new wxStaticText(this, -1, _("USB Port")); + sizer->Add(usbPortLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_usbPort = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + sizer->Add(m_usbPort, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxArrayString ports = CSerialPortSelector::getDevices(); + for (unsigned int i = 0U; i < ports.GetCount(); i++) + m_usbPort->Append(ports.Item(i)); + + bool found = m_usbPort->SetStringSelection(usbPort); + if (!found) + m_usbPort->SetSelection(0); + + wxStaticText* addressLabel = new wxStaticText(this, -1, _("Address")); + sizer->Add(addressLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_address = new CAddressTextCtrl(this, -1, address, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_address->SetMaxLength(ADDRESS_LENGTH); + sizer->Add(m_address, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* portLabel = new wxStaticText(this, -1, _("Port")); + sizer->Add(portLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxString buffer; + buffer.Printf(wxT("%u"), port); + + m_port = new CPortTextCtrl(this, -1, buffer, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_port->SetMaxLength(PORT_LENGTH); + sizer->Add(m_port, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txInvertLabel = new wxStaticText(this, -1, _("TX Inversion")); + sizer->Add(txInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_txInvert->Append(_("Off")); + m_txInvert->Append(_("On")); + sizer->Add(m_txInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_txInvert->SetSelection(txInvert ? 1 : 0); + + wxStaticText* modLevelLabel = new wxStaticText(this, -1, _("TX Level (%)")); + sizer->Add(modLevelLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_modLevel = new wxSlider(this, -1, modLevel, 0, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_modLevel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txDelayLabel = new wxStaticText(this, -1, _("TX Delay (ms)")); + sizer->Add(txDelayLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txDelay = new wxSlider(this, -1, txDelay, 100, 850, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txDelay, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + switch (connectionType) { + case CT_NETWORK: + m_usbPort->Disable(); + m_address->Enable(); + m_port->Enable(); + break; + default: + m_usbPort->Enable(); + m_address->Disable(); + m_port->Disable(); + break; + } + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigDVRPTR3Set::~CDStarRepeaterConfigDVRPTR3Set() +{ +} + +bool CDStarRepeaterConfigDVRPTR3Set::Validate() +{ + if (m_connectionType->GetCurrentSelection() == wxNOT_FOUND) + return false; + + CONNECTION_TYPE type = getConnectionType(); + if (type == CT_USB) { + if (m_usbPort->GetCurrentSelection() == wxNOT_FOUND) + return false; + } else { + unsigned int port = getPort(); + + if (port == 0U || port > 65535U) { + wxMessageDialog dialog(this, _("The Port is not valid"), _("DV-RPTR V2 Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + } + + return m_txInvert->GetCurrentSelection() != wxNOT_FOUND; +} + +CONNECTION_TYPE CDStarRepeaterConfigDVRPTR3Set::getConnectionType() const +{ + int n = m_connectionType->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return CT_USB; + + return n == 1 ? CT_NETWORK : CT_USB; +} + +wxString CDStarRepeaterConfigDVRPTR3Set::getUSBPort() const +{ + int n = m_usbPort->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return wxEmptyString; + + return m_usbPort->GetStringSelection(); +} + +wxString CDStarRepeaterConfigDVRPTR3Set::getAddress() const +{ + return m_address->GetValue(); +} + +unsigned int CDStarRepeaterConfigDVRPTR3Set::getPort() const +{ + unsigned long n; + + m_port->GetValue().ToULong(&n); + + return n; +} + +bool CDStarRepeaterConfigDVRPTR3Set::getTXInvert() const +{ + int n = m_txInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +unsigned int CDStarRepeaterConfigDVRPTR3Set::getModLevel() const +{ + return (unsigned int)m_modLevel->GetValue(); +} + +unsigned int CDStarRepeaterConfigDVRPTR3Set::getTXDelay() const +{ + return (unsigned int)m_txDelay->GetValue(); +} + +void CDStarRepeaterConfigDVRPTR3Set::onConnectionType(wxCommandEvent &event) +{ + int n = event.GetSelection(); + + switch (n) { + case 1: // CT_NETWORK + m_usbPort->Disable(); + m_address->Enable(); + m_port->Enable(); + break; + default: // CT_USB + m_usbPort->Enable(); + m_address->Disable(); + m_port->Disable(); + break; + } +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR3Set.h b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR3Set.h new file mode 100644 index 0000000..10b0718 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDVRPTR3Set.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigDVRPTR3Set_H +#define DStarRepeaterConfigDVRPTR3Set_H + +#include "DStarRepeaterConfigDefs.h" +#include "AddressTextCtrl.h" +#include "DStarDefines.h" +#include "PortTextCtrl.h" + +#include + +class CDStarRepeaterConfigDVRPTR3Set : public wxDialog { +public: + CDStarRepeaterConfigDVRPTR3Set(wxWindow* parent, int id, CONNECTION_TYPE connectionType, const wxString& usbPort, const wxString& address, unsigned int port, bool txInvert, unsigned int modLevel, unsigned int txDelay); + virtual ~CDStarRepeaterConfigDVRPTR3Set(); + + virtual bool Validate(); + + virtual CONNECTION_TYPE getConnectionType() const; + virtual wxString getUSBPort() const; + virtual wxString getAddress() const; + virtual unsigned int getPort() const; + virtual bool getTXInvert() const; + virtual unsigned int getModLevel() const; + virtual unsigned int getTXDelay() const; + + virtual void onConnectionType(wxCommandEvent& event); + +private: + wxChoice* m_connectionType; + wxChoice* m_usbPort; + CAddressTextCtrl* m_address; + CPortTextCtrl* m_port; + wxChoice* m_txInvert; + wxSlider* m_modLevel; + wxSlider* m_txDelay; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigDefs.h b/DStarRepeaterConfig/DStarRepeaterConfigDefs.h new file mode 100644 index 0000000..c358199 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigDefs.h @@ -0,0 +1,28 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigDefs_H +#define DStarRepeaterConfigDefs_H + +#include + +const wxString APPLICATION_NAME = wxT("D-Star Repeater"); +const wxString LOG_BASE_NAME = wxT("DStarRepeater"); +const wxString CONFIG_FILE_NAME = wxT("dstarrepeater"); + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigFrame.cpp b/DStarRepeaterConfig/DStarRepeaterConfigFrame.cpp new file mode 100644 index 0000000..6fd8f39 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigFrame.cpp @@ -0,0 +1,294 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigFrame.h" +#include "Version.h" + +#include +#include + +enum { + Menu_File_Save = 6000 +}; + +BEGIN_EVENT_TABLE(CDStarRepeaterConfigFrame, wxFrame) + EVT_MENU(wxID_EXIT, CDStarRepeaterConfigFrame::onQuit) + EVT_MENU(Menu_File_Save, CDStarRepeaterConfigFrame::onSave) + EVT_MENU(wxID_ABOUT, CDStarRepeaterConfigFrame::onAbout) + + EVT_CLOSE(CDStarRepeaterConfigFrame::onClose) +END_EVENT_TABLE() + +#if defined(__WINDOWS__) +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH = 100U; +const unsigned int LABEL_WIDTH = 70U; +#else +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH = 150U; +const unsigned int LABEL_WIDTH = 70U; +#endif + +CDStarRepeaterConfigFrame::CDStarRepeaterConfigFrame(const wxString& title, const wxString& confDir, const wxString& name) : +wxFrame(NULL, -1, title), +m_config(NULL), +m_callsign(NULL), +m_network(NULL), +m_times(NULL), +m_beacon(NULL), +m_announcement(NULL), +m_modem(NULL), +m_control1(NULL), +m_control2(NULL), +m_controller(NULL) +{ + SetMenuBar(createMenuBar()); + + wxBoxSizer* mainSizer = new wxBoxSizer(wxVERTICAL); + + wxPanel* panel = new wxPanel(this, -1); + + wxBoxSizer* sizer = new wxBoxSizer(wxVERTICAL); + + wxNotebook* noteBook = new wxNotebook(panel, -1); + +#if defined(__WINDOWS__) + if (confDir.IsEmpty()) + m_config = new CDStarRepeaterConfig(new wxConfig(APPLICATION_NAME), ::wxGetHomeDir(), CONFIG_FILE_NAME, name); + else + m_config = new CDStarRepeaterConfig(new wxConfig(APPLICATION_NAME), confDir, CONFIG_FILE_NAME, name); +#else + if (confDir.IsEmpty()) + m_config = new CDStarRepeaterConfig(wxT(CONF_DIR), CONFIG_FILE_NAME, name); + else + m_config = new CDStarRepeaterConfig(confDir, CONFIG_FILE_NAME, name); +#endif + + wxString callsign, gateway; + DSTAR_MODE mode; + ACK_TYPE ack; + bool restriction, rpt1Validation, dtmfBlanking, errorReply; + m_config->getCallsign(callsign, gateway, mode, ack, restriction, rpt1Validation, dtmfBlanking, errorReply); + + m_callsign = new CDStarRepeaterConfigCallsignSet(noteBook, -1, APPLICATION_NAME, callsign, gateway, mode, ack, restriction, rpt1Validation, dtmfBlanking, errorReply); + noteBook->AddPage(m_callsign, _("Callsign"), true); + + wxString gatewayAddress, localAddress, netName; + unsigned int gatewayPort, localPort; + m_config->getNetwork(gatewayAddress, gatewayPort, localAddress, localPort, netName); + + m_network = new CDStarRepeaterConfigNetworkSet(noteBook, -1, APPLICATION_NAME, gatewayAddress, gatewayPort, localAddress, localPort, netName); + noteBook->AddPage(m_network, _("Network"), false); + + unsigned int timeout, ackTime; + m_config->getTimes(timeout, ackTime); + + m_times = new CDStarRepeaterConfigTimesSet(noteBook, -1, APPLICATION_NAME, timeout, ackTime); + noteBook->AddPage(m_times, _("Timers"), false); + + bool announcementEnabled; + unsigned int announcementTime; + wxString announcementRecordRPT1, announcementRecordRPT2; + wxString announcementDeleteRPT1, announcementDeleteRPT2; + m_config->getAnnouncement(announcementEnabled, announcementTime, announcementRecordRPT1, announcementRecordRPT2, announcementDeleteRPT1, announcementDeleteRPT2); + + m_announcement = new CDStarRepeaterConfigAnnouncementSet(noteBook, -1, APPLICATION_NAME, announcementEnabled, announcementTime, announcementRecordRPT1, announcementRecordRPT2, announcementDeleteRPT1, announcementDeleteRPT2); + noteBook->AddPage(m_announcement, _("Announcement"), false); + + unsigned int beaconTime; + wxString beaconText; + bool beaconVoice; + TEXT_LANG language; + m_config->getBeacon(beaconTime, beaconText, beaconVoice, language); + + m_beacon = new CDStarRepeaterConfigBeaconSet(noteBook, -1, APPLICATION_NAME, beaconTime, beaconText, beaconVoice, language); + noteBook->AddPage(m_beacon, _("Beacon"), false); + + wxString modemType; + m_config->getModem(modemType); + + m_modem = new CDStarRepeaterConfigModemSet(noteBook, -1, APPLICATION_NAME, m_config, modemType); + noteBook->AddPage(m_modem, _("Modem"), false); + + bool enabled; + wxString rpt1Callsign, rpt2Callsign; + wxString shutdown, startup; + wxString status1, status2, status3, status4, status5; + wxString command1, command1Line, command2, command2Line; + wxString command3, command3Line, command4, command4Line; + wxString command5, command5Line, command6, command6Line; + wxString output1, output2, output3, output4; + m_config->getControl(enabled, rpt1Callsign, rpt2Callsign, shutdown, startup, status1, status2, status3, status4, status5, command1, command1Line, command2, command2Line, command3, command3Line, command4, command4Line, command5, command5Line, command6, command6Line, output1, output2, output3, output4); + + m_control1 = new CDStarRepeaterConfigControl1Set(noteBook, -1, APPLICATION_NAME, enabled, rpt1Callsign, rpt2Callsign, shutdown, startup, status1, status2, status3, status4, status5, output1, output2, output3, output4); + noteBook->AddPage(m_control1, _("Control 1"), false); + + m_control2 = new CDStarRepeaterConfigControl2Set(noteBook, -1, APPLICATION_NAME, command1, command1Line, command2, command2Line, command3, command3Line, command4, command4Line, command5, command5Line, command6, command6Line); + noteBook->AddPage(m_control2, _("Control 2"), false); + + wxString controllerType; + unsigned int serialConfig, activeHangTime; + bool pttInvert; + m_config->getController(controllerType, serialConfig, pttInvert, activeHangTime); + + m_controller = new CDStarRepeaterConfigControllerSet(noteBook, -1, APPLICATION_NAME, controllerType, serialConfig, pttInvert, activeHangTime); + noteBook->AddPage(m_controller, _("Controller"), false); + + sizer->Add(noteBook, 0, wxEXPAND | wxALL, BORDER_SIZE); + + panel->SetSizer(sizer); + + mainSizer->Add(panel, 0, wxEXPAND | wxALL, BORDER_SIZE); + + mainSizer->SetSizeHints(this); + + SetSizer(mainSizer); +} + +CDStarRepeaterConfigFrame::~CDStarRepeaterConfigFrame() +{ + delete m_config; +} + +wxMenuBar* CDStarRepeaterConfigFrame::createMenuBar() +{ + wxMenu* fileMenu = new wxMenu(); + fileMenu->Append(Menu_File_Save, _("Save")); + fileMenu->AppendSeparator(); + fileMenu->Append(wxID_EXIT, _("Exit")); + + wxMenu* helpMenu = new wxMenu(); + helpMenu->Append(wxID_ABOUT, _("About D-Star Repeater Config")); + + wxMenuBar* menuBar = new wxMenuBar(); + menuBar->Append(fileMenu, _("File")); + menuBar->Append(helpMenu, _("Help")); + + return menuBar; +} + +void CDStarRepeaterConfigFrame::onQuit(wxCommandEvent& event) +{ + Close(false); +} + +void CDStarRepeaterConfigFrame::onClose(wxCloseEvent& event) +{ + Destroy(); +} + +void CDStarRepeaterConfigFrame::onAbout(wxCommandEvent& event) +{ + wxAboutDialogInfo info; + info.AddDeveloper(wxT("Jonathan Naylor, G4KLX")); + info.SetCopyright(wxT("(C) 2013,2014.2015 using GPL v2 or later")); + info.SetName(APPLICATION_NAME); + info.SetVersion(VERSION); + info.SetDescription(_("This program configures the D-Star Repeater.")); + + ::wxAboutBox(info); +} + +void CDStarRepeaterConfigFrame::onSave(wxCommandEvent& event) +{ + if (!m_callsign->Validate() || !m_network->Validate() || !m_times->Validate() || !m_beacon->Validate() || + !m_announcement->Validate() || !m_modem->Validate() || !m_control1->Validate() || !m_control2->Validate() || + !m_controller->Validate()) + return; + + wxString callsign = m_callsign->getCallsign(); + wxString gateway = m_callsign->getGateway(); + DSTAR_MODE mode = m_callsign->getMode(); + ACK_TYPE ack = m_callsign->getAck(); + bool restriction = m_callsign->getRestriction(); + bool rpt1Validation = m_callsign->getRPT1Validation(); + bool dtmfBlanking = m_callsign->getDTMFBlanking(); + bool errorReply = m_callsign->getErrorReply(); + m_config->setCallsign(callsign, gateway, mode, ack, restriction, rpt1Validation, dtmfBlanking, errorReply); + + wxString gatewayAddress = m_network->getGatewayAddress(); + unsigned int gatewayPort = m_network->getGatewayPort(); + wxString localAddress = m_network->getLocalAddress(); + unsigned int localPort = m_network->getLocalPort(); + wxString netName = m_network->getName(); + m_config->setNetwork(gatewayAddress, gatewayPort, localAddress, localPort, netName); + + unsigned int timeout = m_times->getTimeout(); + unsigned int ackTime = m_times->getAckTime(); + m_config->setTimes(timeout, ackTime); + + unsigned int beaconTime = m_beacon->getTime(); + wxString beaconText = m_beacon->getText(); + bool beaconVoice = m_beacon->getVoice(); + TEXT_LANG language = m_beacon->getLanguage(); + m_config->setBeacon(beaconTime, beaconText, beaconVoice, language); + + bool announcementEnabled = m_announcement->getEnabled(); + unsigned int announcementTime = m_announcement->getTime(); + wxString announcementRecordRPT1 = m_announcement->getRecordRPT1(); + wxString announcementRecordRPT2 = m_announcement->getRecordRPT2(); + wxString announcementDeleteRPT1 = m_announcement->getDeleteRPT1(); + wxString announcementDeleteRPT2 = m_announcement->getDeleteRPT2(); + m_config->setAnnouncement(announcementEnabled, announcementTime, announcementRecordRPT1, announcementRecordRPT2, announcementDeleteRPT1, announcementDeleteRPT2); + + wxString modemType = m_modem->getType(); + m_config->setModem(modemType); + + bool enabled = m_control1->getEnabled(); + wxString rpt1Callsign = m_control1->getRPT1Callsign(); + wxString rpt2Callsign = m_control1->getRPT2Callsign(); + wxString shutdown = m_control1->getShutdown(); + wxString startup = m_control1->getStartup(); + wxString status1 = m_control1->getStatus1(); + wxString status2 = m_control1->getStatus2(); + wxString status3 = m_control1->getStatus3(); + wxString status4 = m_control1->getStatus4(); + wxString status5 = m_control1->getStatus5(); + wxString output1 = m_control1->getOutput1(); + wxString output2 = m_control1->getOutput2(); + wxString output3 = m_control1->getOutput3(); + wxString output4 = m_control1->getOutput4(); + wxString command1 = m_control2->getCommand1(); + wxString command1Line = m_control2->getCommand1Line(); + wxString command2 = m_control2->getCommand2(); + wxString command2Line = m_control2->getCommand2Line(); + wxString command3 = m_control2->getCommand3(); + wxString command3Line = m_control2->getCommand3Line(); + wxString command4 = m_control2->getCommand4(); + wxString command4Line = m_control2->getCommand4Line(); + wxString command5 = m_control2->getCommand5(); + wxString command5Line = m_control2->getCommand5Line(); + wxString command6 = m_control2->getCommand6(); + wxString command6Line = m_control2->getCommand6Line(); + m_config->setControl(enabled, rpt1Callsign, rpt2Callsign, shutdown, startup, status1, status2, status3, status4, status5, command1, command1Line, command2, command2Line, command3, command3Line, command4, command4Line, command5, command5Line, command6, command6Line, output1, output2, output3, output4); + + wxString controllerType = m_controller->getType(); + unsigned int serialConfig = m_controller->getConfig(); + bool pttInvert = m_controller->getPTTInvert(); + unsigned int activeHangTime = m_controller->getTime(); + m_config->setController(controllerType, serialConfig, pttInvert, activeHangTime); + + bool ret = m_config->write(); + if (!ret) { + wxMessageDialog dialog(this, _("There was an error when writing the D-Star Repeater configuration file"), _("Error"), wxICON_ERROR); + dialog.ShowModal(); + } else { + wxMessageDialog dialog(this, _("The changes made will not take effect\nuntil the D-Star Repeater is (re)started"), _("Information"), wxICON_INFORMATION); + dialog.ShowModal(); + } +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigFrame.h b/DStarRepeaterConfig/DStarRepeaterConfigFrame.h new file mode 100644 index 0000000..207458f --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigFrame.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2011-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigFrame_H +#define DStarRepeaterConfigFrame_H + +#include "DStarRepeaterConfigAnnouncementSet.h" +#include "DStarRepeaterConfigControllerSet.h" +#include "DStarRepeaterConfigCallsignSet.h" +#include "DStarRepeaterConfigControl1Set.h" +#include "DStarRepeaterConfigControl2Set.h" +#include "DStarRepeaterConfigNetworkSet.h" +#include "DStarRepeaterConfigBeaconSet.h" +#include "DStarRepeaterConfigModemSet.h" +#include "DStarRepeaterConfigTimesSet.h" +#include "DStarRepeaterConfigDefs.h" +#include "DStarRepeaterConfig.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigFrame : public wxFrame { +public: + CDStarRepeaterConfigFrame(const wxString& title, const wxString& confDir, const wxString& name); + virtual ~CDStarRepeaterConfigFrame(); + + virtual void onQuit(wxCommandEvent& event); + virtual void onSave(wxCommandEvent& event); + virtual void onAbout(wxCommandEvent& event); + virtual void onClose(wxCloseEvent& event); + +private: + CDStarRepeaterConfig* m_config; + CDStarRepeaterConfigCallsignSet* m_callsign; + CDStarRepeaterConfigNetworkSet* m_network; + CDStarRepeaterConfigTimesSet* m_times; + CDStarRepeaterConfigBeaconSet* m_beacon; + CDStarRepeaterConfigAnnouncementSet* m_announcement; + CDStarRepeaterConfigModemSet* m_modem; + CDStarRepeaterConfigControl1Set* m_control1; + CDStarRepeaterConfigControl2Set* m_control2; + CDStarRepeaterConfigControllerSet* m_controller; + + DECLARE_EVENT_TABLE() + + wxMenuBar* createMenuBar(); +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigGMSKSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigGMSKSet.cpp new file mode 100644 index 0000000..26f82bb --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigGMSKSet.cpp @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2010,2011,2012,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigGMSKSet.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH = 150U; + +CDStarRepeaterConfigGMSKSet::CDStarRepeaterConfigGMSKSet(wxWindow* parent, int id, USB_INTERFACE type, unsigned int address) : +wxDialog(parent, id, wxString(_("GMSK Modem Settings"))), +#if defined(WIN32) +m_type(NULL), +#endif +m_address(NULL) +{ + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + +#if defined(WIN32) + wxStaticText* typeLabel = new wxStaticText(this, -1, _("Type")); + sizer->Add(typeLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_type = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + m_type->Append(wxT("LibUSB")); + m_type->Append(wxT("WinUSB")); + sizer->Add(m_type, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_type->SetSelection(int(type)); +#endif + + wxStaticText* addressLabel = new wxStaticText(this, -1, _("Address")); + sizer->Add(addressLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_address = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + m_address->Append(wxT("0x300")); + m_address->Append(wxT("0x301")); + m_address->Append(wxT("0x302")); + m_address->Append(wxT("0x303")); + m_address->Append(wxT("0x304")); + m_address->Append(wxT("0x305")); + m_address->Append(wxT("0x306")); + m_address->Append(wxT("0x307")); + m_address->Append(wxT("0x308")); + m_address->Append(wxT("0x309")); + m_address->Append(wxT("0x30A")); + m_address->Append(wxT("0x30B")); + m_address->Append(wxT("0x30C")); + m_address->Append(wxT("0x30D")); + m_address->Append(wxT("0x30E")); + m_address->Append(wxT("0x30F")); + m_address->Append(wxT("0x310")); + m_address->Append(wxT("0x311")); + m_address->Append(wxT("0x312")); + m_address->Append(wxT("0x313")); + m_address->Append(wxT("0x314")); + m_address->Append(wxT("0x315")); + m_address->Append(wxT("0x316")); + m_address->Append(wxT("0x317")); + m_address->Append(wxT("0x318")); + m_address->Append(wxT("0x319")); + m_address->Append(wxT("0x31A")); + m_address->Append(wxT("0x31B")); + m_address->Append(wxT("0x31C")); + m_address->Append(wxT("0x31D")); + m_address->Append(wxT("0x31E")); + m_address->Append(wxT("0x31F")); + sizer->Add(m_address, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_address->SetSelection(address - 0x0300U); + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigGMSKSet::~CDStarRepeaterConfigGMSKSet() +{ +} + +bool CDStarRepeaterConfigGMSKSet::Validate() +{ +#if defined(WIN32) + if (m_type->GetCurrentSelection() == wxNOT_FOUND) + return false; +#endif + + return m_address->GetCurrentSelection() != wxNOT_FOUND; +} + +USB_INTERFACE CDStarRepeaterConfigGMSKSet::getType() const +{ +#if defined(WIN32) + int n = m_type->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return UI_WINUSB; + else + return USB_INTERFACE(n); +#else + return UI_LIBUSB; +#endif +} + +unsigned int CDStarRepeaterConfigGMSKSet::getAddress() const +{ + int n = m_address->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return 0x0300U; + else + return n + 0x0300U; +} + diff --git a/DStarRepeaterConfig/DStarRepeaterConfigGMSKSet.h b/DStarRepeaterConfig/DStarRepeaterConfigGMSKSet.h new file mode 100644 index 0000000..ae2834b --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigGMSKSet.h @@ -0,0 +1,44 @@ +/* + * Copyright (C) 2010,2011,2012 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigGMSKSet_H +#define DStarRepeaterConfigGMSKSet_H + +#include "DStarRepeaterConfigDefs.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigGMSKSet : public wxDialog { +public: + CDStarRepeaterConfigGMSKSet(wxWindow* parent, int id, USB_INTERFACE type, unsigned int address); + virtual ~CDStarRepeaterConfigGMSKSet(); + + virtual bool Validate(); + + virtual USB_INTERFACE getType() const; + virtual unsigned int getAddress() const; + +private: +#if defined(WIN32) + wxChoice* m_type; +#endif + wxChoice* m_address; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigMMDVMSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigMMDVMSet.cpp new file mode 100644 index 0000000..4d6ae30 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigMMDVMSet.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigMMDVMSet.h" +#include "SerialPortSelector.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH1 = 150U; +const unsigned int CONTROL_WIDTH2 = 300U; + +const unsigned int PORT_LENGTH = 5U; + +CDStarRepeaterConfigMMDVMSet::CDStarRepeaterConfigMMDVMSet(wxWindow* parent, int id, const wxString& port, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int rxLevel, unsigned int txLevel) : +wxDialog(parent, id, wxString(_("MMDVM Settings"))), +m_port(NULL), +m_txInvert(NULL), +m_rxInvert(NULL), +m_pttInvert(NULL), +m_txDelay(NULL), +m_rxLevel(NULL), +m_txLevel(NULL) +{ + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* portLabel = new wxStaticText(this, -1, _("Port")); + sizer->Add(portLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_port = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + sizer->Add(m_port, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxArrayString ports = CSerialPortSelector::getDevices(); + for (unsigned int i = 0U; i < ports.GetCount(); i++) + m_port->Append(ports.Item(i)); + + bool found = m_port->SetStringSelection(port); + if (!found) + m_port->SetSelection(0); + + wxStaticText* txInvertLabel = new wxStaticText(this, -1, _("TX Inversion")); + sizer->Add(txInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_txInvert->Append(_("Off")); + m_txInvert->Append(_("On")); + sizer->Add(m_txInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_txInvert->SetSelection(txInvert ? 1 : 0); + + wxStaticText* rxInvertLabel = new wxStaticText(this, -1, _("RX Inversion")); + sizer->Add(rxInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_rxInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_rxInvert->Append(_("Off")); + m_rxInvert->Append(_("On")); + sizer->Add(m_rxInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_rxInvert->SetSelection(rxInvert ? 1 : 0); + + wxStaticText* pttInvertLabel = new wxStaticText(this, -1, _("PTT Inversion")); + sizer->Add(pttInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_pttInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_pttInvert->Append(_("Off")); + m_pttInvert->Append(_("On")); + sizer->Add(m_pttInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_pttInvert->SetSelection(pttInvert ? 1 : 0); + + wxStaticText* txDelayLabel = new wxStaticText(this, -1, _("TX Delay (ms)")); + sizer->Add(txDelayLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txDelay = new wxSlider(this, -1, txDelay, 0, 500, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txDelay, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* rxLevelLabel = new wxStaticText(this, -1, _("RX Level (%)")); + sizer->Add(rxLevelLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_rxLevel = new wxSlider(this, -1, rxLevel, 0, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_rxLevel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txLevelLabel = new wxStaticText(this, -1, _("TX Level (%)")); + sizer->Add(txLevelLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txLevel = new wxSlider(this, -1, txLevel, 0, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txLevel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigMMDVMSet::~CDStarRepeaterConfigMMDVMSet() +{ +} + +bool CDStarRepeaterConfigMMDVMSet::Validate() +{ + if (m_port->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_txInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_rxInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_pttInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + return true; +} + +wxString CDStarRepeaterConfigMMDVMSet::getPort() const +{ + int n = m_port->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return wxEmptyString; + + return m_port->GetStringSelection(); +} + +bool CDStarRepeaterConfigMMDVMSet::getRXInvert() const +{ + int n = m_rxInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +bool CDStarRepeaterConfigMMDVMSet::getTXInvert() const +{ + int n = m_txInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +bool CDStarRepeaterConfigMMDVMSet::getPTTInvert() const +{ + int n = m_pttInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +unsigned int CDStarRepeaterConfigMMDVMSet::getTXDelay() const +{ + return (unsigned int)m_txDelay->GetValue(); +} + +unsigned int CDStarRepeaterConfigMMDVMSet::getRXLevel() const +{ + return (unsigned int)m_rxLevel->GetValue(); +} + +unsigned int CDStarRepeaterConfigMMDVMSet::getTXLevel() const +{ + return (unsigned int)m_txLevel->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigMMDVMSet.h b/DStarRepeaterConfig/DStarRepeaterConfigMMDVMSet.h new file mode 100644 index 0000000..9b5c288 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigMMDVMSet.h @@ -0,0 +1,52 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigMMDVMSet_H +#define DStarRepeaterConfigMMDVMSet_H + +#include "DStarRepeaterConfigDefs.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigMMDVMSet : public wxDialog { +public: + CDStarRepeaterConfigMMDVMSet(wxWindow* parent, int id, const wxString& port, bool rxInvert, bool txInvert, bool pttInvert, unsigned int txDelay, unsigned int rxLevel, unsigned int txLevel); + virtual ~CDStarRepeaterConfigMMDVMSet(); + + virtual bool Validate(); + + virtual wxString getPort() const; + virtual bool getRXInvert() const; + virtual bool getTXInvert() const; + virtual bool getPTTInvert() const; + virtual unsigned int getTXDelay() const; + virtual unsigned int getRXLevel() const; + virtual unsigned int getTXLevel() const; + +private: + wxChoice* m_port; + wxChoice* m_txInvert; + wxChoice* m_rxInvert; + wxChoice* m_pttInvert; + wxSlider* m_txDelay; + wxSlider* m_rxLevel; + wxSlider* m_txLevel; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigModemSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigModemSet.cpp new file mode 100644 index 0000000..d25efcd --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigModemSet.cpp @@ -0,0 +1,272 @@ +/* + * Copyright (C) 2011-2015 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigSoundCardSet.h" +#include "DStarRepeaterConfigDVRPTR1Set.h" +#include "DStarRepeaterConfigDVRPTR2Set.h" +#include "DStarRepeaterConfigDVRPTR3Set.h" +#include "DStarRepeaterConfigDVMegaSet.h" +#include "DStarRepeaterConfigModemSet.h" +#include "DStarRepeaterConfigMMDVMSet.h" +#include "DStarRepeaterConfigGMSKSet.h" +#include "DStarRepeaterConfigDVAPSet.h" +#include "DStarRepeaterConfigSplitSet.h" + +enum { + Button_Configure = 7088 +}; + +BEGIN_EVENT_TABLE(CDStarRepeaterConfigModemSet, wxPanel) + EVT_BUTTON(Button_Configure, CDStarRepeaterConfigModemSet::onConfigure) +END_EVENT_TABLE() + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH = 130U; + +CDStarRepeaterConfigModemSet::CDStarRepeaterConfigModemSet(wxWindow* parent, int id, const wxString& title, CDStarRepeaterConfig* config, const wxString& type) : +wxPanel(parent, id), +m_config(config), +m_title(title), +m_type(NULL) +{ + wxASSERT(config != NULL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* typeLabel = new wxStaticText(this, -1, _("Type")); + sizer->Add(typeLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_type = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + m_type->Append(wxT("DVAP")); + m_type->Append(wxT("DVMEGA")); + m_type->Append(wxT("DV-RPTR V1")); + m_type->Append(wxT("DV-RPTR V2")); + m_type->Append(wxT("DV-RPTR V3")); + m_type->Append(wxT("GMSK Modem")); + m_type->Append(wxT("MMDVM")); + m_type->Append(wxT("Sound Card")); + m_type->Append(wxT("Split")); + sizer->Add(m_type, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* dummy = new wxStaticText(this, -1, wxEmptyString); + sizer->Add(dummy, 0, wxALL, BORDER_SIZE); + + wxButton* configure = new wxButton(this, Button_Configure, _("Configure..."), wxDefaultPosition, wxSize(CONTROL_WIDTH, -1)); + sizer->Add(configure, 0, wxALL, BORDER_SIZE); + + if (type.IsEmpty()) { + m_type->SetSelection(0); + } else { + int n = m_type->SetStringSelection(type); + if (n == wxNOT_FOUND) + m_type->SetSelection(0); + } + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + +CDStarRepeaterConfigModemSet::~CDStarRepeaterConfigModemSet() +{ +} + +bool CDStarRepeaterConfigModemSet::Validate() +{ + return m_type->GetCurrentSelection() != wxNOT_FOUND; +} + +wxString CDStarRepeaterConfigModemSet::getType() const +{ + int n = m_type->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return wxT("DVAP"); + + return m_type->GetStringSelection(); +} + +void CDStarRepeaterConfigModemSet::onConfigure(wxCommandEvent& event) +{ + int n = m_type->GetSelection(); + if (n == wxNOT_FOUND) + return; + + wxString type = m_type->GetStringSelection(); + + if (type.IsSameAs(wxT("DVAP"))) { + wxString port; + unsigned int frequency; + int power, squelch; + m_config->getDVAP(port, frequency, power, squelch); + CDStarRepeaterConfigDVAPSet modem(this, -1, port, frequency, power, squelch); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + port = modem.getPort(); + frequency = modem.getFrequency(); + power = modem.getPower(); + squelch = modem.getSquelch(); + m_config->setDVAP(port, frequency, power, squelch); + } + } + } else if (type.IsSameAs(wxT("GMSK Modem"))) { + USB_INTERFACE modemType; + unsigned int modemAddress; + m_config->getGMSK(modemType, modemAddress); + CDStarRepeaterConfigGMSKSet modem(this, -1, modemType, modemAddress); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + modemType = modem.getType(); + modemAddress = modem.getAddress(); + m_config->setGMSK(modemType, modemAddress); + } + } + } else if (type.IsSameAs(wxT("DV-RPTR V1"))) { + wxString port; + bool txInvert, rxInvert, channel; + unsigned int modLevel, txDelay; + m_config->getDVRPTR1(port, rxInvert, txInvert, channel, modLevel, txDelay); + CDStarRepeaterConfigDVRPTR1Set modem(this, -1, port, rxInvert, txInvert, channel, modLevel, txDelay); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + port = modem.getPort(); + rxInvert = modem.getRXInvert(); + txInvert = modem.getTXInvert(); + channel = modem.getChannel(); + modLevel = modem.getModLevel(); + txDelay = modem.getTXDelay(); + m_config->setDVRPTR1(port, rxInvert, txInvert, channel, modLevel, txDelay); + } + } + } else if (type.IsSameAs(wxT("DV-RPTR V2"))) { + CONNECTION_TYPE connType; + wxString usbPort, address; + bool txInvert; + unsigned int port, modLevel, txDelay; + m_config->getDVRPTR2(connType, usbPort, address, port, txInvert, modLevel, txDelay); + CDStarRepeaterConfigDVRPTR2Set modem(this, -1, connType, usbPort, address, port, txInvert, modLevel, txDelay); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + connType = modem.getConnectionType(); + usbPort = modem.getUSBPort(); + address = modem.getAddress(); + port = modem.getPort(); + txInvert = modem.getTXInvert(); + modLevel = modem.getModLevel(); + txDelay = modem.getTXDelay(); + m_config->setDVRPTR2(connType, usbPort, address, port, txInvert, modLevel, txDelay); + } + } + } else if (type.IsSameAs(wxT("DV-RPTR V3"))) { + CONNECTION_TYPE connType; + wxString usbPort, address; + bool txInvert; + unsigned int port, modLevel, txDelay; + m_config->getDVRPTR3(connType, usbPort, address, port, txInvert, modLevel, txDelay); + CDStarRepeaterConfigDVRPTR3Set modem(this, -1, connType, usbPort, address, port, txInvert, modLevel, txDelay); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + connType = modem.getConnectionType(); + usbPort = modem.getUSBPort(); + address = modem.getAddress(); + port = modem.getPort(); + txInvert = modem.getTXInvert(); + modLevel = modem.getModLevel(); + txDelay = modem.getTXDelay(); + m_config->setDVRPTR3(connType, usbPort, address, port, txInvert, modLevel, txDelay); + } + } + } else if (type.IsSameAs(wxT("DVMEGA"))) { + wxString port; + DVMEGA_VARIANT variant; + bool txInvert, rxInvert; + unsigned int txDelay, rxFrequency, txFrequency, power; + m_config->getDVMEGA(port, variant, rxInvert, txInvert, txDelay, rxFrequency, txFrequency, power); + CDStarRepeaterConfigDVMegaSet modem(this, -1, port, variant, rxInvert, txInvert, txDelay, rxFrequency, txFrequency, power); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + port = modem.getPort(); + variant = modem.getVariant(); + rxInvert = modem.getRXInvert(); + txInvert = modem.getTXInvert(); + txDelay = modem.getTXDelay(); + rxFrequency = modem.getRXFrequency(); + txFrequency = modem.getTXFrequency(); + power = modem.getPower(); + m_config->setDVMEGA(port, variant, rxInvert, txInvert, txDelay, rxFrequency, txFrequency, power); + } + } + } else if (type.IsSameAs(wxT("MMDVM"))) { + wxString port; + bool txInvert, rxInvert, pttInvert; + unsigned int txDelay, rxLevel, txLevel; + m_config->getMMDVM(port, rxInvert, txInvert, pttInvert, txDelay, rxLevel, txLevel); + CDStarRepeaterConfigMMDVMSet modem(this, -1, port, rxInvert, txInvert, pttInvert, txDelay, rxLevel, txLevel); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + port = modem.getPort(); + rxInvert = modem.getRXInvert(); + txInvert = modem.getTXInvert(); + pttInvert = modem.getPTTInvert(); + txDelay = modem.getTXDelay(); + rxLevel = modem.getRXLevel(); + txLevel = modem.getTXLevel(); + m_config->setMMDVM(port, rxInvert, txInvert, pttInvert, txDelay, rxLevel, txLevel); + } + } + } else if (type.IsSameAs(wxT("Sound Card"))) { + wxString rxDevice, txDevice; + bool txInvert, rxInvert; + wxFloat32 rxLevel, txLevel; + unsigned int txDelay, txTail; + m_config->getSoundCard(rxDevice, txDevice, rxInvert, txInvert, rxLevel, txLevel, txDelay, txTail); + CDStarRepeaterConfigSoundCardSet modem(this, -1, rxDevice, txDevice, rxInvert, txInvert, rxLevel, txLevel, txDelay, txTail); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + rxDevice = modem.getRXDevice(); + txDevice = modem.getTXDevice(); + rxInvert = modem.getRXInvert(); + txInvert = modem.getTXInvert(); + rxLevel = modem.getRXLevel(); + txLevel = modem.getTXLevel(); + txDelay = modem.getTXDelay(); + txTail = modem.getTXTail(); + m_config->setSoundCard(rxDevice, txDevice, rxInvert, txInvert, rxLevel, txLevel, txDelay, txTail); + } + } + } else if (type.IsSameAs(wxT("Split"))) { + wxString localAddress; + unsigned int localPort, timeout; + wxArrayString transmitterNames, receiverNames; + m_config->getSplit(localAddress, localPort, transmitterNames, receiverNames, timeout); + CDStarRepeaterConfigSplitSet modem(this, -1, localAddress, localPort, transmitterNames, receiverNames, timeout); + if (modem.ShowModal() == wxID_OK) { + if (modem.Validate()) { + localAddress = modem.getLocalAddress(); + localPort = modem.getLocalPort(); + transmitterNames = modem.getTransmitterNames(); + receiverNames = modem.getReceiverNames(); + timeout = modem.getTimeout(); + m_config->setSplit(localAddress, localPort, transmitterNames, receiverNames, timeout); + } + } + } +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigModemSet.h b/DStarRepeaterConfig/DStarRepeaterConfigModemSet.h new file mode 100644 index 0000000..9a68ef4 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigModemSet.h @@ -0,0 +1,47 @@ +/* + * Copyright (C) 2011,2012,2013 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigModemSet_H +#define DStarRepeaterConfigModemSet_H + +#include "DStarRepeaterConfigDefs.h" +#include "DStarRepeaterConfig.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigModemSet : public wxPanel { +public: + CDStarRepeaterConfigModemSet(wxWindow* parent, int id, const wxString& title, CDStarRepeaterConfig* config, const wxString& type); + virtual ~CDStarRepeaterConfigModemSet(); + + virtual bool Validate(); + + virtual wxString getType() const; + + virtual void onConfigure(wxCommandEvent& event); + +private: + CDStarRepeaterConfig* m_config; + wxString m_title; + wxChoice* m_type; + + DECLARE_EVENT_TABLE() +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigNetworkSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigNetworkSet.cpp new file mode 100644 index 0000000..8b71527 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigNetworkSet.cpp @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2010,2011,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigNetworkSet.h" + +const unsigned int ADDRESS_WIDTH = 120U; +const unsigned int PORT_WIDTH = 80U; + +const unsigned int ADDRESS_LENGTH = 15U; +const unsigned int PORT_LENGTH = 5U; + +const unsigned int BORDER_SIZE = 5U; + +CDStarRepeaterConfigNetworkSet::CDStarRepeaterConfigNetworkSet(wxWindow* parent, int id, const wxString& title, const wxString& gatewayAddress, unsigned int gatewayPort, const wxString& localAddress, unsigned int localPort, const wxString& name) : +wxPanel(parent, id), +m_title(title), +m_gatewayAddress(NULL), +m_gatewayPort(NULL), +m_localAddress(NULL), +m_localPort(NULL), +m_name(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* gatewayAddressLabel = new wxStaticText(this, -1, _("Gateway Address")); + sizer->Add(gatewayAddressLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_gatewayAddress = new CAddressTextCtrl(this, -1, gatewayAddress, wxDefaultPosition, wxSize(ADDRESS_WIDTH, -1)); + m_gatewayAddress->SetMaxLength(ADDRESS_LENGTH); + sizer->Add(m_gatewayAddress, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* gatewayPortLabel = new wxStaticText(this, -1, _("Gateway Port")); + sizer->Add(gatewayPortLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + wxString buffer; + buffer.Printf(wxT("%u"), gatewayPort); + + m_gatewayPort = new CPortTextCtrl(this, -1, buffer, wxDefaultPosition, wxSize(PORT_WIDTH, -1)); + m_gatewayPort->SetMaxLength(PORT_LENGTH); + sizer->Add(m_gatewayPort, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* localAddressLabel = new wxStaticText(this, -1, _("Local Address")); + sizer->Add(localAddressLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_localAddress = new CAddressTextCtrl(this, -1, localAddress, wxDefaultPosition, wxSize(ADDRESS_WIDTH, -1)); + m_localAddress->SetMaxLength(ADDRESS_LENGTH); + sizer->Add(m_localAddress, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* localPortLabel = new wxStaticText(this, -1, _("Local Port")); + sizer->Add(localPortLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + buffer.Printf(wxT("%u"), localPort); + + m_localPort = new CPortTextCtrl(this, -1, buffer, wxDefaultPosition, wxSize(PORT_WIDTH, -1)); + m_localPort->SetMaxLength(PORT_LENGTH); + sizer->Add(m_localPort, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* nameLabel = new wxStaticText(this, -1, _("Name")); + sizer->Add(nameLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_name = new wxTextCtrl(this, -1, name, wxDefaultPosition, wxSize(ADDRESS_WIDTH, -1)); + sizer->Add(m_name, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + SetAutoLayout(true); + + sizer->Fit(this); + sizer->SetSizeHints(this); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigNetworkSet::~CDStarRepeaterConfigNetworkSet() +{ +} + +bool CDStarRepeaterConfigNetworkSet::Validate() +{ + unsigned int port = getGatewayPort(); + + if (port == 0U || port > 65535U) { + wxMessageDialog dialog(this, _("The Gateway Port is not valid"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + port = getLocalPort(); + + if (port == 0U || port > 65535U) { + wxMessageDialog dialog(this, _("The Local Port is not valid"), m_title + _(" Error"), wxICON_ERROR); + dialog.ShowModal(); + return false; + } + + return true; +} + +wxString CDStarRepeaterConfigNetworkSet::getGatewayAddress() const +{ + return m_gatewayAddress->GetValue(); +} + +unsigned int CDStarRepeaterConfigNetworkSet::getGatewayPort() const +{ + unsigned long n; + + m_gatewayPort->GetValue().ToULong(&n); + + return n; +} + +wxString CDStarRepeaterConfigNetworkSet::getLocalAddress() const +{ + return m_localAddress->GetValue(); +} + +unsigned int CDStarRepeaterConfigNetworkSet::getLocalPort() const +{ + unsigned long n; + + m_localPort->GetValue().ToULong(&n); + + return n; +} + +wxString CDStarRepeaterConfigNetworkSet::getName() const +{ + return m_name->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigNetworkSet.h b/DStarRepeaterConfig/DStarRepeaterConfigNetworkSet.h new file mode 100644 index 0000000..e3052b1 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigNetworkSet.h @@ -0,0 +1,49 @@ +/* + * Copyright (C) 2010,2011,2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigNetworkSet_H +#define DStarRepeaterConfigNetworkSet_H + +#include "AddressTextCtrl.h" +#include "PortTextCtrl.h" + +#include + +class CDStarRepeaterConfigNetworkSet : public wxPanel { +public: + CDStarRepeaterConfigNetworkSet(wxWindow* parent, int id, const wxString& title, const wxString& gatewayAddress, unsigned int gatewayPort, const wxString& localAddress, unsigned int localPort, const wxString& name); + virtual ~CDStarRepeaterConfigNetworkSet(); + + virtual bool Validate(); + + virtual wxString getGatewayAddress() const; + virtual unsigned int getGatewayPort() const; + virtual wxString getLocalAddress() const; + virtual unsigned int getLocalPort() const; + virtual wxString getName() const; + +private: + wxString m_title; + CAddressTextCtrl* m_gatewayAddress; + CPortTextCtrl* m_gatewayPort; + CAddressTextCtrl* m_localAddress; + CPortTextCtrl* m_localPort; + wxTextCtrl* m_name; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigSoundCardSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigSoundCardSet.cpp new file mode 100644 index 0000000..98e1c9a --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigSoundCardSet.cpp @@ -0,0 +1,206 @@ +/* + * Copyright (C) 2009-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigSoundCardSet.h" +#include "SoundCardReaderWriter.h" + +const unsigned int BORDER_SIZE = 5U; +const unsigned int CONTROL_WIDTH1 = 150U; +const unsigned int CONTROL_WIDTH2 = 300U; +const unsigned int CONTROL_WIDTH3 = 350U; + +const unsigned int ADDRESS_LENGTH = 15U; +const unsigned int PORT_LENGTH = 5U; + + +CDStarRepeaterConfigSoundCardSet::CDStarRepeaterConfigSoundCardSet(wxWindow* parent, int id, const wxString& rxDevice, const wxString& txDevice, bool rxInvert, bool txInvert, wxFloat32 rxLevel, wxFloat32 txLevel, unsigned int txDelay, unsigned int txTail) : +wxDialog(parent, id, wxString(_("Sound Card Settings"))), +m_rxDevice(NULL), +m_txDevice(NULL), +m_rxInvert(NULL), +m_txInvert(NULL), +m_rxLevel(NULL), +m_txLevel(NULL), +m_txDelay(NULL), +m_txTail(NULL) +{ + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* rxDeviceLabel = new wxStaticText(this, -1, _("RX Device")); + sizer->Add(rxDeviceLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_rxDevice = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH3, -1)); + sizer->Add(m_rxDevice, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxArrayString devs = CSoundCardReaderWriter::getReadDevices(); + for (unsigned int i = 0U; i < devs.GetCount(); i++) + m_rxDevice->Append(devs.Item(i)); + + bool found = m_rxDevice->SetStringSelection(rxDevice); + if (!found) + m_rxDevice->SetSelection(0); + + wxStaticText* txDeviceLabel = new wxStaticText(this, -1, _("TX Device")); + sizer->Add(txDeviceLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txDevice = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH3, -1)); + sizer->Add(m_txDevice, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + devs = CSoundCardReaderWriter::getWriteDevices(); + for (unsigned int i = 0U; i < devs.GetCount(); i++) + m_txDevice->Append(devs.Item(i)); + + found = m_txDevice->SetStringSelection(txDevice); + if (!found) + m_txDevice->SetSelection(0); + + wxStaticText* rxInvertLabel = new wxStaticText(this, -1, _("RX Inversion")); + sizer->Add(rxInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_rxInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_rxInvert->Append(_("Off")); + m_rxInvert->Append(_("On")); + sizer->Add(m_rxInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_rxInvert->SetSelection(rxInvert ? 1 : 0); + + wxStaticText* txInvertLabel = new wxStaticText(this, -1, _("TX Inversion")); + sizer->Add(txInvertLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txInvert = new wxChoice(this, -1, wxDefaultPosition, wxSize(CONTROL_WIDTH1, -1)); + m_txInvert->Append(_("Off")); + m_txInvert->Append(_("On")); + sizer->Add(m_txInvert, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + m_txInvert->SetSelection(txInvert ? 1 : 0); + + wxStaticText* rxLevelLabel = new wxStaticText(this, -1, _("RX Level (%)")); + sizer->Add(rxLevelLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + int level = int(rxLevel * 100.0F + 0.5F); + m_rxLevel = new wxSlider(this, -1, level, 0, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_rxLevel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txLevelLabel = new wxStaticText(this, -1, _("TX Level (%)")); + sizer->Add(txLevelLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + level = int(txLevel * 100.0F + 0.5F); + m_txLevel = new wxSlider(this, -1, level, 0, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txLevel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txDelayLabel = new wxStaticText(this, -1, _("TX Delay (ms)")); + sizer->Add(txDelayLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txDelay = new wxSlider(this, -1, txDelay, 0, 500, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txDelay, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* txTailLabel = new wxStaticText(this, -1, _("TX Tail (ms)")); + sizer->Add(txTailLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_txTail = new wxSlider(this, -1, txTail, 0, 100, wxDefaultPosition, wxSize(CONTROL_WIDTH2, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_txTail, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigSoundCardSet::~CDStarRepeaterConfigSoundCardSet() +{ +} + +bool CDStarRepeaterConfigSoundCardSet::Validate() +{ + if (m_rxDevice->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_txDevice->GetCurrentSelection() == wxNOT_FOUND) + return false; + + if (m_rxInvert->GetCurrentSelection() == wxNOT_FOUND) + return false; + + return m_txInvert->GetCurrentSelection() != wxNOT_FOUND; +} + +wxString CDStarRepeaterConfigSoundCardSet::getRXDevice() const +{ + int n = m_rxDevice->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return wxEmptyString; + + return m_rxDevice->GetStringSelection(); +} + +wxString CDStarRepeaterConfigSoundCardSet::getTXDevice() const +{ + int n = m_txDevice->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return wxEmptyString; + + return m_txDevice->GetStringSelection(); +} + +bool CDStarRepeaterConfigSoundCardSet::getRXInvert() const +{ + int n = m_rxInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +bool CDStarRepeaterConfigSoundCardSet::getTXInvert() const +{ + int n = m_txInvert->GetCurrentSelection(); + + if (n == wxNOT_FOUND) + return false; + + return n == 1; +} + +wxFloat32 CDStarRepeaterConfigSoundCardSet::getRXLevel() const +{ + return wxFloat32(m_rxLevel->GetValue()) / 100.0F; +} + +wxFloat32 CDStarRepeaterConfigSoundCardSet::getTXLevel() const +{ + return wxFloat32(m_txLevel->GetValue()) / 100.0F; +} + +unsigned int CDStarRepeaterConfigSoundCardSet::getTXDelay() const +{ + return (unsigned int)m_txDelay->GetValue(); +} + +unsigned int CDStarRepeaterConfigSoundCardSet::getTXTail() const +{ + return (unsigned int)m_txTail->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigSoundCardSet.h b/DStarRepeaterConfig/DStarRepeaterConfigSoundCardSet.h new file mode 100644 index 0000000..82e32b0 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigSoundCardSet.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2009-2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigSoundCardSet_H +#define DStarRepeaterConfigSoundCardSet_H + +#include "DStarRepeaterConfigDefs.h" +#include "DStarDefines.h" + +#include + +class CDStarRepeaterConfigSoundCardSet : public wxDialog { +public: + CDStarRepeaterConfigSoundCardSet(wxWindow* parent, int id, const wxString& rxDevice, const wxString& txDevice, bool rxInvert, bool txInvert, wxFloat32 rxLevel, wxFloat32 txLevel, unsigned int txDelay, unsigned int txTail); + virtual ~CDStarRepeaterConfigSoundCardSet(); + + virtual bool Validate(); + + virtual wxString getRXDevice() const; + virtual wxString getTXDevice() const; + virtual bool getRXInvert() const; + virtual bool getTXInvert() const; + virtual wxFloat32 getRXLevel() const; + virtual wxFloat32 getTXLevel() const; + virtual unsigned int getTXDelay() const; + virtual unsigned int getTXTail() const; + +private: + wxChoice* m_rxDevice; + wxChoice* m_txDevice; + wxChoice* m_rxInvert; + wxChoice* m_txInvert; + wxSlider* m_rxLevel; + wxSlider* m_txLevel; + wxSlider* m_txDelay; + wxSlider* m_txTail; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigSplitSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigSplitSet.cpp new file mode 100644 index 0000000..5f2f500 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigSplitSet.cpp @@ -0,0 +1,162 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigSplitSet.h" +#include "DStarDefines.h" + +const unsigned int BORDER_SIZE = 5U; + +const unsigned int ADDRESS_WIDTH = 120U; +const unsigned int PORT_WIDTH = 80U; + +const unsigned int ADDRESS_LENGTH = 15U; +const unsigned int PORT_LENGTH = 5U; + +const unsigned int SLIDER_WIDTH = 200U; + +CDStarRepeaterConfigSplitSet::CDStarRepeaterConfigSplitSet(wxWindow* parent, int id, const wxString& localAddress, unsigned int localPort, const wxArrayString& transmitterNames, const wxArrayString& receiverNames, unsigned int timeout) : +wxDialog(parent, id, wxString(_("Split Settings"))), +m_localAddress(NULL), +m_localPort(NULL), +m_transmitterNames(NULL), +m_receiverNames(NULL), +m_timeout(NULL), +m_txCount(0U), +m_rxCount(0U) +{ + m_txCount = transmitterNames.GetCount(); + m_rxCount = receiverNames.GetCount(); + + if (m_txCount > SPLIT_TX_GUI_COUNT) + m_txCount = SPLIT_TX_GUI_COUNT; + + if (m_rxCount > SPLIT_RX_GUI_COUNT) + m_rxCount = SPLIT_RX_GUI_COUNT; + + m_transmitterNames = new wxTextCtrl*[m_txCount]; + m_receiverNames = new wxTextCtrl*[m_rxCount]; + + wxBoxSizer* topSizer = new wxBoxSizer(wxVERTICAL); + + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* localLabel = new wxStaticText(this, -1, _("Local Addr/Port")); + sizer->Add(localLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxBoxSizer* localSizer = new wxBoxSizer(wxHORIZONTAL); + + m_localAddress = new CAddressTextCtrl(this, -1, localAddress, wxDefaultPosition, wxSize(ADDRESS_WIDTH, -1)); + m_localAddress->SetMaxLength(ADDRESS_LENGTH); + localSizer->Add(m_localAddress, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxString buffer; + buffer.Printf(wxT("%u"), localPort); + + m_localPort = new CPortTextCtrl(this, -1, buffer, wxDefaultPosition, wxSize(PORT_WIDTH, -1)); + m_localPort->SetMaxLength(PORT_LENGTH); + localSizer->Add(m_localPort, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + sizer->Add(localSizer); + + for (unsigned int i = 0U; i < m_txCount; i++) { + wxString text; + text.Printf(_("TX %u Name"), i + 1U); + + wxStaticText* transmitterLabel = new wxStaticText(this, -1, text); + sizer->Add(transmitterLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_transmitterNames[i] = new wxTextCtrl(this, -1, transmitterNames.Item(i), wxDefaultPosition, wxSize(ADDRESS_WIDTH, -1)); + sizer->Add(m_transmitterNames[i], 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + } + + for (unsigned int i = 0U; i < m_rxCount; i++) { + wxString text; + text.Printf(_("RX %u Name"), i + 1U); + + wxStaticText* receiverLabel = new wxStaticText(this, -1, text); + sizer->Add(receiverLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_receiverNames[i] = new wxTextCtrl(this, -1, receiverNames.Item(i), wxDefaultPosition, wxSize(ADDRESS_WIDTH, -1)); + sizer->Add(m_receiverNames[i], 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + } + + wxStaticText* timeoutLabel = new wxStaticText(this, -1, _("Packet timeout (ms)")); + sizer->Add(timeoutLabel, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + m_timeout = new wxSlider(this, -1, timeout, 10, 300, wxDefaultPosition, wxSize(SLIDER_WIDTH, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_timeout, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + topSizer->Add(sizer); + + topSizer->Add(CreateButtonSizer(wxOK | wxCANCEL), 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + SetAutoLayout(true); + + topSizer->Fit(this); + topSizer->SetSizeHints(this); + + SetSizer(topSizer); +} + +CDStarRepeaterConfigSplitSet::~CDStarRepeaterConfigSplitSet() +{ +} + +bool CDStarRepeaterConfigSplitSet::Validate() +{ + return true; +} + +wxString CDStarRepeaterConfigSplitSet::getLocalAddress() const +{ + return m_localAddress->GetValue(); +} + +unsigned int CDStarRepeaterConfigSplitSet::getLocalPort() const +{ + unsigned long n; + + m_localPort->GetValue().ToULong(&n); + + return n; +} + +wxArrayString CDStarRepeaterConfigSplitSet::getTransmitterNames() const +{ + wxArrayString names; + + for (unsigned int i = 0U; i < m_txCount; i++) + names.Add(m_transmitterNames[i]->GetValue()); + + return names; +} + +wxArrayString CDStarRepeaterConfigSplitSet::getReceiverNames() const +{ + wxArrayString names; + + for (unsigned int i = 0U; i < m_rxCount; i++) + names.Add(m_receiverNames[i]->GetValue()); + + return names; +} + +unsigned int CDStarRepeaterConfigSplitSet::getTimeout() const +{ + return (unsigned int)m_timeout->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigSplitSet.h b/DStarRepeaterConfig/DStarRepeaterConfigSplitSet.h new file mode 100644 index 0000000..16122d2 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigSplitSet.h @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2014 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigSplitSet_H +#define DStarRepeaterConfigSplitSet_H + +#include "DStarRepeaterConfigDefs.h" +#include "AddressTextCtrl.h" +#include "PortTextCtrl.h" + +#include + +class CDStarRepeaterConfigSplitSet : public wxDialog { +public: + CDStarRepeaterConfigSplitSet(wxWindow* parent, int id, const wxString& localAddress, unsigned int localPort, const wxArrayString& transmitterNames, const wxArrayString& receiverNames, unsigned int timeout); + virtual ~CDStarRepeaterConfigSplitSet(); + + virtual bool Validate(); + + virtual wxString getLocalAddress() const; + virtual unsigned int getLocalPort() const; + + virtual wxArrayString getTransmitterNames() const; + + virtual wxArrayString getReceiverNames() const; + + virtual unsigned int getTimeout() const; + +private: + CAddressTextCtrl* m_localAddress; + CPortTextCtrl* m_localPort; + wxTextCtrl** m_transmitterNames; + wxTextCtrl** m_receiverNames; + wxSlider* m_timeout; + unsigned int m_txCount; + unsigned int m_rxCount; +}; + +#endif diff --git a/DStarRepeaterConfig/DStarRepeaterConfigTimesSet.cpp b/DStarRepeaterConfig/DStarRepeaterConfigTimesSet.cpp new file mode 100644 index 0000000..29598d8 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigTimesSet.cpp @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2011,2012 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "DStarRepeaterConfigTimesSet.h" + +const unsigned int TIMES_WIDTH = 300U; + +const unsigned int BORDER_SIZE = 5U; + +CDStarRepeaterConfigTimesSet::CDStarRepeaterConfigTimesSet(wxWindow* parent, int id, const wxString& title, unsigned int timeout, unsigned int ackTime) : +wxPanel(parent, id), +m_title(title), +m_timeout(NULL), +m_ackTime(NULL) +{ + wxFlexGridSizer* sizer = new wxFlexGridSizer(2); + + wxStaticText* timeoutLabel = new wxStaticText(this, -1, _("Timeout (secs)")); + sizer->Add(timeoutLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + m_timeout = new wxSlider(this, -1, timeout, 0, 300, wxDefaultPosition, wxSize(TIMES_WIDTH, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_timeout, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + wxStaticText* ackTimeLabel = new wxStaticText(this, -1, _("Ack Time (ms)")); + sizer->Add(ackTimeLabel, 0, wxALL | wxALIGN_RIGHT, BORDER_SIZE); + + if (ackTime < 100U) + ackTime = 500U; + + m_ackTime = new wxSlider(this, -1, ackTime, 100, 2000, wxDefaultPosition, wxSize(TIMES_WIDTH, -1), wxSL_HORIZONTAL | wxSL_LABELS); + sizer->Add(m_ackTime, 0, wxALL | wxALIGN_LEFT, BORDER_SIZE); + + SetAutoLayout(true); + + SetSizer(sizer); +} + + +CDStarRepeaterConfigTimesSet::~CDStarRepeaterConfigTimesSet() +{ +} + +bool CDStarRepeaterConfigTimesSet::Validate() +{ + return true; +} + +unsigned int CDStarRepeaterConfigTimesSet::getTimeout() const +{ + return m_timeout->GetValue(); +} + +unsigned int CDStarRepeaterConfigTimesSet::getAckTime() const +{ + return m_ackTime->GetValue(); +} diff --git a/DStarRepeaterConfig/DStarRepeaterConfigTimesSet.h b/DStarRepeaterConfig/DStarRepeaterConfigTimesSet.h new file mode 100644 index 0000000..9d6eef4 --- /dev/null +++ b/DStarRepeaterConfig/DStarRepeaterConfigTimesSet.h @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2011,2012 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef DStarRepeaterConfigTimesSet_H +#define DStarRepeaterConfigTimesSet_H + +#include + +class CDStarRepeaterConfigTimesSet : public wxPanel { +public: + CDStarRepeaterConfigTimesSet(wxWindow* parent, int id, const wxString& title, unsigned int timeout, unsigned int ackTime); + virtual ~CDStarRepeaterConfigTimesSet(); + + virtual bool Validate(); + + virtual unsigned int getTimeout() const; + virtual unsigned int getAckTime() const; + +private: + wxString m_title; + wxSlider* m_timeout; + wxSlider* m_ackTime; +}; + +#endif diff --git a/DStarRepeaterConfig/Makefile b/DStarRepeaterConfig/Makefile new file mode 100644 index 0000000..d9b92db --- /dev/null +++ b/DStarRepeaterConfig/Makefile @@ -0,0 +1,92 @@ +all: dstarrepeaterconfig + +CFLAGS := -I../Common -I../GUICommon $(CFLAGS) + +dstarrepeaterconfig: DStarRepeaterConfigActiveHangSet.o DStarRepeaterConfigAnnouncementSet.o DStarRepeaterConfigApp.o DStarRepeaterConfigBeaconSet.o \ + DStarRepeaterConfigCallsignSet.o DStarRepeaterConfigControl1Set.o DStarRepeaterConfigControl2Set.o DStarRepeaterConfigControllerSet.o \ + DStarRepeaterConfigDVAPSet.o DStarRepeaterConfigDVMegaSet.o DStarRepeaterConfigDVRPTR1Set.o DStarRepeaterConfigDVRPTR2Set.o \ + DStarRepeaterConfigDVRPTR3Set.o DStarRepeaterConfigFrame.o DStarRepeaterConfigGMSKSet.o DStarRepeaterConfigModemSet.o DStarRepeaterConfigNetworkSet.o \ + DStarRepeaterConfigSoundCardSet.o DStarRepeaterConfigSplitSet.o DStarRepeaterConfigTimesSet.o DStarRepeaterConfigMMDVMSet.o ../Common/Common.a \ + ../GUICommon/GUICommon.a + $(CC) $(LDFLAGS) -o dstarrepeaterconfig DStarRepeaterConfigActiveHangSet.o DStarRepeaterConfigAnnouncementSet.o DStarRepeaterConfigApp.o \ + DStarRepeaterConfigBeaconSet.o DStarRepeaterConfigCallsignSet.o DStarRepeaterConfigControl1Set.o DStarRepeaterConfigControl2Set.o \ + DStarRepeaterConfigControllerSet.o DStarRepeaterConfigDVAPSet.o DStarRepeaterConfigDVMegaSet.o DStarRepeaterConfigDVRPTR1Set.o \ + DStarRepeaterConfigDVRPTR2Set.o DStarRepeaterConfigDVRPTR3Set.o DStarRepeaterConfigFrame.o DStarRepeaterConfigGMSKSet.o DStarRepeaterConfigModemSet.o \ + DStarRepeaterConfigNetworkSet.o DStarRepeaterConfigSoundCardSet.o DStarRepeaterConfigSplitSet.o DStarRepeaterConfigTimesSet.o \ + DStarRepeaterConfigMMDVMSet.o ../Common/Common.a ../GUICommon/GUICommon.a $(LIBS) + +DStarRepeaterConfigActiveHangSet.o: DStarRepeaterConfigActiveHangSet.cpp DStarRepeaterConfigActiveHangSet.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigActiveHangSet.cpp + +DStarRepeaterConfigAnnouncementSet.o: DStarRepeaterConfigAnnouncementSet.cpp DStarRepeaterConfigAnnouncementSet.h ../GUICommon/CallsignTextCtrl.h \ + ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigAnnouncementSet.cpp + +DStarRepeaterConfigApp.o: DStarRepeaterConfigApp.cpp DStarRepeaterConfigApp.h DStarRepeaterConfigFrame.h DStarRepeaterConfigDefs.h ../Common/DStarDefines.h \ + ../Common/Version.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigApp.cpp + +DStarRepeaterConfigBeaconSet.o: DStarRepeaterConfigBeaconSet.cpp DStarRepeaterConfigBeaconSet.h ../GUICommon/MessageTextCtrl.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigBeaconSet.cpp + +DStarRepeaterConfigCallsignSet.o: DStarRepeaterConfigCallsignSet.cpp DStarRepeaterConfigCallsignSet.h ../GUICommon/CallsignTextCtrl.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigCallsignSet.cpp + +DStarRepeaterConfigControl1Set.o: DStarRepeaterConfigControl1Set.cpp DStarRepeaterConfigControl1Set.h ../GUICommon/CallsignTextCtrl.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigControl1Set.cpp + +DStarRepeaterConfigControl2Set.o: DStarRepeaterConfigControl2Set.cpp DStarRepeaterConfigControl2Set.h ../GUICommon/CallsignTextCtrl.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigControl2Set.cpp + +DStarRepeaterConfigControllerSet.o: DStarRepeaterConfigControllerSet.cpp DStarRepeaterConfigControllerSet.h ../Common/SerialPortSelector.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigControllerSet.cpp + +DStarRepeaterConfigDVAPSet.o: DStarRepeaterConfigDVAPSet.cpp DStarRepeaterConfigDVAPSet.h ../Common/SerialPortSelector.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigDVAPSet.cpp + +DStarRepeaterConfigDVMegaSet.o: DStarRepeaterConfigDVMegaSet.cpp DStarRepeaterConfigDVMegaSet.h ../Common/SerialPortSelector.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigDVMegaSet.cpp + +DStarRepeaterConfigDVRPTR1Set.o: DStarRepeaterConfigDVRPTR1Set.cpp DStarRepeaterConfigDVRPTR1Set.h ../Common/SerialPortSelector.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigDVRPTR1Set.cpp + +DStarRepeaterConfigDVRPTR2Set.o: DStarRepeaterConfigDVRPTR2Set.cpp DStarRepeaterConfigDVRPTR2Set.h ../Common/SerialPortSelector.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigDVRPTR2Set.cpp + +DStarRepeaterConfigDVRPTR3Set.o: DStarRepeaterConfigDVRPTR3Set.cpp DStarRepeaterConfigDVRPTR3Set.h ../Common/SerialPortSelector.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigDVRPTR3Set.cpp + +DStarRepeaterConfigFrame.o: DStarRepeaterConfigFrame.cpp DStarRepeaterConfigFrame.h DStarRepeaterConfigDefs.h DStarRepeaterConfigControllerSet.h \ + DStarRepeaterConfigModemSet.h DStarRepeaterConfigTimesSet.h DStarRepeaterConfigCallsignSet.h DStarRepeaterConfigNetworkSet.h \ + DStarRepeaterConfigBeaconSet.h DStarRepeaterConfigAnnouncementSet.h DStarRepeaterConfigControl1Set.h DStarRepeaterConfigControl2Set.h \ + ../Common/DStarRepeaterConfig.h ../Common/DStarDefines.h ../Common/Version.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigFrame.cpp + +DStarRepeaterConfigGMSKSet.o: DStarRepeaterConfigGMSKSet.cpp DStarRepeaterConfigGMSKSet.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigGMSKSet.cpp + +DStarRepeaterConfigMMDVMSet.o: DStarRepeaterConfigMMDVMSet.cpp DStarRepeaterConfigMMDVMSet.h ../Common/SerialPortSelector.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigMMDVMSet.cpp + +DStarRepeaterConfigModemSet.o: DStarRepeaterConfigModemSet.cpp DStarRepeaterConfigModemSet.h DStarRepeaterConfigDefs.h DStarRepeaterConfigSoundCardSet.h \ + DStarRepeaterConfigDVRPTR1Set.h DStarRepeaterConfigDVRPTR2Set.h DStarRepeaterConfigDVRPTR3Set.h DStarRepeaterConfigModemSet.h \ + DStarRepeaterConfigGMSKSet.h DStarRepeaterConfigDVAPSet.h DStarRepeaterConfigDVMegaSet.h DStarRepeaterConfigSplitSet.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigModemSet.cpp + +DStarRepeaterConfigNetworkSet.o: DStarRepeaterConfigNetworkSet.cpp DStarRepeaterConfigNetworkSet.h ../GUICommon/AddressTextCtrl.h ../GUICommon/PortTextCtrl.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigNetworkSet.cpp + +DStarRepeaterConfigSplitSet.o: DStarRepeaterConfigSplitSet.cpp DStarRepeaterConfigSplitSet.h ../GUICommon/AddressTextCtrl.h ../GUICommon/PortTextCtrl.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigSplitSet.cpp + +DStarRepeaterConfigSoundCardSet.o: DStarRepeaterConfigSoundCardSet.cpp DStarRepeaterConfigSoundCardSet.h ../Common/SoundCardReaderWriter.h ../Common/DStarDefines.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigSoundCardSet.cpp + +DStarRepeaterConfigTimesSet.o: DStarRepeaterConfigTimesSet.cpp DStarRepeaterConfigTimesSet.h + $(CC) $(CFLAGS) -c DStarRepeaterConfigTimesSet.cpp + +install: all + install -g bin -o root -m 0775 dstarrepeaterconfig $(BINDIR) + +clean: + rm -f core dstarrepeaterconfig *.o *~ *.bak diff --git a/Data/Makefile b/Data/Makefile new file mode 100644 index 0000000..698a52a --- /dev/null +++ b/Data/Makefile @@ -0,0 +1,22 @@ +install: + install -d -g bin -o root -m 0775 $(DATADIR) + install -g bin -o root -m 0664 de_DE.ambe $(DATADIR) + install -g bin -o root -m 0664 de_DE.indx $(DATADIR) + install -g bin -o root -m 0664 dk_DK.ambe $(DATADIR) + install -g bin -o root -m 0664 dk_DK.indx $(DATADIR) + install -g bin -o root -m 0664 en_GB.ambe $(DATADIR) + install -g bin -o root -m 0664 en_GB.indx $(DATADIR) + install -g bin -o root -m 0664 en_US.ambe $(DATADIR) + install -g bin -o root -m 0664 en_US.indx $(DATADIR) + install -g bin -o root -m 0664 es_ES.ambe $(DATADIR) + install -g bin -o root -m 0664 es_ES.indx $(DATADIR) + install -g bin -o root -m 0664 fr_FR.ambe $(DATADIR) + install -g bin -o root -m 0664 fr_FR.indx $(DATADIR) + install -g bin -o root -m 0664 it_IT.ambe $(DATADIR) + install -g bin -o root -m 0664 it_IT.indx $(DATADIR) + install -g bin -o root -m 0664 no_NO.ambe $(DATADIR) + install -g bin -o root -m 0664 no_NO.indx $(DATADIR) + install -g bin -o root -m 0664 pl_PL.ambe $(DATADIR) + install -g bin -o root -m 0664 pl_PL.indx $(DATADIR) + install -g bin -o root -m 0664 se_SE.ambe $(DATADIR) + install -g bin -o root -m 0664 se_SE.indx $(DATADIR) diff --git a/Data/de_DE.ambe b/Data/de_DE.ambe new file mode 100644 index 0000000000000000000000000000000000000000..3189f055f86061396b285f25e8dbca1ce9a85413 GIT binary patch literal 21262 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 Q5mhj02_70iIwP|93mKUH5&!@I literal 0 HcmV?d00001 diff --git a/Data/de_DE.indx b/Data/de_DE.indx new file mode 100644 index 0000000..0f9fcdd --- /dev/null +++ b/Data/de_DE.indx @@ -0,0 +1,44 @@ +0 78 24 +1 121 24 +2 163 25 +3 202 28 +4 248 30 +5 295 27 +6 340 32 +7 391 32 +8 440 25 +9 484 32 +alpha 533 25 +bravo 576 31 +charlie 629 26 +delta 676 30 +A 725 19 +B 769 21 +C 818 27 +D 870 23 +E 917 23 +F 965 23 +G 1014 22 +H 1060 24 +I 1109 18 +J 1151 26 +K 1207 26 +L 1258 23 +M 1305 21 +N 1351 25 +O 1401 19 +P 1446 19 +Q 1493 24 +R 1543 23 +S 1591 21 +T 1644 23 +U 1693 22 +V 1740 19 +W 1784 22 +X 1831 24 +Y 1883 34 +Z 1943 25 +linkedto 1995 40 +notlinked 2088 50 +linkingto 2158 44 +isbusy 2268 40 diff --git a/Data/dk_DK.ambe b/Data/dk_DK.ambe new file mode 100644 index 0000000000000000000000000000000000000000..d9cc78a32051d41c6101ade209a6139d5a39c3fc GIT binary patch literal 19669 zcma&OcRbZ^A3x4<92|Rd>|<{ol8zCM5t5?FDl;olD62X+I5_q?HpkvGJ9`r%Ma$ls zBBT6heY!vQ{ri5u|DDI%>$<83M;+9J*G4FnRw^G<(=AS-#A%1&jpd(wz%%T8O#W8(Si%lsnTLc&5)Uo#p@0C-e8*f6Obw?Q~wh=l*+M|BUsc zXIi^o8lKHP@TWe1%{}So`iKtvULVY#b^bLkHcTn7F};o9$-RyrGLa8N2g2=Sj2^NX z<496hiTyBr-}t&W9Vp(dOVaSyea>%G{{H3pOl??0Z1L zfam+7&4buC)Jc_xO^qMM253I=zw`Q#qWk)5%8R!Q-{&IRD$qEbe` zjj1iCK6(XCDT>hJmbYIbtZSuF_jqyvn=1@@a)9vO9o}`mWMuTztNAM$#rI}k`q|v= z+}I1R+E*zAJd8rGUSL~6zkNOGWJD8F%dNHI1ig2+^U_KAo>|_V4M5)qUMArVuU(hdEeoTgmp8v+#h+}dL%xba!IVaurh`?5ZyFd)zpx#ijBS<*r8t5V z@;~-=Nk4gF!2YcRC+%{_&8l%PBk%3)rhcq@@%(22z89n9;L?)4BTDJxJqpJN>X#G` zSM?9A$jCb(vG%-A{U)mR;8Z{h6Qa4P+ZGnV1h&c%`YR>9a+4aMaD{!w)pg@~V>4vl(k*^$A@yHZT zlYqpmb+eKu#>F?8>-@*vHtf{f65mCN8emkUDg2Ibo|u;{X?|0xt+>;%E!&F~EMAsp z^Q(`z9~%I*^4Bp5Bx|~M-@)&Y0Ul59^AFT0T_1_33K#zkIwi_6(;5zc^`Bl z>=q;C8P;|Pn1+>N&zpBtZdMzsQ;5(CA5k>H6iKh-UeDqbYDHnmsD-^WU}&pIZGwcS zgJeA!l=q@1_MGz0Ez;e4P`fi!aPe-cy95l5BU{0-$z)aeiNd|C(E=5D@2=#XCrY^0 z`e@54inhZ<894GnQH6kXnOqtjk%5x@$Q+F58yDcK6D~o-DC0z0tkX!KgWiLS0Or3QkL`KLuGnIXgbM^@Nj9ZA74Vk z%k)4XxtZyI%h3Ky1|_v~`sH^&XRT0Dr>%xy#=koJ^`{>lqNGOt5ss_=h!0AUvO^tq zCE9W^T_O9NYg1p=7USmF6U~3>q|WT~AIDK?!6VzU=f|zLFP3G0bSHW)HfaT8`^osw zt$$vywxFm{@A~wM0{E=wwk+>Qq@_a7MggWNL-!cK>?pVCu?I=%w@o^%8PIv0((N*F z@Q!B=HmPNM;#PaI#=#lrV#9a>kMLwC_+_@qzgL#A0&B;hQ;caW#Z!Y6fAvs_i zbn+XEv~tsB1)(N9?b)U{ahRp^QRPk1E+vKc3|VwZA>Er*5FbkCE`!ZtX(%4bjFGWf zMXZ;T^wy)t;1FVF8(-7<6nmk>b4WIzTKw-7TMYSUp?)vjpT+#WDIq^M1u-A~XLeZQcH)=I#dHn3zc*~1Cw zj2aClNp?&>^ou4o#xStSX(HWA*LnEk!QPLUF#yz~BFZW<{Z=*z4 zM_i&jp;{OR32S7z(dqzUCB1h(;+yj&6XT79=aFhV#@=79ylg058P|Qcsv4R}IUe|! z^^3v4lGO`;f_E-cwy%dijER{*-?el;X6P#8$*|X8PO0Est>?3S z@WbuWR^zCzvhT&qzfxRs>p86b3dt82hNpBy zKt6>s^H?{>0Y9Spzha~qYsa54+gU*MBLbm$avCH3j>mpSAebMK6_q2|&!{QV+#8d2 z7NMOT{z)F5+Bp;}XU~3VBftlJ?a}MNFbS<}aygpmHj5l(KlH7P;yuwKF0~lN>DnBf zoXgWGK>oJ5e5p1I0VOWFl-`0u#mx`;*x+86=4YD^d+Je3GWH$wpHPk;jxV>_D=u8$2_BECEfFTA zt{`~XSplozrz%O0l37-3@?@<2ipnJ=NWA?4%h{o6Y%RM)Ce#lMwVKnYOPJ)OB+5ZQbU)GN{ z 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 literal 0 HcmV?d00001 diff --git a/Data/en_GB.indx b/Data/en_GB.indx new file mode 100644 index 0000000..ea3889d --- /dev/null +++ b/Data/en_GB.indx @@ -0,0 +1,44 @@ +0 85 41 +1 144 24 +2 186 25 +3 229 30 +4 278 35 +5 331 35 +6 384 39 +7 441 34 +8 494 29 +9 541 35 +alpha 595 31 +bravo 646 33 +charlie 700 31 +delta 753 30 +A 804 24 +B 850 33 +C 898 23 +D 933 33 +E 990 34 +F 1041 34 +G 1093 34 +H 1145 40 +I 1204 30 +J 1253 35 +K 1307 33 +L 1358 30 +M 1406 28 +N 1452 30 +O 1500 23 +P 1541 32 +Q 1592 39 +R 1648 23 +S 1689 33 +T 1742 32 +U 1791 35 +V 1845 33 +W 1896 38 +X 1953 35 +Y 2007 28 +Z 2060 37 +linked 2124 19 +notlinked 2194 44 +linking 2258 31 +isbusy 2350 33 diff --git a/Data/en_US.ambe b/Data/en_US.ambe new file mode 100644 index 0000000000000000000000000000000000000000..6b91c9187c53a32f3860b3be240e45ebd3bbddc1 GIT binary patch literal 26734 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 literal 0 HcmV?d00001 diff --git a/Data/no_NO.indx b/Data/no_NO.indx new file mode 100644 index 0000000..a3548c0 --- /dev/null +++ b/Data/no_NO.indx @@ -0,0 +1,44 @@ +0 67 28 +1 102 29 +2 137 27 +3 170 25 +4 199 36 +5 238 30 +6 272 38 +7 314 31 +8 349 34 +9 386 28 +alpha 418 36 +bravo 472 30 +charlie 519 34 +delta 569 34 +A 622 23 +B 665 24 +C 710 24 +D 758 21 +E 803 28 +F 856 24 +G 903 32 +H 955 31 +I 1007 18 +J 1045 30 +K 1100 28 +L 1152 19 +M 1196 20 +N 1241 17 +O 1282 27 +P 1332 24 +Q 1378 24 +R 1426 19 +S 1470 31 +T 1524 24 +U 1572 23 +V 1616 20 +W 1658 40 +X 1722 32 +Y 1779 24 +Z 1823 29 +linkedto 1882 40 +notlinked 1966 49 +linkingto 2034 40 +isbusy 2148 38 diff --git a/Data/pl_PL.ambe b/Data/pl_PL.ambe new file mode 100644 index 0000000000000000000000000000000000000000..565d99f569cd41afad4cecf0d67c60adb2bedccf GIT binary patch literal 24169 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 literal 0 HcmV?d00001 diff --git a/Data/pl_PL.indx b/Data/pl_PL.indx new file mode 100644 index 0000000..9f41cf0 --- /dev/null +++ b/Data/pl_PL.indx @@ -0,0 +1,44 @@ +0 99 36 +1 163 38 +2 229 29 +3 290 31 +4 351 37 +5 420 42 +6 490 41 +7 559 43 +8 630 34 +9 692 50 +alpha 771 30 +bravo 820 32 +charlie 873 35 +delta 931 34 +A 987 17 +B 1012 30 +C 1070 25 +D 1118 27 +E 1169 29 +F 1221 36 +G 1281 30 +H 1333 26 +I 1383 18 +J 1425 32 +K 1485 23 +L 1532 26 +M 1581 26 +N 1629 24 +O 1676 18 +P 1724 23 +Q 1773 20 +R 1817 23 +S 1863 35 +T 1926 25 +U 1975 22 +V 2021 28 +W 2070 15 +X 2107 31 +Y 2161 39 +Z 2226 12 +linkedto 2263 45 +notlinked 2360 51 +linkingto 2433 45 +isbusy 2559 59 diff --git a/Data/se_SE.ambe b/Data/se_SE.ambe new file mode 100644 index 0000000000000000000000000000000000000000..7e5567c03ba33b0374a415d11370bbf321d3f55f GIT binary patch literal 24205 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 ROlC3t9BoIjdH#a?{tuS2It>5- literal 0 HcmV?d00001 diff --git a/Data/se_SE.indx b/Data/se_SE.indx new file mode 100644 index 0000000..1f78bef --- /dev/null +++ b/Data/se_SE.indx @@ -0,0 +1,44 @@ +0 83 35 +1 138 33 +2 188 32 +3 240 28 +4 286 41 +5 345 30 +6 393 43 +7 451 34 +8 505 35 +9 555 28 +alpha 603 35 +bravo 660 35 +charlie 716 32 +delta 768 48 +A 836 30 +B 894 28 +C 946 33 +D 1005 22 +E 1053 34 +F 1115 20 +G 1160 36 +H 1223 30 +I 1280 27 +J 1333 35 +K 1393 29 +L 1453 25 +M 1507 26 +N 1560 20 +O 1607 26 +P 1657 28 +Q 1714 34 +R 1781 23 +S 1833 30 +T 1890 25 +U 1943 23 +V 1989 35 +W 2050 47 +X 2126 27 +Y 2178 19 +Z 2223 45 +linkedto 2298 39 +notlinked 2390 51 +linkingto 2461 36 +isbusy 2577 54 diff --git a/GUICommon/AddressTextCtrl.cpp b/GUICommon/AddressTextCtrl.cpp new file mode 100644 index 0000000..c40cfce --- /dev/null +++ b/GUICommon/AddressTextCtrl.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2002,2003,2009 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "AddressTextCtrl.h" + +CAddressTextCtrl::CAddressTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos, const wxSize& size, long style) : +CRestrictedTextCtrl(parent, id, value, pos, size, style, ADDRESS_CHARS) +{ +} + +CAddressTextCtrl::~CAddressTextCtrl() +{ +} + diff --git a/GUICommon/AddressTextCtrl.h b/GUICommon/AddressTextCtrl.h new file mode 100644 index 0000000..a0c99c5 --- /dev/null +++ b/GUICommon/AddressTextCtrl.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2002,2003,2009 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef AddressTextCtrl_H +#define AddressTextCtrl_H + +#include + +#include "RestrictedTextCtrl.h" + +const wxString ADDRESS_CHARS = wxT("0123456789."); + +class CAddressTextCtrl : public CRestrictedTextCtrl { + +public: + CAddressTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0L); + virtual ~CAddressTextCtrl(); + + private: +}; + +#endif diff --git a/GUICommon/CallsignTextCtrl.cpp b/GUICommon/CallsignTextCtrl.cpp new file mode 100644 index 0000000..f37b11a --- /dev/null +++ b/GUICommon/CallsignTextCtrl.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2002,2003 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "CallsignTextCtrl.h" + +CCallsignTextCtrl::CCallsignTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos, const wxSize& size, long style) : +CRestrictedTextCtrl(parent, id, value, pos, size, style, CALLSIGN_CHARS) +{ +} + +CCallsignTextCtrl::~CCallsignTextCtrl() +{ +} + diff --git a/GUICommon/CallsignTextCtrl.h b/GUICommon/CallsignTextCtrl.h new file mode 100644 index 0000000..70a422e --- /dev/null +++ b/GUICommon/CallsignTextCtrl.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2002,2003 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef CallsignTextCtrl_H +#define CallsignTextCtrl_H + +#include + +#include "RestrictedTextCtrl.h" + +const wxString CALLSIGN_CHARS = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789/ "); + +class CCallsignTextCtrl : public CRestrictedTextCtrl { + +public: + CCallsignTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0L); + virtual ~CCallsignTextCtrl(); + + private: +}; + +#endif diff --git a/GUICommon/GUICommon.vcxproj b/GUICommon/GUICommon.vcxproj new file mode 100644 index 0000000..2b5297e --- /dev/null +++ b/GUICommon/GUICommon.vcxproj @@ -0,0 +1,151 @@ + + + + + Debug + Win32 + + + Debug + x64 + + + Release + Win32 + + + Release + x64 + + + + {2C548AAB-A9F7-496F-BCF5-6185BDA304D2} + GUICommon + Win32Proj + 10.0.17134.0 + + + + StaticLibrary + v141 + Unicode + true + + + StaticLibrary + v141 + Unicode + true + + + StaticLibrary + v141 + Unicode + + + StaticLibrary + v141 + Unicode + + + + + + + + + + + + + + + + + + + <_ProjectFileVersion>14.0.24720.0 + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + + + $(SolutionDir)$(Configuration)\ + $(Configuration)\ + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_dll\mswu + + + $(VC_IncludePath);$(WindowsSDK_IncludePath);$(WXWIN)\include;$(IncludePath);$(WXWIN)\lib\vc_dll\mswu + + + + Disabled + $(WXWIN)\include\msvc;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;__WXDEBUG__;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_LIB;%(PreprocessorDefinitions) + true + EnableFastChecks + MultiThreadedDebugDLL + + Level3 + EditAndContinue + + + + + Disabled + $(WXWIN)\include\msvc;$(WXWIN)\include;%(AdditionalIncludeDirectories) + WIN32;_DEBUG;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;__WXDEBUG__;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;_LIB;%(PreprocessorDefinitions) + EnableFastChecks + MultiThreadedDebugDLL + + + Level3 + ProgramDatabase + + + + + MaxSpeed + true + $(SolutionDir)..\WinUSB;$(SolutionDir)..\HID;$(SolutionDir)..\portaudio\include;C:\wxWidgets-2.8.12\include\msvc;C:\wxWidgets-2.8.12\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + Level3 + ProgramDatabase + + + + + MaxSpeed + true + $(SolutionDir)..\WinUSB;$(SolutionDir)..\HID;$(SolutionDir)..\portaudio\include;C:\wxWidgets-2.8.12\include\msvc;C:\wxWidgets-2.8.12\include;%(AdditionalIncludeDirectories) + WIN32;NDEBUG;_LIB;_WINDOWS;WINVER=0x0400;__WXMSW__;WXUSINGDLL;wxUSE_GUI=1;_CRT_SECURE_NO_DEPRECATE;_CRT_NONSTDC_NO_DEPRECATE;%(PreprocessorDefinitions) + MultiThreadedDLL + true + + + Level3 + ProgramDatabase + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/GUICommon/GUICommon.vcxproj.filters b/GUICommon/GUICommon.vcxproj.filters new file mode 100644 index 0000000..2720603 --- /dev/null +++ b/GUICommon/GUICommon.vcxproj.filters @@ -0,0 +1,47 @@ + + + + + {4FC737F1-C7A5-4376-A066-2A32D752A2FF} + cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx + + + {93995380-89BD-4b04-88EB-625FBE52EBFB} + h;hpp;hxx;hm;inl;inc;xsd + + + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + Source Files + + + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + Header Files + + + \ No newline at end of file diff --git a/GUICommon/MessageTextCtrl.cpp b/GUICommon/MessageTextCtrl.cpp new file mode 100644 index 0000000..ff67dbb --- /dev/null +++ b/GUICommon/MessageTextCtrl.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2009 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "MessageTextCtrl.h" + +CMessageTextCtrl::CMessageTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos, const wxSize& size, long style) : +CRestrictedTextCtrl(parent, id, value, pos, size, style, MSG_CHARS) +{ +} + +CMessageTextCtrl::~CMessageTextCtrl() +{ +} + diff --git a/GUICommon/MessageTextCtrl.h b/GUICommon/MessageTextCtrl.h new file mode 100644 index 0000000..970458a --- /dev/null +++ b/GUICommon/MessageTextCtrl.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2009 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef MessageTextCtrl_H +#define MessageTextCtrl_H + +#include + +#include "RestrictedTextCtrl.h" + +const wxString MSG_CHARS = wxT("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~ "); + +class CMessageTextCtrl : public CRestrictedTextCtrl { + +public: + CMessageTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0L); + virtual ~CMessageTextCtrl(); + + private: +}; + +#endif diff --git a/GUICommon/PortTextCtrl.cpp b/GUICommon/PortTextCtrl.cpp new file mode 100644 index 0000000..51435d9 --- /dev/null +++ b/GUICommon/PortTextCtrl.cpp @@ -0,0 +1,29 @@ +/* + * Copyright (C) 2002,2003,2009 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "PortTextCtrl.h" + +CPortTextCtrl::CPortTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos, const wxSize& size, long style) : +CRestrictedTextCtrl(parent, id, value, pos, size, style, PORT_CHARS) +{ +} + +CPortTextCtrl::~CPortTextCtrl() +{ +} + diff --git a/GUICommon/PortTextCtrl.h b/GUICommon/PortTextCtrl.h new file mode 100644 index 0000000..8d096c8 --- /dev/null +++ b/GUICommon/PortTextCtrl.h @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2002,2003,2009 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef PortTextCtrl_H +#define PortTextCtrl_H + +#include + +#include "RestrictedTextCtrl.h" + +const wxString PORT_CHARS = wxT("0123456789"); + +class CPortTextCtrl : public CRestrictedTextCtrl { + +public: + CPortTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos = wxDefaultPosition, const wxSize& size = wxDefaultSize, long style = 0L); + virtual ~CPortTextCtrl(); + + private: +}; + +#endif diff --git a/GUICommon/RestrictedTextCtrl.cpp b/GUICommon/RestrictedTextCtrl.cpp new file mode 100644 index 0000000..a291044 --- /dev/null +++ b/GUICommon/RestrictedTextCtrl.cpp @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2002,2003,2009 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include "RestrictedTextCtrl.h" + +CRestrictedTextCtrl::CRestrictedTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos, const wxSize& size, long style, const wxString& wantedChars) : +wxTextCtrl() +{ + wxASSERT(parent != NULL); + + wxArrayString charList; + + for (unsigned int i = 0; i < wantedChars.Length(); i++) + charList.Add(wantedChars.Mid(i, 1)); + + wxTextValidator validator(wxFILTER_INCLUDE_CHAR_LIST); + validator.SetIncludes(charList); + + Create(parent, id, value, pos, size, style, validator); +} + +CRestrictedTextCtrl::~CRestrictedTextCtrl() +{ +} + diff --git a/GUICommon/RestrictedTextCtrl.h b/GUICommon/RestrictedTextCtrl.h new file mode 100644 index 0000000..f3d57b6 --- /dev/null +++ b/GUICommon/RestrictedTextCtrl.h @@ -0,0 +1,32 @@ +/* + * Copyright (C) 2002,2003 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef RestrictedTextCtrl_H +#define RestrictedTextCtrl_H + +#include + +class CRestrictedTextCtrl : public wxTextCtrl { +public: + CRestrictedTextCtrl(wxWindow* parent, int id, const wxString& value, const wxPoint& pos, const wxSize& size, long style, const wxString& wantedChars); + virtual ~CRestrictedTextCtrl(); + +private: +}; + +#endif diff --git a/LinuxUSB/Makefile.am b/LinuxUSB/Makefile.am new file mode 100644 index 0000000..1336529 --- /dev/null +++ b/LinuxUSB/Makefile.am @@ -0,0 +1,3 @@ +AM_LDFLAGS = -Wl,--as-needed +LDADD = $(LIBUSB_LIBS) +bin_PROGRAMS = rmk8055drv rmuridrv diff --git a/LinuxUSB/Makefile.in b/LinuxUSB/Makefile.in new file mode 100644 index 0000000..026396a --- /dev/null +++ b/LinuxUSB/Makefile.in @@ -0,0 +1,602 @@ +# Makefile.in generated by automake 1.15 from Makefile.am. +# @configure_input@ + +# Copyright (C) 1994-2014 Free Software Foundation, Inc. + +# This Makefile.in is free software; the Free Software Foundation +# gives unlimited permission to copy and/or distribute it, +# with or without modifications, as long as this notice is preserved. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY, to the extent permitted by law; without +# even the implied warranty of MERCHANTABILITY or FITNESS FOR A +# PARTICULAR PURPOSE. + +@SET_MAKE@ + +VPATH = @srcdir@ +am__is_gnu_make = { \ + if test -z '$(MAKELEVEL)'; then \ + false; \ + elif test -n '$(MAKE_HOST)'; then \ + true; \ + elif test -n '$(MAKE_VERSION)' && test -n '$(CURDIR)'; then \ + true; \ + else \ + false; \ + fi; \ +} +am__make_running_with_option = \ + case $${target_option-} in \ + ?) ;; \ + *) echo "am__make_running_with_option: internal error: invalid" \ + "target option '$${target_option-}' specified" >&2; \ + exit 1;; \ + esac; \ + has_opt=no; \ + sane_makeflags=$$MAKEFLAGS; \ + if $(am__is_gnu_make); then \ + sane_makeflags=$$MFLAGS; \ + else \ + case $$MAKEFLAGS in \ + *\\[\ \ ]*) \ + bs=\\; \ + sane_makeflags=`printf '%s\n' "$$MAKEFLAGS" \ + | sed "s/$$bs$$bs[$$bs $$bs ]*//g"`;; \ + esac; \ + fi; \ + skip_next=no; \ + strip_trailopt () \ + { \ + flg=`printf '%s\n' "$$flg" | sed "s/$$1.*$$//"`; \ + }; \ + for flg in $$sane_makeflags; do \ + test $$skip_next = yes && { skip_next=no; continue; }; \ + case $$flg in \ + *=*|--*) continue;; \ + -*I) strip_trailopt 'I'; skip_next=yes;; \ + -*I?*) strip_trailopt 'I';; \ + -*O) strip_trailopt 'O'; skip_next=yes;; \ + -*O?*) strip_trailopt 'O';; \ + -*l) strip_trailopt 'l'; skip_next=yes;; \ + -*l?*) strip_trailopt 'l';; \ + -[dEDm]) skip_next=yes;; \ + -[JT]) skip_next=yes;; \ + esac; \ + case $$flg in \ + *$$target_option*) has_opt=yes; break;; \ + esac; \ + done; \ + test $$has_opt = yes +am__make_dryrun = (target_option=n; $(am__make_running_with_option)) +am__make_keepgoing = (target_option=k; $(am__make_running_with_option)) +pkgdatadir = $(datadir)/@PACKAGE@ +pkgincludedir = $(includedir)/@PACKAGE@ +pkglibdir = $(libdir)/@PACKAGE@ +pkglibexecdir = $(libexecdir)/@PACKAGE@ +am__cd = CDPATH="$${ZSH_VERSION+.}$(PATH_SEPARATOR)" && cd +install_sh_DATA = $(install_sh) -c -m 644 +install_sh_PROGRAM = $(install_sh) -c +install_sh_SCRIPT = $(install_sh) -c +INSTALL_HEADER = $(INSTALL_DATA) +transform = $(program_transform_name) +NORMAL_INSTALL = : +PRE_INSTALL = : +POST_INSTALL = : +NORMAL_UNINSTALL = : +PRE_UNINSTALL = : +POST_UNINSTALL = : +bin_PROGRAMS = rmk8055drv$(EXEEXT) rmuridrv$(EXEEXT) +subdir = LinuxUSB +ACLOCAL_M4 = $(top_srcdir)/aclocal.m4 +am__aclocal_m4_deps = $(top_srcdir)/configure.ac +am__configure_deps = $(am__aclocal_m4_deps) $(CONFIGURE_DEPENDENCIES) \ + $(ACLOCAL_M4) +DIST_COMMON = $(srcdir)/Makefile.am $(am__DIST_COMMON) +mkinstalldirs = $(install_sh) -d +CONFIG_CLEAN_FILES = +CONFIG_CLEAN_VPATH_FILES = +am__installdirs = "$(DESTDIR)$(bindir)" +PROGRAMS = $(bin_PROGRAMS) +rmk8055drv_SOURCES = rmk8055drv.c +rmk8055drv_OBJECTS = rmk8055drv.$(OBJEXT) +rmk8055drv_LDADD = $(LDADD) +am__DEPENDENCIES_1 = +rmk8055drv_DEPENDENCIES = $(am__DEPENDENCIES_1) +rmuridrv_SOURCES = rmuridrv.c +rmuridrv_OBJECTS = rmuridrv.$(OBJEXT) +rmuridrv_LDADD = $(LDADD) +rmuridrv_DEPENDENCIES = $(am__DEPENDENCIES_1) +AM_V_P = $(am__v_P_@AM_V@) +am__v_P_ = $(am__v_P_@AM_DEFAULT_V@) +am__v_P_0 = false +am__v_P_1 = : +AM_V_GEN = $(am__v_GEN_@AM_V@) +am__v_GEN_ = $(am__v_GEN_@AM_DEFAULT_V@) +am__v_GEN_0 = @echo " GEN " $@; +am__v_GEN_1 = +AM_V_at = $(am__v_at_@AM_V@) +am__v_at_ = $(am__v_at_@AM_DEFAULT_V@) +am__v_at_0 = @ +am__v_at_1 = +DEFAULT_INCLUDES = -I.@am__isrc@ +depcomp = $(SHELL) $(top_srcdir)/depcomp +am__depfiles_maybe = depfiles +am__mv = mv -f +COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \ + $(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS) +AM_V_CC = $(am__v_CC_@AM_V@) +am__v_CC_ = $(am__v_CC_@AM_DEFAULT_V@) +am__v_CC_0 = @echo " CC " $@; +am__v_CC_1 = +CCLD = $(CC) +LINK = $(CCLD) $(AM_CFLAGS) $(CFLAGS) $(AM_LDFLAGS) $(LDFLAGS) -o $@ +AM_V_CCLD = $(am__v_CCLD_@AM_V@) +am__v_CCLD_ = $(am__v_CCLD_@AM_DEFAULT_V@) +am__v_CCLD_0 = @echo " CCLD " $@; +am__v_CCLD_1 = +SOURCES = rmk8055drv.c rmuridrv.c +DIST_SOURCES = rmk8055drv.c rmuridrv.c +am__can_run_installinfo = \ + case $$AM_UPDATE_INFO_DIR in \ + n|no|NO) false;; \ + *) (install-info --version) >/dev/null 2>&1;; \ + esac +am__tagged_files = $(HEADERS) $(SOURCES) $(TAGS_FILES) $(LISP) +# Read a list of newline-separated strings from the standard input, +# and print each of them once, without duplicates. Input order is +# *not* preserved. +am__uniquify_input = $(AWK) '\ + BEGIN { nonempty = 0; } \ + { items[$$0] = 1; nonempty = 1; } \ + END { if (nonempty) { for (i in items) print i; }; } \ +' +# Make sure the list of sources is unique. This is necessary because, +# e.g., the same source file might be shared among _SOURCES variables +# for different programs/libraries. +am__define_uniq_tagged_files = \ + list='$(am__tagged_files)'; \ + unique=`for i in $$list; do \ + if test -f "$$i"; then echo $$i; else echo $(srcdir)/$$i; fi; \ + done | $(am__uniquify_input)` +ETAGS = etags +CTAGS = ctags +am__DIST_COMMON = $(srcdir)/Makefile.in $(top_srcdir)/depcomp +DISTFILES = $(DIST_COMMON) $(DIST_SOURCES) $(TEXINFOS) $(EXTRA_DIST) +ACLOCAL = @ACLOCAL@ +ALSA_CFLAGS = @ALSA_CFLAGS@ +ALSA_LIBS = @ALSA_LIBS@ +AMTAR = @AMTAR@ +AM_DEFAULT_VERBOSITY = @AM_DEFAULT_VERBOSITY@ +AR = @AR@ +AUTOCONF = @AUTOCONF@ +AUTOHEADER = @AUTOHEADER@ +AUTOMAKE = @AUTOMAKE@ +AWK = @AWK@ +BASE_WX_CPPFLAGS = @BASE_WX_CPPFLAGS@ +BASE_WX_LIBS = @BASE_WX_LIBS@ +CC = @CC@ +CCDEPMODE = @CCDEPMODE@ +CFLAGS = @CFLAGS@ +CPPFLAGS = @CPPFLAGS@ +CXX = @CXX@ +CXXDEPMODE = @CXXDEPMODE@ +CXXFLAGS = @CXXFLAGS@ +CYGPATH_W = @CYGPATH_W@ +DEFS = @DEFS@ +DEPDIR = @DEPDIR@ +ECHO_C = @ECHO_C@ +ECHO_N = @ECHO_N@ +ECHO_T = @ECHO_T@ +EXEEXT = @EXEEXT@ +GUI_WX_CPPFLAGS = @GUI_WX_CPPFLAGS@ +GUI_WX_LIBS = @GUI_WX_LIBS@ +INSTALL = @INSTALL@ +INSTALL_DATA = @INSTALL_DATA@ +INSTALL_PROGRAM = @INSTALL_PROGRAM@ +INSTALL_SCRIPT = @INSTALL_SCRIPT@ +INSTALL_STRIP_PROGRAM = @INSTALL_STRIP_PROGRAM@ +LDFLAGS = @LDFLAGS@ +LIBOBJS = @LIBOBJS@ +LIBS = @LIBS@ +LIBUSB_CFLAGS = @LIBUSB_CFLAGS@ +LIBUSB_LIBS = @LIBUSB_LIBS@ +LTLIBOBJS = @LTLIBOBJS@ +MAINT = @MAINT@ +MAKEINFO = @MAKEINFO@ +MKDIR_P = @MKDIR_P@ +OBJEXT = @OBJEXT@ +PACKAGE = @PACKAGE@ +PACKAGE_BUGREPORT = @PACKAGE_BUGREPORT@ +PACKAGE_NAME = @PACKAGE_NAME@ +PACKAGE_STRING = @PACKAGE_STRING@ +PACKAGE_TARNAME = @PACKAGE_TARNAME@ +PACKAGE_URL = @PACKAGE_URL@ +PACKAGE_VERSION = @PACKAGE_VERSION@ +PATH_SEPARATOR = @PATH_SEPARATOR@ +PKG_CONFIG = @PKG_CONFIG@ +PKG_CONFIG_LIBDIR = @PKG_CONFIG_LIBDIR@ +PKG_CONFIG_PATH = @PKG_CONFIG_PATH@ +RANLIB = @RANLIB@ +SET_MAKE = @SET_MAKE@ +SHELL = @SHELL@ +STRIP = @STRIP@ +VERSION = @VERSION@ +WX_CFLAGS = @WX_CFLAGS@ +WX_CFLAGS_ONLY = @WX_CFLAGS_ONLY@ +WX_CONFIG_PATH = @WX_CONFIG_PATH@ +WX_CPPFLAGS = @WX_CPPFLAGS@ +WX_CXXFLAGS = @WX_CXXFLAGS@ +WX_CXXFLAGS_ONLY = @WX_CXXFLAGS_ONLY@ +WX_LIBS = @WX_LIBS@ +WX_LIBS_STATIC = @WX_LIBS_STATIC@ +WX_RESCOMP = @WX_RESCOMP@ +WX_VERSION = @WX_VERSION@ +WX_VERSION_MAJOR = @WX_VERSION_MAJOR@ +WX_VERSION_MICRO = @WX_VERSION_MICRO@ +WX_VERSION_MINOR = @WX_VERSION_MINOR@ +abs_builddir = @abs_builddir@ +abs_srcdir = @abs_srcdir@ +abs_top_builddir = @abs_top_builddir@ +abs_top_srcdir = @abs_top_srcdir@ +ac_ct_AR = @ac_ct_AR@ +ac_ct_CC = @ac_ct_CC@ +ac_ct_CXX = @ac_ct_CXX@ +am__include = @am__include@ +am__leading_dot = @am__leading_dot@ +am__quote = @am__quote@ +am__tar = @am__tar@ +am__untar = @am__untar@ +bindir = @bindir@ +build_alias = @build_alias@ +builddir = @builddir@ +datadir = @datadir@ +datarootdir = @datarootdir@ +docdir = @docdir@ +dvidir = @dvidir@ +exec_prefix = @exec_prefix@ +host_alias = @host_alias@ +htmldir = @htmldir@ +includedir = @includedir@ +infodir = @infodir@ +install_sh = @install_sh@ +libdir = @libdir@ +libexecdir = @libexecdir@ +localedir = @localedir@ +localstatedir = @localstatedir@ +mandir = @mandir@ +mkdir_p = @mkdir_p@ +oldincludedir = @oldincludedir@ +pdfdir = @pdfdir@ +prefix = @prefix@ +program_transform_name = @program_transform_name@ +psdir = @psdir@ +sbindir = @sbindir@ +sharedstatedir = @sharedstatedir@ +srcdir = @srcdir@ +sysconfdir = @sysconfdir@ +target_alias = @target_alias@ +top_build_prefix = @top_build_prefix@ +top_builddir = @top_builddir@ +top_srcdir = @top_srcdir@ +AM_LDFLAGS = -Wl,--as-needed +LDADD = $(LIBUSB_LIBS) +all: all-am + +.SUFFIXES: +.SUFFIXES: .c .o .obj +$(srcdir)/Makefile.in: @MAINTAINER_MODE_TRUE@ $(srcdir)/Makefile.am $(am__configure_deps) + @for dep in $?; do \ + case '$(am__configure_deps)' in \ + *$$dep*) \ + ( cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh ) \ + && { if test -f $@; then exit 0; else break; fi; }; \ + exit 1;; \ + esac; \ + done; \ + echo ' cd $(top_srcdir) && $(AUTOMAKE) --foreign LinuxUSB/Makefile'; \ + $(am__cd) $(top_srcdir) && \ + $(AUTOMAKE) --foreign LinuxUSB/Makefile +Makefile: $(srcdir)/Makefile.in $(top_builddir)/config.status + @case '$?' in \ + *config.status*) \ + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh;; \ + *) \ + echo ' cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe)'; \ + cd $(top_builddir) && $(SHELL) ./config.status $(subdir)/$@ $(am__depfiles_maybe);; \ + esac; + +$(top_builddir)/config.status: $(top_srcdir)/configure $(CONFIG_STATUS_DEPENDENCIES) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh + +$(top_srcdir)/configure: @MAINTAINER_MODE_TRUE@ $(am__configure_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(ACLOCAL_M4): @MAINTAINER_MODE_TRUE@ $(am__aclocal_m4_deps) + cd $(top_builddir) && $(MAKE) $(AM_MAKEFLAGS) am--refresh +$(am__aclocal_m4_deps): +install-binPROGRAMS: $(bin_PROGRAMS) + @$(NORMAL_INSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + if test -n "$$list"; then \ + echo " $(MKDIR_P) '$(DESTDIR)$(bindir)'"; \ + $(MKDIR_P) "$(DESTDIR)$(bindir)" || exit 1; \ + fi; \ + for p in $$list; do echo "$$p $$p"; done | \ + sed 's/$(EXEEXT)$$//' | \ + while read p p1; do if test -f $$p \ + ; then echo "$$p"; echo "$$p"; else :; fi; \ + done | \ + sed -e 'p;s,.*/,,;n;h' \ + -e 's|.*|.|' \ + -e 'p;x;s,.*/,,;s/$(EXEEXT)$$//;$(transform);s/$$/$(EXEEXT)/' | \ + sed 'N;N;N;s,\n, ,g' | \ + $(AWK) 'BEGIN { files["."] = ""; dirs["."] = 1 } \ + { d=$$3; if (dirs[d] != 1) { print "d", d; dirs[d] = 1 } \ + if ($$2 == $$4) files[d] = files[d] " " $$1; \ + else { print "f", $$3 "/" $$4, $$1; } } \ + END { for (d in files) print "f", d, files[d] }' | \ + while read type dir files; do \ + if test "$$dir" = .; then dir=; else dir=/$$dir; fi; \ + test -z "$$files" || { \ + echo " $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files '$(DESTDIR)$(bindir)$$dir'"; \ + $(INSTALL_PROGRAM_ENV) $(INSTALL_PROGRAM) $$files "$(DESTDIR)$(bindir)$$dir" || exit $$?; \ + } \ + ; done + +uninstall-binPROGRAMS: + @$(NORMAL_UNINSTALL) + @list='$(bin_PROGRAMS)'; test -n "$(bindir)" || list=; \ + files=`for p in $$list; do echo "$$p"; done | \ + sed -e 'h;s,^.*/,,;s/$(EXEEXT)$$//;$(transform)' \ + -e 's/$$/$(EXEEXT)/' \ + `; \ + test -n "$$list" || exit 0; \ + echo " ( cd '$(DESTDIR)$(bindir)' && rm -f" $$files ")"; \ + cd "$(DESTDIR)$(bindir)" && rm -f $$files + +clean-binPROGRAMS: + -test -z "$(bin_PROGRAMS)" || rm -f $(bin_PROGRAMS) + +rmk8055drv$(EXEEXT): $(rmk8055drv_OBJECTS) $(rmk8055drv_DEPENDENCIES) $(EXTRA_rmk8055drv_DEPENDENCIES) + @rm -f rmk8055drv$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(rmk8055drv_OBJECTS) $(rmk8055drv_LDADD) $(LIBS) + +rmuridrv$(EXEEXT): $(rmuridrv_OBJECTS) $(rmuridrv_DEPENDENCIES) $(EXTRA_rmuridrv_DEPENDENCIES) + @rm -f rmuridrv$(EXEEXT) + $(AM_V_CCLD)$(LINK) $(rmuridrv_OBJECTS) $(rmuridrv_LDADD) $(LIBS) + +mostlyclean-compile: + -rm -f *.$(OBJEXT) + +distclean-compile: + -rm -f *.tab.c + +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rmk8055drv.Po@am__quote@ +@AMDEP_TRUE@@am__include@ @am__quote@./$(DEPDIR)/rmuridrv.Po@am__quote@ + +.c.o: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.o$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ $< &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ $< + +.c.obj: +@am__fastdepCC_TRUE@ $(AM_V_CC)depbase=`echo $@ | sed 's|[^/]*$$|$(DEPDIR)/&|;s|\.obj$$||'`;\ +@am__fastdepCC_TRUE@ $(COMPILE) -MT $@ -MD -MP -MF $$depbase.Tpo -c -o $@ `$(CYGPATH_W) '$<'` &&\ +@am__fastdepCC_TRUE@ $(am__mv) $$depbase.Tpo $$depbase.Po +@AMDEP_TRUE@@am__fastdepCC_FALSE@ $(AM_V_CC)source='$<' object='$@' libtool=no @AMDEPBACKSLASH@ +@AMDEP_TRUE@@am__fastdepCC_FALSE@ DEPDIR=$(DEPDIR) $(CCDEPMODE) $(depcomp) @AMDEPBACKSLASH@ +@am__fastdepCC_FALSE@ $(AM_V_CC@am__nodep@)$(COMPILE) -c -o $@ `$(CYGPATH_W) '$<'` + +ID: $(am__tagged_files) + $(am__define_uniq_tagged_files); mkid -fID $$unique +tags: tags-am +TAGS: tags + +tags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + set x; \ + here=`pwd`; \ + $(am__define_uniq_tagged_files); \ + shift; \ + if test -z "$(ETAGS_ARGS)$$*$$unique"; then :; else \ + test -n "$$unique" || unique=$$empty_fix; \ + if test $$# -gt 0; then \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + "$$@" $$unique; \ + else \ + $(ETAGS) $(ETAGSFLAGS) $(AM_ETAGSFLAGS) $(ETAGS_ARGS) \ + $$unique; \ + fi; \ + fi +ctags: ctags-am + +CTAGS: ctags +ctags-am: $(TAGS_DEPENDENCIES) $(am__tagged_files) + $(am__define_uniq_tagged_files); \ + test -z "$(CTAGS_ARGS)$$unique" \ + || $(CTAGS) $(CTAGSFLAGS) $(AM_CTAGSFLAGS) $(CTAGS_ARGS) \ + $$unique + +GTAGS: + here=`$(am__cd) $(top_builddir) && pwd` \ + && $(am__cd) $(top_srcdir) \ + && gtags -i $(GTAGS_ARGS) "$$here" +cscopelist: cscopelist-am + +cscopelist-am: $(am__tagged_files) + list='$(am__tagged_files)'; \ + case "$(srcdir)" in \ + [\\/]* | ?:[\\/]*) sdir="$(srcdir)" ;; \ + *) sdir=$(subdir)/$(srcdir) ;; \ + esac; \ + for i in $$list; do \ + if test -f "$$i"; then \ + echo "$(subdir)/$$i"; \ + else \ + echo "$$sdir/$$i"; \ + fi; \ + done >> $(top_builddir)/cscope.files + +distclean-tags: + -rm -f TAGS ID GTAGS GRTAGS GSYMS GPATH tags + +distdir: $(DISTFILES) + @srcdirstrip=`echo "$(srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + topsrcdirstrip=`echo "$(top_srcdir)" | sed 's/[].[^$$\\*]/\\\\&/g'`; \ + list='$(DISTFILES)'; \ + dist_files=`for file in $$list; do echo $$file; done | \ + sed -e "s|^$$srcdirstrip/||;t" \ + -e "s|^$$topsrcdirstrip/|$(top_builddir)/|;t"`; \ + case $$dist_files in \ + */*) $(MKDIR_P) `echo "$$dist_files" | \ + sed '/\//!d;s|^|$(distdir)/|;s,/[^/]*$$,,' | \ + sort -u` ;; \ + esac; \ + for file in $$dist_files; do \ + if test -f $$file || test -d $$file; then d=.; else d=$(srcdir); fi; \ + if test -d $$d/$$file; then \ + dir=`echo "/$$file" | sed -e 's,/[^/]*$$,,'`; \ + if test -d "$(distdir)/$$file"; then \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + if test -d $(srcdir)/$$file && test $$d != $(srcdir); then \ + cp -fpR $(srcdir)/$$file "$(distdir)$$dir" || exit 1; \ + find "$(distdir)/$$file" -type d ! -perm -700 -exec chmod u+rwx {} \;; \ + fi; \ + cp -fpR $$d/$$file "$(distdir)$$dir" || exit 1; \ + else \ + test -f "$(distdir)/$$file" \ + || cp -p $$d/$$file "$(distdir)/$$file" \ + || exit 1; \ + fi; \ + done +check-am: all-am +check: check-am +all-am: Makefile $(PROGRAMS) +installdirs: + for dir in "$(DESTDIR)$(bindir)"; do \ + test -z "$$dir" || $(MKDIR_P) "$$dir"; \ + done +install: install-am +install-exec: install-exec-am +install-data: install-data-am +uninstall: uninstall-am + +install-am: all-am + @$(MAKE) $(AM_MAKEFLAGS) install-exec-am install-data-am + +installcheck: installcheck-am +install-strip: + if test -z '$(STRIP)'; then \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + install; \ + else \ + $(MAKE) $(AM_MAKEFLAGS) INSTALL_PROGRAM="$(INSTALL_STRIP_PROGRAM)" \ + install_sh_PROGRAM="$(INSTALL_STRIP_PROGRAM)" INSTALL_STRIP_FLAG=-s \ + "INSTALL_PROGRAM_ENV=STRIPPROG='$(STRIP)'" install; \ + fi +mostlyclean-generic: + +clean-generic: + +distclean-generic: + -test -z "$(CONFIG_CLEAN_FILES)" || rm -f $(CONFIG_CLEAN_FILES) + -test . = "$(srcdir)" || test -z "$(CONFIG_CLEAN_VPATH_FILES)" || rm -f $(CONFIG_CLEAN_VPATH_FILES) + +maintainer-clean-generic: + @echo "This command is intended for maintainers to use" + @echo "it deletes files that may require special tools to rebuild." +clean: clean-am + +clean-am: clean-binPROGRAMS clean-generic mostlyclean-am + +distclean: distclean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +distclean-am: clean-am distclean-compile distclean-generic \ + distclean-tags + +dvi: dvi-am + +dvi-am: + +html: html-am + +html-am: + +info: info-am + +info-am: + +install-data-am: + +install-dvi: install-dvi-am + +install-dvi-am: + +install-exec-am: install-binPROGRAMS + +install-html: install-html-am + +install-html-am: + +install-info: install-info-am + +install-info-am: + +install-man: + +install-pdf: install-pdf-am + +install-pdf-am: + +install-ps: install-ps-am + +install-ps-am: + +installcheck-am: + +maintainer-clean: maintainer-clean-am + -rm -rf ./$(DEPDIR) + -rm -f Makefile +maintainer-clean-am: distclean-am maintainer-clean-generic + +mostlyclean: mostlyclean-am + +mostlyclean-am: mostlyclean-compile mostlyclean-generic + +pdf: pdf-am + +pdf-am: + +ps: ps-am + +ps-am: + +uninstall-am: uninstall-binPROGRAMS + +.MAKE: install-am install-strip + +.PHONY: CTAGS GTAGS TAGS all all-am check check-am clean \ + clean-binPROGRAMS clean-generic cscopelist-am ctags ctags-am \ + distclean distclean-compile distclean-generic distclean-tags \ + distdir dvi dvi-am html html-am info info-am install \ + install-am install-binPROGRAMS install-data install-data-am \ + install-dvi install-dvi-am install-exec install-exec-am \ + install-html install-html-am install-info install-info-am \ + install-man install-pdf install-pdf-am install-ps \ + install-ps-am install-strip installcheck installcheck-am \ + installdirs maintainer-clean maintainer-clean-generic \ + mostlyclean mostlyclean-compile mostlyclean-generic pdf pdf-am \ + ps ps-am tags tags-am uninstall uninstall-am \ + uninstall-binPROGRAMS + +.PRECIOUS: Makefile + + +# Tell versions [3.59,3.63) of GNU make to not export all variables. +# Otherwise a system limit (for SysV at least) may be exceeded. +.NOEXPORT: diff --git a/LinuxUSB/README.txt b/LinuxUSB/README.txt new file mode 100644 index 0000000..579e4b0 --- /dev/null +++ b/LinuxUSB/README.txt @@ -0,0 +1,4 @@ +These programs are designed to remove the usbhid from an K8055 or CM108 (URI +USB) devices on your system. These programs must be run as root, but having been +run successfully run, then any program that accesses them can do so with direct +USB calls and can run as a normal user. diff --git a/LinuxUSB/rmk8055drv.c b/LinuxUSB/rmk8055drv.c new file mode 100644 index 0000000..600f38c --- /dev/null +++ b/LinuxUSB/rmk8055drv.c @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2009,2011 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +const unsigned int VELLEMAN_VENDOR_ID = 0x10CFU; +const unsigned int VELLEMAN_K8055_BOARD0 = 0x5500U; +const unsigned int VELLEMAN_K8055_BOARD1 = 0x5501U; +const unsigned int VELLEMAN_K8055_BOARD2 = 0x5502U; +const unsigned int VELLEMAN_K8055_BOARD3 = 0x5503U; +const unsigned int VELLEMAN_INTERFACE = 0U; + +int main(int argc, char **argv) +{ + unsigned int count = 0U, removed = 0U; + struct libusb_context *context; + struct libusb_device_handle *handle; + struct libusb_device **list; + struct libusb_device_descriptor desc; + ssize_t i; + int err; + + libusb_init(&context); + + ssize_t cnt = libusb_get_device_list(NULL, &list); + if (cnt <= 0) { + fprintf(stderr, "rmk8055drv: cannot find any USB devices!\n"); + return 1; + } + + for (i = 0; i < cnt; i++) { + err = libusb_get_device_descriptor(list[i], &desc); + if (err < 0) + continue; + + if (desc.idVendor == VELLEMAN_VENDOR_ID && + (desc.idProduct == VELLEMAN_K8055_BOARD0 || + desc.idProduct == VELLEMAN_K8055_BOARD1 || + desc.idProduct == VELLEMAN_K8055_BOARD2 || + desc.idProduct == VELLEMAN_K8055_BOARD3)) { + count++; + + err = libusb_open(list[i], &handle); + if (err != 0) { + fprintf(stderr, "rmk8055drv: cannot open the USB device: err=%d\n", err); + continue; + } + + if (libusb_kernel_driver_active(handle, VELLEMAN_INTERFACE) == 1) { + err = libusb_detach_kernel_driver(handle, VELLEMAN_INTERFACE); + if (err == 0) + removed++; + else + fprintf(stderr, "rmk8055drv: could not disconnect from kernel driver: err=%d\n", err); + } + + libusb_close(handle); + } + } + + libusb_free_device_list(list, 1); + + libusb_exit(context); + + fprintf(stdout, "rmk8055drv: found %u K8055 devices and removed %u from the kernel driver\n", count, removed); + + return 0; +} + diff --git a/LinuxUSB/rmuridrv.c b/LinuxUSB/rmuridrv.c new file mode 100644 index 0000000..8ef386f --- /dev/null +++ b/LinuxUSB/rmuridrv.c @@ -0,0 +1,85 @@ +/* + * Copyright (C) 2009,2011 by Jonathan Naylor G4KLX + * + * 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 2 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include +#include +#include +#include + +const unsigned int C108_VENDOR_ID = 0x0D8CU; +const unsigned int C108_PRODUCT_ID = 0x000CU; +const unsigned int C108AH_PRODUCT_ID = 0x013CU; +const unsigned int C119_PRODUCT_ID = 0x0008U; +const unsigned int CM108_INTERFACE = 3U; + +int main(int argc, char **argv) +{ + unsigned int count = 0U, removed = 0U; + struct libusb_context *context; + struct libusb_device_handle *handle; + struct libusb_device **list; + struct libusb_device_descriptor desc; + ssize_t i; + int err; + + libusb_init(&context); + + ssize_t cnt = libusb_get_device_list(NULL, &list); + if (cnt <= 0) { + fprintf(stderr, "rmkuridrv: cannot find any USB devices!\n"); + return 1; + } + + for (i = 0; i < cnt; i++) { + err = libusb_get_device_descriptor(list[i], &desc); + if (err < 0) + continue; + + if (desc.idVendor == C108_VENDOR_ID && + (desc.idProduct == C108_PRODUCT_ID || + desc.idProduct == C108AH_PRODUCT_ID || + desc.idProduct == C119_PRODUCT_ID)) { + count++; + + err = libusb_open(list[i], &handle); + if (err != 0) { + fprintf(stderr, "rmkuridrv: cannot open the USB device: err=%d\n", err); + continue; + } + + if (libusb_kernel_driver_active(handle, CM108_INTERFACE) == 1) { + err = libusb_detach_kernel_driver(handle, CM108_INTERFACE); + if (err == 0) + removed++; + else + fprintf(stderr, "rmkuridrv: could not disconnect from kernel driver: err=%d\n", err); + } + + libusb_close(handle); + } + } + + libusb_free_device_list(list, 1); + + libusb_exit(context); + + fprintf(stdout, "rmuridrv: found %u CM108 devices and removed %u from the kernel driver\n", count, removed); + + return 0; +} + diff --git a/README.md b/README.md new file mode 100644 index 0000000..b50b909 --- /dev/null +++ b/README.md @@ -0,0 +1,13 @@ +This is the D-Star Repeater which controls homebrew repeater hardware and links into the ircDDB Gateway to allow for access to extra non-RF facilities, + +The hardware supported include: +* DVAP +* DV-Mega (D-Star only) +* GMSK Modems +* Soundcard repeaters (including UDRC) +* MMDVM (D-Star only) +* DV-RPTR V1, V2, and V3 + +They all build on 32-bit and 64-bit Linux as well as on Windows using Visual Studio 2017 on x86 and x64. + +This software is licenced under the GPL v2. diff --git a/WindowsUSB/dvrptr_cdc.inf b/WindowsUSB/dvrptr_cdc.inf new file mode 100644 index 0000000..83ed30c --- /dev/null +++ b/WindowsUSB/dvrptr_cdc.inf @@ -0,0 +1,76 @@ +; Driver-Dummy for the Communication Device Class + +[Version] +Signature="$Windows NT$" +Class=Ports +ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318} + +Provider=%DIGISOLUTIONS% +DriverVer=10/15/1999,5.0.2153.1 + +[Manufacturer] +%DIGISOLUTIONS%=DIGISOLUTIONS, NTamd64 + +[DIGISOLUTIONS] +%DVMODEM_CDC%=Reader, USB\VID_03EB&PID_2307 + +[DIGISOLUTIONS.NTamd64] +%DVMODEM_CDC%=DriverInstall, USB\VID_03EB&PID_2307 + +[Reader_Install.NTx86] + +[DestinationDirs] +DefaultDestDir=12 +Reader.NT.Copy=12 + +[Reader.NT] +include=mdmcpq.inf +CopyFiles=Reader.NT.Copy +AddReg=Reader.NT.AddReg + +[Reader.NT.Copy] +usbser.sys + +[Reader.NT.AddReg] +HKR,,DevLoader,,*ntkern +HKR,,NTMPDriver,,usbser.sys +HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider" + +[Reader.NT.Services] +AddService = usbser, 0x00000002, Service_Inst + +[Service_Inst] +DisplayName = %Serial.SvcDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\usbser.sys +LoadOrderGroup = Base + +[DriverInstall.NTamd64] +include=mdmcpq.inf +CopyFiles=DriverCopyFiles.NTamd64 +AddReg=DriverInstall.NTamd64.AddReg + +[DriverCopyFiles.NTamd64] +usbser.sys,,,0x20 + +[DriverInstall.NTamd64.AddReg] +HKR,,DevLoader,,*ntkern +HKR,,NTMPDriver,,usbser.sys +HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider" + +[DriverInstall.NTamd64.Services] +AddService=usbser, 0x00000002, DriverService.NTamd64 + +[DriverService.NTamd64] +DisplayName=%Serial.SvcDesc% +ServiceType=1 +StartType=3 +ErrorControl=1 +ServiceBinary=%12%\usbser.sys + +[Strings] +DIGISOLUTIONS = "dvrptr.de" +DVMODEM_CDC = "DVRPTR CDC" +Serial.SvcDesc = "USB Serial communication driver" diff --git a/WindowsUSB/gmsk.cat b/WindowsUSB/gmsk.cat new file mode 100644 index 0000000000000000000000000000000000000000..40c00c7ab6331e2a6dce706c2d25a10713bdc23c GIT binary patch literal 4061 zcmeHKU1%It6h1SZtR~%bt5h^ax7un-%{H7n`#VLJ-O27Wf+p=&WBsAE(I%{|jjgFc zgR~AM&!PzRr(vleL26sYpQx`A(FZGtl|BfyZ;C%u(E1?gckXQBNZpkP@o5?E%$;-Z zJ@?%6o$uar*^ixzwBI__KXs|oxWcUYu}|5L&6$Rw2$c^b?L860GW=|ZfwmJ6&2Ti* zQ5QxH!&>>-JF`1RoU?bnzL(D({(i?Z*LJd_Y?NG@?bLm|BGlb%gVyLN_Kh#BN|F?1 zQEa?3x~3>eMbtbzeN@8ZP!6RGX-*eKDPpwAJByYy#%J)g(U(a1#w1t%n$I*;iQV{> zP*a%W@c5i@Z{F&M!@=p zRY}7NvCGjG*>FS|);U;O1)kJ6*Cdk-8fu#PMxaJxb-wA41<%88JqYmv^}x%pYQ_mfkvetc%l(Qn^X7xNpw zTiYNuO7KonhkX%Vw(GHxhQIzxY-k<|_r&q6-@>Idrf~3=!4$0F5RSR%qwsY!gu*-l zx~##LkaeLahrbpN;g~)h!)dr@X?Zw$G1hg9^K0Qk_06tV4z3y4Jonxa@bHXP5m0Ra znE~q|SRB5_e<2gyMaz$|<$VT_9&5)9JfK|@l@G^`Umun_0M!vg0o<6&Y# z^Y0+IINV^9ZEdaCRvT@tht@q3Id)`U?es@eg`bY~od0bs*%LtO015?m%DKmYO1LV# z3}BX%OO6H?G6_2cn9CY?0bfSSVG;y@1cVzfwFT+Yv-J6EUg&)8<#i9E5bPYixle19%F88B7A6gY&3EAJV``_t;h*$p+{e2aba zB%lo^?1tqV703qSmRttvmjK_$Z>KG^U7wx_e5EcYYy$TMxnMk@-!{U50rPyne`>p3 zhf3p-fz~0Mw!sx +; +; Copyright 2009 MicroWalt Corporation +; All Rights Reserved. +; +; This program or documentation contains proprietary +; confidential information and trade secrets of MicroWalt +; Corporation. Reverse engineering of object code is +; prohibited. Use of copyright notice is precautionary +; and does not imply publication. Any unauthorized use, +; reproduction or transfer of this program is strictly +; prohibited. +; +; RESTRICTED RIGHTS NOTICE +; +; Use, duplication, or disclosure by the U.S. Government +; is subject to restrictions as set forth in subdivision +; (b)(3)(ii) of the Rights in Technical Data and Computer +; Software clause at 252.227-7013. +; +; MicroWalt Corporation +; P O BOX 8 +; 1400AA, BUSSUM, NH +; THE NETHERLANDS +; PH: +31 (35) 7503090 +; FAX: +31 (35) 7503091 +; +[Version] +Signature = "$Windows NT$" +Class = GMSKDeviceClass +ClassGUID = {FEFBC710-4131-4212-B140-6CEC9AF7B35D} +Provider = %ProviderName% +DriverVer = 12/09/2009,1.0.3 +CatalogFile = gmsk.cat + +; Since our device is not a standard USB device, we +; need to define a new class for the device. +[ClassInstall32] +Addreg = GMSKClassReg + +[GMSKClassReg] +HKR,,,0,"GMSK Devices" +HKR,,Icon,,"-20" + +; List the manufactuer, and the models supported. +[Manufacturer] +%ProviderName% = GMSK,NTx86,NTamd64,NTia64 + +[GMSK.NTx86] +"GMSK Modem 300" = GMSK_Install,USB\VID_04D8&PID_0300 +"GMSK Modem 301" = GMSK_Install,USB\VID_04D8&PID_0301 +"GMSK Modem 302" = GMSK_Install,USB\VID_04D8&PID_0302 +"GMSK Modem 303" = GMSK_Install,USB\VID_04D8&PID_0303 +"GMSK Modem 304" = GMSK_Install,USB\VID_04D8&PID_0304 +"GMSK Modem 305" = GMSK_Install,USB\VID_04D8&PID_0305 +"GMSK Modem 306" = GMSK_Install,USB\VID_04D8&PID_0306 +"GMSK Modem 307" = GMSK_Install,USB\VID_04D8&PID_0307 +"GMSK Modem 308" = GMSK_Install,USB\VID_04D8&PID_0308 +"GMSK Modem 309" = GMSK_Install,USB\VID_04D8&PID_0309 +"GMSK Modem 310" = GMSK_Install,USB\VID_04D8&PID_0310 + +[GMSK.NTamd64] +"GMSK Modem 300 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0300 +"GMSK Modem 301 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0301 +"GMSK Modem 302 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0302 +"GMSK Modem 303 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0303 +"GMSK Modem 304 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0304 +"GMSK Modem 305 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0305 +"GMSK Modem 306 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0306 +"GMSK Modem 307 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0307 +"GMSK Modem 308 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0308 +"GMSK Modem 309 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0309 +"GMSK Modem 310 (AMD64)" = GMSK_Install,USB\VID_04D8&PID_0310 + +[GMSK.NTia64] +"GMSK Modem 300 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0300 +"GMSK Modem 301 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0301 +"GMSK Modem 302 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0302 +"GMSK Modem 303 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0303 +"GMSK Modem 304 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0304 +"GMSK Modem 305 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0305 +"GMSK Modem 306 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0306 +"GMSK Modem 307 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0307 +"GMSK Modem 308 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0308 +"GMSK Modem 309 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0309 +"GMSK Modem 310 (IA64)" = GMSK_Install,USB\VID_04D8&PID_0310 + +; =================== Installation =================== +[GMSK_Install] +Include = winusb.inf +Needs = WINUSB.NT + +[GMSK_Install.Services] +Include = winusb.inf +AddService = WinUSB,0x00000002,WinUSB_ServiceInstall + +[GMSK_Install.Wdf] +KmdfService = WINUSB, WinUsb_Install +UmdfServiceOrder = WINUSB + +[GMSK_Install.HW] +AddReg = GMSK_Install_AddReg + +[GMSK_Install_AddReg] +HKR,,DeviceInterfaceGUIDs,0x10000,"{136C76EF-3F4E-4030-A7E3-E1003EF0A715}" + +[GMSK_Install.CoInstallers] +AddReg = CoInstallers_AddReg +CopyFiles = CoInstallers_CopyFiles + +[WinUSB_ServiceInstall] +DisplayName = "WinUSB Devices" +ServiceType = 1 +StartType = 3 +ErrorControl = 1 +ServiceBinary = %12%\WinUSB.sys + +[WinUSB_Install] +KmdfLibraryVersion = 1.7 + +[CoInstallers_AddReg] +HKR,,CoInstallers32,0x00010000,"WinUSBCoInstaller.dll","WdfCoInstaller01007.dll,WdfCoInstaller" + +[CoInstallers_CopyFiles] +WinUSBCoInstaller.dll +WdfCoInstaller01007.dll +;WUDFUpdate_01007.dll + +[DestinationDirs] +CoInstallers_CopyFiles = 11 + +; Select the Source Media. +[SourceDisksNames.x86] +1 = %DISK_NAME%,,,\i386 + +[SourceDisksNames.amd64] +2 = %DISK_NAME%,,,\amd64 + +[SourceDisksNames.ia64] +3 = %DISK_NAME%,,,\ia64 + +[SourceDisksFiles.x86] +WinUSBCoInstaller.dll = 1 +WdfCoInstaller01007.dll = 1 +WUDFUpdate_01007.dll = 1 + +[SourceDisksFiles.amd64] +WinUSBCoInstaller.dll = 2 +WdfCoInstaller01007.dll = 2 +WUDFUpdate_01007.dll = 2 + +[SourceDisksFiles.ia64] +WinUSBCoInstaller.dll = 3 +WdfCoInstaller01007.dll = 3 +WUDFUpdate_01007.dll = 3 + +; Copy Files section +[_CopyFiles_sys] +winusb.sys + +; Destination Directories +[DestinationDirs] +DefaultDestDir = 12 ; %SystemRoot%\System32\DRIVERS +_CopyFiles_sys = 12 + +; Strings +[Strings] +ProviderName = "MicroWalt Corporation" +DISK_NAME = "DUTCH*Star GMSK Install Disk" + +; End of GMSK.INF. diff --git a/WindowsUSB/xDVRPTR-32-64-2.inf b/WindowsUSB/xDVRPTR-32-64-2.inf new file mode 100644 index 0000000..23062e3 --- /dev/null +++ b/WindowsUSB/xDVRPTR-32-64-2.inf @@ -0,0 +1,95 @@ +; Windows 2000, XP, Vista and 7 (x32 and x64) setup file for PIPEMSG xDVRPTR CDC Devices +; Copyright (C) 2012 pipeMSG UG +; Creator SAT Hamburg 2012 + +[Version] +Signature="$Windows NT$" +Class=Ports +ClassGuid={4D36E978-E325-11CE-BFC1-08002BE10318} +Provider=%PIPEMSG% +LayoutFile=layout.inf +DriverVer=12/09/2012,6.0.2404.0 + +[Manufacturer] +%PIPEMSG%=DeviceList, NTamd64 + +[DestinationDirs] +;FakeModemCopyFileSection=12 +;FakeModemCopyFileSection.NTamd64=12 +DefaultDestDir=12 + + +[DeviceList] +xDVRPTR_32=Reader, USB\VID_03EB&PID_2404 +[DeviceList.NTamd64] +xDVRPTR_64=DriverInstall, USB\VID_03EB&PID_2404 + +;------------------------------------------------------------------------------ +; Windows 2000/XP/Vista/7/8 Sections +;------------------------------------------------------------------------------ + +[Reader_Install.NTx86] + +[DestinationDirs] +DefaultDestDir=12 +Reader.NT.Copy=12 + +[Reader.NT] +include=mdmcpq.inf +CopyFiles=Reader.NT.Copy +AddReg=Reader.NT.AddReg + +[Reader.NT.Copy] +usbser.sys + +[Reader.NT.AddReg] +HKR,,DevLoader,,*ntkern +HKR,,NTMPDriver,,usbser.sys +HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider" + +[Reader.NT.Services] +AddService = usbser, 0x00000002, Service_Inst + +[Service_Inst] +DisplayName = %Serial.SvcDesc% +ServiceType = 1 ; SERVICE_KERNEL_DRIVER +StartType = 3 ; SERVICE_DEMAND_START +ErrorControl = 1 ; SERVICE_ERROR_NORMAL +ServiceBinary = %12%\usbser.sys +LoadOrderGroup = Base + +;------------------------------------------------------------------------------ +; Windows XP/7/8-64bit Sections +;------------------------------------------------------------------------------ + +[DriverInstall.NTamd64] +include=mdmcpq.inf +CopyFiles=DriverCopyFiles.NTamd64 +AddReg=DriverInstall.NTamd64.AddReg + +[DriverCopyFiles.NTamd64] +usbser.sys,,,0x20 + +[DriverInstall.NTamd64.AddReg] +HKR,,DevLoader,,*ntkern +HKR,,NTMPDriver,,usbser.sys +HKR,,EnumPropPages32,,"MsPorts.dll,SerialPortPropPageProvider" + +[DriverInstall.NTamd64.Services] +AddService=usbser, 0x00000002, DriverService.NTamd64 + +[DriverService.NTamd64] +DisplayName=%Serial.SvcDesc% +ServiceType=1 +StartType=3 +ErrorControl=1 +ServiceBinary=%12%\usbser.sys +;------------------------------------------------------------------------------ +; String Definitions +;------------------------------------------------------------------------------ + +[Strings] +PIPEMSG = "PIPEMSG.de" +DVMODEM_CDC="xDVRPTR CDC" +DVMODEM_CDC64="xDVRPTR CDC for x64-based systems" +DESCRIPTION="Provides a xDVRPTR CDC COM-Port when connecting an via USB" diff --git a/debian/README.Debian b/debian/README.Debian new file mode 100644 index 0000000..9224859 --- /dev/null +++ b/debian/README.Debian @@ -0,0 +1,6 @@ +dstarrepeater for Debian +------------------------ + + + + -- Jeremy McDermond Mon, 14 Mar 2016 14:40:56 -0700 diff --git a/debian/README.source b/debian/README.source new file mode 100644 index 0000000..134c031 --- /dev/null +++ b/debian/README.source @@ -0,0 +1,10 @@ +dstarrepeater for Debian +------------------------ + + + + + + -- Jeremy McDermond Mon, 14 Mar 2016 14:40:56 -0700 + diff --git a/debian/changelog b/debian/changelog new file mode 100644 index 0000000..fc04ead --- /dev/null +++ b/debian/changelog @@ -0,0 +1,5 @@ +dstarrepeater (1.20160314-1) unstable; urgency=low + + * Initial release (Closes: #nnnn) + + -- Jeremy McDermond (NH6Z) Mon, 14 Mar 2016 14:40:56 -0700 diff --git a/debian/compat b/debian/compat new file mode 100644 index 0000000..ec63514 --- /dev/null +++ b/debian/compat @@ -0,0 +1 @@ +9 diff --git a/debian/control b/debian/control new file mode 100644 index 0000000..22a5f4e --- /dev/null +++ b/debian/control @@ -0,0 +1,38 @@ +Source: dstarrepeater +Section: hamradio +Priority: optional +Maintainer: Jeremy McDermond +Build-Depends: debhelper (>= 9), autotools-dev , libwxgtk3.0-dev, libasound2-dev, libusb-1.0-0-dev, pkg-config, wiringpi [armhf armel] +Standards-Version: 3.9.6 +Homepage: https://github.com/dl5di/OpenDV + +Package: dstarrepeater +Architecture: i386 amd64 armel armhf +Depends: ${shlibs:Depends}, ${misc:Depends}, dstarrepeater-udev, opendv-base, wiringpi [armhf armel] +Description: D-STAR Digital Voice Repeater Software GUI Components + Provides an implementation of a D-STAR Digital Voice repeater in software. + This package contains all of the GUI components that require X-Windows to + run correctly including the graphical configuration utility. + +Package: dstarrepeaterd +Architecture: armel armhf amd64 i386 +Depends: ${shlibs:Depends}, ${misc:Depends}, opendv-base, dstarrepeater-udev, wiringpi [armhf armel] +Description: D-STAR Digital Voice Repeater Software Daemon Components + Provides an implementation of a D-STAR Digtal Voice repeater in software. + This package contains the Non-GUI componets that do not require an X-Windows + installation to run. This does not include the configuration utility and + users will have to find alternate ways of configuring the daemon. + +Package: opendv-usb-utils +Architecture: armel armhf amd64 i386 +Depends: ${shlibs:Depends}, ${misc:Depends} +Description: USB Utilities for the OpenDV programs + Provides the USB utilities to manipulate the URI and K8055 devices for + OpenDV. + +Package: dstarrepeater-udev +Architecture: all +Depends: ${misc:Depends} +Description: D-STAR Digital Voice Repeater udev rules + udev rules to change permissions for devices used by dstarrepeater and + dstarrepeaterd diff --git a/debian/copyright b/debian/copyright new file mode 100644 index 0000000..dea2d98 --- /dev/null +++ b/debian/copyright @@ -0,0 +1,34 @@ +Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ +Upstream-Name: dstarrepeater +Source: + +Files: * +Copyright: + +License: GPL-3.0+ + +Files: debian/* +Copyright: 2016 Jeremy McDermond +License: GPL-3.0+ + +License: GPL-3.0+ + 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 package 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 . + . + On Debian systems, the complete text of the GNU General + Public License version 3 can be found in "/usr/share/common-licenses/GPL-3". + +# Please also look if there are files or directories which have a +# different copyright/license attached and list them here. +# Please avoid picking licenses with terms that are more restrictive than the +# packaged work, as it may make Debian's contributions unacceptable upstream. diff --git a/debian/docs b/debian/docs new file mode 100644 index 0000000..1d09f01 --- /dev/null +++ b/debian/docs @@ -0,0 +1,5 @@ +BUILD.txt +CHANGES.txt +COPYING.txt +TODO.txt +TODO.txt diff --git a/debian/dstarrepeater-udev.postinst b/debian/dstarrepeater-udev.postinst new file mode 100644 index 0000000..c539795 --- /dev/null +++ b/debian/dstarrepeater-udev.postinst @@ -0,0 +1,52 @@ +#!/bin/sh +# postinst script for dstarrepeater +# +# see: dh_installdeb(1) + +set -e + +# summary of how this script can be called: +# * `configure' +# * `abort-upgrade' +# * `abort-remove' `in-favour' +# +# * `abort-remove' +# * `abort-deconfigure' `in-favour' +# `removing' +# +# for details, see https://www.debian.org/doc/debian-policy/ or +# the debian-policy package + +# same test as udev postinst... +chrooted() { + if [ "$(stat -c %d/%i /)" = "$(stat -Lc %d/%i /proc/1/root 2>/dev/null)" ]; + then + # the devicenumber/inode pair of / is the same as that of /sbin/init's + # root, so we're *not* in a chroot and hence return false. + return 1 + fi + return 0 +} + +case "$1" in + configure) + if ! chrooted ; then + udevadm control --reload-rules || true ; + fi + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac + +# dh_installdeb will replace this with shell code automatically +# generated by other debhelper scripts. + +#DEBHELPER# + +exit 0 diff --git a/debian/dstarrepeater-udev.udev b/debian/dstarrepeater-udev.udev new file mode 100644 index 0000000..2d40185 --- /dev/null +++ b/debian/dstarrepeater-udev.udev @@ -0,0 +1,23 @@ +# +# OpenDV USB Rules. +# This changes the permissions on the USB devices for the OpenDV supported +# devices so that the daemons can access them while running as the opendv +# user. +# + +# Velleman K8055 +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="10cf", ATTR{idProduct}=="5500", MODE="0666" + +# Universal Radio Interface +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0d8c", ATTR{idProduct}=="000c", MODE="0666" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0d8c", ATTR{idProduct}=="013c", MODE="0666" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0d8c", ATTR{idProduct}=="0008", MODE="0666" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="0d8c", ATTR{idProduct}=="013a", MODE="0666" + +# GMSK Node Adapters +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0300", MODE="0666" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0301", MODE="0666" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0310", MODE="0666" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0320", MODE="0666" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0322", MODE="0666" +SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", ATTR{idVendor}=="04d8", ATTR{idProduct}=="0324", MODE="0666" diff --git a/debian/dstarrepeater.install b/debian/dstarrepeater.install new file mode 100644 index 0000000..3e5813b --- /dev/null +++ b/debian/dstarrepeater.install @@ -0,0 +1,2 @@ +usr/bin/dstarrepeater +usr/bin/dstarrepeaterconfig diff --git a/debian/dstarrepeater.logrotate b/debian/dstarrepeater.logrotate new file mode 100644 index 0000000..6008479 --- /dev/null +++ b/debian/dstarrepeater.logrotate @@ -0,0 +1,7 @@ +/var/log/opendv/dstarrepeater.log /var/log/opendv/dstarrepeater_[0-9].log { + rotate 14 + daily + compress + missingok + copytruncate +} diff --git a/debian/dstarrepeaterd.dstarrepeaterd@.service b/debian/dstarrepeaterd.dstarrepeaterd@.service new file mode 100644 index 0000000..16f9092 --- /dev/null +++ b/debian/dstarrepeaterd.dstarrepeaterd@.service @@ -0,0 +1,13 @@ +[Unit] +Description=D-STAR Repeater Daemon +After=network.target + +[Service] +Environment="WIRINGPI_GPIOMEM=1" +User=opendv +ExecStart=/usr/sbin/dstarrepeaterd %i +Restart=on-abort + +[Install] +WantedBy=multi-user.target +DefaultInstance=1 diff --git a/debian/dstarrepeaterd.examples b/debian/dstarrepeaterd.examples new file mode 100644 index 0000000..b604e4d --- /dev/null +++ b/debian/dstarrepeaterd.examples @@ -0,0 +1 @@ +linux/dstarrepeater_1.example diff --git a/debian/dstarrepeaterd.install b/debian/dstarrepeaterd.install new file mode 100644 index 0000000..36f2c6a --- /dev/null +++ b/debian/dstarrepeaterd.install @@ -0,0 +1 @@ +usr/sbin/dstarrepeaterd diff --git a/debian/dstarrepeaterd.logrotate b/debian/dstarrepeaterd.logrotate new file mode 100644 index 0000000..235af46 --- /dev/null +++ b/debian/dstarrepeaterd.logrotate @@ -0,0 +1,7 @@ +/var/log/opendv/dstarrepeaterd.log /var/log/opendv/dstarrepeaterd_[0-9].log { + rotate 14 + daily + compress + missingok + copytruncate +} diff --git a/debian/opendv-usb-utils.install b/debian/opendv-usb-utils.install new file mode 100644 index 0000000..1361e98 --- /dev/null +++ b/debian/opendv-usb-utils.install @@ -0,0 +1,2 @@ +usr/bin/rmuridrv +usr/bin/rmk8055drv diff --git a/debian/rules b/debian/rules new file mode 100644 index 0000000..fd8a82c --- /dev/null +++ b/debian/rules @@ -0,0 +1,41 @@ +#!/usr/bin/make -f +# See debhelper(7) (uncomment to enable) +# output every command that modifies files on the build system. +#export DH_VERBOSE = 1 + +# see EXAMPLES in dpkg-buildflags(1) and read /usr/share/dpkg/* +DPKG_EXPORT_BUILDFLAGS = 1 +include /usr/share/dpkg/default.mk + +# see FEATURE AREAS in dpkg-buildflags(1) +#export DEB_BUILD_MAINT_OPTIONS = hardening=+all + +# see ENVIRONMENT in dpkg-buildflags(1) +# package maintainers to append CFLAGS +#export DEB_CFLAGS_MAINT_APPEND = -Wall -pedantic +# package maintainers to append LDFLAGS +#export DEB_LDFLAGS_MAINT_APPEND = -Wl,--as-needed + + +# main packaging script based on dh7 syntax +%: + dh $@ --with autotools-dev --parallel + +# dh_make generated override targets +# This is example for Cmake (See https://bugs.debian.org/641051 ) +#override_dh_auto_configure: +# dh_auto_configure -- \ +# -DCMAKE_LIBRARY_PATH=$(DEB_HOST_MULTIARCH) + +ifeq ($(DEB_HOST_ARCH),$(filter $(DEB_HOST_ARCH),armhf armel)) +override_dh_auto_configure: + dh_auto_configure -- \ + --with-gpio +endif + +override_dh_installinit: + dh_installinit + dh_installinit --name=dstarrepeaterd@ + +override_dh_installudev: + dh_installudev --priority=60 diff --git a/debian/source/format b/debian/source/format new file mode 100644 index 0000000..163aaf8 --- /dev/null +++ b/debian/source/format @@ -0,0 +1 @@ +3.0 (quilt) diff --git a/linux/dstarrepeater_1.example b/linux/dstarrepeater_1.example new file mode 100644 index 0000000..f527634 --- /dev/null +++ b/linux/dstarrepeater_1.example @@ -0,0 +1,114 @@ +callsign=N0CALL C +gateway= G +mode=0 +ack=1 +restriction=0 +rpt1Validation=1 +dtmfBlanking=1 +errorReply=1 +gatewayAddress=127.0.0.1 +gatewayPort=20010 +localAddress=127.0.0.1 +localPort=20011 +networkName= +modemType=Sound Card +timeout=180 +ackTime=500 +beaconTime=600 +beaconText=D-Star Repeater +beaconVoice=0 +language=0 +announcementEnabled=0 +announcementTime=480 +announcementRecordRPT1= +announcementRecordRPT2= +announcementDeleteRPT1= +announcementDeleteRPT2= +controlEnabled=0 +controlRPT1= +controlRPT2= +controlShutdown= +controlStartup= +controlStatus1= +controlStatus2= +controlStatus3= +controlStatus4= +controlStatus5= +controlCommand1= +controlCommand1Line= +controlCommand2= +controlCommand2Line= +controlCommand3= +controlCommand3Line= +controlCommand4= +controlCommand4Line= +controlCommand5= +controlCommand5Line= +controlCommand6= +controlCommand6Line= +controlOutput1= +controlOutput2= +controlOutput3= +controlOutput4= +controllerType= +serialConfig=1 +pttInvert=0 +activeHangTime=0 +output1=0 +output2=0 +output3=0 +output4=0 +logging=0 +windowX=-1 +windowY=-1 +dvapPort= +dvapFrequency=145500000 +dvapPower=10 +dvapSquelch=-100 +gmskAddress=768 +dvrptr1Port= +dvrptr1RXInvert=0 +dvrptr1TXInvert=0 +dvrptr1Channel=0 +dvrptr1ModLevel=20 +dvrptr1TXDelay=150 +dvrptr2Connection=0 +dvrptr2USBPort= +dvrptr2Address=127.0.0.1 +dvrptr2Port=0 +dvrptr2TXInvert=0 +dvrptr2ModLevel=20 +dvrptr2TXDelay=150 +dvrptr3Connection=0 +dvrptr3USBPort= +dvrptr3Address=127.0.0.1 +dvrptr3Port=0 +dvrptr3TXInvert=0 +dvrptr3ModLevel=20 +dvrptr3TXDelay=150 +dvmegaPort= +dvmegaVariant=0 +dvmegaRXInvert=0 +dvmegaTXInvert=0 +dvmegaTXDelay=150 +dvmegaRXFrequency=145500000 +dvmegaTXFrequency=145500000 +dvmegaPower=100 +mmdvmPort= +mmdvmRXInvert=0 +mmdvmTXInvert=0 +mmdvmPTTInvert=0 +mmdvmTXDelay=50 +mmdvmRXLevel=100 +mmdvmTXLevel=100 +soundCardRXDevice= +soundCardTXDevice= +soundCardRXInvert=0 +soundCardTXInvert=0 +soundCardRXLevel=1.0000 +soundCardTXLevel=1.0000 +soundCardTXDelay=150 +soundCardTXTail=50 +splitLocalAddress= +splitLocalPort=0 +splitTimeout=0